/** * The two assertions that matter here are both about STAYING QUIET. * * A host with no browser provisioned is the NORMAL case — installation is * opt-in — so a check that fired there would put a finding on every default * host in the fleet forever. And a module that bundles no browser client is * almost every module. Getting either wrong turns a guardrail into noise * that trains an operator to ignore the audit. * * The revision is read from the bundle's own `browsers.json`, never from a * version→revision table, so the reader tests use real files. */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { auditBrowserPin, readBundledBrowserClient } from './browser-pin'; const PROVISIONED = { revision: '1223', playwrightVersion: '1.60.0' }; describe('auditBrowserPin', () => { test('says nothing when no browser is provisioned', async () => { // The opt-in default. `system doctor` reports the absence; this must not, // or every host that never wanted a browser carries a permanent finding. const findings = await auditBrowserPin({ consumers: [{ moduleId: 'lunacycle', clientVersion: '1.55.1', expectedRevision: '1193' }], provisioned: null, }); expect(findings).toEqual([]); }); test('says nothing when no module bundles a browser client', async () => { expect(await auditBrowserPin({ consumers: [], provisioned: PROVISIONED })).toEqual([]); }); test('says nothing when the bundled client expects the installed revision', async () => { const findings = await auditBrowserPin({ consumers: [{ moduleId: 'lunacycle', clientVersion: '1.60.0', expectedRevision: '1223' }], provisioned: PROVISIONED, }); expect(findings).toEqual([]); }); test('reports drift — never blocked — when the revisions differ', async () => { const findings = await auditBrowserPin({ consumers: [{ moduleId: 'lunacycle', clientVersion: '1.55.1', expectedRevision: '1193' }], provisioned: PROVISIONED, }); expect(findings).toHaveLength(1); const finding = findings[0]; // Task 4.2: it warns, it never blocks. Under D2 the consumer passes an // explicit executablePath, so this is a soft protocol risk, not a // failure — blocking a deploy on it would be wrong. expect(finding?.severity).toBe('drift'); expect(finding?.category).toBe('browser_pin'); expect(finding?.subject).toBe('lunacycle'); expect(finding?.actionable).toBe(false); // Both revisions named, so the operator can tell which end to move. expect(finding?.message).toContain('1193'); expect(finding?.message).toContain('1223'); expect(finding?.remediation).toContain('1.60.0'); }); test('reports one finding per drifting module', async () => { const findings = await auditBrowserPin({ consumers: [ { moduleId: 'lunacycle', clientVersion: '1.55.1', expectedRevision: '1193' }, { moduleId: 'aligned', clientVersion: '1.60.0', expectedRevision: '1223' }, { moduleId: 'other', clientVersion: '1.50.0', expectedRevision: '1150' }, ], provisioned: PROVISIONED, }); expect(findings.map((f) => f.subject)).toEqual(['lunacycle', 'other']); }); }); describe('readBundledBrowserClient', () => { let root: string; beforeEach(() => { root = mkdtempSync(join(tmpdir(), 'celilo-browser-pin-')); }); afterEach(() => { rmSync(root, { recursive: true, force: true }); }); /** Write a bundled playwright-core under `//node_modules`. */ function bundle(bundleDir: string, version: string, revision: number | string): void { const pkg = join(root, bundleDir, 'node_modules', 'playwright-core'); mkdirSync(pkg, { recursive: true }); writeFileSync(join(pkg, 'package.json'), JSON.stringify({ version })); writeFileSync( join(pkg, 'browsers.json'), JSON.stringify({ browsers: [ { name: 'chromium', revision: 9999 }, { name: 'chromium-headless-shell', revision }, ], }), ); } test('finds a bundle under the conventional scripts/ directory', () => { bundle('scripts', '1.60.0', 1223); expect(readBundledBrowserClient('m', root, ['./scripts/health-check.ts'])).toEqual({ moduleId: 'm', clientVersion: '1.60.0', expectedRevision: '1223', }); }); test('finds a bundle beside a hook script in a non-standard layout', () => { // The one real browser consumer keeps its hooks in celilo/scripts/, // so a hardcoded scripts/ would have missed exactly the module this // check exists for. bundle('celilo/scripts', '1.55.1', 1193); const found = readBundledBrowserClient('lunacycle', root, ['./celilo/scripts/health-check.ts']); expect(found?.expectedRevision).toBe('1193'); expect(found?.clientVersion).toBe('1.55.1'); }); test('reads the revision as a STRING even though the file holds a number', () => { // browsers.json stores it unquoted; comparing a number to the // descriptor's string would never match and the check would be silent. bundle('scripts', '1.60.0', 1223); expect(readBundledBrowserClient('m', root, [])?.expectedRevision).toBe('1223'); }); test('returns null when the module bundles no browser client', () => { expect(readBundledBrowserClient('m', root, ['./scripts/on_install.ts'])).toBeNull(); }); test('returns null when browsers.json is unreadable rather than throwing', () => { const pkg = join(root, 'scripts', 'node_modules', 'playwright-core'); mkdirSync(pkg, { recursive: true }); writeFileSync(join(pkg, 'browsers.json'), 'not json {'); expect(readBundledBrowserClient('m', root, [])).toBeNull(); }); test('a bundle without a readable package.json still reports its revision', () => { // The revision is the load-bearing half; a missing version should not // suppress a real drift finding. const pkg = join(root, 'scripts', 'node_modules', 'playwright-core'); mkdirSync(pkg, { recursive: true }); writeFileSync( join(pkg, 'browsers.json'), JSON.stringify({ browsers: [{ name: 'chromium-headless-shell', revision: 1223 }] }), ); expect(readBundledBrowserClient('m', root, [])).toEqual({ moduleId: 'm', clientVersion: 'unknown', expectedRevision: '1223', }); }); });