triage-supplement2.test.js
1 /** 2 * Triage Agent Supplement 2 — Additional Branch Coverage 3 * 4 * Covers branches in triage.js not hit by existing tests: 5 * - Lines 467-469: determineSeverity low path (validation/invalid input/cosmetic) 6 * - Lines 559: suggestFix database — no unique constraint → generic message 7 * - Lines 571: suggestFix api_error — no 429/401 → generic message 8 * - Lines 580: suggestFix performance — no timeout → generic message 9 * - Lines 864-896: checkKnownErrorDatabase loop body — completed fix_bug tasks with matching errors 10 */ 11 12 import { describe, test, before, after, mock } from 'node:test'; 13 import assert from 'node:assert/strict'; 14 import Database from 'better-sqlite3'; 15 import { createPgMock } from '../helpers/pg-mock.js'; 16 17 // Shared in-memory database with all agent tables 18 const db = new Database(':memory:'); 19 db.exec(` 20 CREATE TABLE IF NOT EXISTS agent_tasks ( 21 id INTEGER PRIMARY KEY AUTOINCREMENT, 22 task_type TEXT NOT NULL, 23 assigned_to TEXT NOT NULL, 24 created_by TEXT, 25 status TEXT DEFAULT 'pending', 26 priority INTEGER DEFAULT 5, 27 context_json TEXT, 28 result_json TEXT, 29 parent_task_id INTEGER, 30 error_message TEXT, 31 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 32 started_at DATETIME, 33 completed_at DATETIME, 34 retry_count INTEGER DEFAULT 0 35 ); 36 CREATE TABLE IF NOT EXISTS agent_messages ( 37 id INTEGER PRIMARY KEY AUTOINCREMENT, 38 task_id INTEGER, from_agent TEXT NOT NULL, to_agent TEXT NOT NULL, 39 message_type TEXT, content TEXT NOT NULL, metadata_json TEXT, 40 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, read_at DATETIME 41 ); 42 CREATE TABLE IF NOT EXISTS agent_logs ( 43 id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, 44 agent_name TEXT NOT NULL, log_level TEXT, message TEXT, 45 data_json TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP 46 ); 47 CREATE TABLE IF NOT EXISTS agent_state ( 48 agent_name TEXT PRIMARY KEY, 49 last_active DATETIME DEFAULT CURRENT_TIMESTAMP, 50 current_task_id INTEGER, status TEXT DEFAULT 'idle', metrics_json TEXT 51 ); 52 CREATE TABLE IF NOT EXISTS agent_outcomes ( 53 id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, 54 agent_name TEXT NOT NULL, task_type TEXT NOT NULL, outcome TEXT NOT NULL, 55 context_json TEXT, result_json TEXT, duration_ms INTEGER, 56 created_at DATETIME DEFAULT CURRENT_TIMESTAMP 57 ); 58 CREATE TABLE IF NOT EXISTS agent_llm_usage ( 59 id INTEGER PRIMARY KEY AUTOINCREMENT, agent_name TEXT NOT NULL, 60 task_id INTEGER, model TEXT NOT NULL, prompt_tokens INTEGER NOT NULL, 61 completion_tokens INTEGER NOT NULL, cost_usd DECIMAL(10,6) NOT NULL, 62 created_at DATETIME DEFAULT CURRENT_TIMESTAMP 63 ); 64 CREATE TABLE IF NOT EXISTS structured_logs ( 65 id INTEGER PRIMARY KEY AUTOINCREMENT, agent_name TEXT, task_id INTEGER, 66 level TEXT, message TEXT, data_json TEXT, 67 created_at DATETIME DEFAULT CURRENT_TIMESTAMP 68 ); 69 CREATE TABLE IF NOT EXISTS cron_locks ( 70 lock_key TEXT PRIMARY KEY, 71 acquired_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 72 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 73 description TEXT 74 ); 75 `); 76 77 process.env.AGENT_REALTIME_NOTIFICATIONS = 'false'; 78 process.env.AGENT_IMMEDIATE_INVOCATION = 'false'; 79 80 mock.module('../../src/utils/db.js', { namedExports: createPgMock(db) }); 81 82 const { TriageAgent } = await import('../../src/agents/triage.js'); 83 84 let agent; 85 86 before(async () => { 87 agent = new TriageAgent(); 88 await agent.initialize(); 89 }); 90 91 after(() => { 92 db.exec('DELETE FROM agent_tasks; DELETE FROM agent_messages; DELETE FROM agent_logs; DELETE FROM agent_state; DELETE FROM agent_outcomes;'); 93 }); 94 95 // ── Lines 467-469: determineSeverity — validation/low path ─────────────── 96 97 describe('TriageAgent - determineSeverity low severity (lines 467-469)', () => { 98 test('validation error yields low severity', () => { 99 const result = agent.determineSeverity('validation error in input', 'enrich', 1); 100 assert.strictEqual(result.severity, 'low'); 101 assert.strictEqual(result.basePriority, 4); 102 }); 103 104 test('invalid input error yields low severity', () => { 105 const result = agent.determineSeverity('invalid input: email format wrong', 'proposals', 1); 106 assert.strictEqual(result.severity, 'low'); 107 assert.strictEqual(result.basePriority, 4); 108 }); 109 110 test('cosmetic error yields low severity', () => { 111 const result = agent.determineSeverity('cosmetic styling issue on page', 'outreach', 1); 112 assert.strictEqual(result.severity, 'low'); 113 assert.strictEqual(result.basePriority, 4); 114 }); 115 }); 116 117 // ── Lines 559, 571, 580: suggestFix default branches ───────────────────── 118 119 describe('TriageAgent - suggestFix default branches (lines 559, 571, 580)', () => { 120 test('database error without unique constraint → generic message (line 559)', () => { 121 const hint = agent.suggestFix('database', 'SELECT query failed with syntax error'); 122 assert.strictEqual(hint, 'Review database schema and query syntax'); 123 }); 124 125 test('api_error without 429 or 401 → generic message (line 571)', () => { 126 const hint = agent.suggestFix('api_error', 'API returned 500 internal server error'); 127 assert.strictEqual(hint, 'Add error handling for API failures'); 128 }); 129 130 test('performance error without timeout → generic message (line 580)', () => { 131 const hint = agent.suggestFix('performance', 'Memory usage too high during processing'); 132 assert.strictEqual(hint, 'Profile and optimize performance bottleneck'); 133 }); 134 }); 135 136 // ── Lines 864-896: checkKnownErrorDatabase loop body ───────────────────── 137 138 describe('TriageAgent - checkKnownErrorDatabase with completed fixes (lines 864-896)', () => { 139 test('returns matching fix when completed fix_bug task has similar error', async () => { 140 // Insert a completed fix_bug task with matching error info 141 db.prepare( 142 `INSERT INTO agent_tasks (task_type, assigned_to, status, context_json, result_json, completed_at) 143 VALUES ('fix_bug', 'developer', 'completed', ?, ?, datetime('now', '-1 hour'))` 144 ).run( 145 JSON.stringify({ 146 error_message: 'Database connection timeout in scoring stage', 147 stack_trace: 'at score.js:123', 148 error_type: 'database', 149 }), 150 JSON.stringify({ 151 fix_description: 'Added retry logic to DB connection', 152 files_changed: ['src/score.js'], 153 summary: 'Fixed DB timeout', 154 }) 155 ); 156 157 // Use the same (or very similar) error message 158 const result = await agent.checkKnownErrorDatabase( 159 'Database connection timeout in scoring stage', 160 'at score.js:123' 161 ); 162 163 // Should find the similar fix (similarity >= 0.7) 164 assert.ok(result !== null, 'should find a similar fix'); 165 assert.ok(result.fix_description, 'should have a fix description'); 166 assert.ok(result.similarity >= 0.7, `similarity should be >= 0.7, got ${result.similarity}`); 167 }); 168 169 test('returns null when no fix matches (similarity < 0.7)', async () => { 170 const result = await agent.checkKnownErrorDatabase( 171 'Completely unrelated obscure rendering glitch in SVG transform matrix', 172 '' 173 ); 174 175 // The previously inserted fix should NOT match this very different error 176 assert.strictEqual(result, null, 'should return null for non-matching error'); 177 }); 178 179 test('handles malformed JSON in completed tasks gracefully (lines 892-895)', async () => { 180 // Insert a completed task with invalid JSON in context_json 181 db.prepare( 182 `INSERT INTO agent_tasks (task_type, assigned_to, status, context_json, result_json, completed_at) 183 VALUES ('fix_bug', 'developer', 'completed', ?, ?, datetime('now', '-2 hours'))` 184 ).run('INVALID_JSON{{{', JSON.stringify({ fix_description: 'some fix' })); 185 186 // Should not throw — malformed JSON is caught and skipped 187 await assert.doesNotReject(() => agent.checkKnownErrorDatabase('Database connection timeout', '')); 188 }); 189 });