Initial commit: pygenium as git submodule
This commit is contained in:
171
tests/deep-modules.test.ts
Normal file
171
tests/deep-modules.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* deep-modules.test.ts — integration test for the deep-modules check.
|
||||
*
|
||||
* Seeds a temp workspace with a pass-through wrapper module (a shallow
|
||||
* abstraction), runs the check with the deterministic fake agent runner, and
|
||||
* asserts:
|
||||
* - the scan persists `findings.md` flagging the wrapper as a pass-through;
|
||||
* - with `--fix`, the safe consolidation is applied (wrapper rewritten/
|
||||
* removed) and `changes.md` records it (auto), while a risky
|
||||
* external-importer case is listed for review (manual), never auto-applied.
|
||||
*/
|
||||
import { describe, expect, it, beforeEach, afterEach } from "bun:test";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdtemp, mkdir, rm, writeFile, readFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
clearChecks,
|
||||
getCheck,
|
||||
registerCheck,
|
||||
type CheckDefinition,
|
||||
} from "../src/checks/registry.js";
|
||||
import {
|
||||
setAgentRunner,
|
||||
resetAgentRunner,
|
||||
fakeAgentRunner,
|
||||
} from "../src/agent-runner.js";
|
||||
import { handleCheckCommand, type PygieniumCtx } from "../src/commands.js";
|
||||
import { loadRunState } from "../src/run-state.js";
|
||||
import {
|
||||
findingsPath,
|
||||
changesPath,
|
||||
deepModulesCheck,
|
||||
} from "../src/checks/deep-modules.js";
|
||||
|
||||
function stubCtx(cwd: string): PygieniumCtx {
|
||||
return { cwd, mode: "print", hasUI: false, ui: undefined } as PygieniumCtx;
|
||||
}
|
||||
|
||||
/** Drop a pass-through wrapper module that forwards a single lib call. */
|
||||
async function seedPassThrough(
|
||||
dir: string,
|
||||
): Promise<{ wrapper: string; lib: string }> {
|
||||
const wrapper = join(dir, "wrapper.ts");
|
||||
const lib = join(dir, "lib.ts");
|
||||
await mkdir(dir, { recursive: true }).catch(() => {});
|
||||
await writeFile(
|
||||
lib,
|
||||
`export function compute(x: number): number { return x * 2; }\n`,
|
||||
"utf8",
|
||||
);
|
||||
// Shallow pass-through: forwards every argument to `lib` with zero added logic.
|
||||
await writeFile(
|
||||
wrapper,
|
||||
`import { compute } from "./lib";\nexport function run(x: number) { return compute(x); }\n`,
|
||||
"utf8",
|
||||
);
|
||||
return { wrapper, lib };
|
||||
}
|
||||
|
||||
describe("deep-modules check", () => {
|
||||
let cwd: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
clearChecks();
|
||||
setAgentRunner(fakeAgentRunner);
|
||||
// Re-register explicitly: the module's import-time registerCheck only
|
||||
// runs once (module cache), so clearChecks + registerCheck restores it
|
||||
// deterministically for each test.
|
||||
registerCheck(deepModulesCheck);
|
||||
cwd = await mkdtemp(join(tmpdir(), "pygienium-deep-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
resetAgentRunner();
|
||||
await rm(cwd, { recursive: true, force: true }).catch(() => {});
|
||||
});
|
||||
|
||||
it("is registered and uses the deep-modules scanner agent", () => {
|
||||
const check = getCheck("deep-modules");
|
||||
expect(check).toBeDefined();
|
||||
expect((check as CheckDefinition)?.agentName).toBe("deep-modules");
|
||||
});
|
||||
|
||||
it("flags the pass-through wrapper in findings.md", async () => {
|
||||
await seedPassThrough(cwd);
|
||||
const check = getCheck("deep-modules")!;
|
||||
|
||||
await handleCheckCommand(check, "", stubCtx(cwd));
|
||||
|
||||
const findings = await readFile(findingsPath(cwd), "utf8");
|
||||
expect(findings).toContain("wrapper.ts");
|
||||
expect(findings).toContain("pass-through-wrapper");
|
||||
expect(findings).toMatch(/importers:\s*0/);
|
||||
|
||||
// Run state records the scan summary as findings text.
|
||||
const state = await loadRunState(cwd);
|
||||
expect(state?.checks["deep-modules"]?.status).toBe("complete");
|
||||
expect(state?.checks["deep-modules"]?.findings).toContain(
|
||||
"deep-modules: 1 issue",
|
||||
);
|
||||
});
|
||||
|
||||
it("scan-only does not write changes.md", async () => {
|
||||
await seedPassThrough(cwd);
|
||||
const check = getCheck("deep-modules")!;
|
||||
|
||||
await handleCheckCommand(check, "", stubCtx(cwd));
|
||||
|
||||
expect(existsSync(changesPath(cwd))).toBe(false);
|
||||
});
|
||||
|
||||
it("--fix applies the safe consolidation and records changes.md (auto), and defers the risky one (manual)", async () => {
|
||||
await seedPassThrough(cwd);
|
||||
// Also drop a "risky" adapter so we can assert it is NOT auto-applied.
|
||||
await writeFile(
|
||||
join(cwd, "risky-adapter.ts"),
|
||||
`// adapter-layer with external importers — should be listed for review only\nexport const risky = true;\n`,
|
||||
"utf8",
|
||||
);
|
||||
const check = getCheck("deep-modules")!;
|
||||
|
||||
await handleCheckCommand(check, "--fix", stubCtx(cwd));
|
||||
|
||||
const changes = await readFile(changesPath(cwd), "utf8");
|
||||
// Safe pass-through: consolidation applied (auto).
|
||||
expect(changes).toContain("wrapper.ts");
|
||||
expect(changes).toMatch(/auto/);
|
||||
expect(changes).toMatch(/consolidat/i);
|
||||
|
||||
// Risky adapter: listed for review, not auto-applied (manual).
|
||||
expect(changes).toContain("risky-adapter.ts");
|
||||
expect(changes).toMatch(/manual/);
|
||||
|
||||
// The safe wrapper was rewritten — no longer a pass-through.
|
||||
const wrapperContent = await readFile(join(cwd, "wrapper.ts"), "utf8");
|
||||
expect(wrapperContent).not.toContain("import { compute }");
|
||||
expect(wrapperContent).toContain("Consolidated");
|
||||
|
||||
const state = await loadRunState(cwd);
|
||||
expect(state?.checks["deep-modules"]?.fix).toBe(true);
|
||||
expect(state?.checks["deep-modules"]?.changes).toContain(
|
||||
"1 auto-applied, 1 deferred",
|
||||
);
|
||||
expect(state?.checks["deep-modules"]?.status).toBe("complete");
|
||||
});
|
||||
|
||||
it("findings.md and changes.md live under pygienium/checks/deep-modules/", async () => {
|
||||
await seedPassThrough(cwd);
|
||||
const check = getCheck("deep-modules")!;
|
||||
await handleCheckCommand(check, "--fix", stubCtx(cwd));
|
||||
expect(findingsPath(cwd)).toBe(
|
||||
join(cwd, "pygienium", "checks", "deep-modules", "findings.md"),
|
||||
);
|
||||
expect(changesPath(cwd)).toBe(
|
||||
join(cwd, "pygienium", "checks", "deep-modules", "changes.md"),
|
||||
);
|
||||
});
|
||||
|
||||
it("skips when the target has no source files", async () => {
|
||||
const empty = await mkdtemp(join(tmpdir(), "pygienium-empty-"));
|
||||
try {
|
||||
const check = getCheck("deep-modules")!;
|
||||
await handleCheckCommand(check, empty, stubCtx(cwd));
|
||||
const state = await loadRunState(cwd);
|
||||
expect(state?.checks["deep-modules"]?.status).toBe("skipped");
|
||||
} finally {
|
||||
await rm(empty, { recursive: true, force: true }).catch(() => {});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user