Changeset 0.22.6 (#148)
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
// policy exists but the frontend doesn't
|
||||
// check it.
|
||||
//
|
||||
// v0.22.5: Updated for server-rendered Go
|
||||
// templates. HTML is now in server/pages/
|
||||
// templates/ — not in src/index.html.
|
||||
//
|
||||
// Run: node --test src/js/__tests__/policy-gating.test.js
|
||||
// ==========================================
|
||||
|
||||
@@ -16,6 +20,21 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SRC = path.join(__dirname, '..');
|
||||
const TEMPLATES = path.join(__dirname, '..', '..', '..', 'server', 'pages', 'templates');
|
||||
|
||||
// ── Helper: read all server template HTML ────
|
||||
function readAllTemplates() {
|
||||
const files = [];
|
||||
function walk(dir) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(full);
|
||||
else if (entry.name.endsWith('.html')) files.push(fs.readFileSync(full, 'utf-8'));
|
||||
}
|
||||
}
|
||||
walk(TEMPLATES);
|
||||
return files.join('\n');
|
||||
}
|
||||
|
||||
// ── Source code audits ───────────────────────
|
||||
// These tests read the actual source files and verify that required
|
||||
@@ -28,7 +47,9 @@ describe('Policy wiring audit — source code', () => {
|
||||
// Read all app-side files (app.js + extracted handler files replace old monolith app.js)
|
||||
const appSrc = ['app.js', 'settings-handlers.js', 'admin-handlers.js', 'chat.js', 'tokens.js', 'notes.js']
|
||||
.map(f => fs.readFileSync(path.join(SRC, f), 'utf-8')).join('\n');
|
||||
const indexSrc = fs.readFileSync(path.join(SRC, '..', 'index.html'), 'utf-8');
|
||||
// Pages.js — server-rendered page handlers (v0.22.5+)
|
||||
const pagesSrc = fs.readFileSync(path.join(SRC, 'pages.js'), 'utf-8');
|
||||
const templateSrc = readAllTemplates();
|
||||
|
||||
// ── allow_user_byok ──
|
||||
|
||||
@@ -54,18 +75,18 @@ describe('Policy wiring audit — source code', () => {
|
||||
'MISSING: allow_user_personas check in UI');
|
||||
});
|
||||
|
||||
it('admin settings UI has adminUserPresetsToggle', () => {
|
||||
assert.ok(indexSrc.includes('adminUserPresetsToggle'),
|
||||
'MISSING: preset toggle in admin settings HTML');
|
||||
it('admin settings template has user-personas toggle', () => {
|
||||
assert.ok(templateSrc.includes('settUserPersonas'),
|
||||
'MISSING: settUserPersonas toggle in admin settings template');
|
||||
});
|
||||
|
||||
it('admin settings load reads allow_user_personas', () => {
|
||||
assert.ok(uiSrc.includes("adminUserPresetsToggle"),
|
||||
'MISSING: loadAdminSettings must read allow_user_personas into toggle');
|
||||
it('Pages.saveSettings writes allow_user_personas', () => {
|
||||
assert.ok(pagesSrc.includes('allow_user_personas'),
|
||||
'MISSING: allow_user_personas in Pages.saveSettings');
|
||||
});
|
||||
|
||||
it('admin settings save writes allow_user_personas', () => {
|
||||
assert.ok(appSrc.includes("allow_user_personas"),
|
||||
it('admin settings save writes allow_user_personas (SPA bridge)', () => {
|
||||
assert.ok(appSrc.includes('allow_user_personas'),
|
||||
'MISSING: handleSaveAdminSettings must write allow_user_personas');
|
||||
});
|
||||
|
||||
@@ -199,55 +220,101 @@ describe('Team member dropdown population', () => {
|
||||
});
|
||||
|
||||
// ── Admin settings field mapping ─────────────
|
||||
// v0.22.5: Server-rendered admin settings template uses new element IDs.
|
||||
// Pages.saveSettings() in pages.js is the primary handler.
|
||||
|
||||
describe('Admin settings field mapping', () => {
|
||||
// Maps what the frontend sends to what the backend expects
|
||||
describe('Admin settings field mapping (server templates)', () => {
|
||||
// New element IDs used by server-rendered admin/settings.html + pages.js
|
||||
const settingsFieldMap = {
|
||||
'adminRegToggle': 'allow_registration',
|
||||
'adminRegDefaultState': 'default_user_active',
|
||||
'adminUserProvidersToggle': 'allow_user_byok',
|
||||
'adminUserPresetsToggle': 'allow_user_personas',
|
||||
'adminBannerEnabled': 'banner',
|
||||
'settRegEnabled': 'allow_registration',
|
||||
'settRegDefaultState': 'default_user_active',
|
||||
'settUserBYOK': 'allow_user_byok',
|
||||
'settUserPersonas': 'allow_user_personas',
|
||||
'settBannerEnabled': 'banner',
|
||||
};
|
||||
|
||||
// Read all app-side files (handleSaveAdminSettings is in settings-handlers.js)
|
||||
const appSrc = ['app.js', 'settings-handlers.js', 'admin-handlers.js']
|
||||
.map(f => fs.readFileSync(path.join(SRC, f), 'utf-8')).join('\n');
|
||||
const indexSrc = fs.readFileSync(path.join(SRC, '..', 'index.html'), 'utf-8');
|
||||
const pagesSrc = fs.readFileSync(path.join(SRC, 'pages.js'), 'utf-8');
|
||||
const templateSrc = readAllTemplates();
|
||||
|
||||
for (const [elementId, settingKey] of Object.entries(settingsFieldMap)) {
|
||||
it(`HTML has element #${elementId}`, () => {
|
||||
assert.ok(indexSrc.includes(`id="${elementId}"`),
|
||||
`MISSING: #${elementId} in index.html — admin settings incomplete`);
|
||||
it(`template has element #${elementId}`, () => {
|
||||
assert.ok(templateSrc.includes(`id="${elementId}"`),
|
||||
`MISSING: #${elementId} in server templates — admin settings incomplete`);
|
||||
});
|
||||
|
||||
it(`frontend writes setting "${settingKey}"`, () => {
|
||||
assert.ok(appSrc.includes(settingKey),
|
||||
`MISSING: "${settingKey}" in handleSaveAdminSettings`);
|
||||
it(`Pages.saveSettings writes setting "${settingKey}"`, () => {
|
||||
assert.ok(pagesSrc.includes(settingKey),
|
||||
`MISSING: "${settingKey}" in Pages.saveSettings`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── SPA bridge field mapping (backward compat) ──
|
||||
// The SPA chat surface still loads settings-handlers.js + ui-admin.js.
|
||||
// These use legacy element IDs for handleSaveAdminSettings().
|
||||
// Verified at the JS level (elements are SPA-modal DOM, not templates).
|
||||
|
||||
describe('SPA bridge — admin settings handler references policy keys', () => {
|
||||
const appSrc = ['settings-handlers.js', 'admin-handlers.js']
|
||||
.map(f => fs.readFileSync(path.join(SRC, f), 'utf-8')).join('\n');
|
||||
|
||||
const requiredPolicies = [
|
||||
'allow_registration',
|
||||
'default_user_active',
|
||||
'allow_user_byok',
|
||||
'allow_user_personas',
|
||||
];
|
||||
|
||||
for (const key of requiredPolicies) {
|
||||
it(`SPA bridge writes policy "${key}"`, () => {
|
||||
assert.ok(appSrc.includes(key),
|
||||
`MISSING: "${key}" in SPA bridge handler — policy not saved`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── HTML element existence checks ────────────
|
||||
// v0.22.5: Elements now live in server templates, not index.html.
|
||||
// Settings surface dynamic sections (providers, personas) have scaffold
|
||||
// containers rendered by Go templates that JS then populates.
|
||||
|
||||
describe('Critical HTML elements exist', () => {
|
||||
const indexSrc = fs.readFileSync(path.join(SRC, '..', 'index.html'), 'utf-8');
|
||||
describe('Critical HTML elements exist in server templates', () => {
|
||||
const templateSrc = readAllTemplates();
|
||||
|
||||
const requiredElements = [
|
||||
'adminMemberUser', // Team member user dropdown
|
||||
// Settings surface — provider section scaffold
|
||||
'userPresetList', // User preset list container
|
||||
'userAddPresetBtn', // New preset button (policy-gated)
|
||||
'userAddPresetForm', // Preset form container
|
||||
'userProvidersDisabled', // BYOK disabled notice
|
||||
'providerShowAddBtn', // Add provider button (policy-gated)
|
||||
'adminUserProvidersToggle', // Admin toggle for BYOK
|
||||
'adminUserPresetsToggle', // Admin toggle for presets
|
||||
// Admin settings — policy toggles
|
||||
'settUserBYOK', // Admin toggle for BYOK (was adminUserProvidersToggle)
|
||||
'settUserPersonas', // Admin toggle for presets (was adminUserPresetsToggle)
|
||||
];
|
||||
|
||||
for (const id of requiredElements) {
|
||||
it(`#${id} exists in index.html`, () => {
|
||||
assert.ok(indexSrc.includes(`id="${id}"`),
|
||||
it(`#${id} exists in server templates`, () => {
|
||||
assert.ok(templateSrc.includes(`id="${id}"`),
|
||||
`MISSING element: #${id} — UI feature will break`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── SPA bridge — dynamic DOM elements ────────
|
||||
// adminMemberUser is created by ui-admin.js loadMemberUserDropdown()
|
||||
// which runs inside the SPA chat surface. Verify the JS function exists.
|
||||
|
||||
describe('SPA bridge — dynamic element creators', () => {
|
||||
const uiAdminSrc = fs.readFileSync(path.join(SRC, 'ui-admin.js'), 'utf-8');
|
||||
|
||||
it('ui-admin.js has loadMemberUserDropdown', () => {
|
||||
assert.ok(uiAdminSrc.includes('loadMemberUserDropdown'),
|
||||
'MISSING: loadMemberUserDropdown — team member add will break');
|
||||
});
|
||||
|
||||
it('loadMemberUserDropdown references adminMemberUser', () => {
|
||||
assert.ok(uiAdminSrc.includes('adminMemberUser'),
|
||||
'MISSING: adminMemberUser reference in loadMemberUserDropdown');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user