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;
            });
        });

        test("should retry create measurement on status 500", 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.mockImplementationOnce(() => ({
                ok: false,
                response: {
                    status: 500,
                },
            }));
            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);

            const expectedCreateMeasurement = {
                type: "ping",
                target: "example.com",
                inProgressUpdates: false,
                limit: 1,
                locations: [{ magic: "North America" }],
                measurementOptions: {
                    packets: 3,
                    protocol: "ICMP",
                    ipVersion: 4,
                },
            };
            assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 2);
            assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], expectedCreateMeasurement);
            assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[1].arguments[0], expectedCreateMeasurement);

            assert.deepStrictEqual(heartbeat, {
                status: UP,
                msg: "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1) : OK",
                ping: 2.169,
            });
        });
    });

    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;
            });
        });

        test("should retry create measurement on status 500", 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.mockImplementationOnce(() => ({
                ok: false,
                response: {
                    status: 500,
                },
            }));
            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);

            const expectedToken = encodeBase64(monitor.basic_auth_user, monitor.basic_auth_pass);
            const expectedCreateMeasurement = {
                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.strictEqual(mockClient.createMeasurement.mock.calls.length, 2);
            assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], expectedCreateMeasurement);
            assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[1].arguments[0], expectedCreateMeasurement);

            assert.deepStrictEqual(heartbeat, {
                status: UP,
                msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : OK",
                ping: 1440,
            });
        });
    });

    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"],
            ]);
        });

        test("should retry create measurement on status 500", 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.mockImplementationOnce(() => ({
                ok: false,
                response: {
                    status: 500,
                },
            }));
            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);

            const expectedCreateMeasurement = {
                type: "dns",
                target: "example.com",
                inProgressUpdates: false,
                limit: 1,
                locations: [{ magic: "us-east-1" }],
                measurementOptions: {
                    query: { type: "A" },
                    port: 53,
                    protocol: "udp",
                },
            };
            assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 2);
            assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], expectedCreateMeasurement);
            assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[1].arguments[0], expectedCreateMeasurement);

            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"],
            ]);
        });
    });

    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,
                    },
                },
            },
        ],
    };
}
