Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
58 lines
2.5 KiB
JavaScript
58 lines
2.5 KiB
JavaScript
'use strict';
|
|
/**
|
|
* Tests for the hook-bus seam (ADR-1239 Phase C-1, AC4 / #1680).
|
|
* Pins the three ownership modes + portable floor + fail-closed.
|
|
*/
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { createHookBus, PORTABLE_EVENT_FLOOR } = require('../msd-core/bin/lib/hook-bus.cjs');
|
|
|
|
test('PORTABLE_EVENT_FLOOR: the 5 portable events (the claude dialect all hook hosts share)', () => {
|
|
assert.deepStrictEqual([...PORTABLE_EVENT_FLOOR], ['SessionStart', 'PreToolUse', 'PostToolUse', 'Stop', 'SessionEnd']);
|
|
});
|
|
|
|
test('engine bus: in-process pub/sub (subscribe + emit reaches handlers)', () => {
|
|
const bus = createHookBus({ bus: 'engine' });
|
|
assert.strictEqual(bus.bus, 'engine');
|
|
const received = [];
|
|
bus.subscribe('PreToolUse', (p) => received.push(['PreToolUse', p]));
|
|
bus.subscribe('Stop', () => received.push(['Stop']));
|
|
bus.emit('PreToolUse', { tool: 'Edit' });
|
|
bus.emit('SessionStart');
|
|
bus.emit('Stop');
|
|
assert.deepStrictEqual(received, [['PreToolUse', { tool: 'Edit' }], ['Stop']]);
|
|
});
|
|
|
|
test('engine bus: a throwing handler is isolated (does not break the bus or other handlers)', () => {
|
|
const bus = createHookBus({ bus: 'engine' });
|
|
const seen = [];
|
|
bus.subscribe('PostToolUse', () => { throw new Error('boom'); });
|
|
bus.subscribe('PostToolUse', (p) => seen.push(p));
|
|
assert.doesNotThrow(() => bus.emit('PostToolUse', { ok: true }));
|
|
assert.deepStrictEqual(seen, [{ ok: true }]);
|
|
});
|
|
|
|
test('none bus: subscribe + emit are silent no-ops (degrade to rule-text)', () => {
|
|
const bus = createHookBus({ bus: 'none' });
|
|
assert.strictEqual(bus.bus, 'none');
|
|
assert.doesNotThrow(() => bus.subscribe('SessionStart', () => { throw new Error('must not be called'); }));
|
|
assert.doesNotThrow(() => bus.emit('SessionStart', {}));
|
|
});
|
|
|
|
test('host bus: emit delegates to the bound host emitter; fail-closed when unbound', () => {
|
|
const emitted = [];
|
|
const bound = createHookBus({ bus: 'host' }, { hostEmit: (e, p) => emitted.push([e, p]) });
|
|
assert.strictEqual(bound.bus, 'host');
|
|
bound.emit('Stop', { reason: 'done' });
|
|
assert.deepStrictEqual(emitted, [['Stop', { reason: 'done' }]]);
|
|
const unbound = createHookBus({ bus: 'host' });
|
|
assert.throws(() => unbound.emit('Stop'), /no host emitter bound/, 'unbound host emit must fail closed');
|
|
});
|
|
|
|
test('createHookBus: invalid mode throws (fail-closed construction)', () => {
|
|
for (const bad of ['cloud', '', null, undefined, 1]) {
|
|
assert.throws(() => createHookBus({ bus: bad }), TypeError, `bus=${JSON.stringify(bad)} must throw`);
|
|
}
|
|
});
|