/ __quarantined_tests__ / agents / utils / context-builder.test.js
context-builder.test.js
  1  /**
  2   * Context Builder — additional branch coverage tests
  3   *
  4   * Targets uncovered branches in src/agents/utils/context-builder.js:
  5   * - getDb() reuse path (line 15: db already initialised)
  6   * - getCached() TTL-hit path for related tasks cache key
  7   * - tryParseJSON with related-task JSON fields
  8   * - extractFilePathFromContext null guard
  9   * - normalizeErrorMessage null/undefined input
 10   * - estimateTokens null/empty input
 11   */
 12  
 13  import { test, describe } from 'node:test';
 14  import assert from 'node:assert/strict';
 15  import Database from 'better-sqlite3';
 16  import fs from 'fs/promises';
 17  import path from 'path';
 18  import { fileURLToPath } from 'url';
 19  import {
 20    buildAgentContext,
 21    clearCache,
 22    resetDb,
 23  } from '../../../src/agents/utils/context-builder.js';
 24  import {
 25    createAgentTask,
 26    completeTask,
 27    failTask,
 28    resetDb as resetTaskDb,
 29  } from '../../../src/agents/utils/task-manager.js';
 30  
 31  const __filename = fileURLToPath(import.meta.url);
 32  const __dirname = path.dirname(__filename);
 33  
 34  const dbPath = path.join('/tmp', `test-ctx-builder-utils-${Date.now()}.db`);
 35  
 36  async function initDb() {
 37    try {
 38      await fs.unlink(dbPath);
 39    } catch (_) {
 40      /* ignore */
 41    }
 42  
 43    const db = new Database(dbPath);
 44    db.pragma('foreign_keys = ON');
 45  
 46    const migrationsDir = path.join(__dirname, '..', '..', '..', 'db', 'migrations');
 47    const migrations = [
 48      '047-create-agent-system.sql',
 49      '052-create-agent-llm-usage.sql',
 50      '053-create-agent-outcomes.sql',
 51    ];
 52  
 53    for (const f of migrations) {
 54      try {
 55        const sql = await fs.readFile(path.join(migrationsDir, f), 'utf8');
 56        db.exec(sql);
 57      } catch (_) {
 58        /* ignore missing files */
 59      }
 60    }
 61  
 62    db.close();
 63  }
 64  
 65  async function cleanup() {
 66    resetDb();
 67    resetTaskDb();
 68    clearCache();
 69    delete process.env.DATABASE_PATH;
 70    delete process.env.AGENT_ENABLE_TASK_HISTORY;
 71    delete process.env.AGENT_REALTIME_NOTIFICATIONS;
 72    try {
 73      await fs.unlink(dbPath);
 74    } catch (_) {
 75      /* ignore */
 76    }
 77  }
 78  
 79  describe('context-builder branch coverage', () => {
 80    test('getDb() reuse — second call returns same connection (line 15 branch)', async () => {
 81      await initDb();
 82      process.env.DATABASE_PATH = dbPath;
 83      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
 84  
 85      try {
 86        // First buildAgentContext call initialises db
 87        const ctx1 = await buildAgentContext('developer', ['base.md']);
 88        // Second call should reuse the same db connection (line 14: if (!db) is false)
 89        const ctx2 = await buildAgentContext('developer', ['base.md']);
 90        assert.ok(ctx1.fullContext, 'First call should produce context');
 91        assert.ok(ctx2.fullContext, 'Second call should reuse db and produce context');
 92      } finally {
 93        await cleanup();
 94      }
 95    });
 96  
 97    test('getCached hit for related tasks cache key (line 215 branch)', async () => {
 98      await initDb();
 99      process.env.DATABASE_PATH = dbPath;
100      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
101  
102      try {
103        // Create a completed task so getRelatedTasks has something to find
104        const taskId = await createAgentTask({
105          task_type: 'fix_bug',
106          assigned_to: 'developer',
107          context: { file_path: 'src/utils/logger.js', error_type: 'import_error' },
108        });
109        completeTask(taskId, { approach: 'Fixed import' });
110  
111        clearCache();
112  
113        const currentTask = {
114          context_json: { file_path: 'src/utils/logger.js', error_type: 'import_error' },
115        };
116  
117        // First call populates the related-tasks cache
118        const ctx1 = await buildAgentContext('developer', ['base.md'], currentTask);
119        // Second call hits the cache for the related-tasks key
120        const ctx2 = await buildAgentContext('developer', ['base.md'], currentTask);
121  
122        assert.strictEqual(
123          ctx1.metadata.historyStats.relatedTasks,
124          ctx2.metadata.historyStats.relatedTasks,
125          'Related task count should match from cache'
126        );
127      } finally {
128        await cleanup();
129      }
130    });
131  
132    test('extractFilePathFromContext returns null for null context', async () => {
133      await initDb();
134      process.env.DATABASE_PATH = dbPath;
135      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
136  
137      try {
138        clearCache();
139  
140        // currentTask.context_json is an object but with no recognisable path fields
141        // AND no error_message or stack_trace — forces extractFilePathFromContext to return null
142        const currentTask = { context_json: { random_key: 'random_value' } };
143        const ctx = await buildAgentContext('developer', ['base.md'], currentTask);
144        assert.strictEqual(ctx.metadata.historyStats.relatedTasks, 0);
145      } finally {
146        await cleanup();
147      }
148    });
149  
150    test('estimateTokens with null/empty string returns 0 (line 495 branch)', async () => {
151      await initDb();
152      process.env.DATABASE_PATH = dbPath;
153      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
154      process.env.AGENT_ENABLE_TASK_HISTORY = 'false';
155  
156      try {
157        // When history is disabled, historyContext is null -> estimateTokens(null) -> 0
158        const ctx = await buildAgentContext('developer', ['base.md']);
159        assert.strictEqual(ctx.historyTokens, 0, 'Null historyContext should yield 0 tokens');
160        assert.strictEqual(ctx.historyContext, null);
161      } finally {
162        await cleanup();
163      }
164    });
165  
166    test('normalizeErrorMessage with null error returns "Unknown error" (line 475 branch)', async () => {
167      await initDb();
168      process.env.DATABASE_PATH = dbPath;
169      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
170  
171      try {
172        // Insert a failed task with empty string error_message
173        // normalizeErrorMessage('') → the function checks !error which is truthy for ''
174        // Actually '' is falsy so normalizeErrorMessage('') returns 'Unknown error'
175        const db = new Database(dbPath);
176        db.prepare(
177          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, context_json, completed_at)
178           VALUES (?, ?, ?, ?, ?, datetime('now'))`
179        ).run('fix_bug', 'developer', 'failed', '', JSON.stringify({ error_type: 'test' }));
180        db.close();
181  
182        clearCache();
183  
184        const ctx = await buildAgentContext('developer', ['base.md']);
185        assert.ok(ctx.fullContext, 'Should handle empty error_message');
186      } finally {
187        await cleanup();
188      }
189    });
190  
191    test('normalizeErrorMessage strips file paths and home paths', async () => {
192      await initDb();
193      process.env.DATABASE_PATH = dbPath;
194      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
195  
196      try {
197        // Insert failed task with error containing file path + home path
198        const db = new Database(dbPath);
199        db.prepare(
200          `INSERT INTO agent_tasks (task_type, assigned_to, status, error_message, context_json, completed_at)
201           VALUES (?, ?, ?, ?, ?, datetime('now'))`
202        ).run(
203          'fix_bug',
204          'developer',
205          'failed',
206          'Error at /home/jason/code/333Method/src/test.js:42:10 something broke',
207          JSON.stringify({ error_type: 'runtime' })
208        );
209        db.close();
210  
211        clearCache();
212  
213        const ctx = await buildAgentContext('developer', ['base.md']);
214        assert.ok(ctx.fullContext);
215        // The history should contain normalised error, not raw paths
216        assert.ok(
217          ctx.historyContext.includes('Past Failures to Avoid') ||
218            ctx.historyContext.includes('No historical task data'),
219          'Should have failure section or empty history'
220        );
221      } finally {
222        await cleanup();
223      }
224    });
225  
226    test('getRelatedTasks with only errorType (no filePath) builds single-condition query', async () => {
227      await initDb();
228      process.env.DATABASE_PATH = dbPath;
229      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
230  
231      try {
232        // Create a completed task with error_type but no file info
233        const taskId = await createAgentTask({
234          task_type: 'fix_bug',
235          assigned_to: 'developer',
236          context: { error_type: 'timeout' },
237        });
238        completeTask(taskId, { approach: 'Added retry logic' });
239  
240        clearCache();
241  
242        // currentTask has only error_type, no file paths at all
243        const currentTask = { context_json: { error_type: 'timeout' } };
244        const ctx = await buildAgentContext('developer', ['base.md'], currentTask);
245        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
246      } finally {
247        await cleanup();
248      }
249    });
250  
251    test('getRelatedTasks with only filePath (no errorType) builds single-condition query', async () => {
252      await initDb();
253      process.env.DATABASE_PATH = dbPath;
254      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
255  
256      try {
257        const taskId = await createAgentTask({
258          task_type: 'fix_bug',
259          assigned_to: 'developer',
260          context: { file_path: 'src/pipeline.js' },
261        });
262        completeTask(taskId, { approach: 'Fixed pipeline' });
263  
264        clearCache();
265  
266        // currentTask has file_path but no error_type
267        const currentTask = { context_json: { file_path: 'src/pipeline.js' } };
268        const ctx = await buildAgentContext('developer', ['base.md'], currentTask);
269        assert.ok(typeof ctx.metadata.historyStats.relatedTasks === 'number');
270      } finally {
271        await cleanup();
272      }
273    });
274  
275    test('formatSuccessfulTask returns null when result_json and outcome_result are both null (line 337)', async () => {
276      await initDb();
277      process.env.DATABASE_PATH = dbPath;
278      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
279  
280      try {
281        // Create a completed task with NO result_json
282        const db = new Database(dbPath);
283        db.prepare(
284          `INSERT INTO agent_tasks (task_type, assigned_to, status, result_json, completed_at)
285           VALUES (?, ?, ?, ?, datetime('now'))`
286        ).run('fix_bug', 'developer', 'completed', null);
287        db.close();
288  
289        clearCache();
290  
291        const ctx = await buildAgentContext('developer', ['base.md']);
292        // The task has no result data so formatSuccessfulTask returns null, it gets filtered
293        assert.ok(ctx.fullContext);
294      } finally {
295        await cleanup();
296      }
297    });
298  
299    test('formatSuccessfulTask with duration_ms shows duration (line 353)', async () => {
300      await initDb();
301      process.env.DATABASE_PATH = dbPath;
302      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
303  
304      try {
305        const taskId = await createAgentTask({
306          task_type: 'fix_bug',
307          assigned_to: 'developer',
308        });
309        completeTask(taskId, { approach: 'Quick fix' });
310  
311        const db = new Database(dbPath);
312        db.prepare(
313          `INSERT INTO agent_outcomes (task_id, agent_name, task_type, outcome, duration_ms, result_json)
314           VALUES (?, ?, ?, ?, ?, ?)`
315        ).run(
316          taskId,
317          'developer',
318          'fix_bug',
319          'success',
320          5000,
321          JSON.stringify({ approach: 'Quick fix' })
322        );
323        db.close();
324  
325        clearCache();
326  
327        const ctx = await buildAgentContext('developer', ['base.md']);
328        assert.ok(
329          ctx.historyContext.includes('Duration') || ctx.historyContext.includes('5s'),
330          'Should show duration from outcome'
331        );
332      } finally {
333        await cleanup();
334      }
335    });
336  
337    test('formatRelatedTask with no file info and no insight still produces output', async () => {
338      await initDb();
339      process.env.DATABASE_PATH = dbPath;
340      process.env.AGENT_REALTIME_NOTIFICATIONS = 'false';
341  
342      try {
343        // Insert a task with error_type but no file in context and no approach in result
344        const db = new Database(dbPath);
345        db.prepare(
346          `INSERT INTO agent_tasks (task_type, assigned_to, status, context_json, result_json, completed_at)
347           VALUES (?, ?, ?, ?, ?, datetime('now'))`
348        ).run('fix_bug', 'developer', 'completed', JSON.stringify({ error_type: 'mem_leak' }), null);
349        db.close();
350  
351        clearCache();
352  
353        const currentTask = { context_json: { error_type: 'mem_leak' } };
354        const ctx = await buildAgentContext('developer', ['base.md'], currentTask);
355        assert.ok(ctx.fullContext);
356      } finally {
357        await cleanup();
358      }
359    });
360  });