Spaces:
Sleeping
Sleeping
| /** | |
| * 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", "<html><body></body></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 <div/>; }"), | |
| ]); | |
| 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 (<div><h1>Hello</h1></div>); }"), | |
| ]); | |
| 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 <div/>; }"), | |
| ]); | |
| 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 (<MyComponent/>) senza export default → warning", () => { | |
| const r = verifyRuntime([ | |
| mkFile("src/components/Widget.tsx", | |
| // Tag uppercase = JSX component; nessun export default | |
| "function Widget() { return <MyContainer><MyItem /></MyContainer>; }" | |
| ), | |
| ]); | |
| 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 <MyBtn/>; }\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 <div>Hello</div>; }"), | |
| ]); | |
| 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 <div/>; }")]); | |
| 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); | |
| } | |
| }); | |
| }); | |