AUDIT / src /lib /acceptancePatternsUI.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
11.2 kB
// acceptancePatternsUI.ts — S-P3-1: split da acceptancePatterns.ts
// Sezioni: Roles / RBAC, Report / Export, Search / Filter, File Upload,
// Inventory / Warehouse, Notifications, Form Validation, Mobile / PWA
import type { GoalPattern } from './acceptancePatternsCore';
export const UI_PATTERNS: GoalPattern[] = [
// ── S411: Roles / RBAC ──────────────────────────────────────────────────────
{
re: /ruoli?|roles?|rbac|permiss|autorizzaz|permission|access.?control|admin.*panel|superuser|staff/i,
tests: [
{
requirement: "Sistema ruoli/permessi presente (RBAC)",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasRoleField = /role|ruolo|permission|is_admin|is_staff|user_role/i.test(allCode);
const hasRoleFile = files.some(f => /role|permission|auth.*guard|middleware.*auth/i.test(f.path));
const hasRoleCheck = /hasRole|checkRole|can\\(|cannot\\(|isAdmin|role\\s*===|role\\s*!==|role\\s*===/i.test(allCode);
const pass = hasRoleField && (hasRoleFile || hasRoleCheck);
return { pass,
detail: pass
? "RBAC trovato: campo ruolo + logica verifica presente"
: "Manca: " + (!hasRoleField ? "campo role/permission nel modello; " : "") +
(!hasRoleFile && !hasRoleCheck ? "logica verifica ruolo (hasRole/middleware auth)" : "") };
`,
},
{
requirement: "Almeno 2 ruoli distinti definiti",
critical: false,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const roleMatches = allCode.match(/['"\`](admin|user|editor|viewer|manager|staff|superuser|moderator|guest)['"\`]/gi) || [];
const unique = new Set(roleMatches.map(r => r.toLowerCase()));
return { pass: unique.size >= 2,
detail: unique.size >= 2
? "Ruoli trovati: " + [...unique].join(", ")
: "Trovati meno di 2 ruoli distinti nel codice" };
`,
},
],
},
// ── S411: Report / Export ───────────────────────────────────────────────────
{
re: /report|esport.*(?:pdf|csv|excel|xlsx)|export.*(?:pdf|csv|excel|xlsx)|stampa|download.*(?:report|file)|genera.*(?:report|pdf)/i,
tests: [
{
requirement: "Funzionalità report/export implementata",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasPdf = /pdf|pdfkit|jspdf|react-pdf|fpdf|reportlab/i.test(allCode);
const hasCsv = /csv|xlsx|exceljs|papaparse|to_csv|writecsv/i.test(allCode);
const hasExport = /export.*function|exportReport|generateReport|createReport|downloadReport/i.test(allCode);
const hasTable = /<table|DataTable|TableComponent|reportFile/i.test(allCode);
const pass = hasPdf || hasCsv || hasExport || hasTable;
return { pass,
detail: pass
? "Export trovato: " + [hasPdf && "PDF", hasCsv && "CSV/Excel", hasExport && "exportFn", hasTable && "tabella"].filter(Boolean).join(", ")
: "Manca implementazione report/export (nessun PDF, CSV, exportFn né tabella)" };
`,
},
{
requirement: "File dedicato report o componente export",
critical: false,
checkFn: `
const reportFile = files.find(f => /report|export|invoice|fattura/i.test(f.path));
return { pass: !!reportFile,
detail: reportFile ? reportFile.path : "Nessun file dedicato report/export/invoice trovato" };
`,
},
],
},
// ── S411: Search / Filter ───────────────────────────────────────────────────
{
re: /cerca|ricerca|search|filtro|filter(?:ing)?|query.*list|lista.*filtr/i,
tests: [
{
requirement: "Funzionalità ricerca/filtro implementata",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasSearch = /search|query|filter|ilike|icontains|like.*%|where.*name|fulltext/i.test(allCode);
const hasInput = /searchInput|searchQuery|filterValue|searchTerm|\\binput.*search|search.*input/i.test(allCode);
const pass = hasSearch && hasInput;
return { pass,
detail: pass
? "Ricerca: logica query + input trovati"
: "Manca: " + (!hasSearch ? "logica query/filtro backend; " : "") +
(!hasInput ? "input ricerca frontend" : "") };
`,
},
],
},
// ── S411: File Upload ───────────────────────────────────────────────────────
{
re: /upload|carica.*file|importa.*file|allegato|attachment|file.*upload|import.*csv|import.*excel/i,
tests: [
{
requirement: "Funzionalità upload file presente",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasInputFile = /<input.*type.*file|type=.file.|multer|fastapi.*UploadFile|storage\\.upload/i.test(allCode);
const hasHandler = /handleUpload|onUpload|uploadFile|file_upload|supabase.*storage/i.test(allCode);
const pass = hasInputFile || hasHandler;
return { pass,
detail: pass
? "Upload trovato: " + (hasInputFile ? "input[type=file] o multer; " : "") + (hasHandler ? "handler upload" : "")
: "Manca input[type=file], multer, FastAPI UploadFile o storage handler" };
`,
},
],
},
// ── S411: Inventory / Warehouse ─────────────────────────────────────────────
{
re: /magazzino|inventario|warehouse|stock|giacenza|scorte|prodott.*quantit|quantit.*prodott|inventory/i,
tests: [
{
requirement: "Modello prodotto/stock con quantità presente",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasQty = /quantity|qty|quantit[àa]|stock|giacenza|scorte/i.test(allCode);
const hasProduct = /product|prodott|item|articol|sku|cod.*art/i.test(allCode);
const pass = hasQty && hasProduct;
return { pass,
detail: pass
? "Modello magazzino OK (prodotto + quantità)"
: "Manca: " + (!hasProduct ? "campo prodotto/item/sku; " : "") + (!hasQty ? "campo quantity/stock" : "") };
`,
},
{
requirement: "Operazione movimentazione stock (carico/scarico)",
critical: false,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasMovement = /carico|scarico|moviment|add.*stock|remove.*stock|update.*quantity|decrease.*stock|increase.*stock|restock/i.test(allCode);
return { pass: hasMovement,
detail: hasMovement ? "Movimentazione stock trovata" : "Manca operazione carico/scarico/movimentazione" };
`,
},
],
},
// ── S411: Notifications ─────────────────────────────────────────────────────
{
re: /notific|avviso|alert.*send|email.*send|send.*email|webhook|push.?notification|toast.*error|sms/i,
tests: [
{
requirement: "Sistema notifiche implementato",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasEmail = /nodemailer|sendgrid|resend|smtp|ses\\.send|send_email|smtplib/i.test(allCode);
const hasToast = /toast|sonner|notify|notification|alert.*message/i.test(allCode);
const hasWebhook = /webhook|pusher|socket\\.emit|sse.*notify/i.test(allCode);
const pass = hasEmail || hasToast || hasWebhook;
return { pass,
detail: pass
? "Notifiche: " + [hasEmail && "email", hasToast && "toast/UI", hasWebhook && "webhook/realtime"].filter(Boolean).join(", ")
: "Manca sistema notifiche (email, toast, webhook o push)" };
`,
},
],
},
// ── S411: Form Validation ───────────────────────────────────────────────────
{
re: /form.*validaz|validaz.*form|validat.*input|campo.*obbligator|required.*field|zod|yup|react.hook.form|form.*schema/i,
tests: [
{
requirement: "Validazione form implementata",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasLib = /zod|yup|joi|valibot|react-hook-form|formik|pydantic/i.test(allCode);
const hasValidate = /validate|required|minLength|maxLength|pattern|isEmail|is_email|validators\\.Email/i.test(allCode);
const hasError = /errorMessage|fieldError|formError|errors\\.|setError|ValidationError/i.test(allCode);
const pass = (hasLib || hasValidate) && hasError;
return { pass,
detail: pass
? "Validazione trovata: " + (hasLib ? "libreria validazione; " : "validazione manuale; ") + "gestione errori presente"
: "Manca: " + (!hasLib && !hasValidate ? "logica validazione; " : "") + (!hasError ? "gestione messaggi errore form" : "") };
`,
},
],
},
// ── S411: Mobile / PWA / Responsive ────────────────────────────────────────
{
re: /mobile.*first|pwa|responsive|app.*android|app.*ios|flutter|react.native|viewport.*mobile/i,
tests: [
{
requirement: "Layout responsive / mobile-first presente",
critical: true,
checkFn: `
const allCode = files.map(f => f.content || "").join("\\n");
const hasViewport = /viewport|meta.*viewport/i.test(allCode);
const hasResponsive = /sm:|md:|lg:|xl:|@media|breakpoint|flex|grid.*col/i.test(allCode);
const hasManifest = files.some(f => f.path.includes("manifest"));
const pass = hasResponsive || hasManifest || hasViewport;
return { pass,
detail: pass
? "Responsive trovato: " + [hasViewport && "viewport meta", hasResponsive && "breakpoint/Tailwind", hasManifest && "PWA manifest"].filter(Boolean).join(", ")
: "Manca viewport meta, breakpoint responsive o manifest PWA" };
`,
},
],
},
];