// biome-ignore-all lint/suspicious/noExplicitAny: Any in tests
import assert from "node:assert";
import { afterEach, beforeEach, describe, it, mock } from "node:test";
import { createTestFastifyInstance, mockLogger } from "../test-utils/setup.js";
import { createTestRoutes } from "./test.js";
describe("Test Routes", () => {
    let app;
    let originalFetch;
    let fetchMock;
    beforeEach(async () => {
        app = await createTestFastifyInstance();
        await app.register(createTestRoutes, {
            prefix: "/test",
            logger: mockLogger,
        });
        await app.ready();
        // Mock global fetch
        originalFetch = global.fetch;
        fetchMock = mock.fn(global.fetch);
        global.fetch = fetchMock;
    });
    afterEach(async () => {
        await app.close();
        // Restore original fetch
        global.fetch = originalFetch;
        fetchMock.mock.resetCalls();
    });
    describe("POST /test/", () => {
        it("should proxy GET request to the target url", async () => {
            // Mock successful response
            fetchMock.mock.mockImplementation(async () => {
                return new Response(JSON.stringify({ message: "success" }), {
                    status: 200,
                    statusText: "OK",
                    headers: {
                        "content-type": "application/json",
                    },
                });
            });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://localhost:9000/v1/hello",
                    deployment: "local",
                    headers: {
                        "x-custom-header": "test-value",
                    },
                },
            });
            assert.strictEqual(response.statusCode, 200);
            const body = JSON.parse(response.body);
            assert.strictEqual(body.status, 200);
            assert.strictEqual(body.statusText, "OK");
            assert.strictEqual(body.headers["content-type"], "application/json");
            assert.strictEqual(body.body, JSON.stringify({ message: "success" }));
            // Verify fetch was called with correct URL
            assert.strictEqual(fetchMock.mock.calls.length, 1);
            assert.strictEqual(fetchMock.mock.calls[0].arguments[0], "http://localhost:9000/v1/hello");
        });
        it("should proxy POST request with body", async () => {
            fetchMock.mock.mockImplementation(async () => {
                return new Response(JSON.stringify({ created: true }), {
                    status: 201,
                    statusText: "Created",
                    headers: {
                        "content-type": "application/json",
                    },
                });
            });
            const testPayload = JSON.stringify({ name: "test" });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "POST",
                    url: "http://localhost:9000/v1/items",
                    headers: {
                        "content-type": "application/json",
                    },
                    body: testPayload,
                },
            });
            assert.strictEqual(response.statusCode, 200);
            const body = JSON.parse(response.body);
            assert.strictEqual(body.status, 201);
            assert.strictEqual(body.body, JSON.stringify({ created: true }));
            // Verify fetch was called with body
            const fetchCall = fetchMock.mock.calls[0].arguments;
            assert.strictEqual(fetchCall[0], "http://localhost:9000/v1/items");
            assert.strictEqual(fetchCall[1]?.method, "POST");
            assert.strictEqual(fetchCall[1]?.body, testPayload);
        });
        it("should filter out problematic headers", async () => {
            fetchMock.mock.mockImplementation(async () => {
                return new Response("ok", { status: 200 });
            });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://localhost:9000/v1/hello",
                    headers: {
                        host: "example.com",
                        connection: "keep-alive",
                        "x-custom": "should-be-kept",
                    },
                },
            });
            assert.strictEqual(response.statusCode, 200);
            // Verify headers were cleaned
            const fetchCall = fetchMock.mock.calls[0].arguments;
            const headers = fetchCall[1]?.headers;
            assert.ok(headers);
            assert.strictEqual(headers["x-custom"], "should-be-kept");
            assert.strictEqual(headers.host, undefined);
            assert.strictEqual(headers.connection, undefined);
        });
        it("should proxy to whatever host the url targets", async () => {
            fetchMock.mock.mockImplementation(async () => {
                return new Response("ok", { status: 200 });
            });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://custom-host:8080/v1/hello",
                },
            });
            assert.strictEqual(response.statusCode, 200);
            // Verify custom URL was used
            assert.strictEqual(fetchMock.mock.calls[0].arguments[0], "http://custom-host:8080/v1/hello");
        });
        it("should reject requests without a url", async () => {
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                },
            });
            assert.strictEqual(response.statusCode, 400);
        });
        it("should reject a url that is not an absolute http(s) URL", async () => {
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "/v1/hello",
                },
            });
            assert.strictEqual(response.statusCode, 400);
            assert.strictEqual(response.headers["x-test-proxy-error"], "invalid_request");
            // No upstream request should be attempted for an unrelayable url.
            assert.strictEqual(fetchMock.mock.calls.length, 0);
        });
        it("should return a proxy-level error on gateway connection errors", async () => {
            // Mock connection error
            fetchMock.mock.mockImplementation(async () => {
                throw new TypeError("fetch failed");
            });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://localhost:9000/v1/hello",
                },
            });
            // Proxy-level failures are non-2xx problem+json flagged with the
            // x-test-proxy-error header (never a 200-wrapped body).
            assert.strictEqual(response.statusCode, 502);
            assert.strictEqual(response.headers["x-test-proxy-error"], "upstream_request_failed");
            assert.ok(String(response.headers["content-type"]).includes("application/problem+json"));
            const problemDetails = JSON.parse(response.body);
            assert.strictEqual(problemDetails.status, 502);
            assert.strictEqual(problemDetails.code, "upstream_request_failed");
            assert.strictEqual(problemDetails.title, "Local Gateway Unavailable");
            assert.ok(problemDetails.detail.includes("local Zuplo gateway"));
        });
        it("should reject GET requests with a body", async () => {
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://localhost:9000/v1/hello",
                    body: "this should be rejected",
                },
            });
            assert.strictEqual(response.statusCode, 400);
            assert.strictEqual(response.headers["x-test-proxy-error"], "get_or_head_with_body");
            // The request must not be relayed.
            assert.strictEqual(fetchMock.mock.calls.length, 0);
        });
        it("should reject HEAD requests with a body", async () => {
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "HEAD",
                    url: "http://localhost:9000/v1/hello",
                    body: "this should be rejected",
                },
            });
            assert.strictEqual(response.statusCode, 400);
            assert.strictEqual(response.headers["x-test-proxy-error"], "get_or_head_with_body");
            assert.strictEqual(fetchMock.mock.calls.length, 0);
        });
        it("should handle different HTTP status codes", async () => {
            fetchMock.mock.mockImplementation(async () => {
                return new Response(JSON.stringify({ error: "Not Found" }), {
                    status: 404,
                    statusText: "Not Found",
                    headers: {
                        "content-type": "application/json",
                    },
                });
            });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://localhost:9000/v1/nonexistent",
                },
            });
            assert.strictEqual(response.statusCode, 200);
            const body = JSON.parse(response.body);
            assert.strictEqual(body.status, 404);
            assert.strictEqual(body.statusText, "Not Found");
        });
        it("should preserve response headers", async () => {
            fetchMock.mock.mockImplementation(async () => {
                return new Response("ok", {
                    status: 200,
                    headers: {
                        "content-type": "text/plain",
                        "x-custom-response": "test-value",
                        "cache-control": "no-cache",
                    },
                });
            });
            const response = await app.inject({
                method: "POST",
                url: "/test/",
                payload: {
                    method: "GET",
                    url: "http://localhost:9000/v1/hello",
                },
            });
            assert.strictEqual(response.statusCode, 200);
            const body = JSON.parse(response.body);
            assert.strictEqual(body.headers["content-type"], "text/plain");
            assert.strictEqual(body.headers["x-custom-response"], "test-value");
            assert.strictEqual(body.headers["cache-control"], "no-cache");
        });
    });
});
//# sourceMappingURL=test.spec.js.map