49 lines
3.7 KiB
JavaScript
49 lines
3.7 KiB
JavaScript
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('<!doctype html><html><head></head><body><div id="toast-container"></div><div class="card"><div id="synthetic-note">Synthetic text</div><button id="read" data-action="speak" data-target="synthetic-note">Read</button><button id="export" data-action="nc-export">Export</button></div><div id="lazy"></div><select class="tab-model-select"></select></body></html>', { 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');
|
|
});
|