Files
stack/v1/packages/macp/__tests__/event-emitter.test.ts

142 lines
4.2 KiB
TypeScript

import { mkdirSync, readFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { randomUUID } from 'node:crypto';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { nowISO, appendEvent, emitEvent } from '../src/event-emitter.js';
import type { MACPEvent } from '../src/types.js';
function makeTmpDir(): string {
const dir = join(tmpdir(), `macp-event-${randomUUID()}`);
mkdirSync(dir, { recursive: true });
return dir;
}
describe('nowISO', () => {
it('returns a valid ISO timestamp', () => {
const ts = nowISO();
expect(() => new Date(ts)).not.toThrow();
expect(new Date(ts).toISOString()).toBe(ts);
});
});
describe('appendEvent', () => {
let tmp: string;
beforeEach(() => {
tmp = makeTmpDir();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('appends event as ndjson line', () => {
const eventsPath = join(tmp, 'events.ndjson');
const event: MACPEvent = {
event_id: 'evt-1',
event_type: 'task.started',
task_id: 'task-1',
status: 'running',
timestamp: nowISO(),
source: 'test',
message: 'Test event',
metadata: {},
};
appendEvent(eventsPath, event);
const content = readFileSync(eventsPath, 'utf-8');
const lines = content.trim().split('\n');
expect(lines).toHaveLength(1);
const parsed = JSON.parse(lines[0]!);
expect(parsed.event_id).toBe('evt-1');
expect(parsed.event_type).toBe('task.started');
expect(parsed.task_id).toBe('task-1');
});
it('appends multiple events', () => {
const eventsPath = join(tmp, 'events.ndjson');
const base: MACPEvent = {
event_id: '',
event_type: 'task.started',
task_id: 'task-1',
status: 'running',
timestamp: nowISO(),
source: 'test',
message: '',
metadata: {},
};
appendEvent(eventsPath, { ...base, event_id: 'evt-1', message: 'first' });
appendEvent(eventsPath, { ...base, event_id: 'evt-2', message: 'second' });
const lines = readFileSync(eventsPath, 'utf-8').trim().split('\n');
expect(lines).toHaveLength(2);
});
it('creates parent directories', () => {
const eventsPath = join(tmp, 'nested', 'deep', 'events.ndjson');
const event: MACPEvent = {
event_id: 'evt-1',
event_type: 'task.started',
task_id: 'task-1',
status: 'running',
timestamp: nowISO(),
source: 'test',
message: 'nested',
metadata: {},
};
appendEvent(eventsPath, event);
expect(readFileSync(eventsPath, 'utf-8')).toContain('nested');
});
});
describe('emitEvent', () => {
let tmp: string;
beforeEach(() => {
tmp = makeTmpDir();
});
afterEach(() => {
rmSync(tmp, { recursive: true, force: true });
});
it('creates event with all required fields', () => {
const eventsPath = join(tmp, 'events.ndjson');
emitEvent(eventsPath, 'task.completed', 'task-42', 'completed', 'controller', 'Task done');
const content = readFileSync(eventsPath, 'utf-8');
const event = JSON.parse(content.trim());
expect(event.event_id).toBeTruthy();
expect(event.event_type).toBe('task.completed');
expect(event.task_id).toBe('task-42');
expect(event.status).toBe('completed');
expect(event.source).toBe('controller');
expect(event.message).toBe('Task done');
expect(event.timestamp).toBeTruthy();
expect(event.metadata).toEqual({});
});
it('includes metadata when provided', () => {
const eventsPath = join(tmp, 'events.ndjson');
emitEvent(eventsPath, 'task.failed', 'task-1', 'failed', 'worker', 'err', {
exit_code: 1,
});
const event = JSON.parse(readFileSync(eventsPath, 'utf-8').trim());
expect(event.metadata).toEqual({ exit_code: 1 });
});
it('generates unique event_ids', () => {
const eventsPath = join(tmp, 'events.ndjson');
emitEvent(eventsPath, 'task.started', 'task-1', 'running', 'test', 'a');
emitEvent(eventsPath, 'task.started', 'task-1', 'running', 'test', 'b');
const events = readFileSync(eventsPath, 'utf-8')
.trim()
.split('\n')
.map((l) => JSON.parse(l));
expect(events[0].event_id).not.toBe(events[1].event_id);
});
});