// Mock heavy dependencies
jest.mock("simple-git", () => ({
    simpleGit: jest.fn(() => ({
        checkIsRepo: jest.fn().mockResolvedValue(false),
    })),
}));
jest.mock("../utils/logger.js", () => ({
    logger: {
        info: jest.fn(),
        debug: jest.fn(),
        error: jest.fn(),
        warning: jest.fn(),
    },
}));
// fast-glob must return real file paths from the temp directory.
// We use the real implementation by not mocking it.
// Jest resolves .js → .ts via moduleNameMapper in jest.config.
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import { TestDiscoveryService } from "./TestDiscoveryService.js";
import { TestSource } from "../types/TestAnalysis.js";
describe("TestDiscoveryService", () => {
    let service;
    let tmpDir;
    beforeEach(() => {
        service = new TestDiscoveryService();
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-discovery-"));
    });
    afterEach(() => {
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    function writeFile(relPath, content) {
        const fullPath = path.join(tmpDir, relPath);
        fs.mkdirSync(path.dirname(fullPath), { recursive: true });
        fs.writeFileSync(fullPath, content, "utf-8");
        return fullPath;
    }
    // ── classifyTestFiles ──
    describe("classifyTestFiles", () => {
        it("classifies files with Skyramp marker as skyramp", async () => {
            writeFile("tests/test_orders_smoke.py", '# Generated by Skyramp\nimport skyramp\nskyramp generate smoke rest');
            const result = await service.discoverTests(tmpDir);
            const skyrampTests = result.tests.filter((t) => t.source === TestSource.Skyramp);
            expect(skyrampTests.length).toBe(1);
            expect(skyrampTests[0].testFile).toContain("test_orders_smoke.py");
        });
        it("classifies test files without Skyramp marker as external", async () => {
            writeFile("tests/test_orders.py", 'import pytest\nimport requests\nresponse = requests.get("/api/orders")');
            const result = await service.discoverTests(tmpDir);
            const externalTests = result.tests.filter((t) => t.source === TestSource.External);
            expect(externalTests.length).toBe(1);
            expect(externalTests[0].testFile).toContain("test_orders.py");
        });
        it("skips non-test files that lack the Skyramp marker", async () => {
            writeFile("src/utils.py", 'def helper():\n    return True');
            const result = await service.discoverTests(tmpDir);
            expect(result.tests.length).toBe(0);
        });
        it("handles both Skyramp and external tests in the same repo", async () => {
            writeFile("tests/test_orders_smoke.py", '# Generated by Skyramp\nskyramp generate smoke rest');
            writeFile("tests/test_api.py", 'import pytest\nrequests.get("/api/users")');
            const result = await service.discoverTests(tmpDir);
            expect(result.tests.length).toBe(2);
            expect(result.tests.filter((t) => t.source === TestSource.Skyramp).length).toBe(1);
            expect(result.tests.filter((t) => t.source === TestSource.External).length).toBe(1);
        });
    });
    // ── isExternalTestFile (via classification) ──
    describe("external test file detection", () => {
        const patterns = [
            ["test_orders.py", true],
            ["orders_test.py", true],
            ["orders.test.ts", true],
            ["orders.spec.js", true],
            ["OrdersTest.java", true],
            ["orders_test.go", true],
            ["orders_spec.rb", true],
            ["OrdersTest.kt", true],
            ["OrdersTests.cs", true],
            ["orders_test.rs", true],
            ["OrdersTest.php", true],
            ["test_orders.rb", true],
            ["utils.py", false],
            ["main.ts", false],
            ["main.go", false],
        ];
        it.each(patterns)("file %s → isExternal: %s", async (filename, expected) => {
            writeFile(filename, 'import pytest\ndef test_something():\n    pass');
            const result = await service.discoverTests(tmpDir);
            const externalTests = result.tests.filter((t) => t.source === TestSource.External);
            if (expected) {
                expect(externalTests.length).toBe(1);
            }
            else {
                expect(externalTests.length).toBe(0);
            }
        });
        it("detects files in test directories", async () => {
            writeFile("tests/helpers.py", 'import requests\nrequests.get("/api/health")');
            const result = await service.discoverTests(tmpDir);
            const externalTests = result.tests.filter((t) => t.source === TestSource.External);
            expect(externalTests.length).toBe(1);
        });
        it("detects files in __tests__ directory", async () => {
            writeFile("src/__tests__/api.ts", 'import axios from "axios";\naxios.get("/api/items");');
            const result = await service.discoverTests(tmpDir);
            const externalTests = result.tests.filter((t) => t.source === TestSource.External);
            expect(externalTests.length).toBe(1);
        });
    });
    // ── extractExternalEndpoints ──
    describe("extractExternalEndpoints", () => {
        it("extracts endpoints from Python requests library", async () => {
            writeFile("test_api.py", 'import requests\nrequests.get("/api/orders")\nrequests.post("/api/orders")');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
            expect(ext.apiEndpoint).toContain("POST /api/orders");
        });
        it("extracts endpoints from Python test client", async () => {
            writeFile("test_api.py", 'def test_orders(client):\n    client.get("/api/orders")\n    client.post("/api/orders")');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
        });
        it("extracts endpoints from axios", async () => {
            writeFile("api.test.ts", 'import axios from "axios";\naxios.get("/api/items");\naxios.post("/api/items");');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/items");
            expect(ext.apiEndpoint).toContain("POST /api/items");
        });
        it("extracts endpoints from supertest", async () => {
            writeFile("api.test.ts", 'import supertest from "supertest";\nconst req = supertest(app);\nreq.get("/api/users");\nreq.post("/api/users");');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/users");
            expect(ext.apiEndpoint).toContain("POST /api/users");
        });
        it("extracts endpoints from fetch with explicit method", async () => {
            writeFile("api.test.ts", 'fetch("/api/orders", { method: "POST" });\nfetch("/api/orders");');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("POST /api/orders");
            expect(ext.apiEndpoint).toContain("GET /api/orders");
        });
        it("strips base URL from endpoints", async () => {
            writeFile("test_api.py", 'import requests\nrequests.get("http://localhost:8000/api/orders")');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
            expect(ext.apiEndpoint).not.toContain("localhost");
        });
        it("extracts endpoints from Java RestAssured", async () => {
            writeFile("OrdersTest.java", 'import io.restassured.*;\ngiven().when().get("/api/orders").then();');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
        });
        it("extracts endpoints from Go http.NewRequest", async () => {
            writeFile("orders_test.go", 'package main\nimport "testing"\nimport "net/http"\nfunc TestOrders(t *testing.T) {\n  req, _ := http.NewRequest("GET", "/api/orders", nil)\n  http.NewRequest("POST", "/api/orders", body)\n}');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
            expect(ext.apiEndpoint).toContain("POST /api/orders");
        });
        it("extracts endpoints from C# HttpClient", async () => {
            writeFile("OrdersTests.cs", 'using Xunit;\npublic class OrdersTests {\n  [Fact]\n  public async Task GetOrders() {\n    await client.GetAsync("/api/orders");\n    await client.PostAsync("/api/orders", content);\n  }\n}');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
            expect(ext.apiEndpoint).toContain("POST /api/orders");
        });
        it("extracts endpoints from Ruby Net::HTTP", async () => {
            writeFile("orders_spec.rb", 'require "rspec"\nRSpec.describe "Orders" do\n  it "lists" do\n    Net::HTTP.get("/api/orders")\n    Net::HTTP.post("/api/orders", body)\n  end\nend');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext).toBeDefined();
            expect(ext.apiEndpoint).toContain("GET /api/orders");
        });
    });
    // ── detectExternalTestType ──
    describe("detectExternalTestType", () => {
        it("detects integration from filename", async () => {
            writeFile("test_orders_integration.py", "import pytest\ndef test(): pass");
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.testType).toBe("integration");
        });
        it("detects e2e from filename", async () => {
            writeFile("test_e2e_flow.py", "import pytest\ndef test(): pass");
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.testType).toBe("e2e");
        });
        it("detects contract from filename", async () => {
            writeFile("test_contract.py", "import pytest\ndef test(): pass");
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.testType).toBe("contract");
        });
        it("falls back to integration for files with HTTP calls", async () => {
            writeFile("test_api.py", 'import requests\nrequests.get("/api/orders")');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.testType).toBe("integration");
        });
        it("returns unknown when no heuristic matches", async () => {
            writeFile("test_utils.py", "def test_helper():\n    assert True");
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.testType).toBe("unknown");
        });
    });
    // ── detectExternalFramework ──
    describe("detectExternalFramework", () => {
        it("detects pytest", async () => {
            writeFile("test_api.py", "import pytest\ndef test(): pass");
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("pytest");
        });
        it("detects jest", async () => {
            writeFile("api.test.ts", 'import { jest } from "jest";\ndescribe("api", () => {});');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("jest");
        });
        it("detects junit", async () => {
            writeFile("OrdersTest.java", "import org.junit.Test;\n@Test\npublic void test() {}");
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("junit");
        });
        it("detects supertest", async () => {
            writeFile("api.test.ts", 'import supertest from "supertest";\nconst req = supertest(app);');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("supertest");
        });
        it("detects go-testing", async () => {
            writeFile("orders_test.go", 'package main\nimport "testing"\nfunc TestOrders(t *testing.T) {}');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("go-testing");
        });
        it("detects rspec", async () => {
            writeFile("orders_spec.rb", 'require "rspec"\nRSpec.describe "Orders" do\n  it "works" do; end\nend');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("rspec");
        });
        it("detects xunit", async () => {
            writeFile("OrdersTests.cs", 'using Xunit;\npublic class OrdersTests {\n  [Fact]\n  public void Test() {}\n}');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("xunit");
        });
        it("detects phpunit", async () => {
            writeFile("OrdersTest.php", '<?php\nuse PHPUnit\\Framework\\TestCase;\nclass OrdersTest extends TestCase {\n  public function testList() {}\n}');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("phpunit");
        });
        it("detects rust-test", async () => {
            writeFile("orders_test.rs", '#[cfg(test)]\nmod tests {\n  #[test]\n  fn test_orders() {}\n}');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext.framework).toBe("rust-test");
        });
    });
    // ── source field ──
    describe("source field tagging", () => {
        it("tags Skyramp tests with source: skyramp", async () => {
            writeFile("test_smoke.py", '# Generated by Skyramp\nskyramp generate smoke rest');
            const result = await service.discoverTests(tmpDir);
            expect(result.tests[0].source).toBe(TestSource.Skyramp);
        });
        it("tags external tests with source: external", async () => {
            writeFile("test_api.py", "import pytest\ndef test(): pass");
            const result = await service.discoverTests(tmpDir);
            expect(result.tests[0].source).toBe(TestSource.External);
        });
    });
    // ── relevance-based partitioning ──
    describe("relevance-based external test partitioning", () => {
        it("returns all external test files without a hard cap in PR mode", async () => {
            for (let i = 0; i < 60; i++) {
                writeFile(`test_file_${i}.py`, `def test_${i}(): pass`);
            }
            const result = await service.discoverTests(tmpDir, { changedResources: ["file"] });
            const externalTests = result.tests.filter((t) => t.source === TestSource.External);
            expect(externalTests.length).toBe(60);
        });
        it("extracts endpoints only for relevant files when changedResources provided", async () => {
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            writeFile("test_products_api.py", 'import requests\nrequests.get("/api/products")');
            const result = await service.discoverTests(tmpDir, { changedResources: ["orders"] });
            const externalTests = result.tests.filter((t) => t.source === TestSource.External);
            expect(externalTests.length).toBe(2);
            const ordersTest = externalTests.find(t => t.testFile.includes("orders"));
            const productsTest = externalTests.find(t => t.testFile.includes("products"));
            // Relevant file gets endpoint extraction
            expect(ordersTest?.apiEndpoint).toContain("GET /api/orders");
            // Low-relevance file gets name-only entry (no endpoint extraction)
            expect(productsTest?.apiEndpoint).toBe("");
        });
        it("returns relevantExternalTestPaths including files where extraction may have failed", async () => {
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            writeFile("test_orders_helpers.py", 'def create_order(): pass'); // no extractable endpoints
            writeFile("test_products_api.py", 'import requests\nrequests.get("/api/products")');
            const result = await service.discoverTests(tmpDir, { changedResources: ["orders"] });
            // Both orders-related files should be in relevantExternalTestPaths
            expect(result.relevantExternalTestPaths.some(p => p.includes("test_orders_api"))).toBe(true);
            expect(result.relevantExternalTestPaths.some(p => p.includes("test_orders_helpers"))).toBe(true);
            // Products file should NOT be in relevantExternalTestPaths
            expect(result.relevantExternalTestPaths.some(p => p.includes("test_products_api"))).toBe(false);
        });
        it("does full extraction for all files when no changedResources provided", async () => {
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            const result = await service.discoverTests(tmpDir);
            const ext = result.tests.find((t) => t.source === TestSource.External);
            expect(ext?.apiEndpoint).toContain("GET /api/orders");
        });
        it("caps full-repo mode at MAX_EXTERNAL_FULL_REPO for full extraction", async () => {
            // Create 12 external test files to test the boundary (MAX_EXTERNAL_FULL_REPO=100 is too
            // large to create in a unit test, so we verify the partitioning logic is wired correctly)
            for (let i = 0; i < 12; i++) {
                writeFile(`test_api_${i}.py`, `import requests\nrequests.get("/api/items/${i}")`);
            }
            const result = await service.discoverTests(tmpDir);
            const externalTests = result.tests.filter(t => t.source === TestSource.External);
            // All 12 files should be discovered
            expect(externalTests.length).toBe(12);
            // In full-repo mode (no changedResources) all files under cap go to relevantExternal
            expect(result.relevantExternalTestPaths.length).toBe(12);
            // All relevant files get full extraction (apiEndpoint populated)
            const withEndpoints = externalTests.filter(t => t.apiEndpoint !== "");
            expect(withEndpoints.length).toBe(12);
        });
        it("returns zero external tests when changedResources is empty array (PR mode, no endpoints)", async () => {
            // Simulate PR mode where a parsed diff produced no detected endpoints:
            // newEndpoints=[], modifiedEndpoints=[], and removedEndpoints=[] → changedResources = []
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            writeFile("test_products_api.py", 'import requests\nrequests.get("/api/products")');
            const result = await service.discoverTests(tmpDir, { changedResources: [] });
            const externalTests = result.tests.filter(t => t.source === TestSource.External);
            // Empty changedResources = PR mode with no detected endpoints → zero external tests
            expect(externalTests.length).toBe(0);
            expect(result.relevantExternalTestPaths.length).toBe(0);
        });
        it("still returns external tests in full-repo mode (changedResources undefined)", async () => {
            // Full-repo mode: changedResources not provided → should use capped full-repo behavior
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            writeFile("test_products_api.py", 'import requests\nrequests.get("/api/products")');
            const result = await service.discoverTests(tmpDir); // no options → undefined
            const externalTests = result.tests.filter(t => t.source === TestSource.External);
            expect(externalTests.length).toBe(2);
            expect(result.relevantExternalTestPaths.length).toBe(2);
        });
        it("Skyramp tests are unaffected by empty changedResources", async () => {
            writeFile("tests/test_orders_smoke.py", '# Generated by Skyramp\nskyramp generate smoke rest');
            writeFile("test_external.py", 'import pytest\ndef test(): pass');
            const result = await service.discoverTests(tmpDir, { changedResources: [] });
            const skyrampTests = result.tests.filter(t => t.source === TestSource.Skyramp);
            const externalTests = result.tests.filter(t => t.source === TestSource.External);
            // Skyramp tests always discovered regardless of changedResources
            expect(skyrampTests.length).toBe(1);
            // External tests suppressed in PR-mode-no-endpoints
            expect(externalTests.length).toBe(0);
        });
        it("returns external tests as name-only with ['unknown'] sentinel (unresolvable resources)", async () => {
            // When diff endpoints exist but all paths resolve to "unknown" (e.g. decorator-relative
            // paths like "/{order_id}"), changedResources = ["unknown"]. External tests should be
            // discovered (not skipped) but scored as low-relevance since "unknown" won't match filenames.
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            writeFile("test_products_api.py", 'import requests\nrequests.get("/api/products")');
            const result = await service.discoverTests(tmpDir, { changedResources: ["unknown"] });
            const externalTests = result.tests.filter(t => t.source === TestSource.External);
            // External tests discovered (not skipped like empty array)
            expect(externalTests.length).toBe(2);
            // But all are low-relevance (name-only) since "unknown" doesn't match any filename tokens
            expect(result.relevantExternalTestPaths.length).toBe(0);
        });
        it("low-relevance files have empty apiEndpoint and empty framework in PR mode", async () => {
            writeFile("test_orders_api.py", 'import requests\nrequests.get("/api/orders")');
            writeFile("test_products_api.py", 'import requests\nrequests.get("/api/products")');
            const result = await service.discoverTests(tmpDir, { changedResources: ["orders"] });
            const productsTest = result.tests.find(t => t.testFile.includes("test_products_api"));
            expect(productsTest?.source).toBe(TestSource.External);
            expect(productsTest?.apiEndpoint).toBe("");
            expect(productsTest?.framework).toBe("");
        });
    });
});
