mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-03-03 00:37:01 +00:00
1201 lines
45 KiB
JavaScript
1201 lines
45 KiB
JavaScript
const { describe, test, mock } = require("node:test");
|
|
const assert = require("node:assert");
|
|
const { encodeBase64 } = require("../../server/util-server");
|
|
const { UP, PENDING } = require("../../src/util");
|
|
|
|
describe("GlobalpingMonitorType", () => {
|
|
const { GlobalpingMonitorType } = require("../../server/monitor-types/globalping");
|
|
|
|
describe("ping", () => {
|
|
test("should handle successful ping", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createPingMeasurement();
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
hostname: "example.com",
|
|
location: "North America",
|
|
ping_count: 3,
|
|
protocol: "ICMP",
|
|
ipFamily: "ipv4",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
ping: 0,
|
|
};
|
|
|
|
await monitorType.ping(mockClient, monitor, heartbeat, true);
|
|
|
|
assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 1);
|
|
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], {
|
|
type: "ping",
|
|
target: "example.com",
|
|
inProgressUpdates: false,
|
|
limit: 1,
|
|
locations: [{ magic: "North America" }],
|
|
measurementOptions: {
|
|
packets: 3,
|
|
protocol: "ICMP",
|
|
ipVersion: 4,
|
|
},
|
|
});
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1) : OK",
|
|
ping: 2.169,
|
|
});
|
|
});
|
|
|
|
test("should handle failed ping with status failed", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createPingMeasurement();
|
|
measurement.results[0].result.status = "failed";
|
|
measurement.results[0].result.rawOutput = "Host unreachable";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
hostname: "unreachable.example.com",
|
|
location: "Europe",
|
|
ping_count: 3,
|
|
protocol: "ICMP",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.ping(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1) : Failed: Host unreachable")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle API error on create measurement", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
error: {
|
|
type: "validation_error",
|
|
message: "Invalid target",
|
|
params: { target: "example.com" },
|
|
},
|
|
});
|
|
createResponse.ok = false;
|
|
createResponse.response.status = 400;
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
|
|
const monitor = {
|
|
hostname: "example.com",
|
|
location: "North America",
|
|
ping_count: 3,
|
|
protocol: "ICMP",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.ping(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Failed to create measurement: validation_error Invalid target.\ntarget: example.com")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle API error on await measurement", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const awaitResponse = createMockResponse({
|
|
error: {
|
|
type: "internal_error",
|
|
message: "Server error",
|
|
},
|
|
});
|
|
awaitResponse.ok = false;
|
|
awaitResponse.response.status = 400;
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
hostname: "example.com",
|
|
location: "North America",
|
|
ping_count: 3,
|
|
protocol: "ICMP",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.ping(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Failed to fetch measurement (2g8T7V3OwXG3JV6Y10011zF2v): internal_error Server error.")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("http", () => {
|
|
test("should handle successful HTTP request", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com:444/api/test?test=1",
|
|
location: "North America",
|
|
method: "GET",
|
|
accepted_statuscodes_json: JSON.stringify(["200-299", "300-399"]),
|
|
headers: '{"Test-Header": "Test-Value"}',
|
|
ipFamily: "ipv4",
|
|
dns_resolve_server: "8.8.8.8",
|
|
auth_method: "basic",
|
|
basic_auth_user: "username",
|
|
basic_auth_pass: "password",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
ping: 0,
|
|
};
|
|
|
|
await monitorType.http(mockClient, monitor, heartbeat, true);
|
|
|
|
assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 1);
|
|
const expectedToken = encodeBase64(monitor.basic_auth_user, monitor.basic_auth_pass);
|
|
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], {
|
|
type: "http",
|
|
target: "example.com",
|
|
inProgressUpdates: false,
|
|
limit: 1,
|
|
locations: [{ magic: "North America" }],
|
|
measurementOptions: {
|
|
request: {
|
|
host: "example.com",
|
|
path: "/api/test",
|
|
query: "test=1",
|
|
method: "GET",
|
|
headers: {
|
|
"Test-Header": "Test-Value",
|
|
Authorization: `Basic ${expectedToken}`,
|
|
},
|
|
},
|
|
port: 444,
|
|
protocol: "HTTPS",
|
|
ipVersion: 4,
|
|
resolver: "8.8.8.8",
|
|
},
|
|
});
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : OK",
|
|
ping: 1440,
|
|
});
|
|
});
|
|
|
|
test("should handle failed HTTP request", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.status = "failed";
|
|
measurement.results[0].result.rawOutput = "Host unreachable";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com",
|
|
location: "North America",
|
|
method: "GET",
|
|
accepted_statuscodes_json: JSON.stringify(["200-299", "300-399"]),
|
|
headers: null,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.http(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("New York (NY), US, NA, MASSIVEGRID (AS49683) : Failed: Host unreachable")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle API error on create measurement", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
error: {
|
|
type: "validation_error",
|
|
message: "Invalid target",
|
|
params: { target: "example.com" },
|
|
},
|
|
});
|
|
createResponse.ok = false;
|
|
createResponse.response.status = 400;
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com",
|
|
location: "North America",
|
|
method: "GET",
|
|
accepted_statuscodes_json: JSON.stringify(["200-299", "300-399"]),
|
|
headers: null,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.http(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Failed to create measurement: validation_error Invalid target.\ntarget: example.com")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle API error on await measurement", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const awaitResponse = createMockResponse({
|
|
error: {
|
|
type: "internal_error",
|
|
message: "Server error",
|
|
},
|
|
});
|
|
awaitResponse.ok = false;
|
|
awaitResponse.response.status = 400;
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com",
|
|
location: "North America",
|
|
method: "GET",
|
|
accepted_statuscodes_json: JSON.stringify(["200-299", "300-399"]),
|
|
headers: null,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.http(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Failed to fetch measurement (2g8T7V3OwXG3JV6Y10011zF2v): internal_error Server error.")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle invalid status code", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.rawOutput = "RAW OUTPUT";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com/api/test",
|
|
location: "North America",
|
|
method: "GET",
|
|
accepted_statuscodes_json: JSON.stringify(["200-299"]),
|
|
headers: null,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
ping: 0,
|
|
};
|
|
|
|
await assert.rejects(monitorType.http(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error(
|
|
"New York (NY), US, NA, MASSIVEGRID (AS49683) : Status code 301 not accepted. Output: RAW OUTPUT"
|
|
)
|
|
);
|
|
return true;
|
|
});
|
|
|
|
// heartbeat.ping should still be set before the error is thrown
|
|
assert.strictEqual(heartbeat.ping, 1440);
|
|
});
|
|
|
|
test("should handle keyword check (keyword present)", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.rawOutput = "Response body with KEYWORD word";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com",
|
|
location: "North America",
|
|
protocol: "HTTPS",
|
|
accepted_statuscodes_json: JSON.stringify(["300-399"]),
|
|
keyword: "KEYWORD",
|
|
invertKeyword: false,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await monitorType.http(mockClient, monitor, heartbeat, true);
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : 301 - Moved Permanently, keyword is found",
|
|
ping: 1440,
|
|
});
|
|
});
|
|
|
|
test("should handle keyword check (keyword not present)", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.rawOutput = "Response body with KEYWORD word";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com",
|
|
location: "North America",
|
|
protocol: "HTTPS",
|
|
accepted_statuscodes_json: JSON.stringify(["300-399"]),
|
|
keyword: "MISSING_KEYWORD",
|
|
invertKeyword: false,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.http(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error(
|
|
"New York (NY), US, NA, MASSIVEGRID (AS49683) : 301 - Moved Permanently, but keyword is not in [Response body with KEYWORD word]"
|
|
)
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle inverted keyword check", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.rawOutput = "Response body with KEYWORD word";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://example.com",
|
|
location: "North America",
|
|
protocol: "HTTPS",
|
|
accepted_statuscodes_json: JSON.stringify(["300-399"]),
|
|
keyword: "ERROR",
|
|
invertKeyword: true,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await monitorType.http(mockClient, monitor, heartbeat, true);
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : 301 - Moved Permanently, keyword not found",
|
|
ping: 1440,
|
|
});
|
|
});
|
|
|
|
test("should handle JSON query check (valid)", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.rawOutput = JSON.stringify({
|
|
status: "success",
|
|
value: 42,
|
|
});
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://api.example.com/status",
|
|
location: "North America",
|
|
protocol: "HTTPS",
|
|
accepted_statuscodes_json: JSON.stringify(["300-399"]),
|
|
jsonPath: "$.status",
|
|
jsonPathOperator: "==",
|
|
expectedValue: "success",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await monitorType.http(mockClient, monitor, heartbeat, true);
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : JSON query passes (comparing success == success)",
|
|
ping: 1440,
|
|
});
|
|
});
|
|
|
|
test("should handle JSON query check (invalid)", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createHttpMeasurement();
|
|
measurement.results[0].result.rawOutput = JSON.stringify({
|
|
status: "failed",
|
|
value: 42,
|
|
});
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
url: "https://api.example.com/status",
|
|
location: "North America",
|
|
protocol: "HTTPS",
|
|
accepted_statuscodes_json: JSON.stringify(["300-399"]),
|
|
jsonPath: "$.status",
|
|
jsonPathOperator: "==",
|
|
expectedValue: "success",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.http(mockClient, monitor, heartbeat, true), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error(
|
|
"New York (NY), US, NA, MASSIVEGRID (AS49683) : JSON query does not pass (comparing failed == success)"
|
|
)
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("dns", () => {
|
|
test("should handle successful dns", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createDnsMeasurement();
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createMockResponse(createResponse));
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const redbeanMock = createRedbeanMock();
|
|
redbeanMock.exec.mock.mockImplementation(() => Promise.resolve());
|
|
|
|
const monitor = {
|
|
id: "1",
|
|
hostname: "example.com",
|
|
location: "us-east-1",
|
|
dns_resolve_type: "A",
|
|
port: 53,
|
|
protocol: "udp",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
ping: null,
|
|
};
|
|
|
|
await monitorType.dns(mockClient, monitor, heartbeat, false, redbeanMock);
|
|
|
|
assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 1);
|
|
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], {
|
|
type: "dns",
|
|
target: "example.com",
|
|
inProgressUpdates: false,
|
|
limit: 1,
|
|
locations: [{ magic: "us-east-1" }],
|
|
measurementOptions: {
|
|
query: { type: "A" },
|
|
port: 53,
|
|
protocol: "udp",
|
|
},
|
|
});
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : 93.184.216.34",
|
|
ping: 25,
|
|
});
|
|
|
|
assert.strictEqual(redbeanMock.exec.mock.calls.length, 1);
|
|
assert.deepStrictEqual(redbeanMock.exec.mock.calls[0].arguments, [
|
|
"UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ",
|
|
["93.184.216.34", "1"],
|
|
]);
|
|
});
|
|
|
|
test("should handle failed dns with status failed", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const measurement = createDnsMeasurement();
|
|
measurement.results[0].result.status = "failed";
|
|
measurement.results[0].result.rawOutput = "NXDOMAIN";
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createMockResponse(createResponse));
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
hostname: "nonexistent.example.com",
|
|
location: "us-east-1",
|
|
dns_resolve_type: "A",
|
|
port: 53,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(
|
|
async () => {
|
|
await monitorType.dns(mockClient, monitor, heartbeat, false);
|
|
},
|
|
(error) => {
|
|
assert.strictEqual(
|
|
error.message,
|
|
"New York (NY), US, NA, MASSIVEGRID (AS49683) : Failed: NXDOMAIN"
|
|
);
|
|
return true;
|
|
}
|
|
);
|
|
});
|
|
|
|
test("should handle API error on create measurement", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
error: {
|
|
type: "validation_error",
|
|
message: "Invalid target",
|
|
params: { target: "example.com" },
|
|
},
|
|
});
|
|
createResponse.ok = false;
|
|
createResponse.response.status = 400;
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
|
|
const monitor = {
|
|
hostname: "example.com",
|
|
location: "us-east-1",
|
|
dns_resolve_type: "A",
|
|
port: 53,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.dns(mockClient, monitor, heartbeat, false), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Failed to create measurement: validation_error Invalid target.\ntarget: example.com")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle API error on await measurement", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = createMockResponse({
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
});
|
|
const awaitResponse = createMockResponse({
|
|
error: {
|
|
type: "internal_error",
|
|
message: "Server error",
|
|
},
|
|
});
|
|
awaitResponse.ok = false;
|
|
awaitResponse.response.status = 400;
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const monitor = {
|
|
hostname: "example.com",
|
|
location: "us-east-1",
|
|
dns_resolve_type: "A",
|
|
port: 53,
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.dns(mockClient, monitor, heartbeat, false), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("Failed to fetch measurement (2g8T7V3OwXG3JV6Y10011zF2v): internal_error Server error.")
|
|
);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test("should handle regex matched", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = {
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
};
|
|
const measurement = createDnsMeasurement();
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createMockResponse(createResponse));
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const redbeanMock = createRedbeanMock();
|
|
redbeanMock.exec.mock.mockImplementation(() => Promise.resolve());
|
|
|
|
const monitor = {
|
|
id: "1",
|
|
hostname: "example.com",
|
|
location: "us-east-1",
|
|
dns_resolve_type: "A",
|
|
port: 53,
|
|
protocol: "udp",
|
|
keyword: "93\\.184\\.216\\.34",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
ping: null,
|
|
};
|
|
|
|
await monitorType.dns(mockClient, monitor, heartbeat, false, redbeanMock);
|
|
|
|
assert.deepStrictEqual(heartbeat, {
|
|
status: UP,
|
|
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : 93.184.216.34",
|
|
ping: 25,
|
|
});
|
|
|
|
assert.deepStrictEqual(redbeanMock.exec.mock.calls[0].arguments, [
|
|
"UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ",
|
|
["93.184.216.34", "1"],
|
|
]);
|
|
});
|
|
|
|
test("should handle regex not matched", async () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
const mockClient = createGlobalpingClientMock();
|
|
const createResponse = {
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
};
|
|
const measurement = createDnsMeasurement();
|
|
const awaitResponse = createMockResponse(measurement);
|
|
|
|
mockClient.createMeasurement.mock.mockImplementation(() => createMockResponse(createResponse));
|
|
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
|
|
|
|
const redbeanMock = createRedbeanMock();
|
|
redbeanMock.exec.mock.mockImplementation(() => Promise.resolve());
|
|
|
|
const monitor = {
|
|
id: "1",
|
|
hostname: "example.com",
|
|
location: "us-east-1",
|
|
dns_resolve_type: "A",
|
|
port: 53,
|
|
protocol: "udp",
|
|
keyword: "192\\.168\\.1\\.1",
|
|
};
|
|
|
|
const heartbeat = {
|
|
status: PENDING,
|
|
msg: "",
|
|
};
|
|
|
|
await assert.rejects(monitorType.dns(mockClient, monitor, heartbeat, false, redbeanMock), (error) => {
|
|
assert.deepStrictEqual(
|
|
error,
|
|
new Error("New York (NY), US, NA, MASSIVEGRID (AS49683) : No record matched. 93.184.216.34")
|
|
);
|
|
return true;
|
|
});
|
|
|
|
assert.deepStrictEqual(redbeanMock.exec.mock.calls[0].arguments, [
|
|
"UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ",
|
|
["93.184.216.34", "1"],
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("helper methods", () => {
|
|
test("formatProbeLocation should format location correctly", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const probe = {
|
|
city: "New York",
|
|
state: "NY",
|
|
country: "US",
|
|
continent: "NA",
|
|
network: "Amazon.com",
|
|
asn: 14618,
|
|
tags: ["aws-us-east-1", "datacenter"],
|
|
};
|
|
|
|
const result = monitorType.formatProbeLocation(probe);
|
|
|
|
assert.strictEqual(result, "New York (NY), US, NA, Amazon.com (AS14618), (aws-us-east-1)");
|
|
});
|
|
|
|
test("formatProbeLocation should handle missing state", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const probe = {
|
|
city: "London",
|
|
state: null,
|
|
country: "GB",
|
|
continent: "EU",
|
|
network: "Example Network",
|
|
asn: 12345,
|
|
tags: [],
|
|
};
|
|
|
|
const result = monitorType.formatProbeLocation(probe);
|
|
|
|
assert.strictEqual(result, "London, GB, EU, Example Network (AS12345)");
|
|
});
|
|
|
|
test("formatResponse should combine location and text", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const probe = {
|
|
city: "Tokyo",
|
|
state: null,
|
|
country: "JP",
|
|
continent: "AS",
|
|
network: "Example ISP",
|
|
asn: 54321,
|
|
tags: [],
|
|
};
|
|
|
|
const result = monitorType.formatResponse(probe, "Test message");
|
|
|
|
assert.strictEqual(result, "Tokyo, JP, AS, Example ISP (AS54321) : Test message");
|
|
});
|
|
|
|
test("formatApiError should format error with params", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const error = {
|
|
type: "validation_error",
|
|
message: "Invalid request",
|
|
params: {
|
|
field: "target",
|
|
value: "invalid",
|
|
},
|
|
};
|
|
|
|
const result = monitorType.formatApiError(error);
|
|
|
|
assert.strictEqual(result, "validation_error Invalid request.\nfield: target\nvalue: invalid");
|
|
});
|
|
|
|
test("formatApiError should format error without params", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const error = {
|
|
type: "internal_error",
|
|
message: "Server error",
|
|
};
|
|
|
|
const result = monitorType.formatApiError(error);
|
|
|
|
assert.strictEqual(result, "internal_error Server error.");
|
|
});
|
|
|
|
test("formatTooManyRequestsError with API token", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const result = monitorType.formatTooManyRequestsError(true);
|
|
|
|
assert.strictEqual(
|
|
result,
|
|
"You have run out of credits. Get higher limits by sponsoring us or hosting probes. Learn more at https://dash.globalping.io?view=add-credits."
|
|
);
|
|
});
|
|
|
|
test("formatTooManyRequestsError without API token", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const result = monitorType.formatTooManyRequestsError(false);
|
|
|
|
assert.strictEqual(
|
|
result,
|
|
"You have run out of credits. Get higher limits by creating an account. Sign up at https://dash.globalping.io?view=add-credits."
|
|
);
|
|
});
|
|
|
|
test("getBasicAuthHeader should return empty for non-basic auth", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const monitor = {
|
|
auth_method: "none",
|
|
};
|
|
|
|
const result = monitorType.getBasicAuthHeader(monitor);
|
|
|
|
assert.deepStrictEqual(result, {});
|
|
});
|
|
|
|
test("getBasicAuthHeader should return Authorization header", () => {
|
|
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
|
|
|
|
const monitor = {
|
|
auth_method: "basic",
|
|
basic_auth_user: "testuser",
|
|
basic_auth_pass: "testpass",
|
|
};
|
|
|
|
const result = monitorType.getBasicAuthHeader(monitor);
|
|
|
|
const expectedToken = encodeBase64(monitor.basic_auth_user, monitor.basic_auth_pass);
|
|
|
|
assert.strictEqual(result.Authorization, `Basic ${expectedToken}`);
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Reusable mock factory for Globalping client
|
|
* @returns {object} Mocked Globalping client
|
|
*/
|
|
function createGlobalpingClientMock() {
|
|
return {
|
|
createMeasurement: mock.fn(),
|
|
awaitMeasurement: mock.fn(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reusable mock factory for RedBean
|
|
* @returns {object} Mocked RedBean
|
|
*/
|
|
function createRedbeanMock() {
|
|
return {
|
|
exec: mock.fn(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reusable mock factory for Globalping response
|
|
* @param {object} data Response data
|
|
* @returns {object} Mocked Globalping response
|
|
*/
|
|
function createMockResponse(data) {
|
|
return {
|
|
ok: true,
|
|
response: {
|
|
status: 200,
|
|
},
|
|
data,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a successful ping measurement response
|
|
* @returns {object} Mock measurement response
|
|
*/
|
|
function createPingMeasurement() {
|
|
return {
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
type: "ping",
|
|
status: "finished",
|
|
createdAt: "2025-11-05T08:25:33.173Z",
|
|
updatedAt: "2025-11-05T08:25:34.750Z",
|
|
target: "google.com",
|
|
probesCount: 1,
|
|
locations: [{ magic: "us-east-1" }],
|
|
results: [
|
|
{
|
|
probe: {
|
|
continent: "NA",
|
|
region: "Northern America",
|
|
country: "US",
|
|
state: "VA",
|
|
city: "Ashburn",
|
|
asn: 14618,
|
|
longitude: -77.49,
|
|
latitude: 39.04,
|
|
network: "Amazon.com",
|
|
tags: [
|
|
"aws-us-east-1",
|
|
"aws",
|
|
"datacenter-network",
|
|
"u-cloudlookingglass:aws-us-east-1-use1-az6",
|
|
"u-cloudlookingglass:aws-us-east-1-use1-az6-net",
|
|
],
|
|
resolvers: ["private"],
|
|
},
|
|
result: {
|
|
status: "finished",
|
|
rawOutput:
|
|
"PING (142.251.16.100) 56(84) bytes of data.\n64 bytes from bl-in-f100.1e100.net (142.251.16.100): icmp_seq=1 ttl=106 time=2.07 ms\n64 bytes from bl-in-f100.1e100.net (142.251.16.100): icmp_seq=2 ttl=106 time=2.08 ms\n64 bytes from bl-in-f100.1e100.net (142.251.16.100): icmp_seq=3 ttl=106 time=2.35 ms\n\n--- ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 1002ms\nrtt min/avg/max/mdev = 2.073/2.169/2.351/0.128 ms",
|
|
resolvedAddress: "142.251.16.100",
|
|
resolvedHostname: "bl-in-f100.1e100.net",
|
|
timings: [
|
|
{ ttl: 106, rtt: 2.07 },
|
|
{ ttl: 106, rtt: 2.08 },
|
|
{ ttl: 106, rtt: 2.35 },
|
|
],
|
|
stats: {
|
|
min: 2.073,
|
|
max: 2.351,
|
|
avg: 2.169,
|
|
total: 3,
|
|
loss: 0,
|
|
rcv: 3,
|
|
drop: 0,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a successful HTTP measurement response
|
|
* @returns {object} Mock measurement response
|
|
*/
|
|
function createHttpMeasurement() {
|
|
return {
|
|
id: "2m6DeD067jeT6licX0011zF2x",
|
|
type: "http",
|
|
status: "finished",
|
|
createdAt: "2025-11-05T08:27:29.034Z",
|
|
updatedAt: "2025-11-05T08:27:30.718Z",
|
|
target: "google.com",
|
|
probesCount: 1,
|
|
locations: [{ magic: "New York" }],
|
|
results: [
|
|
{
|
|
probe: {
|
|
continent: "NA",
|
|
region: "Northern America",
|
|
country: "US",
|
|
state: "NY",
|
|
city: "New York",
|
|
asn: 49683,
|
|
longitude: -74.01,
|
|
latitude: 40.71,
|
|
network: "MASSIVEGRID",
|
|
tags: ["datacenter-network", "u-gbzret4d"],
|
|
resolvers: ["private"],
|
|
},
|
|
result: {
|
|
status: "finished",
|
|
resolvedAddress: "209.85.201.101",
|
|
headers: {
|
|
location: "https://www.google.com/",
|
|
"content-type": "text/html; charset=UTF-8",
|
|
"content-security-policy-report-only":
|
|
"object-src 'none';base-uri 'self';script-src 'nonce-Eft2LKpM01f69RvQoV6QJA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp",
|
|
date: "Wed, 05 Nov 2025 08:27:30 GMT",
|
|
expires: "Fri, 05 Dec 2025 08:27:30 GMT",
|
|
"cache-control": "public, max-age=2592000",
|
|
server: "gws",
|
|
"content-length": "220",
|
|
"x-xss-protection": "0",
|
|
"x-frame-options": "SAMEORIGIN",
|
|
"alt-svc": 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
|
|
connection: "close",
|
|
},
|
|
rawHeaders:
|
|
"Location: https://www.google.com/\nContent-Type: text/html; charset=UTF-8\nContent-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-Eft2LKpM01f69RvQoV6QJA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp\nDate: Wed, 05 Nov 2025 08:27:30 GMT\nExpires: Fri, 05 Dec 2025 08:27:30 GMT\nCache-Control: public, max-age=2592000\nServer: gws\nContent-Length: 220\nX-XSS-Protection: 0\nX-Frame-Options: SAMEORIGIN\nAlt-Svc: h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000\nConnection: close",
|
|
rawBody: null,
|
|
rawOutput:
|
|
"HTTP/1.1 301\nLocation: https://www.google.com/\nContent-Type: text/html; charset=UTF-8\nContent-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-Eft2LKpM01f69RvQoV6QJA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp\nDate: Wed, 05 Nov 2025 08:27:30 GMT\nExpires: Fri, 05 Dec 2025 08:27:30 GMT\nCache-Control: public, max-age=2592000\nServer: gws\nContent-Length: 220\nX-XSS-Protection: 0\nX-Frame-Options: SAMEORIGIN\nAlt-Svc: h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000\nConnection: close",
|
|
truncated: false,
|
|
statusCode: 301,
|
|
statusCodeName: "Moved Permanently",
|
|
timings: {
|
|
total: 1440,
|
|
download: 1,
|
|
firstByte: 1391,
|
|
dns: 9,
|
|
tls: 22,
|
|
tcp: 16,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a successful DNS measurement response
|
|
* @returns {object} Mock measurement response
|
|
*/
|
|
function createDnsMeasurement() {
|
|
return {
|
|
id: "2g8T7V3OwXG3JV6Y10011zF2v",
|
|
type: "dns",
|
|
status: "finished",
|
|
createdAt: "2025-11-05T08:30:00.000Z",
|
|
updatedAt: "2025-11-05T08:30:01.000Z",
|
|
target: "example.com",
|
|
probesCount: 1,
|
|
locations: [{ magic: "us-east-1" }],
|
|
results: [
|
|
{
|
|
probe: {
|
|
continent: "NA",
|
|
region: "Northern America",
|
|
country: "US",
|
|
state: "NY",
|
|
city: "New York",
|
|
asn: 49683,
|
|
longitude: -74.01,
|
|
latitude: 40.71,
|
|
network: "MASSIVEGRID",
|
|
tags: ["datacenter-network", "u-gbzret4d"],
|
|
resolvers: ["private"],
|
|
},
|
|
result: {
|
|
status: "finished",
|
|
rawOutput: ";; ANSWER SECTION:\nexample.com.\t\t86400\tIN\tA\t93.184.216.34",
|
|
answers: [
|
|
{
|
|
name: "example.com.",
|
|
type: "A",
|
|
ttl: 86400,
|
|
class: "IN",
|
|
value: "93.184.216.34",
|
|
},
|
|
],
|
|
timings: {
|
|
total: 25,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|