/ __quarantined_tests__ / agents / monitor-slo.test.js
monitor-slo.test.js
  1  /**
  2   * Monitor Agent SLO Integration Tests
  3   *
  4   * Tests Monitor agent's integration with SLO tracker
  5   */
  6  
  7  import { test } from 'node:test';
  8  import assert from 'node:assert';
  9  import Database from 'better-sqlite3';
 10  import fs from 'fs';
 11  
 12  // MUST set before monitor.js imports (it opens DB at module level)
 13  const TEST_DB_PATH = './db/test-monitor-slo.db';
 14  process.env.DATABASE_PATH = TEST_DB_PATH;
 15  process.env.AGENT_IMMEDIATE_INVOCATION = 'false';
 16  
 17  import { MonitorAgent, resetDb as resetMonitorDb } from '../../src/agents/monitor.js';
 18  import { resetDb as resetSLODb } from '../../src/agents/utils/slo-tracker.js';
 19  import { resetDb as resetBaseDb } from '../../src/agents/base-agent.js';
 20  import { resetDbConnection as resetTaskManagerDb } from '../../src/agents/utils/task-manager.js';
 21  
 22  function setupTestDb() {
 23    if (fs.existsSync(TEST_DB_PATH)) fs.unlinkSync(TEST_DB_PATH);
 24  
 25    const db = new Database(TEST_DB_PATH);
 26  
 27    // Create full schema needed for agent tests
 28    db.exec(`
 29      CREATE TABLE IF NOT EXISTS sites (
 30        id INTEGER PRIMARY KEY AUTOINCREMENT,
 31        domain TEXT,
 32        landing_page_url TEXT,
 33        status TEXT DEFAULT 'found',
 34        error_message TEXT,
 35        score REAL,
 36        grade TEXT,
 37        recapture_count INTEGER DEFAULT 0,
 38        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 39        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 40      );
 41  
 42      CREATE TABLE IF NOT EXISTS site_status (
 43        id INTEGER PRIMARY KEY AUTOINCREMENT,
 44        site_id INTEGER,
 45        status TEXT,
 46        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 47      );
 48  
 49      CREATE TABLE IF NOT EXISTS cron_locks (lock_key TEXT PRIMARY KEY, acquired_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, description TEXT);
 50  
 51      CREATE TABLE IF NOT EXISTS pipeline_metrics (
 52        id INTEGER PRIMARY KEY AUTOINCREMENT,
 53        stage_name TEXT NOT NULL,
 54        sites_processed INTEGER DEFAULT 0,
 55        sites_succeeded INTEGER DEFAULT 0,
 56        sites_failed INTEGER DEFAULT 0,
 57        duration_ms INTEGER NOT NULL,
 58        started_at DATETIME NOT NULL,
 59        finished_at DATETIME NOT NULL,
 60        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 61      );
 62  
 63      CREATE TABLE IF NOT EXISTS agent_tasks (
 64        id INTEGER PRIMARY KEY AUTOINCREMENT,
 65        task_type TEXT NOT NULL,
 66        assigned_to TEXT NOT NULL,
 67        created_by TEXT,
 68        status TEXT DEFAULT 'pending',
 69        priority INTEGER DEFAULT 5,
 70        context_json TEXT,
 71        result_json TEXT,
 72        parent_task_id INTEGER,
 73        error_message TEXT,
 74        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 75        started_at DATETIME,
 76        completed_at DATETIME,
 77        retry_count INTEGER DEFAULT 0
 78      );
 79  
 80      CREATE TABLE IF NOT EXISTS agent_logs (
 81        id INTEGER PRIMARY KEY AUTOINCREMENT,
 82        task_id INTEGER,
 83        agent_name TEXT NOT NULL,
 84        log_level TEXT,
 85        message TEXT NOT NULL,
 86        data_json TEXT,
 87        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 88      );
 89  
 90      CREATE TABLE IF NOT EXISTS agent_state (
 91        agent_name TEXT PRIMARY KEY,
 92        status TEXT DEFAULT 'idle',
 93        current_task_id INTEGER,
 94        metrics_json TEXT,
 95        last_active DATETIME DEFAULT CURRENT_TIMESTAMP
 96      );
 97  
 98      CREATE TABLE IF NOT EXISTS agent_messages (
 99        id INTEGER PRIMARY KEY AUTOINCREMENT,
100        task_id INTEGER,
101        from_agent TEXT NOT NULL,
102        to_agent TEXT NOT NULL,
103        message_type TEXT,
104        content TEXT NOT NULL,
105        metadata_json TEXT,
106        context_json TEXT,
107        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
108        read_at DATETIME
109      );
110  
111      CREATE TABLE IF NOT EXISTS settings (
112        key TEXT PRIMARY KEY,
113        value TEXT NOT NULL,
114        description TEXT,
115        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
116      );
117  
118      CREATE TABLE IF NOT EXISTS human_review_queue (
119        id INTEGER PRIMARY KEY AUTOINCREMENT,
120        file TEXT NOT NULL,
121        reason TEXT NOT NULL,
122        type TEXT NOT NULL,
123        priority TEXT NOT NULL,
124        metadata TEXT,
125        status TEXT DEFAULT 'pending',
126        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
127      );
128  
129      INSERT OR IGNORE INTO agent_state (agent_name, status) VALUES ('monitor', 'idle');
130      INSERT OR IGNORE INTO agent_state (agent_name, status) VALUES ('triage', 'idle');
131      INSERT OR IGNORE INTO agent_state (agent_name, status) VALUES ('developer', 'idle');
132      INSERT OR IGNORE INTO agent_state (agent_name, status) VALUES ('qa', 'idle');
133      INSERT OR IGNORE INTO agent_state (agent_name, status) VALUES ('security', 'idle');
134      INSERT OR IGNORE INTO agent_state (agent_name, status) VALUES ('architect', 'idle');
135    `);
136  
137    // ATTACH in-memory databases as ops and tel so queries like ops.settings, tel.agent_tasks resolve
138    db.exec(`
139      ATTACH ':memory:' AS ops;
140      ATTACH ':memory:' AS tel;
141      CREATE TABLE IF NOT EXISTS ops.settings (key TEXT PRIMARY KEY, value TEXT NOT NULL, description TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP);
142      CREATE TABLE IF NOT EXISTS tel.agent_tasks (id INTEGER PRIMARY KEY AUTOINCREMENT, task_type TEXT NOT NULL, assigned_to TEXT NOT NULL, created_by TEXT, status TEXT DEFAULT 'pending', priority INTEGER DEFAULT 5, context_json TEXT, result_json TEXT, parent_task_id INTEGER, error_message TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, started_at DATETIME, completed_at DATETIME, retry_count INTEGER DEFAULT 0);
143      CREATE TABLE IF NOT EXISTS tel.agent_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, agent_name TEXT NOT NULL, log_level TEXT, message TEXT NOT NULL, data_json TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
144      CREATE TABLE IF NOT EXISTS tel.agent_state (agent_name TEXT PRIMARY KEY, last_active DATETIME DEFAULT CURRENT_TIMESTAMP, current_task_id INTEGER, status TEXT DEFAULT 'idle', metrics_json TEXT);
145      CREATE TABLE IF NOT EXISTS tel.agent_messages (id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, from_agent TEXT NOT NULL, to_agent TEXT NOT NULL, message_type TEXT, content TEXT NOT NULL, metadata_json TEXT, context_json TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, read_at DATETIME);
146      CREATE TABLE IF NOT EXISTS tel.agent_outcomes (id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, agent_name TEXT NOT NULL, task_type TEXT NOT NULL, outcome TEXT NOT NULL, context_json TEXT, result_json TEXT, duration_ms INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
147      CREATE TABLE IF NOT EXISTS tel.pipeline_metrics (id INTEGER PRIMARY KEY AUTOINCREMENT, stage_name TEXT NOT NULL, sites_processed INTEGER DEFAULT 0, sites_succeeded INTEGER DEFAULT 0, sites_failed INTEGER DEFAULT 0, duration_ms INTEGER NOT NULL, started_at DATETIME NOT NULL, finished_at DATETIME NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
148      CREATE TABLE IF NOT EXISTS tel.structured_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, agent_name TEXT, task_id INTEGER, level TEXT, message TEXT, data_json TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);
149      INSERT OR IGNORE INTO tel.agent_state (agent_name, status) VALUES ('monitor', 'idle');
150      INSERT OR IGNORE INTO tel.agent_state (agent_name, status) VALUES ('triage', 'idle');
151      INSERT OR IGNORE INTO tel.agent_state (agent_name, status) VALUES ('developer', 'idle');
152      INSERT OR IGNORE INTO tel.agent_state (agent_name, status) VALUES ('qa', 'idle');
153      INSERT OR IGNORE INTO tel.agent_state (agent_name, status) VALUES ('security', 'idle');
154      INSERT OR IGNORE INTO tel.agent_state (agent_name, status) VALUES ('architect', 'idle');
155    `);
156  
157    return db;
158  }
159  
160  test('Monitor Agent - processTask handles check_slo_compliance', async () => {
161    // Reset all module DB connections, then create fresh test DB
162    resetBaseDb();
163    resetSLODb();
164    resetTaskManagerDb();
165    const db = setupTestDb();
166    // Inject shared DB into monitor.js so it uses same connection
167    resetMonitorDb(db);
168  
169    // Create monitor task
170    const taskResult = db
171      .prepare(
172        `INSERT INTO agent_tasks (task_type, assigned_to, priority, context_json)
173         VALUES (?, ?, ?, ?)`
174      )
175      .run('check_slo_compliance', 'monitor', 7, '{}');
176  
177    const taskId = taskResult.lastInsertRowid;
178  
179    const monitor = new MonitorAgent();
180    await monitor.initialize();
181  
182    // Get the task
183    const task = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(taskId);
184  
185    // Process it
186    await monitor.processTask(task);
187  
188    // Verify task was completed
189    const completedTask = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(taskId);
190  
191    assert.strictEqual(completedTask.status, 'completed');
192  
193    const result = JSON.parse(completedTask.result_json);
194    assert.ok(typeof result.total_slos === 'number');
195    assert.ok(typeof result.violations === 'number');
196    assert.ok(typeof result.compliance_rate === 'number');
197  
198    // Detach monitor db, then close and cleanup
199    resetMonitorDb(null);
200    resetBaseDb();
201    resetSLODb();
202    resetTaskManagerDb();
203    db.close();
204    try {
205      fs.unlinkSync(TEST_DB_PATH);
206    } catch {
207      /* ignore */
208    }
209  });
210  
211  test('Monitor Agent - SLO compliance creates Architect tasks on violations', async () => {
212    // Reset all module DB connections, then create fresh test DB
213    resetBaseDb();
214    resetSLODb();
215    resetTaskManagerDb();
216    const db = setupTestDb();
217    // Inject shared DB into monitor.js so it uses same connection
218    resetMonitorDb(db);
219  
220    // Create violations by adding slow sites
221    const now = new Date();
222  
223    for (let i = 1; i <= 20; i++) {
224      const start = new Date(now.getTime() - 150 * 60 * 1000); // 150 min ago (violates 60 min SLO)
225  
226      db.prepare('INSERT INTO sites (id, domain, status) VALUES (?, ?, ?)').run(
227        i,
228        `site${i}.com`,
229        'assets_captured'
230      );
231  
232      db.prepare('INSERT INTO site_status (site_id, status, created_at) VALUES (?, ?, ?)').run(
233        i,
234        'found',
235        start.toISOString()
236      );
237  
238      db.prepare('INSERT INTO site_status (site_id, status, created_at) VALUES (?, ?, ?)').run(
239        i,
240        'assets_captured',
241        now.toISOString()
242      );
243    }
244  
245    // Create monitor task
246    const taskResult = db
247      .prepare(
248        `INSERT INTO agent_tasks (task_type, assigned_to, priority, context_json)
249         VALUES (?, ?, ?, ?)`
250      )
251      .run('check_slo_compliance', 'monitor', 7, '{}');
252  
253    const taskId = taskResult.lastInsertRowid;
254  
255    const monitor = new MonitorAgent();
256    await monitor.initialize();
257  
258    const task = db.prepare('SELECT * FROM agent_tasks WHERE id = ?').get(taskId);
259  
260    await monitor.processTask(task);
261  
262    // Check that Architect tasks were created
263    const architectTasks = db
264      .prepare(
265        `SELECT * FROM agent_tasks
266         WHERE assigned_to = 'architect'
267         AND task_type = 'design_optimization'`
268      )
269      .all();
270  
271    assert.ok(architectTasks.length > 0, 'Should create at least one Architect task');
272  
273    // Verify task context
274    const architectTask = architectTasks[0];
275    const context = JSON.parse(architectTask.context_json);
276  
277    assert.strictEqual(context.optimization_type, 'slo_violation');
278    assert.ok(context.stage_name);
279    assert.ok(context.description);
280    assert.ok(typeof context.current_p95 === 'number');
281    assert.ok(typeof context.target_duration === 'number');
282    assert.ok(context.severity);
283  
284    // Detach monitor db, then close and cleanup
285    resetMonitorDb(null);
286    resetBaseDb();
287    resetSLODb();
288    resetTaskManagerDb();
289    db.close();
290    try {
291      fs.unlinkSync(TEST_DB_PATH);
292    } catch {
293      /* ignore */
294    }
295  });
296  
297  test('Monitor Agent - recurring tasks include check_slo_compliance', async () => {
298    // Reset all module DB connections, then create fresh test DB
299    resetBaseDb();
300    resetSLODb();
301    resetTaskManagerDb();
302    const db = setupTestDb();
303    // Inject shared DB into monitor.js so it uses same connection
304    resetMonitorDb(db);
305  
306    const monitor = new MonitorAgent();
307    await monitor.initialize();
308  
309    // Call ensureRecurringTasks
310    await monitor.ensureRecurringTasks();
311  
312    // Check that check_slo_compliance task was created
313    const sloTask = db
314      .prepare(
315        `SELECT * FROM agent_tasks
316         WHERE assigned_to = 'monitor'
317         AND task_type = 'check_slo_compliance'
318         AND status = 'pending'`
319      )
320      .get();
321  
322    assert.ok(sloTask, 'Should create check_slo_compliance recurring task');
323    assert.strictEqual(sloTask.priority, 7);
324  
325    // Detach monitor db, then close and cleanup
326    resetMonitorDb(null);
327    resetBaseDb();
328    resetSLODb();
329    resetTaskManagerDb();
330    db.close();
331    try {
332      fs.unlinkSync(TEST_DB_PATH);
333    } catch {
334      /* ignore */
335    }
336  });