diff --git a/public/test-runner.php b/public/test-runner.php index 37e027b..25c2a10 100644 --- a/public/test-runner.php +++ b/public/test-runner.php @@ -9,6 +9,7 @@ * L3 Compliance Core * L4 B2B & Services API * L5 Export & Reports + * L6 AI Features (Cross-Analysis, Workflow, Normative) * SIM Simulazioni scenari reali */ @@ -187,6 +188,50 @@ function getCommands(): array 'cwd' => $root, 'timeout' => 60, 'continue_on_fail' => true, ], + // ── L6: AI FEATURES ───────────────────────────────────────────────── + 'l6-ai' => [ + 'label' => 'L6 — AI Features (Cross-Analysis)', + 'level' => 'l6', + 'bash' => implode(' && ', [ + // Login come consultant (ha accesso a cross-analysis) + "echo '━━━ L6.0 Setup: login consultant ━━━'", + "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && echo \"Token consultant: \${TOKEN_CONS:0:25}...\" || echo '[SKIP] consultant non trovato — eseguire SIM-01 prima'", + "echo ''", + + // L6.1: portfolio (senza AI, solo dati aggregati) + "echo '━━━ L6.1 Cross-Analysis Portfolio (no AI) ━━━'", + "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN_CONS\" {$api}/cross-analysis/portfolio | python3 -m json.tool || echo '[SKIP] token non disponibile'", + "echo ''", + + // L6.2: history (vuota, ma endpoint deve rispondere 200) + "echo '━━━ L6.2 Cross-Analysis History ━━━'", + "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN_CONS\" {$api}/cross-analysis/history | python3 -c \"import sys,json; d=json.load(sys.stdin); h=d.get('data',{}).get('history',[]); print(f'History entries: {len(h)} — HTTP OK')\" || echo '[SKIP]'", + "echo ''", + + // L6.3: analyze con domanda breve (chiama AI Anthropic) + "echo '━━━ L6.3 Cross-Analysis AI Analyze ━━━'", + "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -X POST -H \"Authorization: Bearer \$TOKEN_CONS\" -H 'Content-Type: application/json' -d '{\"question\":\"Qual e il livello medio di compliance e quali sono le categorie NIS2 piu deboli nel portfolio?\"}' {$api}/cross-analysis/analyze | python3 -c \"import sys,json; d=json.load(sys.stdin); r=d.get('data',{}); ans=r.get('result',{}).get('answer',''); orgs=r.get('org_count',0); print(f'Org analizzate: {orgs}'); print(f'Risposta AI ({len(ans)} chars): {ans[:300]}...' if len(ans)>300 else f'Risposta: {ans}')\" || echo '[SKIP/ERRORE]'", + "echo ''", + + // L6.4: accesso negato a utente normale (403) + "echo '━━━ L6.4 Cross-Analysis 403 per utente non-consultant ━━━'", + "TOKEN_EMP=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_EMP\" ] && curl -sf -o /dev/null -w 'GET /cross-analysis/portfolio (org_admin) → HTTP %{http_code}\\n' -H \"Authorization: Bearer \$TOKEN_EMP\" {$api}/cross-analysis/portfolio || echo '[SKIP]'", + "echo ''", + + // L6.5: normative feed + "echo '━━━ L6.5 Normative Feed ━━━'", + "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/normative/list | python3 -c \"import sys,json; d=json.load(sys.stdin); items=d.get('data',[]); print(f'Aggiornamenti normativi: {len(items)}')\" || echo '[SKIP]'", + "echo ''", + + // L6.6: whistleblowing stats + "echo '━━━ L6.6 Whistleblowing Stats ━━━'", + "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/whistleblowing/stats | python3 -m json.tool || echo '[SKIP]'", + + "echo '[OK] L6 AI Features completato'", + ]), + 'cwd' => $root, 'timeout' => 120, 'continue_on_fail' => true, + ], + // ── SMOKE ─────────────────────────────────────────────────────────── 'smoke' => [ 'label' => 'Smoke Tests (curl rapido)', @@ -259,16 +304,20 @@ function getCommands(): array 'cwd' => $root, 'timeout' => 30, 'continue_on_fail' => false, ], 'full-suite' => [ - 'label' => 'Full Suite L1+L2+L3+L4+L5', + 'label' => 'Full Suite L1+L2+L3+L4+L5+L6', 'level' => 'infra', 'bash' => implode(' && ', [ "echo '════════════ L1 AUTH ════════════'", "curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"admin@datacore.demo\",\"password\":\"Demo2026!\"}' | python3 -c \"import sys,json; d=json.load(sys.stdin); print('Login:', 'OK' if d.get('success') else 'FAIL')\" || echo 'L1 SKIP'", "echo '════════════ L2 TENANT ════════════'", - "curl -sf {$api}/../api-status.php | python3 -c \"import sys,json; d=json.load(sys.stdin); print('API:', d.get('status','?'))\"", + "TOKEN={$getToken} && curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(f'Orgs: {len(orgs)}')\" || echo 'L2 SKIP'", + "echo '════════════ L3 COMPLIANCE ════════════'", + "TOKEN={$getToken} && ORG_ID=\$(curl -sf -H \"Authorization: Bearer \$TOKEN\" {$api}/organizations/list 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); orgs=d.get('data',[]); print(orgs[0]['id'] if orgs else '')\" 2>/dev/null) && [ -n \"\$ORG_ID\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN\" -H \"X-Organization-Id: \$ORG_ID\" {$api}/dashboard/compliance-score | python3 -c \"import sys,json; d=json.load(sys.stdin); print('Score:', d.get('data',{}).get('score','?'))\" || echo 'L3 SKIP'", "echo '════════════ L5 EXPORT ════════════'", - "curl -sf {$api}/../api-status.php | python3 -m json.tool", - "echo '[OK] Full Suite completata'", + "curl -sf {$api}/../api-status.php | python3 -c \"import sys,json; d=json.load(sys.stdin); print('API:', d.get('status','?'))\"", + "echo '════════════ L6 AI CROSS ════════════'", + "TOKEN_CONS=\$(curl -sf -X POST {$api}/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"consultant@nis2agile.demo\",\"password\":\"Demo2026!\"}' 2>/dev/null | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('access_token',''))\" 2>/dev/null) && [ -n \"\$TOKEN_CONS\" ] && curl -sf -H \"Authorization: Bearer \$TOKEN_CONS\" {$api}/cross-analysis/portfolio | python3 -c \"import sys,json; d=json.load(sys.stdin); print('Portfolio orgs:', d.get('data',{}).get('org_count','?'))\" || echo 'L6 SKIP (eseguire SIM-01 prima)'", + "echo '[OK] Full Suite L1→L6 completata'", ]), 'cwd' => $root, 'timeout' => 300, 'continue_on_fail' => true, ], @@ -576,8 +625,9 @@ function serveUI(): void 'l3-compliance' => ['L3', 'Compliance Core', 'l3'], 'l4-b2b' => ['L4', 'B2B & Services', 'l4'], 'l5-export' => ['L5', 'Export & Reports', 'l5'], + 'l6-ai' => ['L6', 'AI Features', 'l6'], 'chain-verify' => ['—', 'Hash Chain Verify', 'infra'], - 'full-suite' => ['ALL', 'Full Suite L1→L5', 'infra'], + 'full-suite' => ['ALL', 'Full Suite L1→L6', 'infra'], 'reset' => ['⚠', 'Reset Dati Demo', 'danger'], ]; foreach ($levelDefs as $id => [$badge, $label, $cls]) { @@ -615,6 +665,13 @@ function serveUI(): void ['NCR', 'GET /api/ncr/stats', 'L4', 'ok'], ['Onboard', 'POST /api/onboarding/complete', 'sim','ok'], ['Admin', 'GET /api/admin/stats', 'admin','skip'], + ['CrossAI', 'GET /api/cross-analysis/portfolio', 'L6', 'ok'], + ['CrossAI', 'GET /api/cross-analysis/history', 'L6', 'ok'], + ['CrossAI', 'POST /api/cross-analysis/analyze', 'L6', 'ok'], + ['Normative','GET /api/normative/list', 'L6', 'ok'], + ['Normative','POST /api/normative/{id}/ack', 'L6', 'sim'], + ['Whistle', 'GET /api/whistleblowing/stats', 'L6', 'ok'], + ['Whistle', 'POST /api/whistleblowing/submit', 'L6', 'sim'], ]; $covRows = ''; foreach ($coverageEndpoints as [$module, $ep, $level, $status]) { @@ -728,6 +785,7 @@ body { display: flex; height: 100vh; background: var(--navy); color: var(--text) .lvl-l3 { background: rgba(249,115,22,.15); color: var(--orange); } .lvl-l4 { background: rgba(168,85,247,.15); color: var(--purple); } .lvl-l5 { background: rgba(234,179,8,.15); color: var(--yellow); } +.lvl-l6 { background: rgba(16,185,129,.15); color: #10b981; } .lvl-infra { background: rgba(100,116,139,.15);color: var(--muted); } .lvl-sim { background: rgba(6,182,212,.1); color: var(--cyan); } .lvl-admin { background: rgba(239,68,68,.1); color: var(--red); } @@ -885,7 +943,7 @@ body { display: flex; height: 100vh; background: var(--navy); color: var(--text)
- Copertura endpoint API (27 totali) + Copertura endpoint API (35 totali)
| Modulo | Endpoint | Lvl | Status |
|---|