atlas-iac/testing/tests/test_hermes_webui_hux_integration_node.js

398 lines
23 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const api = require('../../dockerfiles/hermes-webui-hux/bootstrap.js');
const IDENTITY = Object.freeze({tenant_slot: 'slot-3', subject: 'usr_0123456789abcdef',
surface: 'chat', trust: 'relay'});
function session(overrides) {
return {session_id: 'webui-session-1', hux_context: {schema: 'hux.webui_context.v1',
webui_session_id: 'webui-session-1', session_id: 'ses_session1234',
conversation_id: 'conv_converse1', project_id: 'prj_project123',
project_source: 'profile:default', identity: IDENTITY,
...overrides}};
}
function card(name, enabled, routes) {
const flags = {'HUX-11': 'hux.foundation', 'HUX-01': 'hux.activity_timeline',
'HUX-02': 'hux.memory_control', 'HUX-03': 'hux.projects', 'HUX-04': 'hux.artifacts',
'HUX-05': 'hux.autonomy', 'HUX-06': 'hux.friendly_modes', 'HUX-07': 'hux.multimodal',
'HUX-08': 'hux.research', 'HUX-09': 'hux.onboarding', 'HUX-10': 'hux.privacy',
'HUX-12': 'hux.release_followthrough'};
return {card: name, flag: flags[name], enabled, routes: routes || []};
}
function capability(cards, overrides) {
return {schema: 'hux.capabilities.v1', contract_version: '1.1.0', identity: IDENTITY,
cards: [card('HUX-11', true, ['/hux/v1/capabilities', '/hux/v1/context/bootstrap']),
...(cards || [])], server: {}, ...(overrides || {})};
}
function response(status, body) {
return {status, ok: status >= 200 && status < 300, json: async () => body};
}
function binding(value, overrides) {
const context = api.normalizeContext(value || session());
return {schema: 'hux.context_bootstrap.v1', contract_version: '1.1.0', identity: IDENTITY,
session_id: context.sessionId, project_id: context.projectId,
conversation_id: context.conversationId, revisions: {project: 1, conversation: 1},
created: {project: false, conversation: false}, ...(overrides || {})};
}
function huxFetcher(cards, sessionReader, calls) {
return async (url, init) => {
if (calls) calls.push({url, init});
return url === '/hux/v1/context/bootstrap' ? response(200, binding(sessionReader())) :
response(200, cards);
};
}
class FakeNode {
constructor(tag) {
this.tagName = tag.toUpperCase(); this.attributes = {}; this.children = [];
this.parentNode = null; this.listeners = {}; this.hidden = false; this.textContent = '';
}
setAttribute(name, value) { this.attributes[name] = String(value); }
getAttribute(name) { return this.attributes[name]; }
appendChild(child) { child.parentNode = this; this.children.push(child); return child; }
removeChild(child) { this.children.splice(this.children.indexOf(child), 1); child.parentNode = null; }
addEventListener(name, callback) { this.listeners[name] = callback; }
trigger(name, event) { if (this.listeners[name]) this.listeners[name](event || {}); }
focus() { this.focused = true; }
}
class FakeDocument {
constructor() { this.body = new FakeNode('body'); this.listeners = {}; this.readyState = 'complete'; }
createElement(tag) { return new FakeNode(tag); }
addEventListener(name, callback) { this.listeners[name] = callback; }
removeEventListener(name, callback) { if (this.listeners[name] === callback) delete this.listeners[name]; }
trigger(name, event) { if (this.listeners[name]) this.listeners[name](event || {}); }
}
function fakeRoot(document) {
const listeners = {}; const intervals = new Map(); let sequence = 0;
return {document, __HERMES_CONFIG__: {csrfToken: '0123456789abcdef'}, fetch: null,
setTimeout, clearTimeout,
setInterval(callback, delay) { const id = ++sequence; intervals.set(id, {callback, delay}); return id; },
clearInterval(id) { intervals.delete(id); },
addEventListener(name, callback) { listeners[name] = callback; },
removeEventListener(name, callback) { if (listeners[name] === callback) delete listeners[name]; },
listeners, intervals};
}
test('trusted context accepts exact opaque scope and rejects browser-controlled substitutes', () => {
const normalized = api.normalizeContext(session({run_id: 'run_alpha1234', message_id: 'msg_message9',
branch_point_message_id: 'msg_message8', notebook_id: 'nb_notebook1'}));
assert.equal(normalized.identity.tenantSlot, 'slot-3');
assert.equal(normalized.conversationId, 'conv_converse1');
assert.equal(normalized.runId, 'run_alpha1234');
assert.equal(Object.isFrozen(normalized.identity), true);
const invalid = [null, [], {}, {session_id: 'x', hux_context: {}},
{hux_context: session().hux_context},
session({schema: 'old'}), session({webui_session_id: 'another'}),
session({session_id: 'bad'}), session({conversation_id: 'bad'}), session({project_id: 'bad'}),
session({project_source: 'bad/source'}), session({unexpected: true}),
session({identity: null}), session({identity: {...IDENTITY, tenant_slot: 'tenant'}}),
session({identity: {...IDENTITY, subject: 'raw-subject'}}),
session({identity: {...IDENTITY, surface: 'browser'}}),
session({identity: {...IDENTITY, trust: 'cookie'}}), session({run_id: 'run-7'}),
session({message_id: ''}), session({branch_point_message_id: 'bad message'}),
session({notebook_id: 'bad'})];
invalid.forEach((item) => assert.equal(api.normalizeContext(item), null));
});
test('chat-start lifecycle context is copied only from an exact trusted server response', () => {
const current = session();
const enriched = session({run_id: 'run_turn1234', message_id: 'msg_message9'}).hux_context;
assert.equal(api.mergeTrustedContext(current, {session_id: current.session_id,
hux_context: enriched}), true);
assert.equal(api.normalizeContext(current).runId, 'run_turn1234');
assert.equal(api.mergeTrustedContext(current, {session_id: 'another', hux_context: enriched}), false);
assert.equal(api.mergeTrustedContext(current, {session_id: current.session_id,
hux_context: {...enriched, run_id: 'stream-forged'}}), false);
assert.equal(api.mergeTrustedContext(null, {}), false);
});
test('owned stop uses only the current server session stream and stays bounded', async () => {
const current = session(); current.active_stream_id = 'stream-owned-1';
const context = api.normalizeContext(current); let active = 'stream-owned-1'; const calls = [];
const root = {setTimeout, clearTimeout, async cancelStream(reason) { calls.push(reason); return true; }};
const control = api.createOwnedStop(root, () => current, () => active, context, 250);
assert.equal(api.ownedActiveStream(current, active, context), active);
assert.equal(control.canStopModelResponse(), true);
assert.deepEqual(await control.stopModelResponse(), {accepted: true,
sessionId: 'webui-session-1', streamId: active});
assert.deepEqual(calls, ['hux-stop']);
active = 'browser-forged'; assert.equal(control.canStopModelResponse(), false);
assert.deepEqual(await control.stopModelResponse(), {accepted: false});
const stale = session(); stale.active_stream_id = 'stream-owned-1';
assert.equal(api.ownedActiveStream(stale, stale.active_stream_id,
api.normalizeContext(session({conversation_id: 'conv_other1234'}))), null);
const slow = api.createOwnedStop({setTimeout, clearTimeout,
cancelStream: () => new Promise(() => {})}, () => current,
() => current.active_stream_id, context, 250);
assert.deepEqual(await slow.stopModelResponse(), {accepted: false,
sessionId: 'webui-session-1', streamId: 'stream-owned-1'});
});
test('capabilities require exact identity, cards, routes and complete wave advertisements', () => {
const payload = capability([
card('HUX-01', true, ['/hux/v1/conversations/{id}/events']),
card('HUX-03', true, ['/hux/v1/projects/{id}', '/hux/v1/conversations',
'/hux/v1/conversations/{id}', '/hux/v1/conversations/{id}/lineage']),
card('HUX-05', true, ['/hux/v1/policy', '/hux/v1/approvals',
'/hux/v1/approvals/{id}', '/hux/v1/runs/{id}/stop', '/hux/v1/runs/{id}/budget',
'/hux/v1/runs/{id}/gate']),
card('HUX-10', true, ['/hux/v1/privacy/policy', '/hux/v1/privacy/notices',
'/hux/v1/conversations/{id}/forget', '/hux/v1/privacy/audit']),
card('HUX-12', true, ['/hux/v1/projects/{project_id}/conversations/{id}/releases',
'/hux/v1/projects/{project_id}/conversations/{id}/releases/{release_id}',
'/hux/v1/projects/{project_id}/conversations/{id}/releases/{release_id}/transitions']),
]);
const expected = api.normalizeContext(session()).identity;
const cards = api.normalizeCapabilities(payload, expected);
assert.deepEqual(api.enabledWaves(cards), ['a', 'b', 'governance', 'c']);
assert.equal(api.cardComplete(cards, 'HUX-01'), true);
assert.equal(api.cardComplete(cards, 'HUX-02'), false);
assert.equal(Object.isFrozen(cards['HUX-01'].routes), true);
const incomplete = api.normalizeCapabilities(capability([
card('HUX-01', true, []), card('HUX-03', true, ['/hux/v1/projects/{id}'])]), expected);
assert.deepEqual(api.enabledWaves(incomplete), []);
const invalid = [null, [], {...payload, schema: 'old'}, {...payload, contract_version: '2.0.0'},
{...payload, unexpected: true}, {...payload, identity: {...IDENTITY, unexpected: true}},
{...payload, identity: {...IDENTITY, subject: 'usr_ffffffffffffffff'}},
{...payload, server: null}, {...payload, server: {unexpected: 'x'}},
{...payload, server: {commit: 'x'.repeat(161)}},
{...payload, cards: null}, {...payload, cards: Array(13).fill(payload.cards[0])},
capability([card('HUX-01', true, []), card('HUX-01', false, [])]),
capability([{...card('HUX-01', true, []), extra: true}]),
capability([{...card('HUX-01', true, []), flag: 'wrong'}]),
capability([{...card('HUX-01', true, []), enabled: 'yes'}]),
capability([{...card('HUX-01', true, []), routes: null}]),
capability([{...card('HUX-01', true, []), card: 'HUX-99'}]),
capability([{...card('HUX-01', true, []), routes: ['/outside']}]),
capability([{...card('HUX-01', true, []), routes: ['/hux/v1/memory', '/hux/v1/memory']}]),
capability([], {cards: [card('HUX-11', false, ['/hux/v1/capabilities'])]}),
capability([], {cards: [card('HUX-11', true, [])]}),
capability([], {cards: [card('HUX-11', true, ['/hux/v1/capabilities'])]})];
invalid.forEach((item) => assert.equal(api.normalizeCapabilities(item, expected), null));
});
test('scoped transport is same-origin, no-store and adds CSRF only to mutations', async () => {
const calls = [];
const fetcher = api.scopedFetcher(async (url, init) => { calls.push({url, init}); return response(200, {}); },
'0123456789abcdef');
await fetcher('/hux/v1/capabilities', {headers: {Accept: api.ACCEPT}});
await fetcher('/hux/v1/capabilities', {method: 'HEAD'});
await fetcher('/hux/v1/capabilities');
await assert.rejects(() => fetcher(), /same-origin/);
await fetcher('/hux/v1/memory', {method: 'POST', headers: {'X-Hermes-CSRF-Token': 'forged'}, body: '{}'});
assert.equal(calls[0].init.headers['X-Hermes-CSRF-Token'], undefined);
const mutation = calls.find((item) => item.init.method === 'POST');
assert.equal(mutation.init.headers['X-Hermes-CSRF-Token'], '0123456789abcdef');
assert.equal(mutation.init.cache, 'no-store');
assert.equal(mutation.init.credentials, 'same-origin');
await assert.rejects(() => fetcher('https://evil.test/hux/v1/memory'), /same-origin/);
await assert.rejects(() => fetcher('//evil/hux/v1/memory'), /same-origin/);
await assert.rejects(() => fetcher('/hux/v1/../secrets'), /same-origin/);
assert.equal(api.safeCsrf({csrfToken: '0123456789abcdef'}), '0123456789abcdef');
[null, {}, {csrfToken: 'short'}, {csrfToken: 'x'.repeat(513)}, {csrfToken: `good-enough-token\n`}]
.forEach((item) => assert.equal(api.safeCsrf(item), null));
});
test('preflight returns only usable waves and hides all errors and disabled states', async () => {
const context = api.normalizeContext(session());
const good = capability([card('HUX-01', true, ['/hux/v1/conversations/{id}/events'])]);
assert.deepEqual((await api.preflight(async () => response(200, good), context)).waves, ['a']);
for (const result of [response(404, {}), response(500, {}), response(200, capability([])),
response(200, {...good, identity: {...IDENTITY, subject: 'usr_ffffffffffffffff'}}), null]) {
assert.equal(await api.preflight(async () => result, context), null);
}
assert.equal(await api.preflight(async () => { throw new Error('private'); }, context), null);
});
test('context bootstrap sends exact server scope and verifies exact binding response', async () => {
const context = api.normalizeContext(session()); const calls = [];
const created = binding(session(), {created: {project: true, conversation: false}});
assert.deepEqual((await api.bootstrapContext(async (url, init) => {
calls.push({url, init}); return response(201, created);
}, context)).created, {project: true, conversation: false});
assert.equal(calls[0].url, '/hux/v1/context/bootstrap');
assert.equal(calls[0].init.headers['Idempotency-Key'], `hux:context:${context.sessionId}`);
assert.deepEqual(JSON.parse(calls[0].init.body), {raw_session_id: 'webui-session-1',
project_source: 'profile:default', session_id: context.sessionId,
conversation_id: context.conversationId, project_id: context.projectId});
const invalid = [null, {...binding(), extra: true}, {...binding(), schema: 'old'},
{...binding(), contract_version: '2.0.0'}, {...binding(), identity: {...IDENTITY, trust: 'worker'}},
{...binding(), session_id: 'ses_other1234'}, {...binding(), project_id: 'prj_other1234'},
{...binding(), conversation_id: 'conv_other1234'}, {...binding(), revisions: null},
{...binding(), revisions: {project: 0, conversation: 1}},
{...binding(), created: {project: 'yes', conversation: false}}];
invalid.forEach((item) => assert.equal(api.normalizeBootstrap(item, context, 200), null));
assert.equal(api.normalizeBootstrap(binding(), context, 201), null);
for (const reply of [response(404, {}), response(500, {}), response(200, invalid[1]), null]) {
assert.equal(await api.bootstrapContext(async () => reply, context), null);
}
assert.equal(await api.bootstrapContext(async () => { throw new Error('private'); }, context), null);
});
test('chrome is accessible, keyboard closable, and fully removable', () => {
const doc = new FakeDocument(); const chrome = api.createChrome(doc);
assert.equal(doc.body.children.length, 2);
assert.equal(chrome.drawer.hidden, true);
chrome.open.trigger('click');
assert.equal(chrome.drawer.hidden, false);
assert.equal(chrome.open.getAttribute('aria-expanded'), 'true');
doc.trigger('keydown', {key: 'Enter'}); assert.equal(chrome.drawer.hidden, false);
doc.trigger('keydown', {key: 'Escape'}); assert.equal(chrome.drawer.hidden, true);
chrome.setOpen(true);
chrome.drawer.children[0].children[1].trigger('click');
assert.equal(chrome.drawer.hidden, true);
chrome.destroy(); assert.equal(doc.body.children.length, 0);
});
test('coordinator tracks trusted session rotation and destroys stale UI', async () => {
const doc = new FakeDocument(); const root = fakeRoot(doc); let current = session();
const capabilities = capability([card('HUX-01', true, ['/hux/v1/conversations/{id}/events'])]);
const destroyed = []; const calls = [];
const coordinator = api.createCoordinator({root, document: doc,
fetcher: huxFetcher(capabilities, () => current),
sessionReader: () => current, runtimeFactory: async (target, context, _fetcher, waves) => {
calls.push({target, context, waves}); return {destroy() { destroyed.push(context.conversationId); }};
}});
assert.equal(await coordinator.refresh(), true);
assert.equal(coordinator.getState().active, true);
assert.deepEqual(calls[0].waves, ['a']);
assert.equal(await coordinator.refresh(), false);
current = session({webui_session_id: 'webui-session-2', conversation_id: 'conv_second1234'});
current.session_id = 'webui-session-2';
assert.equal(await coordinator.refresh(), true);
assert.deepEqual(destroyed, ['conv_converse1']);
current = null; assert.equal(await coordinator.refresh(), false);
assert.deepEqual(destroyed, ['conv_converse1', 'conv_second1234']);
assert.equal(doc.body.children.length, 0);
coordinator.start(); coordinator.start();
assert.equal(root.intervals.size, 1); assert.equal(root.listeners.pageshow instanceof Function, true);
coordinator.destroy(); assert.equal(root.intervals.size, 0); assert.equal(coordinator.getState().active, false);
});
test('coordinator refuses missing CSRF, 404, runtime failure, and superseded negotiation', async () => {
const doc = new FakeDocument(); const root = fakeRoot(doc); let current = session();
const good = capability([card('HUX-01', true, ['/hux/v1/conversations/{id}/events'])]);
const pending = []; let factoryCalls = 0; let staleDestroyed = 0;
const coordinator = api.createCoordinator({root, fetcher: () => new Promise((resolve) => pending.push(resolve)),
sessionReader: () => current, runtimeFactory: async () => { factoryCalls += 1;
return {destroy() { staleDestroyed += 1; }}; }});
const first = coordinator.refresh();
current = session({webui_session_id: 'webui-session-2', conversation_id: 'conv_second1234'});
current.session_id = 'webui-session-2';
const second = coordinator.refresh();
pending[1](response(200, good)); await new Promise((resolve) => setImmediate(resolve));
pending[2](response(200, binding(current))); assert.equal(await second, true);
pending[0](response(200, good)); assert.equal(await first, false);
assert.equal(factoryCalls, 1);
coordinator.destroy(); assert.equal(staleDestroyed, 1);
const noCsrf = api.createCoordinator({root, fetcher: async () => response(200, good),
configReader: () => ({}), sessionReader: () => session()});
assert.equal(await noCsrf.refresh(), false);
const notFound = api.createCoordinator({root, fetcher: async () => response(404, {}),
sessionReader: () => session()});
assert.equal(await notFound.refresh(), false);
const failed = api.createCoordinator({root, fetcher: huxFetcher(good, () => session()),
sessionReader: () => session(), runtimeFactory: async () => { throw new Error('private'); }});
assert.equal(await failed.refresh(), false); assert.equal(doc.body.children.length, 0);
assert.throws(() => api.createCoordinator({root: {}, sessionReader: () => null}), /requires/);
});
test('transient capability and bootstrap outages retry the same trusted session', async () => {
const doc = new FakeDocument(); const root = fakeRoot(doc); const current = session();
const good = capability([card('HUX-01', true, ['/hux/v1/conversations/{id}/events'])]);
let capabilityCalls = 0; let bootstrapCalls = 0;
const coordinator = api.createCoordinator({root, sessionReader: () => current,
fetcher: async (url) => {
if (url === '/hux/v1/capabilities') {
capabilityCalls += 1;
return capabilityCalls === 1 ? response(404, {}) : response(200, good);
}
bootstrapCalls += 1;
return bootstrapCalls === 1 ? response(503, {}) : response(200, binding(current));
}, runtimeFactory: async () => ({destroy() {}})});
assert.equal(await coordinator.refresh(), false);
assert.equal(await coordinator.refresh(), false);
assert.equal(await coordinator.refresh(), true);
assert.equal(capabilityCalls, 3); assert.equal(bootstrapCalls, 2);
assert.equal(await coordinator.refresh(), false);
coordinator.destroy();
});
test('default runtime factory mounts requested waves and cleans partial failures', async () => {
const doc = new FakeDocument(); const events = [];
function module(name, fail) {
return {create(options) { assert.equal(options.conversationId, 'conv_converse1');
return {async mount(target) { events.push(`mount-${name}-${target.tagName}`); if (fail) throw new Error('fail'); },
destroy() { events.push(`destroy-${name}`); }}; }};
}
const root = {document: doc,
HermesHuxWaveA: {createWaveARuntime: module('a').create},
HermesHuxWaveBRuntime: {createWaveBRuntime: module('b').create},
HermesHuxAutonomyPrivacy: {createAutonomyPrivacyRuntime: module('governance').create},
HermesHuxWaveC: {createWaveCRuntime: module('c').create}};
const context = api.normalizeContext(session());
const runtime = await api.defaultRuntimeFactory(root, doc.body, context, async () => {},
['a', 'b', 'governance', 'c']);
assert.deepEqual(events.slice(0, 4), ['mount-a-SECTION', 'mount-b-SECTION',
'mount-governance-SECTION', 'mount-c-SECTION']);
runtime.destroy(); assert.deepEqual(events.slice(4), ['destroy-c', 'destroy-governance',
'destroy-b', 'destroy-a']);
await assert.rejects(() => api.defaultRuntimeFactory({...root, HermesHuxWaveA: null},
doc.body, context, async () => {}, ['a']), /incomplete/);
const partial = [];
root.HermesHuxWaveA.createWaveARuntime = () => ({async mount() { partial.push('mount'); },
destroy() { partial.push('destroy'); }});
await assert.rejects(() => api.defaultRuntimeFactory({...root, HermesHuxWaveBRuntime: null},
doc.body, context, async () => {}, ['a', 'b']), /incomplete/);
assert.deepEqual(partial, ['mount', 'destroy']);
});
test('auto-start is inert outside a complete browser and waits for DOM readiness', () => {
assert.equal(api.autoStart(null), null);
assert.equal(api.autoStart({document: {}, fetch: null}), null);
const doc = new FakeDocument(); doc.readyState = 'loading'; const root = fakeRoot(doc);
root.fetch = async () => response(404, {});
const coordinator = api.autoStart(root);
assert.equal(root.intervals.size, 0);
doc.trigger('DOMContentLoaded'); assert.equal(root.intervals.size, 1);
coordinator.destroy();
const readyDoc = new FakeDocument(); const readyRoot = fakeRoot(readyDoc);
readyRoot.fetch = async () => response(404, {});
const ready = api.autoStart(readyRoot); assert.equal(readyRoot.intervals.size, 1); ready.destroy();
});
test('coordinator default factory and polling callback remain generation fenced', async () => {
const doc = new FakeDocument(); const root = fakeRoot(doc); let current = session();
const good = capability([card('HUX-01', true, ['/hux/v1/conversations/{id}/events'])]);
root.fetch = huxFetcher(good, () => current);
let destroyed = 0;
root.HermesHuxWaveA = {createWaveARuntime() { return {async mount() {}, destroy() { destroyed += 1; }}; }};
root.HermesHuxWaveBRuntime = {}; root.HermesHuxAutonomyPrivacy = {}; root.HermesHuxWaveC = {};
const coordinator = api.createCoordinator({root, sessionReader: () => current});
assert.equal(await coordinator.refresh(), true);
coordinator.start();
await root.intervals.values().next().value.callback();
assert.equal(coordinator.getState().active, true);
coordinator.destroy(); assert.equal(destroyed, 1);
let resolveRuntime; current = session();
const stale = api.createCoordinator({root, fetcher: huxFetcher(good, () => current),
sessionReader: () => current, runtimeFactory: () => new Promise((resolve) => { resolveRuntime = resolve; })});
const first = stale.refresh();
await new Promise((resolve) => setImmediate(resolve));
current = null; await stale.refresh();
let staleDestroyed = 0; resolveRuntime({destroy() { staleDestroyed += 1; }});
assert.equal(await first, false); assert.equal(staleDestroyed, 1); stale.destroy();
});