Files
stack/packages/macp/__tests__/event-emitter.test.ts
Mos (Agent) 10689a30d2
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
feat: monorepo consolidation — forge pipeline, MACP protocol, framework plugin, profiles/guides/skills
Work packages completed:
- WP1: packages/forge — pipeline runner, stage adapter, board tasks, brief classifier,
  persona loader with project-level overrides. 89 tests, 95.62% coverage.
- WP2: packages/macp — credential resolver, gate runner, event emitter, protocol types.
  65 tests, 96.24% coverage. Full Python-to-TS port preserving all behavior.
- WP3: plugins/mosaic-framework — OC rails injection plugin (before_agent_start +
  subagent_spawning hooks for Mosaic contract enforcement).
- WP4: profiles/ (domains, tech-stacks, workflows), guides/ (17 docs),
  skills/ (5 universal skills), forge pipeline assets (48 markdown files).

Board deliberation: docs/reviews/consolidation-board-memo.md
Brief: briefs/monorepo-consolidation.md

Consolidates mosaic/stack (forge, MACP, bootstrap framework) into mosaic/mosaic-stack.
154 new tests total. Zero Python — all TypeScript/ESM.
2026-03-30 19:43:24 +00:00

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