// Test suite for OpenRouter Code Node in n8n workflow // Run with: node workflows/test_openrouter.js const assert = require('assert'); // ============================================================================ // MOCK DATA - Simula input dal nodo Store Log // ============================================================================ const mockInputData = { client_id: "550e8400-e29b-41d4-a716-446655440000", hostname: "server-db-01", source: "/var/log/postgresql/postgresql-14-main.log", severity: "critical", timestamp: "2026-04-02T10:30:00Z", raw_log: "2026-04-02 10:29:58.123 UTC [12345] FATAL: could not write to file 'base/16384/2619': No space left on device", matched_pattern: "No space left on device" }; // ============================================================================ // TEST 1: Verifica struttura input/output attesa // ============================================================================ console.log('\nπŸ§ͺ TEST 1: Verifica struttura dati input/output'); function testDataStructure() { // Input deve avere campi richiesti const requiredInputFields = ['client_id', 'hostname', 'source', 'severity', 'timestamp', 'raw_log']; for (const field of requiredInputFields) { assert(mockInputData[field] !== undefined, `Campo input mancante: ${field}`); } console.log(' βœ… Input contiene tutti i campi richiesti'); // Output atteso deve avere questi campi const expectedOutputStructure = { client_id: "string", hostname: "string", severity: "string", raw_log: "string", ai_analysis: "object", ai_status: "string", ai_timestamp: "string", ai_model: "string" }; console.log(' βœ… Struttura output definita correttamente'); return true; } // ============================================================================ // TEST 2: Verifica truncamento log // ============================================================================ console.log('\nπŸ§ͺ TEST 2: Verifica truncamento log > 2000 caratteri'); function testLogTruncation() { const maxLogLength = 2000; const longLog = "a".repeat(5000); const truncatedLog = longLog.substring(0, maxLogLength); assert(truncatedLog.length === maxLogLength, 'Log deve essere troncato a 2000 caratteri'); assert(truncatedLog.length < longLog.length, 'Log troncato deve essere piΓΉ corto di originale'); console.log(' βœ… Truncamento log funziona correttamente (2000 char max)'); return true; } // ============================================================================ // TEST 3: Verifica payload OpenRouter // ============================================================================ console.log('\nπŸ§ͺ TEST 3: Verifica payload API OpenRouter'); function testOpenRouterPayload() { const payload = { model: "openai/gpt-4o-mini", messages: [ { role: "system", content: "SYSTEM_PROMPT" }, { role: "user", content: JSON.stringify(mockInputData) } ], temperature: 0.3, max_tokens: 500, response_format: { type: "json_object" }, store: false }; assert(payload.model === "openai/gpt-4o-mini", 'Modello deve essere openai/gpt-4o-mini'); assert(payload.temperature === 0.3, 'Temperature deve essere 0.3'); assert(payload.max_tokens === 500, 'Max tokens deve essere 500'); assert(payload.response_format.type === "json_object", 'Response format deve essere json_object'); assert(payload.store === false, 'Store deve essere false per privacy'); assert(payload.messages.length === 2, 'Deve avere 2 messaggi (system + user)'); assert(payload.messages[0].role === "system", 'Primo messaggio deve essere system'); assert(payload.messages[1].role === "user", 'Secondo messaggio deve essere user'); console.log(' βœ… Payload API strutturato correttamente'); return true; } // ============================================================================ // TEST 4: Verifica headers HTTP richiesti // ============================================================================ console.log('\nπŸ§ͺ TEST 4: Verifica headers HTTP richiesti da OpenRouter'); function testRequiredHeaders() { const requiredHeaders = [ 'Content-Type', 'Authorization', 'HTTP-Referer', 'X-Title' ]; // Simula headers che verranno inviati const headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer sk-or-v1-test', 'HTTP-Referer': 'https://logwhisperer.ai', 'X-Title': 'LogWhispererAI' }; for (const header of requiredHeaders) { assert(headers[header] !== undefined, `Header richiesto mancante: ${header}`); } assert(headers['Authorization'].startsWith('Bearer '), 'Authorization deve iniziare con Bearer'); assert(headers['HTTP-Referer'].startsWith('https://'), 'HTTP-Referer deve essere URL HTTPS'); console.log(' βœ… Tutti gli headers richiesti da OpenRouter presenti'); return true; } // ============================================================================ // TEST 5: Verifica struttura risposta AI // ============================================================================ console.log('\nπŸ§ͺ TEST 5: Verifica struttura risposta AI attesa'); function testAIResponseStructure() { // Simula risposta attesa da OpenRouter const mockAIResponse = { sintesi: "PostgreSQL ha esaurito lo spazio disco", severita: "critical", comando: "df -h /var/lib/postgresql && du -sh /var/log/postgresql/* | sort -hr | head -5", sicuro: true, note: "Verifica spazio disponibile. Se < 10%, pulisci log vecchi.", richiede_conferma: false }; const requiredFields = ['sintesi', 'severita', 'comando', 'sicuro', 'note', 'richiede_conferma']; for (const field of requiredFields) { assert(mockAIResponse[field] !== undefined, `Campo AI response mancante: ${field}`); } const validSeverities = ['low', 'medium', 'critical']; assert(validSeverities.includes(mockAIResponse.severita), 'SeveritΓ  deve essere low|medium|critical'); assert(typeof mockAIResponse.sicuro === 'boolean', 'sicuro deve essere boolean'); assert(typeof mockAIResponse.richiede_conferma === 'boolean', 'richiede_conferma deve essere boolean'); console.log(' βœ… Struttura risposta AI valida'); return true; } // ============================================================================ // TEST 6: Verifica fallback // ============================================================================ console.log('\nπŸ§ͺ TEST 6: Verifica fallback in caso di errore API'); function testFallbackResponse() { // Simula risposta fallback quando API fallisce const fallbackResponse = { sintesi: "Errore durante analisi AI", severita: "critical", comando: null, sicuro: true, note: "Fallback: OpenRouter API non disponibile. Controlla manualmente il log.", richiede_conferma: true }; assert(fallbackResponse.sintesi.includes("Errore") || fallbackResponse.sintesi.includes("Fallback"), 'Fallback deve indicare errore'); assert(fallbackResponse.sicuro === true, 'Fallback deve essere sempre sicuro'); assert(fallbackResponse.richiede_conferma === true, 'Fallback deve richiedere conferma'); console.log(' βœ… Fallback response strutturato correttamente'); return true; } // ============================================================================ // TEST 7: Verifica timeout // ============================================================================ console.log('\nπŸ§ͺ TEST 7: Verifica timeout configurazione'); function testTimeoutConfiguration() { const TIMEOUT_MS = 10000; // 10 secondi assert(TIMEOUT_MS === 10000, 'Timeout deve essere 10000ms (10s)'); assert(TIMEOUT_MS > 5000, 'Timeout deve essere maggiore di 5s per performance target'); console.log(' βœ… Timeout configurato correttamente (10s)'); return true; } // ============================================================================ // TEST 8: Verifica validazione comandi pericolosi // ============================================================================ console.log('\nπŸ§ͺ TEST 8: Verifica sicurezza comandi'); function testCommandSafety() { const forbiddenPatterns = [ /rm\s+-rf\s+\//, />\s*\/dev\/sda/, /mkfs/, /dd\s+if=\/dev\/zero/, /chmod\s+-R\s+777/, /:\(\)\{\s*:\|:\s*&\s*\};\s*:/ ]; const dangerousCommands = [ "rm -rf / --no-preserve-root", "dd if=/dev/zero of=/dev/sda", "mkfs.ext4 /dev/sda1", ":(){ :|:& };:" ]; for (const cmd of dangerousCommands) { let isBlocked = false; for (const pattern of forbiddenPatterns) { if (pattern.test(cmd)) { isBlocked = true; break; } } assert(isBlocked, `Comando pericoloso dovrebbe essere rilevato: ${cmd}`); } console.log(' βœ… Pattern comandi pericolosi correttamente rilevati'); return true; } // ============================================================================ // TEST 9: Verifica System Prompt contiene Metodo Sacchi // ============================================================================ console.log('\nπŸ§ͺ TEST 9: Verifica System Prompt include Metodo Sacchi'); function testSystemPromptMetodoSacchi() { // Leggi il file del workflow e verifica che il system prompt contenga i principi const fs = require('fs'); const path = require('path'); const workflowPath = path.join(__dirname, 'logwhisperer_ingest.json'); const workflow = JSON.parse(fs.readFileSync(workflowPath, 'utf8')); // Trova il nodo Call OpenRouter const openrouterNode = workflow.nodes.find(n => n.name === "Call OpenRouter"); assert(openrouterNode, 'Nodo Call OpenRouter deve esistere nel workflow'); const jsCode = openrouterNode.parameters.jsCode; // Verifica principi Metodo Sacchi nel system prompt assert(jsCode.includes("SAFETY FIRST"), 'System prompt deve includere SAFETY FIRST'); assert(jsCode.includes("LITTLE OFTEN"), 'System prompt deve includere LITTLE OFTEN'); assert(jsCode.includes("DOUBLE CHECK"), 'System prompt deve includere DOUBLE CHECK'); assert(jsCode.includes("Metodo Sacchi"), 'System prompt deve fare riferimento al Metodo Sacchi'); console.log(' βœ… System Prompt include principi Metodo Sacchi'); return true; } // ============================================================================ // TEST 10: Verifica connessioni workflow // ============================================================================ console.log('\nπŸ§ͺ TEST 10: Verifica connessioni workflow'); function testWorkflowConnections() { const fs = require('fs'); const path = require('path'); const workflowPath = path.join(__dirname, 'logwhisperer_ingest.json'); const workflow = JSON.parse(fs.readFileSync(workflowPath, 'utf8')); // Verifica che Store Log si connetta a Call OpenRouter const storeLogConnections = workflow.connections["Store Log"]; assert(storeLogConnections, 'Store Log deve avere connessioni'); const connectsToOpenRouter = storeLogConnections.main[0].some( conn => conn.node === "Call OpenRouter" ); assert(connectsToOpenRouter, 'Store Log deve connettersi a Call OpenRouter'); // Verifica che Call OpenRouter si connetta a Critical Severity? const openrouterConnections = workflow.connections["Call OpenRouter"]; assert(openrouterConnections, 'Call OpenRouter deve avere connessioni'); const connectsToSeverity = openrouterConnections.main[0].some( conn => conn.node === "Critical Severity?" ); assert(connectsToSeverity, 'Call OpenRouter deve connettersi a Critical Severity?'); console.log(' βœ… Connessioni workflow configurate correttamente'); return true; } // ============================================================================ // ESECUZIONE TEST // ============================================================================ console.log('╔════════════════════════════════════════════════════════════════╗'); console.log('β•‘ πŸ§ͺ TEST SUITE: OpenRouter Integration Node β•‘'); console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•'); const tests = [ testDataStructure, testLogTruncation, testOpenRouterPayload, testRequiredHeaders, testAIResponseStructure, testFallbackResponse, testTimeoutConfiguration, testCommandSafety, testSystemPromptMetodoSacchi, testWorkflowConnections ]; let passed = 0; let failed = 0; for (const test of tests) { try { test(); passed++; } catch (error) { console.log(` ❌ FAILED: ${error.message}`); failed++; } } console.log('\n╔════════════════════════════════════════════════════════════════╗'); console.log(`β•‘ πŸ“Š TEST RESULTS: ${passed} passed, ${failed} failed β•‘`); console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•'); if (failed > 0) { process.exit(1); } else { console.log('\n✨ Tutti i test sono passati! Il nodo OpenRouter Γ¨ pronto.'); process.exit(0); }