diff --git a/packages/scanner/tests/scan.test.ts b/packages/scanner/tests/scan.test.ts new file mode 100644 index 0000000..2d62c67 --- /dev/null +++ b/packages/scanner/tests/scan.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it, mock, beforeEach } from "bun:test"; +import { existsSync, readFileSync } from "node:fs"; + +const mockExistsSync = mock(); +const mockReadFileSync = mock(); + +mock.module("node:fs", () => ({ + existsSync: mockExistsSync, + readFileSync: mockReadFileSync, +})); + +import { scan } from "../src/scan"; + +describe("scan - detectInjection", () => { + beforeEach(() => { + mockExistsSync.mockReset(); + mockReadFileSync.mockReset(); + }); + + it("should return empty findings if package.json is missing or unreadable", async () => { + mockExistsSync.mockImplementation((path: string) => { + if (path.endsWith("package-lock.json")) return true; + if (path.endsWith("package.json")) return false; + return false; + }); + + mockReadFileSync.mockImplementation((path: string) => { + if (path.endsWith("package-lock.json")) { + return JSON.stringify({ + lockfileVersion: 2, + packages: { + "node_modules/pkg-a": { version: "1.0.0" } + } + }); + } + if (path.endsWith("package.json")) { + throw new Error("File not found"); + } + return ""; + }); + + const result = await scan("/test-dir"); + expect(result.findings).toEqual([]); + }); + + it("should return empty findings if package.json is invalid JSON", async () => { + mockExistsSync.mockImplementation((path: string) => { + if (path.endsWith("package-lock.json")) return true; + return true; + }); + + mockReadFileSync.mockImplementation((path: string) => { + if (path.endsWith("package-lock.json")) { + return JSON.stringify({ + lockfileVersion: 2, + packages: { + "node_modules/pkg-a": { version: "1.0.0" } + } + }); + } + if (path.endsWith("package.json")) { + return "invalid { json"; + } + return ""; + }); + + const result = await scan("/test-dir"); + expect(result.findings).toEqual([]); + }); + + it("should detect injection when package is in lockfile but not in package.json", async () => { + mockExistsSync.mockImplementation((path: string) => { + if (path.endsWith("package-lock.json")) return true; + if (path.endsWith("package.json")) return true; + return false; + }); + + mockReadFileSync.mockImplementation((path: string) => { + if (path.endsWith("package-lock.json")) { + return JSON.stringify({ + lockfileVersion: 3, + packages: { + "": { version: "1.0.0" }, + "node_modules/pkg-a": { version: "1.0.0" }, + "node_modules/pkg-b": { version: "2.0.0" } + } + }); + } + if (path.endsWith("package.json")) { + return JSON.stringify({ + dependencies: { + "pkg-a": "1.0.0" + } + }); + } + return ""; + }); + + const result = await scan("/test-dir"); + const injections = result.findings.filter(f => f.type === 'injection'); + expect(injections).toHaveLength(1); + expect(injections[0].package).toBe("pkg-b"); + }); +});