ktg-plugin-marketplace/plugins/ultraplan-local/tests/lib/stats-event-emit.test.mjs
Kjell Tore Guttormsen bbe7971d01 feat(ultraplan-local): add stats event-emit for autonomy lifecycle events
Step 6 of plan-v2 (ultra-pipeline-speedup).

lib/stats/event-emit.mjs (NEW)
  Atomic JSONL append to ${CLAUDE_PLUGIN_DATA}/ultraexecute-stats.jsonl.
  Every record carries:
    ts          : ISO-8601 timestamp (REQUIRED per SC4)
    event       : caller-supplied name
    known_event : true for { brief-approved, main-merge-gate, user_input },
                  false for everything else (still emitted — audit-complete)
    payload     : caller object (defaults to {})

  Stats failures NEVER block workflow: missing CLAUDE_PLUGIN_DATA, missing
  dir, mkdir failure, append failure → all return { written: false, reason }
  without throwing.

  CLI shim:
    node lib/stats/event-emit.mjs --event NAME [--payload JSON]
  Always exits 0 (telemetry is best-effort).

Tests: 12 (record-build + ISO-8601 ts + known/unknown distinction + silent
skip + dir-on-demand + CLI shim happy-path + bad-payload tolerance +
concurrent-append smoke).

[skip-docs]
2026-05-04 06:31:52 +02:00

158 lines
5.5 KiB
JavaScript

// tests/lib/stats-event-emit.test.mjs
// Cover lib/stats/event-emit.mjs:
// - emit appends a JSONL line with required ISO-8601 ts
// - known_event flag distinguishes recognized vs unknown events
// - missing CLAUDE_PLUGIN_DATA does NOT throw (stats must never block)
// - CLI shim parses --payload JSON and writes via emit()
// - concurrent appends don't corrupt the file (smoke test)
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { emit, buildRecord, resolveStatsPath, KNOWN_EVENTS } from '../../lib/stats/event-emit.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const SHIM = join(HERE, '..', '..', 'lib', 'stats', 'event-emit.mjs');
const ISO_8601_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
function tmp(prefix = 'stats-event-emit-') {
return mkdtempSync(join(tmpdir(), prefix));
}
test('KNOWN_EVENTS contains plan-v2 spec set', () => {
for (const e of ['brief-approved', 'main-merge-gate', 'user_input']) {
assert.ok(KNOWN_EVENTS.has(e), `missing recognized event: ${e}`);
}
});
test('buildRecord emits ISO-8601 ts (REQUIRED per SC4)', () => {
const r = buildRecord('brief-approved', { foo: 1 });
assert.match(r.ts, ISO_8601_RE);
assert.equal(r.event, 'brief-approved');
assert.equal(r.known_event, true);
assert.deepEqual(r.payload, { foo: 1 });
});
test('buildRecord marks unrecognized events known_event: false', () => {
const r = buildRecord('totally-made-up-event');
assert.equal(r.known_event, false);
assert.deepEqual(r.payload, {});
});
test('buildRecord rejects empty event name', () => {
assert.throws(() => buildRecord(''), TypeError);
assert.throws(() => buildRecord(null), TypeError);
});
test('emit appends one JSONL line per call', () => {
const dir = tmp();
try {
const path = join(dir, 'stats.jsonl');
const r1 = emit('brief-approved', { ok: true }, { path });
const r2 = emit('main-merge-gate', { branch: 'main' }, { path });
assert.equal(r1.written, true);
assert.equal(r2.written, true);
const lines = readFileSync(path, 'utf-8').trim().split('\n');
assert.equal(lines.length, 2);
const a = JSON.parse(lines[0]);
const b = JSON.parse(lines[1]);
assert.match(a.ts, ISO_8601_RE);
assert.match(b.ts, ISO_8601_RE);
assert.equal(a.event, 'brief-approved');
assert.equal(b.event, 'main-merge-gate');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('emit creates the stats directory on demand', () => {
const dir = tmp();
try {
const path = join(dir, 'nested', 'stats.jsonl');
const r = emit('user_input', {}, { path });
assert.equal(r.written, true);
assert.ok(existsSync(path));
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('emit with no CLAUDE_PLUGIN_DATA returns { written: false } (silent skip)', () => {
const r = emit('brief-approved', {}, { env: {} });
assert.equal(r.written, false);
assert.equal(r.path, null);
assert.match(r.reason, /CLAUDE_PLUGIN_DATA unset/);
});
test('emit never throws when stats path is unwritable', () => {
// Pointing at a path under a non-existent dir on a readonly mount would
// be brittle in CI; instead, force the env-resolved path to be empty
// and confirm no exception leaks.
let threw = false;
try { emit('user_input', { foo: 'bar' }, { env: {} }); }
catch { threw = true; }
assert.equal(threw, false);
});
test('resolveStatsPath honors CLAUDE_PLUGIN_DATA env var', () => {
const r = resolveStatsPath({ CLAUDE_PLUGIN_DATA: '/var/data/plugin' });
assert.equal(r, '/var/data/plugin/ultraexecute-stats.jsonl');
assert.equal(resolveStatsPath({}), null);
});
test('CLI shim writes via emit when CLAUDE_PLUGIN_DATA is set', () => {
const dir = tmp();
try {
execFileSync(process.execPath, [
SHIM, '--event', 'brief-approved', '--payload', '{"foo":42}',
], {
env: { ...process.env, CLAUDE_PLUGIN_DATA: dir },
encoding: 'utf-8',
});
const path = join(dir, 'ultraexecute-stats.jsonl');
assert.ok(existsSync(path));
const line = readFileSync(path, 'utf-8').trim();
const parsed = JSON.parse(line);
assert.equal(parsed.event, 'brief-approved');
assert.deepEqual(parsed.payload, { foo: 42 });
assert.match(parsed.ts, ISO_8601_RE);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('CLI shim with malformed --payload returns reason payload-not-json (exit 0)', () => {
const r = execFileSync(process.execPath, [
SHIM, '--event', 'user_input', '--payload', 'not-json{{',
], { encoding: 'utf-8' });
const parsed = JSON.parse(r.trim());
assert.equal(parsed.written, false);
assert.equal(parsed.reason, 'payload-not-json');
});
test('concurrent appends do not corrupt JSONL (smoke)', async () => {
const dir = tmp();
try {
const path = join(dir, 'stats.jsonl');
const N = 25;
await Promise.all(
Array.from({ length: N }, (_, i) =>
Promise.resolve().then(() => emit('user_input', { i }, { path })),
),
);
const lines = readFileSync(path, 'utf-8').trim().split('\n');
assert.equal(lines.length, N);
for (const l of lines) {
const parsed = JSON.parse(l); // throws if any line is corrupt
assert.ok('ts' in parsed);
assert.equal(parsed.event, 'user_input');
}
} finally {
rmSync(dir, { recursive: true, force: true });
}
});