// Mock @skyramp/skyramp before importing TestGenerationService to avoid
// pulling in playwright (dynamic imports fail on Node 18 in CI).
jest.mock("@skyramp/skyramp", () => ({
    SkyrampClient: jest.fn().mockImplementation(() => ({})),
}));
import { TestGenerationService } from "./TestGenerationService.js";
import { TestType } from "../types/TestTypes.js";
import { AUTH_PLACEHOLDER_TOKEN } from "../types/TestTypes.js";
import fs from "fs";
import os from "os";
import path from "path";
class StubService extends TestGenerationService {
    buildGenerationOptions() {
        return {};
    }
    getTestType() {
        return TestType.SMOKE;
    }
    validate(params) {
        return this.validateInputs(params);
    }
}
const BASE = {
    outputDir: "/tmp/tests",
    force: true,
};
function validateOutput(framework, output) {
    const svc = new StubService();
    return svc.validate({ ...BASE, framework, output });
}
function playwrightError(result) {
    for (const c of result.content) {
        if (c.type === "text" && c.text.includes("Playwright")) {
            return c.text;
        }
    }
    return undefined;
}
describe("TestGenerationService — Playwright filename validation", () => {
    it.each([
        "my_test.spec.ts",
        "my_test.test.ts",
        "my_test.spec.js",
        "my_test.test.js",
    ])("accepts valid Playwright filename: %s", (filename) => {
        const result = validateOutput("playwright", filename);
        expect(playwrightError(result)).toBeUndefined();
    });
    it.each([
        "my_test.ts",
        "my_test.py",
        "my_test.java",
        "tests",
        "my_test.js",
    ])("rejects invalid Playwright filename: %s", (filename) => {
        const result = validateOutput("playwright", filename);
        expect(playwrightError(result)).toBeDefined();
        expect(playwrightError(result)).toContain("Playwright requires");
    });
    it("suggests .spec.ts replacement for .ts file", () => {
        const err = playwrightError(validateOutput("playwright", "crud_items.ts"));
        expect(err).toContain("crud_items.spec.ts");
    });
    it("suggests .spec.ts replacement for .js file", () => {
        const err = playwrightError(validateOutput("playwright", "crud_items.js"));
        expect(err).toContain("crud_items.spec.ts");
    });
    it("appends .spec.ts for non-JS extension (e.g. .java)", () => {
        const err = playwrightError(validateOutput("playwright", "my_test.java"));
        expect(err).toContain("my_test.java.spec.ts");
    });
    it("appends .spec.ts for extensionless filename", () => {
        const err = playwrightError(validateOutput("playwright", "tests"));
        expect(err).toContain("tests.spec.ts");
    });
    it("skips validation when output is empty string", () => {
        expect(playwrightError(validateOutput("playwright", ""))).toBeUndefined();
    });
    it("skips validation for non-playwright frameworks", () => {
        expect(playwrightError(validateOutput("pytest", "my_test.py"))).toBeUndefined();
    });
    it("is case-insensitive on framework name", () => {
        expect(playwrightError(validateOutput("Playwright", "bad.ts"))).toBeDefined();
    });
});
describe("TestGenerationService — authType/authScheme not passed to library", () => {
    let mockGenerateRestTest;
    beforeEach(() => {
        mockGenerateRestTest = jest.fn().mockResolvedValue("Test generation success");
        const { SkyrampClient } = require("@skyramp/skyramp");
        SkyrampClient.mockImplementation(() => ({
            generateRestTest: mockGenerateRestTest,
        }));
    });
    it("maps authScheme to authType for the npm client", async () => {
        const svc = new StubService();
        svc.buildGenerationOptions = () => ({
            authHeader: "Authorization",
            authScheme: "Token",
            authType: "Token",
            outputDir: "/tmp/tests",
            language: "python",
            framework: "pytest",
        });
        svc.handleApiAnalysis = async () => null;
        await svc.generateTest({ ...BASE });
        const callArgs = mockGenerateRestTest.mock.calls[0][0];
        expect(callArgs.authType).toBe("Token");
        expect(callArgs.authScheme).toBeUndefined();
        // authHeader is cleared for Authorization when authType is set (CLI conflict fix)
        expect(callArgs.authHeader).toBeUndefined();
    });
    it("passes authHeader through to library without modification", async () => {
        const svc = new StubService();
        svc.buildGenerationOptions = () => ({
            authHeader: "X-Api-Key",
            outputDir: "/tmp/tests",
            language: "python",
            framework: "pytest",
        });
        svc.handleApiAnalysis = async () => null;
        await svc.generateTest({ ...BASE });
        const callArgs = mockGenerateRestTest.mock.calls[0][0];
        expect(callArgs.authHeader).toBe("X-Api-Key");
        expect(callArgs.authType).toBeUndefined();
        expect(callArgs.authScheme).toBeUndefined();
    });
});
describe("TestGenerationService — extractAuthFromTrace", () => {
    let tmpDir;
    beforeEach(() => {
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-test-"));
    });
    afterEach(() => {
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    function writeTrace(requests) {
        const filePath = path.join(tmpDir, "trace.json");
        fs.writeFileSync(filePath, JSON.stringify(requests), "utf8");
        return filePath;
    }
    function extract(filePath, include, exclude) {
        const svc = new StubService();
        return svc.extractAuthFromTrace(filePath, include, exclude);
    }
    it("extracts Authorization + Bearer scheme from trace", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Authorization: [`Bearer ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "Authorization",
            authScheme: "Bearer",
        });
    });
    it("extracts Authorization + Token scheme from trace", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Authorization: [`Token ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "Authorization",
            authScheme: "Token",
        });
    });
    it("extracts X-Api-Key with empty scheme from trace", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    "X-Api-Key": [AUTH_PLACEHOLDER_TOKEN],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "X-Api-Key",
            authScheme: "",
        });
    });
    it("returns null when no auth header in trace", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                },
            },
        ]);
        expect(extract(fp)).toBeNull();
    });
    it("returns null for invalid JSON", () => {
        const fp = path.join(tmpDir, "bad.json");
        fs.writeFileSync(fp, "not json", "utf8");
        expect(extract(fp)).toBeNull();
    });
    it("returns null for non-existent file", () => {
        expect(extract("/tmp/does-not-exist-xyz.json")).toBeNull();
    });
    it("skips Pragma header (standard HTTP/1.0 caching header, not auth)", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Cache-Control": ["no-cache"],
                    Pragma: ["no-cache"],
                    "User-Agent": ["Mozilla/5.0"],
                },
            },
        ]);
        expect(extract(fp)).toBeNull();
    });
    it("skips Pragma and finds real auth header behind it", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Cache-Control": ["no-cache"],
                    Pragma: ["no-cache"],
                    "User-Agent": ["Mozilla/5.0"],
                    Authorization: [`Bearer ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "Authorization",
            authScheme: "Bearer",
        });
    });
    it("uses include filter to skip noise and find auth on matching requests", () => {
        const fp = writeTrace([
            {
                // requests[0]: unrelated traffic (e.g. Google time sync) with no auth
                Destination: "clients2.google.com",
                Path: "/time/1/current",
                RequestHeaders: {
                    "Cache-Control": ["no-cache"],
                    "User-Agent": ["Mozilla/5.0"],
                },
            },
            {
                // requests[1]: actual API request matching include filter
                Destination: "demoshop.skyramp.dev",
                Path: "/api/v1/products",
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Authorization: [`Bearer ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp, ["demoshop.skyramp.dev/api/*"])).toEqual({
            authHeader: "Authorization",
            authScheme: "Bearer",
        });
    });
    it("uses exclude filter to skip noise and find auth on remaining requests", () => {
        const fp = writeTrace([
            {
                Destination: "clients2.google.com",
                Path: "/time/1/current",
                RequestHeaders: {
                    "Cache-Control": ["no-cache"],
                    "User-Agent": ["Mozilla/5.0"],
                },
            },
            {
                Destination: "demoshop.skyramp.dev",
                Path: "/api/v1/products",
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Authorization: [`Bearer ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp, undefined, ["clients2.google.com/*"])).toEqual({
            authHeader: "Authorization",
            authScheme: "Bearer",
        });
    });
    it("prefers Authorization over Cookie even when Cookie appears first", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Cookie: ["cf_clearance=abc123; _dd_s=xyz"],
                    Authorization: [`Bearer ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "Authorization",
            authScheme: "Bearer",
        });
    });
    it("falls back to known auth header (X-Api-Key) when no Authorization present", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    "Sec-Fetch-Mode": ["cors"],
                    "X-Api-Key": ["my-secret-key"],
                    Traceparent: ["00-abc-def-01"],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "X-Api-Key",
            authScheme: "",
        });
    });
    it("skips standard headers like Accept", () => {
        const fp = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Accept: ["application/json"],
                    Authorization: [`Token ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        expect(extract(fp)).toEqual({
            authHeader: "Authorization",
            authScheme: "Token",
        });
    });
});
describe("TestGenerationService — trace-based auth in executeGeneration", () => {
    let mockGenerateRestTest;
    let tmpDir;
    beforeEach(() => {
        mockGenerateRestTest = jest.fn().mockResolvedValue("Test generation success");
        const { SkyrampClient } = require("@skyramp/skyramp");
        SkyrampClient.mockImplementation(() => ({
            generateRestTest: mockGenerateRestTest,
        }));
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-test-"));
    });
    afterEach(() => {
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    function writeTrace(requests) {
        const filePath = path.join(tmpDir, "trace.json");
        fs.writeFileSync(filePath, JSON.stringify(requests), "utf8");
        return filePath;
    }
    it("populates authScheme from trace when not provided (clears Authorization authHeader)", async () => {
        const traceFile = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Authorization: [`Token ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        const svc = new StubService();
        svc.buildGenerationOptions = () => ({
            traceFilePath: traceFile,
            authHeader: "",
            authScheme: "",
            outputDir: "/tmp/tests",
            language: "python",
            framework: "pytest",
        });
        svc.handleApiAnalysis = async () => null;
        await svc.generateTest({ ...BASE });
        const callArgs = mockGenerateRestTest.mock.calls[0][0];
        // authHeader is cleared for Authorization when authType is set (CLI conflict fix)
        expect(callArgs.authHeader).toBeUndefined();
        expect(callArgs.authType).toBe("Token");
        expect(callArgs.authScheme).toBeUndefined();
    });
    it("populates X-Api-Key from trace with empty authScheme", async () => {
        const traceFile = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    "X-Api-Key": [AUTH_PLACEHOLDER_TOKEN],
                },
            },
        ]);
        const svc = new StubService();
        svc.buildGenerationOptions = () => ({
            traceFilePath: traceFile,
            authHeader: "",
            authScheme: "",
            outputDir: "/tmp/tests",
            language: "python",
            framework: "pytest",
        });
        svc.handleApiAnalysis = async () => null;
        await svc.generateTest({ ...BASE });
        const callArgs = mockGenerateRestTest.mock.calls[0][0];
        expect(callArgs.authHeader).toBe("X-Api-Key");
        expect(callArgs.authType).toBe("");
        expect(callArgs.authScheme).toBeUndefined();
    });
    it("overrides conflicting authScheme with trace value and clears Authorization authHeader", async () => {
        const traceFile = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                    Authorization: [`Token ${AUTH_PLACEHOLDER_TOKEN}`],
                },
            },
        ]);
        const svc = new StubService();
        svc.buildGenerationOptions = () => ({
            traceFilePath: traceFile,
            authHeader: "Authorization",
            authScheme: "Bearer",
            outputDir: "/tmp/tests",
            language: "python",
            framework: "pytest",
        });
        svc.handleApiAnalysis = async () => null;
        await svc.generateTest({ ...BASE });
        const callArgs = mockGenerateRestTest.mock.calls[0][0];
        // authHeader is cleared for Authorization when authType is set (CLI conflict fix)
        expect(callArgs.authHeader).toBeUndefined();
        expect(callArgs.authType).toBe("Token");
        expect(callArgs.authScheme).toBeUndefined();
    });
    it("does not override auth when trace has no auth header (clears Authorization authHeader)", async () => {
        const traceFile = writeTrace([
            {
                RequestHeaders: {
                    "Content-Type": ["application/json"],
                },
            },
        ]);
        const svc = new StubService();
        svc.buildGenerationOptions = () => ({
            traceFilePath: traceFile,
            authHeader: "Authorization",
            authScheme: "Bearer",
            outputDir: "/tmp/tests",
            language: "python",
            framework: "pytest",
        });
        svc.handleApiAnalysis = async () => null;
        await svc.generateTest({ ...BASE });
        const callArgs = mockGenerateRestTest.mock.calls[0][0];
        // authHeader is cleared for Authorization when authType is set (CLI conflict fix)
        expect(callArgs.authHeader).toBeUndefined();
        expect(callArgs.authType).toBe("Bearer");
        expect(callArgs.authScheme).toBeUndefined();
    });
});
