()
| 13 | |
| 14 | const OPENCODE_FIXTURE = path.join(__dirname, 'fixtures', 'opencode-hook-events.json'); |
| 15 | |
| 16 | function fail(message) { |
| 17 | console.error(message); |
| 18 | process.exit(1); |
| 19 | } |
| 20 | |
| 21 | // A hook must not be able to tell which runtime it is running under. For every |
| 22 | // fixture pair, the opencode payload and the equivalent Claude Code payload have |
| 23 | // to reduce to the same envelope fields. |
| 24 | function assertOpencodeEquivalence() { |
| 25 | const fixture = JSON.parse(fs.readFileSync(OPENCODE_FIXTURE, 'utf8')); |
| 26 | assert(fixture.equivalents.length > 0, 'opencode fixture has no equivalence cases'); |
| 27 | |
| 28 | for (const item of fixture.equivalents) { |
| 29 | const opencode = normalizeOpencodeHookInput(item.opencode); |
| 30 | const claude = normalizeClaudeHookInput(item.claude); |
| 31 | |
| 32 | for (const [field, expected] of Object.entries(item.expect)) { |
| 33 | assert.deepStrictEqual( |
| 34 | opencode[field], expected, |
| 35 | `${item.name}: opencode ${field} mismatch`, |
| 36 | ); |
| 37 | assert.deepStrictEqual( |
| 38 | claude[field], expected, |
| 39 | `${item.name}: claude ${field} mismatch (fixture pair is not equivalent)`, |
| 40 | ); |
| 41 | } |
| 42 | |
| 43 | assert.equal(opencode.runtime, 'opencode', `${item.name}: runtime mismatch`); |
| 44 | assert.equal( |
| 45 | opencode.native_event_name, item.opencode.hook_event_name, |
| 46 | `${item.name}: native event name must be preserved for skip reporting`, |
| 47 | ); |
| 48 | // The untouched payload has to survive: the plugin adapter mutates |
| 49 | // opencode's own args object in place, which is the only mutation opencode |
| 50 | // honors, so it cannot be handed a normalized copy. |
| 51 | assert.strictEqual(opencode.raw, item.opencode, `${item.name}: raw payload must be the caller's object`); |
| 52 | } |
| 53 | |
| 54 | // The fixture's mapped list and the shipped map must not drift apart. |
| 55 | assert.deepStrictEqual( |
| 56 | Object.keys(normalizeEvent.OPENCODE_EVENT_MAP).sort(), |
| 57 | [...fixture.mapped].sort(), |
| 58 | 'opencode event map and fixture mapped list have drifted', |
| 59 | ); |
| 60 | |
| 61 | // Events we chose not to map must stay unmapped. Any of these silently |
| 62 | // acquiring a Citadel event id would misreport the runtime's lifecycle. |
| 63 | for (const native of Object.keys(fixture.deliberatelyUnmapped)) { |
| 64 | assert.equal( |
| 65 | normalizeEvent.OPENCODE_EVENT_MAP[native], undefined, |
| 66 | `${native} must stay unmapped: ${fixture.deliberatelyUnmapped[native]}`, |
| 67 | ); |
| 68 | // An unmapped name passes through rather than becoming a wrong event id. |
| 69 | assert.equal( |
no test coverage detected