/** * 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 (