/ tests / agents / triage-supplement2.test.js
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  });