AUDIT / src /lib /__tests__ /shellVerifier.test.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
13.4 kB
/**
* shellVerifier.test.ts — S415b test suite
*
* Copre:
* - detectEntryPoint: rileva entry point da VFS
* · package.json "main" field
* · package.json "scripts.start" field
* · candidati standard (src/main.ts, index.ts, app.py, ecc.)
* · TypeScript vs JavaScript vs Python
* · tsconfig.json presente → usa tsc --noEmit
* - buildShellVerifyPrompt: genera prompt solo quando opportuno
* - parseShellCheckFromSteps: estrae risultato da execute_shell steps
* - formatShellCheckRepairHint: formatta hint per il loop
*/
import { describe, it, expect } from "vitest";
import {
detectEntryPoint,
buildShellVerifyPrompt,
parseShellCheckFromSteps,
formatShellCheckRepairHint,
} from "../shellVerifier";
// ─── Helpers ─────────────────────────────────────────────────────────────────
const mkFile = (path: string, content = "") => ({ path, content });
const pkgJson = (main?: string, scripts?: Record<string, string>, deps?: Record<string, string>) =>
mkFile("package.json", JSON.stringify({
name: "test-app",
...(main ? { main } : {}),
...(scripts ? { scripts } : {}),
...(deps ? { dependencies: deps } : {}),
}));
// ─── detectEntryPoint ─────────────────────────────────────────────────────────
describe("detectEntryPoint", () => {
it("ritorna null per VFS vuoto", () => {
expect(detectEntryPoint([])).toBeNull();
});
it("rileva entry da package.json 'main'", () => {
const files = [pkgJson("src/server.ts"), mkFile("src/server.ts", "")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("src/server.ts");
expect(ep?.projectType).toBe("typescript");
});
it("rileva entry da package.json 'scripts.start'", () => {
const files = [
pkgJson(undefined, { start: "node dist/index.js" }),
mkFile("dist/index.js", ""),
];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("dist/index.js");
expect(ep?.projectType).toBe("javascript");
});
it("usa candidato standard src/main.tsx per TypeScript", () => {
const files = [mkFile("src/main.tsx", ""), mkFile("src/App.tsx", "")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("src/main.tsx");
expect(ep?.projectType).toBe("typescript");
});
it("usa candidato standard src/index.ts", () => {
const files = [mkFile("src/index.ts", "const x = 1;")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("src/index.ts");
expect(ep?.projectType).toBe("typescript");
});
it("usa candidato standard src/main.js", () => {
const files = [mkFile("src/main.js", "console.log('ok')"), pkgJson()];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("src/main.js");
expect(ep?.projectType).toBe("javascript");
});
it("usa candidato standard index.js quando non c'è src/", () => {
const files = [mkFile("index.js", "module.exports = {}"), pkgJson()];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("index.js");
expect(ep?.projectType).toBe("javascript");
});
it("rileva progetto Python con app.py", () => {
const files = [mkFile("app.py", "from flask import Flask")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("app.py");
expect(ep?.projectType).toBe("python");
expect(ep?.checkCmd).toContain("py_compile");
});
it("rileva progetto Python con main.py", () => {
const files = [mkFile("main.py", "if __name__ == '__main__': main()")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("main.py");
expect(ep?.projectType).toBe("python");
});
it("usa tsc --noEmit quando tsconfig.json è presente", () => {
const files = [mkFile("src/main.ts", ""), mkFile("tsconfig.json", "{}")];
const ep = detectEntryPoint(files);
expect(ep?.checkCmd).toContain("tsc --noEmit");
expect(ep?.hasConfig).toBe(true);
});
it("usa tsc con opzioni inline senza tsconfig", () => {
const files = [mkFile("src/main.ts", "")];
const ep = detectEntryPoint(files);
expect(ep?.checkCmd).toContain("tsc --noEmit");
expect(ep?.hasConfig).toBe(false);
});
it("usa node --check per JavaScript", () => {
const files = [mkFile("server.js", "")];
const ep = detectEntryPoint(files);
expect(ep?.checkCmd).toContain("node --check");
expect(ep?.checkCmd).toContain("server.js");
});
it("path package.json main con ./ prefix normalizzato", () => {
const files = [pkgJson("./src/app.ts"), mkFile("src/app.ts", "")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("src/app.ts");
});
it("ignora main non trovato nel VFS → fallback a candidati", () => {
const files = [pkgJson("dist/missing.js"), mkFile("src/index.ts", "")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("src/index.ts");
});
it("server.ts riconosciuto come TypeScript entry", () => {
const files = [mkFile("server.ts", "import express from 'express';")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("server.ts");
expect(ep?.projectType).toBe("typescript");
});
it("app.ts riconosciuto come TypeScript entry", () => {
const files = [mkFile("app.ts", "")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("app.ts");
expect(ep?.projectType).toBe("typescript");
});
it("fallback a primo .py trovato se non è app.py/main.py", () => {
const files = [mkFile("service/worker.py", "")];
const ep = detectEntryPoint(files);
expect(ep?.path).toBe("service/worker.py");
expect(ep?.projectType).toBe("python");
});
it("ritorna null se solo file non-codice nel VFS", () => {
const files = [mkFile("README.md", "# App"), mkFile("styles.css", "body{}")];
const ep = detectEntryPoint(files);
expect(ep).toBeNull();
});
});
// ─── buildShellVerifyPrompt ───────────────────────────────────────────────────
describe("buildShellVerifyPrompt", () => {
it("ritorna null all'iter 0 (codice non ancora generato)", () => {
const files = [mkFile("src/main.ts", "")];
const prompt = buildShellVerifyPrompt(files, [], 0);
expect(prompt).toBeNull();
});
it("ritorna null se entry point non trovato", () => {
const files = [mkFile("README.md", "# docs only")];
const prompt = buildShellVerifyPrompt(files, [], 1);
expect(prompt).toBeNull();
});
it("genera prompt con iter >= 1 e entry point trovato", () => {
const files = [mkFile("src/main.ts", "export const x = 1;")];
const prompt = buildShellVerifyPrompt(files, [], 1);
expect(prompt).not.toBeNull();
expect(prompt).toContain("SHELL_VERIFY");
expect(prompt).toContain("execute_shell");
expect(prompt).toContain("SHELL_CHECK_OK");
});
it("non genera prompt se shell check già richiesto (execute_shell con --check)", () => {
const files = [mkFile("server.js", "")];
const steps = [{
tool: "execute_shell",
args: { command: "node --check server.js" },
result: "SHELL_CHECK_OK",
}];
const prompt = buildShellVerifyPrompt(files, steps, 2);
expect(prompt).toBeNull(); // già richiesto
});
it("prompt contiene il comando corretto (node --check per JS)", () => {
const files = [mkFile("index.js", "const x = 1;")];
const prompt = buildShellVerifyPrompt(files, [], 1);
expect(prompt).toContain("node --check");
});
it("prompt contiene il comando corretto (tsc --noEmit per TS)", () => {
const files = [mkFile("src/main.ts", ""), mkFile("tsconfig.json", "{}")];
const prompt = buildShellVerifyPrompt(files, [], 1);
expect(prompt).toContain("tsc --noEmit");
});
it("prompt contiene python py_compile per Python", () => {
const files = [mkFile("app.py", "print('hello')")];
const prompt = buildShellVerifyPrompt(files, [], 1);
expect(prompt).toContain("py_compile");
});
});
// ─── parseShellCheckFromSteps ─────────────────────────────────────────────────
describe("parseShellCheckFromSteps", () => {
it("checked=false se nessuno step execute_shell di check", () => {
const result = parseShellCheckFromSteps([]);
expect(result.checked).toBe(false);
});
it("checked=false per execute_shell non-check (es: npm install)", () => {
const steps = [{ tool: "execute_shell", args: { command: "npm install" }, result: "ok" }];
const result = parseShellCheckFromSteps(steps);
expect(result.checked).toBe(false);
});
it("ok=true se output vuoto dopo --check", () => {
const steps = [{
tool: "execute_shell",
args: { command: "node --check server.js" },
result: "",
}];
const result = parseShellCheckFromSteps(steps);
expect(result.checked).toBe(true);
expect(result.ok).toBe(true);
expect(result.errors).toHaveLength(0);
});
it("ok=true se output contiene SHELL_CHECK_OK", () => {
const steps = [{
tool: "execute_shell",
args: { command: "tsc --noEmit" },
result: "SHELL_CHECK_OK\nEverything looks good.",
}];
const result = parseShellCheckFromSteps(steps);
expect(result.ok).toBe(true);
});
it("estrae errori tsc da output", () => {
const steps = [{
tool: "execute_shell",
args: { command: "tsc --noEmit" },
result: "src/main.ts(5,3): error TS2345: Argument of type 'string' is not assignable to 'number'.\nsrc/index.ts(1,1): error TS2304: Cannot find name 'x'.",
}];
const result = parseShellCheckFromSteps(steps);
expect(result.checked).toBe(true);
expect(result.ok).toBe(false);
expect(result.errors.length).toBe(2);
expect(result.errors[0]).toContain("error TS2345");
});
it("estrae SyntaxError da node --check", () => {
const steps = [{
tool: "execute_shell",
args: { command: "node --check index.js" },
result: "/app/index.js:3\nSyntaxError: Unexpected token '}'",
}];
const result = parseShellCheckFromSteps(steps);
expect(result.ok).toBe(false);
expect(result.errors.some(e => /SyntaxError/.test(e))).toBe(true);
});
it("estrae ModuleNotFoundError da Python", () => {
const steps = [{
tool: "execute_shell",
args: { command: "python -m py_compile app.py" },
result: "ModuleNotFoundError: No module named 'flask'",
}];
const result = parseShellCheckFromSteps(steps);
expect(result.ok).toBe(false);
expect(result.errors[0]).toContain("ModuleNotFoundError");
});
it("usa l'ultimo step execute_shell di check se ce ne sono più di uno", () => {
const steps = [
{ tool: "execute_shell", args: { command: "tsc --noEmit" }, result: "error TS1234: qualcosa" },
{ tool: "write_file", args: { path: "src/x.ts" }, result: "ok" },
{ tool: "execute_shell", args: { command: "tsc --noEmit" }, result: "" }, // secondo check → ok
];
const result = parseShellCheckFromSteps(steps);
expect(result.ok).toBe(true); // usa l'ultimo
});
it("max 8 errori estratti", () => {
const manyErrors = Array.from({ length: 15 }, (_, i) =>
`src/file${i}.ts(1,1): error TS${1000 + i}: msg`
).join("\n");
const steps = [{
tool: "execute_shell",
args: { command: "tsc --noEmit" },
result: manyErrors,
}];
const result = parseShellCheckFromSteps(steps);
expect(result.errors.length).toBeLessThanOrEqual(8);
});
});
// ─── formatShellCheckRepairHint ───────────────────────────────────────────────
describe("formatShellCheckRepairHint", () => {
it("ritorna stringa vuota per errors vuoto", () => {
expect(formatShellCheckRepairHint([], "src/main.ts", "tsc --noEmit")).toBe("");
});
it("contiene [SHELL_CHECK_FAIL] come header", () => {
const hint = formatShellCheckRepairHint(["error TS2345: ..."], "src/main.ts", "tsc --noEmit");
expect(hint).toContain("[SHELL_CHECK_FAIL");
});
it("include tutti gli errori con prefisso ❌", () => {
const errors = ["error TS2345: wrong type", "error TS2304: cannot find"];
const hint = formatShellCheckRepairHint(errors, "src/main.ts", "tsc --noEmit");
expect(hint).toContain("❌ error TS2345");
expect(hint).toContain("❌ error TS2304");
});
it("include il nome dell'entry point", () => {
const hint = formatShellCheckRepairHint(["SyntaxError"], "src/server.ts", "node --check src/server.ts");
expect(hint).toContain("src/server.ts");
});
it("include il comando nel header", () => {
const hint = formatShellCheckRepairHint(["err"], "main.py", "python -m py_compile main.py");
expect(hint).toContain("py_compile");
});
it("contiene call-to-action NON puoi dichiarare DONE", () => {
const hint = formatShellCheckRepairHint(["error"], "x.ts", "tsc --noEmit");
expect(hint).toContain("NON");
expect(hint.toLowerCase()).toContain("done");
});
});