const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const { JSDOM } = require('jsdom'); const read = file => fs.readFileSync(path.join(__dirname, '..', file), 'utf8'); const tick = () => new Promise(resolve => setImmediate(resolve)); test('actual app hides disabled/lazy-loaded feature UI, uses only advertised models, and never browser-falls-back on policy denial', async t => { const dom = new JSDOM('
Synthetic text
', { runScripts: 'outside-only', url: 'https://app.example' }); t.after(() => dom.window.close()); const { window } = dom; const style = window.document.createElement('style'); style.textContent = read('public/css/styles.css'); window.document.head.appendChild(style); let features = { read_aloud: false, nextcloud: false, memories: false }; let speechCalls = 0; let ttsCalls = 0; window.getAuthHeaders = () => ({}); window.console = { log() {}, warn() {}, error() {} }; window.speechSynthesis = { cancel() {}, speak() { speechCalls++; } }; window.SpeechSynthesisUtterance = function() {}; window.fetch = async url => { if (url === '/api/models') return { json: async () => ({ models: [{ id: 'allowed', name: 'Allowed' }], defaultModel: 'allowed' }) }; if (url === '/api/user/features') return { ok: true, json: async () => ({ features }) }; if (url === '/api/text-to-speech') { ttsCalls++; return { ok: false, status: 403 }; } if (url === '/api/memories/context') return { json: async () => ({ error: 'Feature disabled' }) }; throw new Error('Unexpected browser request ' + url); }; window.eval(read('public/js/accountBoundary.js')); assert.equal(window.AccountBoundary.enter({ id: 'synthetic-policy-user' }, true), true); window.eval(read('public/js/authFetch.js')); window.eval(read('public/js/app.js')); await tick(); const select = window.document.querySelector('select'); assert.equal(select.value, 'allowed'); assert.equal(select.options.length, 1); window._defaultModelId = 'removed'; window._buildModelOptions(select); assert.equal(select.options.length, 1); assert.equal(select.options[0].value, 'allowed'); await window.loadUserFeatures(); window.document.getElementById('lazy').innerHTML = read('public/components/settings.html') + read('public/components/cms.html'); const hidden = selector => assert.equal(window.getComputedStyle(window.document.querySelector(selector)).display, 'none', selector); hidden('#read'); hidden('#export'); hidden('[data-feature="read_aloud"]'); hidden('[data-feature="memories"]'); hidden('[data-feature="nextcloud"]'); hidden('#lh-ai-tab-webdav'); hidden('#lh-ai-tp-webdav'); window.speakText('synthetic-note'); assert.equal(ttsCalls, 0); features = { read_aloud: true, nextcloud: true, memories: true }; await window.loadUserFeatures(); assert.notEqual(window.getComputedStyle(window.document.getElementById('read')).display, 'none'); assert.notEqual(window.getComputedStyle(window.document.querySelector('[data-feature="memories"]')).display, 'none'); window.speakText('synthetic-note'); await tick(); await tick(); assert.equal(ttsCalls, 1); assert.equal(speechCalls, 0, '403 must not bypass policy using browser speech'); window.eval(read('public/js/memories.js')); assert.equal(await window.getUserMemoryContext(), '', 'disabled context never reuses cached memories'); });