/**
 * Tests for API Security Tactics Module
 */
import { describe, it, expect } from "vitest";
import { apiTactic } from "../../../../agents/adversary/tactics/api.js";
import { createFileContext } from "../../../../agents/adversary/tactics/index.js";
const mockConfig = {
    model: "claude-sonnet-4-20250514",
    aggressiveness: "passive",
    focusAreas: ["api"],
    maxAnalysisTime: 60000,
    generatePoC: false,
};
describe("API Security Tactics", () => {
    describe("Module Registration", () => {
        it("has correct focus area", () => {
            expect(apiTactic.focusArea).toBe("api");
        });
        it("has correct name and description", () => {
            expect(apiTactic.name).toBe("API Security");
            expect(apiTactic.description).toContain("IDOR");
            expect(apiTactic.description).toContain("BOLA");
        });
        it("has patterns defined", () => {
            expect(apiTactic.patterns.length).toBeGreaterThan(0);
        });
        it("returns relevant file patterns", () => {
            const patterns = apiTactic.getRelevantFilePatterns();
            expect(patterns).toContain("**/api/**");
            expect(patterns).toContain("**/routes/**");
            expect(patterns).toContain("**/controllers/**");
            expect(patterns).toContain("**/graphql/**");
        });
        it("provides LLM prompt enhancement", () => {
            const prompt = apiTactic.getPromptEnhancement();
            expect(prompt).toContain("IDOR");
            expect(prompt).toContain("BOLA");
            expect(prompt).toContain("GraphQL");
            expect(prompt).toContain("Mass Assignment");
        });
    });
    describe("IDOR Detection", () => {
        it("detects direct ID usage without authorization", async () => {
            const code = `
        router.get('/api/posts/:id', async (req, res) => {
          const post = await Post.findById(req.params.id);
          res.json(post);
        });
      `;
            const file = createFileContext("/test", "/test/api/routes.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const idorFinding = findings.find((f) => f.patternId === "idor-direct-id");
            expect(idorFinding).toBeDefined();
            expect(idorFinding?.severity).toBe("high");
            expect(idorFinding?.cweIds).toContain("CWE-639");
        });
        it("detects file path IDOR", async () => {
            const code = `
        app.get('/download', async (req, res) => {
          const data = await fs.readFile(req.query.path);
          res.send(data);
        });
      `;
            const file = createFileContext("/test", "/test/api/download.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const fileFinding = findings.find((f) => f.patternId === "idor-file-path");
            expect(fileFinding).toBeDefined();
            expect(fileFinding?.severity).toBe("critical");
        });
        it("skips test files", async () => {
            const code = `
        const testPost = await Post.findById(req.params.id);
      `;
            const file = createFileContext("/test", "/test/api/routes.test.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            expect(findings.length).toBe(0);
        });
    });
    describe("BOLA Detection", () => {
        it("detects missing object-level authorization on read", async () => {
            const code = `
        async function getOrder(req, res) {
          const order = await Order.findOne({ id: req.params.id })
            .then(order => res.json(order));
        }
      `;
            const file = createFileContext("/test", "/test/api/orders.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const bolaFinding = findings.find((f) => f.patternId === "bola-missing-authz");
            expect(bolaFinding).toBeDefined();
            expect(bolaFinding?.severity).toBe("high");
        });
        it("detects BOLA on update operations", async () => {
            const code = `
        router.put('/posts/:id', async (req, res) => {
          await Post.update({ id: req.params.id }, req.body);
          res.json({ success: true });
        });
      `;
            const file = createFileContext("/test", "/test/api/posts.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const updateFinding = findings.find((f) => f.patternId === "bola-update-no-check");
            expect(updateFinding).toBeDefined();
            expect(updateFinding?.severity).toBe("critical");
        });
        it("detects BOLA on delete operations", async () => {
            const code = `
        async deletePost(req, res) {
          await Post.delete(req.params.id);
          res.sendStatus(204);
        }
      `;
            const file = createFileContext("/test", "/test/api/posts.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const deleteFinding = findings.find((f) => f.patternId === "bola-delete-no-check");
            expect(deleteFinding).toBeDefined();
            expect(deleteFinding?.severity).toBe("critical");
        });
    });
    describe("GraphQL Detection", () => {
        it("detects missing depth limit", async () => {
            const code = `
        const server = new ApolloServer({
          typeDefs,
          resolvers,
        });
      `;
            const file = createFileContext("/test", "/test/graphql/server.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const depthFinding = findings.find((f) => f.patternId === "graphql-no-depth-limit");
            expect(depthFinding).toBeDefined();
            expect(depthFinding?.severity).toBe("medium");
            expect(depthFinding?.cweIds).toContain("CWE-770");
        });
        it("detects missing cost analysis", async () => {
            const code = `
        const server = new GraphQLServer({
          schema,
        });
      `;
            const file = createFileContext("/test", "/test/graphql/server.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const costFinding = findings.find((f) => f.patternId === "graphql-no-cost-analysis");
            expect(costFinding).toBeDefined();
        });
        it("detects introspection enabled", async () => {
            const code = `
        const server = new ApolloServer({
          schema,
          introspection: true,
        });
      `;
            const file = createFileContext("/test", "/test/graphql/server.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const introspectionFinding = findings.find((f) => f.patternId === "graphql-introspection-prod");
            expect(introspectionFinding).toBeDefined();
            expect(introspectionFinding?.severity).toBe("low");
        });
    });
    describe("Mass Assignment Detection", () => {
        it("detects object spread with user input", async () => {
            const code = `
        async createUser(req, res) {
          const user = await User.create({ ...req.body });
          res.json(user);
        }
      `;
            const file = createFileContext("/test", "/test/api/users.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const massFinding = findings.find((f) => f.patternId === "mass-assignment-spread");
            expect(massFinding).toBeDefined();
            expect(massFinding?.severity).toBe("high");
            expect(massFinding?.cweIds).toContain("CWE-915");
        });
        it("detects Object.assign with unfiltered input", async () => {
            const code = `
        function updateProfile(req, res) {
          Object.assign(user, req.body);
          user.save();
        }
      `;
            const file = createFileContext("/test", "/test/api/profile.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const assignFinding = findings.find((f) => f.patternId === "mass-assignment-assign");
            expect(assignFinding).toBeDefined();
            expect(assignFinding?.severity).toBe("high");
        });
        it("detects ORM mass assignment", async () => {
            const code = `
        router.post('/users', async (req, res) => {
          const user = await User.create(req.body);
          res.json(user);
        });
      `;
            const file = createFileContext("/test", "/test/api/users.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const ormFinding = findings.find((f) => f.patternId === "mass-assignment-orm");
            expect(ormFinding).toBeDefined();
        });
    });
    describe("Rate Limiting Detection", () => {
        it("detects missing rate limit on endpoint", async () => {
            const code = `
        app.post('/api/login', async (req, res) => {
          const user = await authenticate(req.body);
          res.json({ token: user.token });
        });
      `;
            const file = createFileContext("/test", "/test/api/auth.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const rateFinding = findings.find((f) => f.patternId === "api-no-rate-limit");
            expect(rateFinding).toBeDefined();
            expect(rateFinding?.severity).toBe("medium");
            expect(rateFinding?.cweIds).toContain("CWE-770");
        });
        it("detects expensive operation without limit", async () => {
            const code = `
        const process = async (req, res) => {
          const data = await processLargeDataset();
          const report = computeComplexAnalysis(data);
          res.json(report);
        };
      `;
            const file = createFileContext("/test", "/test/api/reports.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const expensiveFinding = findings.find((f) => f.patternId === "api-expensive-no-limit");
            expect(expensiveFinding).toBeDefined();
            expect(expensiveFinding?.severity).toBe("high");
        });
    });
    describe("Data Exposure Detection", () => {
        it("detects full object return", async () => {
            const code = `
        router.get('/users/:id', async (req, res) => {
          const user = await User.findById(req.params.id);
          res.json(user);
        });
      `;
            const file = createFileContext("/test", "/test/api/users.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const exposureFinding = findings.find((f) => f.patternId === "api-full-object-return");
            expect(exposureFinding).toBeDefined();
            expect(exposureFinding?.severity).toBe("medium");
            expect(exposureFinding?.cweIds).toContain("CWE-200");
        });
        it("detects SELECT * queries", async () => {
            const code = `
        const users = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
      `;
            const file = createFileContext("/test", "/test/api/users.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const selectFinding = findings.find((f) => f.patternId === "api-all-fields-select");
            expect(selectFinding).toBeDefined();
        });
        it("detects sensitive fields in response", async () => {
            const code = `
        res.json({
          id: user.id,
          email: user.email,
          password: user.password,
          token: user.apiToken
        });
      `;
            const file = createFileContext("/test", "/test/api/auth.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            const sensitiveFinding = findings.find((f) => f.patternId === "api-sensitive-fields");
            expect(sensitiveFinding).toBeDefined();
            expect(sensitiveFinding?.severity).toBe("high");
        });
    });
    describe("PoC Generation", () => {
        it("generates IDOR PoC", async () => {
            const finding = {
                id: "test-idor",
                patternId: "idor-direct-id",
                file: "api/posts.ts",
                line: 10,
            };
            const poc = await apiTactic.generatePoC(finding);
            expect(poc).toBeDefined();
            expect(poc?.steps.length).toBeGreaterThan(0);
            expect(poc?.safeTestInstructions).toContain("test account");
        });
        it("generates BOLA PoC", async () => {
            const finding = {
                id: "test-bola",
                patternId: "bola-missing-authz",
                file: "api/orders.ts",
                line: 15,
            };
            const poc = await apiTactic.generatePoC(finding);
            expect(poc).toBeDefined();
            expect(poc?.steps.length).toBeGreaterThan(0);
            expect(poc?.expectedResult).toContain("ownership");
        });
        it("generates GraphQL depth limit PoC", async () => {
            const finding = {
                id: "test-graphql",
                patternId: "graphql-no-depth-limit",
                file: "graphql/server.ts",
                line: 20,
            };
            const poc = await apiTactic.generatePoC(finding);
            expect(poc).toBeDefined();
            expect(poc?.payload).toContain("nested");
        });
        it("generates mass assignment PoC", async () => {
            const finding = {
                id: "test-mass",
                patternId: "mass-assignment-spread",
                file: "api/users.ts",
                line: 25,
            };
            const poc = await apiTactic.generatePoC(finding);
            expect(poc).toBeDefined();
            expect(poc?.expectedResult).toContain("assignment");
        });
        it("returns null for unsupported pattern", async () => {
            const finding = {
                id: "test-unknown",
                patternId: "unknown-pattern",
                file: "test.ts",
                line: 1,
            };
            const poc = await apiTactic.generatePoC(finding);
            expect(poc).toBeNull();
        });
    });
    describe("Pattern Coverage", () => {
        it("covers all CWEs mentioned in requirements", () => {
            const cwes = apiTactic.patterns.map((p) => p.cwe);
            expect(cwes).toContain("CWE-639"); // IDOR
            expect(cwes).toContain("CWE-284"); // BOLA
            expect(cwes).toContain("CWE-770"); // GraphQL complexity
            expect(cwes).toContain("CWE-915"); // Mass assignment
            expect(cwes).toContain("CWE-200"); // Data exposure
        });
        it("has reasonable severity distribution", () => {
            const critical = apiTactic.patterns.filter((p) => p.severity === "critical").length;
            const high = apiTactic.patterns.filter((p) => p.severity === "high").length;
            const medium = apiTactic.patterns.filter((p) => p.severity === "medium").length;
            expect(critical).toBeGreaterThan(0);
            expect(high).toBeGreaterThan(0);
            expect(medium).toBeGreaterThan(0);
        });
    });
    describe("False Positive Handling", () => {
        it("skips comments", async () => {
            const code = `
        // const post = await Post.findById(req.params.id);
        /*
         * await User.update(req.params.id, req.body);
         */
      `;
            const file = createFileContext("/test", "/test/api/test.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            expect(findings.length).toBe(0);
        });
        it("skips mock/fixture files", async () => {
            const code = `
        const user = await User.create({ ...req.body });
      `;
            const file = createFileContext("/test", "/test/mocks/user-fixture.ts", code);
            const findings = await apiTactic.analyzeFile(file, mockConfig);
            expect(findings.length).toBe(0);
        });
    });
});
//# sourceMappingURL=api.test.js.map