/** * runtimeVerifier.test.ts — S415 test suite * * Copre tutti i 5 check di verifyRuntime: * 1. Entry point esistente * 2. Import relativi risolti nel VFS * 3. Dipendenze npm in package.json * 4. Bilanciamento sintattico (bracket/paren/brace balance) * 5. Anti-pattern (require() in TS, TODO/FIXME placeholder, JSX senza export default) * * + casi edge: VFS vuoto, file non-codice, struttura RuntimeVerifyResult */ import { describe, it, expect } from "vitest"; import { verifyRuntime } from "../runtimeVerifier"; // ─── helper ─────────────────────────────────────────────────────────────────── const mkFile = (path: string, content: string) => ({ path, content }); // ─── 1. VFS vuoto ───────────────────────────────────────────────────────────── describe("verifyRuntime — VFS vuoto", () => { it("VFS vuoto → ok:true, hasCritical:false, issues:[], passed non vuoto", () => { const r = verifyRuntime([]); expect(r.ok).toBe(true); expect(r.hasCritical).toBe(false); expect(r.issues).toEqual([]); expect(r.passed.length).toBeGreaterThan(0); }); it("VFS vuoto → summary contiene 'VFS'", () => { const r = verifyRuntime([]); expect(r.summary.toLowerCase()).toContain("vfs"); }); }); // ─── 2. Entry point check ───────────────────────────────────────────────────── describe("verifyRuntime — check 1: entry point", () => { it("src/main.tsx presente → 'Entry point ✓' in passed[]", () => { const r = verifyRuntime([mkFile("src/main.tsx", "import React from 'react'; export default App;")]); expect(r.passed.some(p => /entry point/i.test(p))).toBe(true); expect(r.issues.some(i => i.severity === "error" && /entry/i.test(i.message))).toBe(false); }); it("src/index.tsx presente → entry point ok", () => { const r = verifyRuntime([mkFile("src/index.tsx", "export default App;")]); expect(r.passed.some(p => /entry point/i.test(p))).toBe(true); }); it("index.html presente → entry point ok", () => { const r = verifyRuntime([mkFile("index.html", "")]); expect(r.passed.some(p => /entry point/i.test(p))).toBe(true); }); it("app.py presente → entry point ok", () => { const r = verifyRuntime([mkFile("app.py", "from flask import Flask\napp = Flask(__name__)")]); expect(r.passed.some(p => /entry point/i.test(p))).toBe(true); }); it("server.js presente → entry point ok", () => { const r = verifyRuntime([mkFile("server.js", "const express = require('express');")]); expect(r.passed.some(p => /entry point/i.test(p))).toBe(true); }); it("solo file utility senza entry point → error severity", () => { const r = verifyRuntime([ mkFile("src/utils/helper.ts", "export const add = (a: number, b: number) => a + b;"), mkFile("src/components/Card.tsx", "export function Card() { return
; }"), ]); const epIssue = r.issues.find(i => i.severity === "error" && /entry/i.test(i.message)); expect(epIssue).toBeDefined(); expect(r.hasCritical).toBe(true); expect(r.ok).toBe(false); }); }); // ─── 3. Import relativi check ───────────────────────────────────────────────── describe("verifyRuntime — check 2: import relativi", () => { it("import relativo risolto nel VFS → nessun errore import", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import { useAuth } from './lib/auth';"), mkFile("src/lib/auth.ts", "export const useAuth = () => {};"), ]); const importIssues = r.issues.filter(i => i.message.includes("Import non risolto")); expect(importIssues.length).toBe(0); }); it("import relativo non nel VFS → errore", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import { helper } from './utils/missing-file';"), ]); const importIssue = r.issues.find(i => i.severity === "error" && /import non risolto|missing/i.test(i.message)); expect(importIssue).toBeDefined(); }); it("import di pacchetto npm (non relativo) → nessun import error", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import React from 'react'; import axios from 'axios';"), ]); const importIssues = r.issues.filter(i => i.message.includes("Import non risolto")); expect(importIssues.length).toBe(0); }); it("import con ../dir risolto correttamente", () => { const r = verifyRuntime([ mkFile("src/pages/Login.tsx", "import { auth } from '../lib/auth';"), mkFile("src/lib/auth.ts", "export const auth = {};"), ]); const importIssues = r.issues.filter(i => i.message.includes("Import non risolto")); expect(importIssues.length).toBe(0); }); it("import con extension .tsx esplicita risolto", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import Button from './components/Button.tsx';"), mkFile("src/components/Button.tsx", "export default function Button() {}"), ]); const importIssues = r.issues.filter(i => i.message.includes("Import non risolto")); expect(importIssues.length).toBe(0); }); it("import con index.ts → risolto correttamente", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import { db } from './db';"), mkFile("src/db/index.ts", "export const db = {};"), ]); const importIssues = r.issues.filter(i => i.message.includes("Import non risolto")); expect(importIssues.length).toBe(0); }); }); // ─── 4. Package.json deps check ─────────────────────────────────────────────── describe("verifyRuntime — check 3: package.json deps", () => { it("pacchetto dichiarato in dependencies → nessun warning", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import axios from 'axios';"), mkFile("package.json", JSON.stringify({ dependencies: { axios: "^1.6.0" } })), ]); const depIssues = r.issues.filter(i => i.message.toLowerCase().includes("axios")); expect(depIssues.length).toBe(0); }); it("pacchetto NON in package.json → warning", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import moment from 'moment';"), mkFile("package.json", JSON.stringify({ dependencies: { react: "^18.0.0" } })), ]); const depIssues = r.issues.filter(i => i.severity === "warning" && i.message.includes("moment")); expect(depIssues.length).toBeGreaterThan(0); }); it("pacchetto scoped @scope/pkg in devDeps → nessun warning", () => { const r = verifyRuntime([ mkFile("src/test.ts", "import { render } from '@testing-library/react';"), mkFile("package.json", JSON.stringify({ devDependencies: { "@testing-library/react": "^14.0.0" } })), ]); const depIssues = r.issues.filter(i => i.message.includes("testing-library")); expect(depIssues.length).toBe(0); }); it("built-in Node (fs, path, os) → nessun warning", () => { const r = verifyRuntime([ mkFile("src/server.ts", "import fs from 'fs'; import path from 'path'; import os from 'os';"), mkFile("package.json", JSON.stringify({ dependencies: {} })), ]); const builtinIssues = r.issues.filter(i => i.severity === "warning" && ( i.message.includes("'fs'") || i.message.includes("'path'") || i.message.includes("'os'") ) ); expect(builtinIssues.length).toBe(0); }); it("senza package.json → dep check skippato", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import unknownPkg from 'unknown-package-xyz';"), ]); // Senza package.json il check non può confrontare → nessun false positive const pkgMissing = r.issues.filter(i => i.message.includes("package.json mancante")); expect(pkgMissing.length).toBe(0); }); }); // ─── 5. Syntax balance check ────────────────────────────────────────────────── describe("verifyRuntime — check 4: bilanciamento sintattico", () => { it("codice bilanciato → nessun warning sintassi", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "function App() { return (

