import { test } from 'node:test'; import assert from 'node:assert/strict'; import { existsSync, mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { readJsonl, readJson, readHead } from '../src/telemetry/telemetry.reader.js'; import { APP_ROOT, PATHS, isStale, metricsAgeSeconds } from '../src/common/app-root.js'; import { TelemetryService, buildChatLoopWidget } from '../src/telemetry/telemetry.service.js'; test('readJsonl parses valid lines and skips malformed', () => { const d = mkdtempSync(join(tmpdir(), 'cp-')); const f = join(d, 'x.jsonl'); writeFileSync(f, '{"a":1}\n\nnot-json\n{"a":2}\n'); const rows = readJsonl<{ a: number }>(f); assert.equal(rows.length, 2); assert.equal(rows[0].a, 1); assert.equal(rows[1].a, 2); }); test('readJsonl/readJson tolerate missing files (no throw)', () => { assert.deepEqual(readJsonl('/no/such/file.jsonl'), []); assert.equal(readJson('/no/such/file.json'), null); assert.equal(readHead('/no/such/head.txt'), null); }); test('APP_ROOT resolves to the CASAN app root (has a harness marker)', () => { const marker = existsSync(join(APP_ROOT, 'packages', 'casan-harness')) || existsSync(join(APP_ROOT, '.specify')); assert.ok(marker, `APP_ROOT=${APP_ROOT} should contain .specify or packages/casan-harness`); }); test('overview() returns real aggregated shape, never throws on the repo state', () => { const svc = new TelemetryService(); const o = svc.overview() as any; assert.ok(o.totals && typeof o.totals.runs === 'number'); assert.ok(o.harness_signals && o.harness_signals['H5-governance']); assert.ok(o.audit_chain && typeof o.audit_chain.records === 'number'); assert.ok('stale' in o && 'stale_after_s' in o); // real data: metrics + audit feeds exist in this repo, so counts are non-negative assert.ok(o.totals.runs >= 0); assert.ok(o.audit_chain.records >= 0); }); test('prometheusMetrics() exports aggregate-safe operational metrics only', () => { const metrics = new TelemetryService().prometheusMetrics(); assert.match(metrics, /^# HELP casan_telemetry_stale/m); assert.match(metrics, /^casan_runs_total \d+/m); assert.match(metrics, /^casan_security_blocks_total \d+/m); assert.match(metrics, /^casan_governance_denials_total \d+/m); assert.doesNotMatch(metrics, /trace_id|tenant|prompt|evidence/i); }); test('security()/governance()/cost() return objects with expected keys', () => { const svc = new TelemetryService(); assert.ok(typeof (svc.security() as any).by_status === 'object'); assert.ok(typeof (svc.governance() as any).by_decision === 'object'); assert.ok('provider_tokens' in (svc.cost() as any)); }); test('commandCenter() returns evidence-backed widget contract', () => { const svc = new TelemetryService(); const command = svc.commandCenter() as any; const ids = Object.keys(command.widgets); assert.deepEqual(ids.sort(), [ 'certification', 'chat_loop', 'finops', 'hitl', 'kill_switch', 'maturity', 'security', 'self_improve', 'traceability', ]); for (const w of Object.values(command.widgets) as any[]) { assert.ok(w.id && w.title && w.status); assert.ok(w.envelope && typeof w.envelope.artifact_path === 'string'); assert.ok(['verified', 'present_unverified', 'missing'].includes(w.envelope.status)); assert.equal(typeof w.envelope.verified, 'boolean'); } assert.ok(Array.isArray(command.briefing)); assert.ok(Array.isArray(command.ticker)); }); test('buildChatLoopWidget summarizes chat replay fixture and evidence', () => { const d = mkdtempSync(join(tmpdir(), 'cp-chat-')); const auditPath = join(d, 'chat-turns.jsonl'); writeFileSync(auditPath, '{"seq":1}\n{"seq":2}\n'); const prevBudget = process.env.CASAN_CHAT_TOKEN_BUDGET; process.env.CASAN_CHAT_TOKEN_BUDGET = '200'; try { const widget = buildChatLoopWidget( [ { seq: 1, timestamp: '2026-07-08T00:00:00Z', chat_id: 'chat1', turn_id: 't1', mode: 'READ_ONLY', decision: 'ANSWERED' }, { seq: 2, timestamp: '2026-07-08T00:01:00Z', chat_id: 'chat1', turn_id: 't2', mode: 'OPERATOR', decision: 'ACTION_COMPLETED', loop_run: { run_id: 'loop1', decision: 'CERTIFIED' }, record_hash: 'abc123', }, ], [ { agent: 'chat.ask-casan', step: 'ask-casan-readonly', total_tokens: 40 }, { agent: 'chat.operator', step: 'operator:generate_report', total_tokens: 60 }, { agent: 'planner', step: 'unrelated', total_tokens: 900 }, ], auditPath, { ok: true, decision: 'MATCH', records: 2, loop_replayed: 1, diffs: [], audit_path: auditPath }, ) as any; assert.equal(widget.id, 'chat_loop'); assert.equal(widget.status, 'ok'); assert.equal(widget.metrics.turns, 2); assert.equal(widget.metrics.answered, 1); assert.equal(widget.metrics.action_completed, 1); assert.equal(widget.metrics.loop_runs, 1); assert.equal(widget.metrics.replay_loop_replayed, 1); assert.equal(widget.metrics.budget_used_pct, 50); assert.equal(widget.envelope.verified, true); assert.equal(widget.evidence.latest_turn.loop_run.run_id, 'loop1'); assert.equal(widget.evidence.replay.decision, 'MATCH'); } finally { if (prevBudget === undefined) delete process.env.CASAN_CHAT_TOKEN_BUDGET; else process.env.CASAN_CHAT_TOKEN_BUDGET = prevBudget; } }); test('freshness helpers behave (age is number|null, isStale is boolean)', () => { const age = metricsAgeSeconds(); assert.ok(age === null || typeof age === 'number'); assert.equal(typeof isStale(), 'boolean'); }); test('missing metrics feed => stale (fail-loud)', () => { const prev = process.env.CASAN_DASHBOARD_METRICS; process.env.CASAN_DASHBOARD_METRICS = '/no/such/metrics.jsonl'; // re-import is not trivial (module caches PATHS); assert the reader-level contract instead: assert.deepEqual(readJsonl('/no/such/metrics.jsonl'), []); if (prev === undefined) delete process.env.CASAN_DASHBOARD_METRICS; else process.env.CASAN_DASHBOARD_METRICS = prev; void PATHS; });