// 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 }); } });