/** * Tests for the backfill-metrics script. * * Uses an in-memory test database to verify that backfillMetrics correctly * accumulates counts from agent_log_chunks and upserts into agent_metrics. */ import { describe, it, expect, beforeEach } from 'vitest'; import { createTestDatabase } from '../db/repositories/drizzle/test-helpers.js'; import type { DrizzleDatabase } from '../db/index.js'; import { agentLogChunks, agentMetrics } from '../db/index.js'; import { backfillMetrics } from './backfill-metrics.js'; import { nanoid } from 'nanoid'; import { eq } from 'drizzle-orm'; async function insertChunk(db: DrizzleDatabase, agentId: string, content: object | string) { await db.insert(agentLogChunks).values({ id: nanoid(), agentId, agentName: 'test-agent', sessionNumber: 1, content: typeof content === 'string' ? content : JSON.stringify(content), createdAt: new Date(), }); } describe('backfillMetrics', () => { let db: DrizzleDatabase; beforeEach(() => { db = createTestDatabase(); }); it('AskUserQuestion chunks — questionsCount correct', async () => { await insertChunk(db, 'agent-a', { type: 'tool_use', name: 'AskUserQuestion', input: { questions: [{}, {}] } }); await insertChunk(db, 'agent-a', { type: 'tool_use', name: 'AskUserQuestion', input: { questions: [{}] } }); await backfillMetrics(db); const rows = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-a')); expect(rows).toHaveLength(1); expect(rows[0].questionsCount).toBe(3); expect(rows[0].subagentsCount).toBe(0); expect(rows[0].compactionsCount).toBe(0); }); it('Agent tool chunks — subagentsCount correct', async () => { await insertChunk(db, 'agent-b', { type: 'tool_use', name: 'Agent' }); await insertChunk(db, 'agent-b', { type: 'tool_use', name: 'Agent' }); await backfillMetrics(db); const rows = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-b')); expect(rows).toHaveLength(1); expect(rows[0].questionsCount).toBe(0); expect(rows[0].subagentsCount).toBe(2); expect(rows[0].compactionsCount).toBe(0); }); it('Compaction chunks — compactionsCount correct', async () => { await insertChunk(db, 'agent-c', { type: 'system', subtype: 'init', source: 'compact' }); await backfillMetrics(db); const rows = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-c')); expect(rows).toHaveLength(1); expect(rows[0].questionsCount).toBe(0); expect(rows[0].subagentsCount).toBe(0); expect(rows[0].compactionsCount).toBe(1); }); it('Irrelevant chunk type — no metrics row created', async () => { await insertChunk(db, 'agent-d', { type: 'text', text: 'hello' }); await backfillMetrics(db); const rows = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-d')); expect(rows).toEqual([]); }); it('Malformed JSON chunk — skipped, no crash', async () => { await insertChunk(db, 'agent-e', 'not-valid-json'); await insertChunk(db, 'agent-e', { type: 'tool_use', name: 'Agent' }); await expect(backfillMetrics(db)).resolves.not.toThrow(); const rows = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-e')); expect(rows).toHaveLength(1); expect(rows[0].subagentsCount).toBe(1); }); it('Multiple agents — counts isolated per agent', async () => { await insertChunk(db, 'agent-f', { type: 'tool_use', name: 'AskUserQuestion', input: { questions: [{}, {}, {}] } }); await insertChunk(db, 'agent-f', { type: 'tool_use', name: 'AskUserQuestion', input: { questions: [{}, {}, {}] } }); await insertChunk(db, 'agent-g', { type: 'tool_use', name: 'Agent' }); await backfillMetrics(db); const rowsF = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-f')); expect(rowsF).toHaveLength(1); expect(rowsF[0].questionsCount).toBe(6); expect(rowsF[0].subagentsCount).toBe(0); expect(rowsF[0].compactionsCount).toBe(0); const rowsG = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-g')); expect(rowsG).toHaveLength(1); expect(rowsG[0].questionsCount).toBe(0); expect(rowsG[0].subagentsCount).toBe(1); expect(rowsG[0].compactionsCount).toBe(0); }); it('Empty database — completes without error', async () => { await expect(backfillMetrics(db)).resolves.not.toThrow(); const rows = await db.select().from(agentMetrics); expect(rows).toEqual([]); }); it('Re-run idempotency note — second run doubles counts', async () => { // Documented behavior: run only once against a fresh agent_metrics table await insertChunk(db, 'agent-h', { type: 'tool_use', name: 'Agent' }); await backfillMetrics(db); const rowsAfterFirst = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-h')); expect(rowsAfterFirst[0].subagentsCount).toBe(1); await backfillMetrics(db); const rowsAfterSecond = await db.select().from(agentMetrics).where(eq(agentMetrics.agentId, 'agent-h')); expect(rowsAfterSecond[0].subagentsCount).toBe(2); }); });