triage-supplement.test.js
1 /** 2 * Triage Agent Supplement — Priority Calculation Branch Coverage 3 * 4 * Covers branches in calculatePriority() not hit by existing tests: 5 * - Line 723: frequency > 10 (but <= 100) → score -= 2 6 * - Lines 725-726: frequency > 1 (but <= 10) → score -= 1 7 * - Line 731: severity === 'high' → score -= 2 8 * - Lines 740-741: ageHours > 24 → score -= 1 (old task) 9 * - Lines 748-749: stage in criticalStages → score -= 2 (assets, rescoring, proposals) 10 * - Lines 756-757: errorType includes 'auth', 'data_loss' → score -= 1 11 * - Lines 824-825: getStageFile returns null for unknown stage 12 */ 13 14 import { describe, test, before, after } from 'node:test'; 15 import assert from 'node:assert/strict'; 16 import Database from 'better-sqlite3'; 17 import { TriageAgent } from '../../src/agents/triage.js'; 18 import { resetDb as resetBaseDb } from '../../src/agents/base-agent.js'; 19 import { resetDb as resetTaskDb } from '../../src/agents/utils/task-manager.js'; 20 import { resetDb as resetMessageDb } from '../../src/agents/utils/message-manager.js'; 21 import { tmpdir } from 'os'; 22 import { join } from 'path'; 23 import { existsSync, unlinkSync } from 'fs'; 24 25 const TEST_DB = join(tmpdir(), `triage-supplement-${Date.now()}.db`); 26 27 let agent; 28 let db; 29 30 before(async () => { 31 process.env.DATABASE_PATH = TEST_DB; 32 process.env.AGENT_REALTIME_NOTIFICATIONS = 'false'; 33 process.env.AGENT_IMMEDIATE_INVOCATION = 'false'; 34 35 db = new Database(TEST_DB); 36 db.exec(` 37 CREATE TABLE IF NOT EXISTS agent_tasks ( 38 id INTEGER PRIMARY KEY AUTOINCREMENT, 39 task_type TEXT NOT NULL, 40 assigned_to TEXT NOT NULL, 41 created_by TEXT, 42 status TEXT DEFAULT 'pending', 43 priority INTEGER DEFAULT 5, 44 context_json TEXT, 45 result_json TEXT, 46 parent_task_id INTEGER, 47 error_message TEXT, 48 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 49 started_at DATETIME, 50 completed_at DATETIME, 51 retry_count INTEGER DEFAULT 0 52 ); 53 CREATE TABLE IF NOT EXISTS agent_messages ( 54 id INTEGER PRIMARY KEY AUTOINCREMENT, 55 task_id INTEGER, from_agent TEXT NOT NULL, to_agent TEXT NOT NULL, 56 message_type TEXT, content TEXT NOT NULL, metadata_json TEXT, 57 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, read_at DATETIME 58 ); 59 CREATE TABLE IF NOT EXISTS agent_logs ( 60 id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, 61 agent_name TEXT NOT NULL, log_level TEXT, message TEXT, 62 data_json TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP 63 ); 64 CREATE TABLE IF NOT EXISTS agent_state ( 65 agent_name TEXT PRIMARY KEY, 66 last_active DATETIME DEFAULT CURRENT_TIMESTAMP, 67 current_task_id INTEGER, status TEXT DEFAULT 'idle', metrics_json TEXT 68 ); 69 CREATE TABLE IF NOT EXISTS agent_outcomes ( 70 id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, 71 agent_name TEXT NOT NULL, task_type TEXT NOT NULL, outcome TEXT NOT NULL, 72 context_json TEXT, result_json TEXT, duration_ms INTEGER, 73 created_at DATETIME DEFAULT CURRENT_TIMESTAMP 74 ); 75 CREATE TABLE IF NOT EXISTS agent_llm_usage ( 76 id INTEGER PRIMARY KEY AUTOINCREMENT, agent_name TEXT NOT NULL, 77 task_id INTEGER, model TEXT NOT NULL, prompt_tokens INTEGER NOT NULL, 78 completion_tokens INTEGER NOT NULL, cost_usd DECIMAL(10,6) NOT NULL, 79 created_at DATETIME DEFAULT CURRENT_TIMESTAMP 80 ); 81 CREATE TABLE IF NOT EXISTS structured_logs ( 82 id INTEGER PRIMARY KEY AUTOINCREMENT, agent_name TEXT, task_id INTEGER, 83 level TEXT, message TEXT, data_json TEXT, 84 created_at DATETIME DEFAULT CURRENT_TIMESTAMP 85 ); 86 CREATE TABLE IF NOT EXISTS cron_locks ( 87 lock_key TEXT PRIMARY KEY, 88 acquired_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 89 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 90 description TEXT 91 ); 92 `); 93 94 agent = new TriageAgent(); 95 await agent.initialize(); 96 }); 97 98 after(() => { 99 resetBaseDb(); 100 resetTaskDb(); 101 resetMessageDb(); 102 try { 103 db.close(); 104 } catch { 105 /* ignore */ 106 } 107 if (existsSync(TEST_DB)) { 108 try { 109 unlinkSync(TEST_DB); 110 } catch { 111 /* ignore */ 112 } 113 } 114 }); 115 116 describe('TriageAgent - calculatePriority branch coverage', () => { 117 const freshTask = () => ({ id: 1, created_at: new Date().toISOString() }); 118 const oldTask = () => ({ 119 id: 2, 120 created_at: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(), 121 }); 122 123 test('frequency > 10 (not > 100) reduces score by 2', () => { 124 const p = agent.calculatePriority(freshTask(), { frequency: 50, severity: 'low', stage: '' }); 125 // base 5 - 2 (freq > 10) = 3 126 assert.strictEqual(p, 3); 127 }); 128 129 test('frequency > 1 (not > 10) reduces score by 1', () => { 130 const p = agent.calculatePriority(freshTask(), { frequency: 5, severity: 'low', stage: '' }); 131 // base 5 - 1 (freq > 1) = 4 132 assert.strictEqual(p, 4); 133 }); 134 135 test('severity === high reduces score by 2', () => { 136 const p = agent.calculatePriority(freshTask(), { frequency: 1, severity: 'high', stage: '' }); 137 // base 5 - 2 (high severity) = 3 138 assert.strictEqual(p, 3); 139 }); 140 141 test('age > 24h reduces score by 1', () => { 142 const p = agent.calculatePriority(oldTask(), { frequency: 1, severity: 'low', stage: '' }); 143 // base 5 - 1 (age > 24h) = 4 144 assert.strictEqual(p, 4); 145 }); 146 147 test('assets stage is critical → reduces score by 2', () => { 148 const p = agent.calculatePriority(freshTask(), { 149 frequency: 1, 150 severity: 'low', 151 stage: 'assets', 152 }); 153 // base 5 - 2 (critical stage) = 3 154 assert.strictEqual(p, 3); 155 }); 156 157 test('rescoring stage is critical → reduces score by 2', () => { 158 const p = agent.calculatePriority(freshTask(), { 159 frequency: 1, 160 severity: 'low', 161 stage: 'rescoring', 162 }); 163 assert.strictEqual(p, 3); 164 }); 165 166 test('proposals stage is critical → reduces score by 2', () => { 167 const p = agent.calculatePriority(freshTask(), { 168 frequency: 1, 169 severity: 'low', 170 stage: 'proposals', 171 }); 172 assert.strictEqual(p, 3); 173 }); 174 175 test('auth error type reduces score by 1', () => { 176 const p = agent.calculatePriority(freshTask(), { 177 frequency: 1, 178 severity: 'low', 179 stage: '', 180 error_type: 'auth_failure', 181 }); 182 // base 5 - 1 (auth) = 4 183 assert.strictEqual(p, 4); 184 }); 185 186 test('data_loss error type reduces score by 1', () => { 187 const p = agent.calculatePriority(freshTask(), { 188 frequency: 1, 189 severity: 'low', 190 stage: '', 191 error_type: 'data_loss', 192 }); 193 assert.strictEqual(p, 4); 194 }); 195 196 test('database error type reduces score by 1', () => { 197 const p = agent.calculatePriority(freshTask(), { 198 frequency: 1, 199 severity: 'low', 200 stage: '', 201 error_type: 'database_constraint', 202 }); 203 assert.strictEqual(p, 4); 204 }); 205 }); 206 207 describe('TriageAgent - resolveFilePath null return (unknown stage)', () => { 208 test('returns null for unknown stage name with no stack trace', () => { 209 // No src/ path in error, no pattern match, unknown stage 210 const result = agent.resolveFilePath('generic error occurred', '', 'unknown_stage_xyz'); 211 assert.strictEqual(result, null); 212 }); 213 214 test('returns null when error, stackTrace, and stage have no matches', () => { 215 const result = agent.resolveFilePath('something totally unrecognized', '', 'custom_stage'); 216 assert.strictEqual(result, null); 217 }); 218 219 test('returns stage file for known stage even with unknown error', () => { 220 const result = agent.resolveFilePath('generic error', '', 'scoring'); 221 assert.ok(result && result.includes('scoring'), 'Should resolve to scoring file'); 222 }); 223 });