Hello

); }"), ]); const syntaxIssues = r.issues.filter(i => i.message.includes("sintattico") || i.message.includes("sbilanciata") ); expect(syntaxIssues.length).toBe(0); }); it("4 brace aperte senza chiusura → warning (delta > 2)", () => { const r = verifyRuntime([ mkFile("src/broken.ts", "function a() { function b() { function c() { function d() { return 1;"), ]); const syntaxIssues = r.issues.filter(i => i.severity === "warning" && (i.message.includes("sintattico") || i.message.includes("sbilanciata")) ); expect(syntaxIssues.length).toBeGreaterThan(0); }); it("file .md non analizzato per syntax", () => { const r = verifyRuntime([ mkFile("README.md", "# {{{ titolo senza chiusura {{{{"), mkFile("src/main.tsx", "function App() { return
; }"), ]); const mdIssues = r.issues.filter(i => i.file === "README.md"); expect(mdIssues.length).toBe(0); }); }); // ─── 6. Anti-pattern check ──────────────────────────────────────────────────── describe("verifyRuntime — check 5: anti-pattern", () => { it("require() in file .ts → warning", () => { const r = verifyRuntime([ mkFile("src/lib/db.ts", "const pg = require('pg'); module.exports = { pg };"), ]); const apIssues = r.issues.filter(i => i.severity === "warning" && i.message.includes("require()")); expect(apIssues.length).toBeGreaterThan(0); }); it("require() in file .tsx → warning (TypeScript)", () => { const r = verifyRuntime([ mkFile("src/App.tsx", "const x = require('something');"), ]); const apIssues = r.issues.filter(i => i.severity === "warning" && i.message.includes("require()")); expect(apIssues.length).toBeGreaterThan(0); }); it("TODO nel commento → warning placeholder", () => { const r = verifyRuntime([ mkFile("src/service.ts", "// TODO: implementare questa funzione\nexport function doSomething() {}"), ]); const toDoIssues = r.issues.filter(i => i.severity === "warning" && /placeholder|TODO/i.test(i.message)); expect(toDoIssues.length).toBeGreaterThan(0); }); it("FIXME nel commento → warning placeholder", () => { const r = verifyRuntime([ mkFile("src/api.ts", "// FIXME: bug qui\nexport function handler() {}"), ]); const fixmeIssues = r.issues.filter(i => i.severity === "warning" && /placeholder|FIXME/i.test(i.message)); expect(fixmeIssues.length).toBeGreaterThan(0); }); it("JSX con tag uppercase () senza export default → warning", () => { const r = verifyRuntime([ mkFile("src/components/Widget.tsx", // Tag uppercase = JSX component; nessun export default "function Widget() { return ; }" ), ]); const exportIssues = r.issues.filter(i => i.severity === "warning" && i.message.includes("export default") ); expect(exportIssues.length).toBeGreaterThan(0); }); it("JSX con export default → nessun warning export", () => { const r = verifyRuntime([ mkFile("src/components/Button.tsx", "function Button() { return ; }\nexport default Button;"), ]); const exportIssues = r.issues.filter(i => i.message.includes("export default")); expect(exportIssues.length).toBe(0); }); it("codice pulito → 0 anti-pattern warnings", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import React from 'react';\nexport default function App() { return
Hello
; }"), ]); const apIssues = r.issues.filter(i => i.severity === "warning" && ( i.message.includes("require()") || i.message.includes("TODO") || i.message.includes("FIXME") ) ); expect(apIssues.length).toBe(0); }); }); // ─── 7. RuntimeVerifyResult — ok / hasCritical / summary / repairHint ───────── describe("verifyRuntime — struttura RuntimeVerifyResult", () => { it("senza errori critici → ok:true, hasCritical:false", () => { const r = verifyRuntime([mkFile("src/main.tsx", "export default function App() { return
; }")]); if (!r.hasCritical) { expect(r.ok).toBe(true); } }); it("errore critico → ok:false, hasCritical:true", () => { const r = verifyRuntime([ mkFile("src/main.tsx", "import { missing } from './does-not-exist';"), ]); if (r.issues.some(i => i.severity === "error")) { expect(r.hasCritical).toBe(true); expect(r.ok).toBe(false); } }); it("summary è stringa non vuota", () => { const r = verifyRuntime([mkFile("src/main.tsx", "export default function App() {}")]); expect(typeof r.summary).toBe("string"); expect(r.summary.length).toBeGreaterThan(0); }); it("repairHint definita quando hasCritical=true", () => { const r = verifyRuntime([ mkFile("src/utils/helper.ts", "export const x = 1;"), ]); if (r.hasCritical) { expect(r.repairHint).toBeDefined(); expect(r.repairHint!.length).toBeGreaterThan(0); } }); it("passed[] contiene stringhe con ✓ quando check superati", () => { const r = verifyRuntime([mkFile("src/main.tsx", "export default function App() {}")]); if (r.passed.length > 0) { expect(r.passed.some(p => p.includes("✓"))).toBe(true); } }); it("issues[] — ogni issue ha severity, file, message", () => { const r = verifyRuntime([ mkFile("src/utils/x.ts", "// TODO: fix this"), ]); for (const issue of r.issues) { expect(issue).toHaveProperty("severity"); expect(issue).toHaveProperty("file"); expect(issue).toHaveProperty("message"); expect(["error", "warning", "info"]).toContain(issue.severity); } }); });