diff --git a/packages/webui/tests/decide-browser.test.mjs b/packages/webui/tests/decide-browser.test.mjs index 2599ee67a..fd1c65123 100644 --- a/packages/webui/tests/decide-browser.test.mjs +++ b/packages/webui/tests/decide-browser.test.mjs @@ -6,6 +6,7 @@ // through its reader session. Refresh timers fire after 150 ms. import { test } from 'node:test'; import assert from 'node:assert/strict'; +import { randomUUID } from 'node:crypto'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -46,12 +47,28 @@ const STUB = `(() => { }; })();`; +// The open grant request, as host-fixtures' grantDecision writes it but with the hostile question. +// decisions is append-only (migration 3b), so the question goes in with the row, not by an UPDATE. +function hostileGrant(store, grant) { + const id = randomUUID(), at = '2026-10-21T00:00:00.000Z'; + store.run( + 'INSERT INTO decisions(id,at,business,raised_by_role,raised_by_run,class,action,route_to,question,options,recommendation,blocking) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)', + id, at, 'demo', 'chief', 'chief-run', 'gated', 'credential.mint', 'human', `Enroll web1? ${HOSTILE}`, + JSON.stringify([{ key: 'yes', text: 'Yes' }, { key: 'no', text: 'No' }]), 'yes', 0, + ); + store.run( + "INSERT INTO events(id,at,business,kind,actor_role,actor_run,subject,body) VALUES(?,?,?,'action.allowed',?,?,?,?)", + randomUUID(), at, 'demo', 'chief', 'chief-run', id, + JSON.stringify({ decision: id, action: 'credential.mint', operation: 'decision.raise', approvalChoice: 'yes', grant }), + ); + return id; +} + async function setup() { const root = mkdtempSync(join(tmpdir(), 'webui-decide-browser-')); const store = new Store(root), b0 = new Broker({ store, businesses }); const grant = { method: 'host-enroll', host: 'web1', address: '192.0.2.10', port: 22, user: 'jarvis', sudo: true, fingerprints: [] }; - const open = grantDecision(store, { grant, close: null }); - store.run('UPDATE decisions SET question=? WHERE id=?', `Enroll web1? ${HOSTILE}`, open); + const open = hostileGrant(store, grant); const deploy = grantDecision(store, { action: 'deploy', grant, close: null }); const bare = grantDecision(store, { close: null }); const reader = b0.bindReader({ business: 'demo' }); @@ -67,6 +84,8 @@ async function setup() { const queue = a => b.evaluate(`window.__answers.push(${JSON.stringify(a)}), true`); const click = step => b.evaluate(`document.querySelector('#decide-section [data-decide="${step}"]').click(), true`); const page = async (query, id) => { + // The same URL again is a same-document hash change, not a load, so load about:blank first (as editor-browser does). + await b.navigate('about:blank'); await b.navigate(`${base}?${query}#/inbox/${id}`); await wait('document.querySelector("#bus-view h1")?.textContent.startsWith("Decision") && !document.querySelector("#bus-view [aria-busy]")'); // The config read redraws once it lands; a refresh tick after it settles the page.