89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
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 } 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('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',
|
|
'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('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;
|
|
});
|