live-browser-source.test.mjs

 1import { describe, it } from 'node:test';
 2import assert from 'node:assert/strict';
 3import { readFileSync } from 'node:fs';
 4import { join } from 'node:path';
 5
 6const SOURCE = readFileSync(join(process.cwd(), 'skill/scripts/live-browser.js'), 'utf-8');
 7
 8describe('live-browser source contracts', () => {
 9  it('keeps sendEvent fire-and-forget by default while accept/discard opt into rejection', () => {
10    assert.match(
11      SOURCE,
12      /function sendEvent\(msg, opts\)[\s\S]*if \(opts && opts\.throwOnError\) throw err;[\s\S]*return null;/,
13      'event=live_browser.send_event_contract actor=browser operation=send_event_failure risk=fire_and_forget_callers_get_unhandled_rejections expected=default swallow with opt-in throw actual=missing',
14    );
15    assert.match(SOURCE, /if \(res\.ok\) return res;[\s\S]*handleFailure\(new Error\('HTTP ' \+ res\.status \+ ' ' \+ res\.statusText\)\)/);
16    assert.match(
17      SOURCE,
18      /\.then\(res => \{[\s\S]*if \(res\.ok\) return res;[\s\S]*\}\)\.catch\(handleFailure\)/,
19      'event=live_browser.http_error_contract actor=browser operation=accept_discard_ack risk=http_500_clears_local_state_without_durable_receipt expected=non-ok response handled before then-success actual=missing',
20    );
21    assert.match(SOURCE, /sendEvent\(acceptPayload, \{ throwOnError: true \}\)/);
22    assert.match(SOURCE, /sendEvent\(\{ type: 'discard', id: currentSessionId \}, \{ throwOnError: true \}\)/);
23  });
24});