/ __quarantined_tests__ / agents / runner-coverage2.test.js
runner-coverage2.test.js
  1  /**
  2   * Runner Coverage 2 - Additional coverage for src/agents/runner.js
  3   * Tests exported functions that work without mock.module():
  4   * - getAgentStats (lines 137-184): only needs DB with agent_tasks
  5   * - checkCircuitBreakers (lines 193-305): needs agent_state + agent_tasks
  6   * - runAgentCycle disabled path (lines 61-64): AGENT_SYSTEM_ENABLED=false
  7   * - resetDb (lines 27-37)
  8   *
  9   * NOTE: Does NOT use mock.module() — safe to include in full coverage suite.
 10   */
 11  
 12  import { describe, test, beforeEach, afterEach } from 'node:test';
 13  import assert from 'node:assert/strict';
 14  import Database from 'better-sqlite3';
 15  import path from 'path';
 16  import { fileURLToPath } from 'url';
 17  
 18  const __filename = fileURLToPath(import.meta.url);
 19  const __dirname = path.dirname(__filename);
 20  
 21  // ── Set DATABASE_PATH BEFORE importing runner.js ────────────────────────────
 22  const TEST_DB_PATH = `/tmp/runner-coverage2-test-${Date.now()}.db`;
 23  process.env.DATABASE_PATH = TEST_DB_PATH;
 24  process.env.AGENT_SYSTEM_ENABLED = 'false'; // prevent real agent execution
 25  process.env.AGENT_AUTO_COMMIT = 'false';
 26  process.env.AGENT_RUN_TYPES_IN_PARALLEL = 'false';
 27  process.env.AGENT_PARALLEL_EXECUTION = 'false';
 28  
 29  // ── Create test DB schema ────────────────────────────────────────────────────
 30  const TEST_SCHEMA = `
 31    CREATE TABLE IF NOT EXISTS agent_tasks (
 32      id INTEGER PRIMARY KEY AUTOINCREMENT,
 33      task_type TEXT NOT NULL,
 34      assigned_to TEXT NOT NULL,
 35      created_by TEXT,
 36      status TEXT DEFAULT 'pending',
 37      priority INTEGER DEFAULT 5,
 38      context_json TEXT,
 39      result_json TEXT,
 40      error_message TEXT,
 41      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 42      started_at DATETIME,
 43      completed_at DATETIME,
 44      retry_count INTEGER DEFAULT 0
 45    );
 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,
 51      status TEXT DEFAULT 'idle',
 52      metrics_json TEXT
 53    );
 54  
 55    CREATE TABLE IF NOT EXISTS agent_logs (
 56      id INTEGER PRIMARY KEY AUTOINCREMENT,
 57      task_id INTEGER,
 58      agent_name TEXT NOT NULL,
 59      log_level TEXT,
 60      message TEXT,
 61      data_json TEXT,
 62      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 63    );
 64  `;
 65  
 66  const setupDb = new Database(TEST_DB_PATH);
 67  setupDb.exec(TEST_SCHEMA);
 68  setupDb.close();
 69  
 70  // ── Import runner AFTER DB is set up and env vars are set ───────────────────
 71  const { runAgentCycle, getAgentStats, checkCircuitBreakers, resetDb } =
 72    await import('../../src/agents/runner.js');
 73  
 74  describe('runner.js - runAgentCycle disabled path', () => {
 75    beforeEach(() => {
 76      process.env.AGENT_SYSTEM_ENABLED = 'false';
 77    });
 78  
 79    afterEach(() => {
 80      process.env.AGENT_SYSTEM_ENABLED = 'false';
 81    });
 82  
 83    test('returns disabled result when AGENT_SYSTEM_ENABLED is false', async () => {
 84      const result = await runAgentCycle({ tasksPerAgent: 5 });
 85      assert.strictEqual(result.enabled, false);
 86      assert.strictEqual(result.processed, 0);
 87    });
 88  });
 89  
 90  describe('runner.js - getAgentStats', () => {
 91    test('returns stats structure with empty DB', () => {
 92      const stats = getAgentStats();
 93      assert.ok(stats.agents !== undefined);
 94      assert.ok(Array.isArray(stats.agents));
 95      assert.ok(stats.overall !== undefined);
 96      assert.ok(typeof stats.overall.total === 'number');
 97      assert.ok(typeof stats.overall.success_rate === 'number');
 98      assert.ok(typeof stats.overall.failure_rate === 'number');
 99    });
100  
101    test('returns stats with tasks in DB', () => {
102      // Insert test tasks
103      const db = new Database(TEST_DB_PATH);
104      db.prepare(
105        `INSERT INTO agent_tasks (task_type, assigned_to, status, created_at)
106         VALUES ('fix_bug', 'Developer', 'completed', datetime('now', '-30 minutes'))`
107      ).run();
108      db.prepare(
109        `INSERT INTO agent_tasks (task_type, assigned_to, status, created_at, started_at, completed_at)
110         VALUES ('run_tests', 'QA', 'completed', datetime('now', '-30 minutes'), datetime('now', '-25 minutes'), datetime('now', '-20 minutes'))`
111      ).run();
112      db.prepare(
113        `INSERT INTO agent_tasks (task_type, assigned_to, status, created_at)
114         VALUES ('fix_bug', 'Developer', 'failed', datetime('now', '-30 minutes'))`
115      ).run();
116      db.close();
117  
118      const stats = getAgentStats();
119      assert.ok(Array.isArray(stats.agents));
120      // Should have Developer and/or QA in stats
121      const hasAgents = stats.agents.length > 0;
122      if (hasAgents) {
123        const agent = stats.agents[0];
124        assert.ok('success_rate' in agent);
125        assert.ok('failure_rate' in agent);
126      }
127    });
128  });
129  
130  describe('runner.js - checkCircuitBreakers', () => {
131    test('returns empty array with no agent state', () => {
132      const blocked = checkCircuitBreakers();
133      assert.ok(Array.isArray(blocked));
134      // No agents in state, so none blocked
135    });
136  
137    test('returns blocked agents when failure rate exceeds threshold', () => {
138      const db = new Database(TEST_DB_PATH);
139  
140      // Insert agent with high failure rate (11 total, 8 failed = 72.7%)
141      db.prepare(
142        `INSERT OR REPLACE INTO agent_state (agent_name, status) VALUES ('TestAgent', 'active')`
143      ).run();
144      for (let i = 0; i < 11; i++) {
145        const status = i < 8 ? 'failed' : 'completed';
146        db.prepare(
147          `INSERT INTO agent_tasks (task_type, assigned_to, status, created_at)
148           VALUES ('fix_bug', 'TestAgent', ?, datetime('now', '-30 minutes'))`
149        ).run(status);
150      }
151      db.close();
152  
153      // AGENT_CIRCUIT_BREAKER_THRESHOLD is 0.3 (30%), TestAgent has 72.7% failure rate
154      process.env.AGENT_CIRCUIT_BREAKER_THRESHOLD = '0.3';
155      const blocked = checkCircuitBreakers();
156      assert.ok(Array.isArray(blocked));
157      // TestAgent should be blocked (failure_rate 0.727 > threshold 0.3, total >= 10)
158      const testAgentBlocked = blocked.find(b => b.agent === 'TestAgent');
159      if (testAgentBlocked) {
160        assert.ok(testAgentBlocked.failure_rate > 0.3);
161      }
162    });
163  
164    test('auto-recovers blocked agent after cooldown', () => {
165      const db = new Database(TEST_DB_PATH);
166  
167      // Insert agent state that is blocked with expired cooldown
168      const triggeredAt = new Date(Date.now() - 40 * 60 * 1000).toISOString(); // 40 min ago
169      db.prepare(
170        `INSERT OR REPLACE INTO agent_state (agent_name, status, metrics_json)
171         VALUES ('RecoveryAgent', 'blocked', ?)`
172      ).run(JSON.stringify({ circuit_breaker_triggered_at: triggeredAt }));
173  
174      // Insert low failure rate for this agent (1 total, 0 failed = 0%)
175      db.prepare(
176        `INSERT INTO agent_tasks (task_type, assigned_to, status, created_at)
177         VALUES ('fix_bug', 'RecoveryAgent', 'completed', datetime('now', '-30 minutes'))`
178      ).run();
179      db.close();
180  
181      // Cooldown is 30 min, triggered 40 min ago → should auto-recover
182      process.env.AGENT_CIRCUIT_BREAKER_COOLDOWN = '30';
183      const blocked = checkCircuitBreakers();
184      assert.ok(Array.isArray(blocked));
185      // RecoveryAgent should NOT be blocked (auto-recovered)
186      const recoveryAgentBlocked = blocked.find(b => b.agent === 'RecoveryAgent');
187      assert.ok(!recoveryAgentBlocked, 'RecoveryAgent should have been auto-recovered');
188    });
189  });
190  
191  describe('runner.js - resetDb', () => {
192    test('resetDb closes and resets the db instance', () => {
193      // Just verify it doesn't throw
194      assert.doesNotThrow(() => {
195        resetDb();
196      });
197    });
198  
199    test('resetDb with external db sets it as the active connection', () => {
200      const externalDb = new Database(TEST_DB_PATH);
201      assert.doesNotThrow(() => {
202        resetDb(externalDb);
203      });
204      // Close it after
205      externalDb.close();
206      resetDb();
207    });
208  });