Changeset 0.9.0 (#50)

This commit is contained in:
2026-02-23 01:57:28 +00:00
parent 15be26c516
commit 8264aa6016
94 changed files with 9812 additions and 8574 deletions

View File

@@ -0,0 +1,247 @@
// ==========================================
// Policy Gating & UI Logic Tests
// ==========================================
// Validates that admin policies correctly
// gate user-facing features. These catch
// the exact class of bugs where a backend
// policy exists but the frontend doesn't
// check it.
//
// Run: node --test src/js/__tests__/policy-gating.test.js
// ==========================================
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const SRC = path.join(__dirname, '..');
// ── Source code audits ───────────────────────
// These tests read the actual source files and verify that required
// wiring exists. If someone removes a policy check, CI breaks.
describe('Policy wiring audit — source code', () => {
const uiSrc = fs.readFileSync(path.join(SRC, 'ui.js'), 'utf-8');
const appSrc = fs.readFileSync(path.join(SRC, 'app.js'), 'utf-8');
const indexSrc = fs.readFileSync(path.join(SRC, '..', 'index.html'), 'utf-8');
// ── allow_user_byok ──
it('ui.js has checkUserProvidersAllowed function', () => {
assert.ok(uiSrc.includes('checkUserProvidersAllowed'),
'MISSING: checkUserProvidersAllowed — provider tab will show when policy is off');
});
it('checkUserProvidersAllowed reads App.policies.allow_user_byok', () => {
assert.ok(uiSrc.includes('allow_user_byok'),
'MISSING: allow_user_byok check in UI');
});
// ── allow_user_personas ──
it('ui.js has checkUserPresetsAllowed function', () => {
assert.ok(uiSrc.includes('checkUserPresetsAllowed'),
'MISSING: checkUserPresetsAllowed — preset button will show when policy is off');
});
it('checkUserPresetsAllowed reads App.policies.allow_user_personas', () => {
assert.ok(uiSrc.includes('allow_user_personas'),
'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 load reads allow_user_personas', () => {
assert.ok(uiSrc.includes("adminUserPresetsToggle"),
'MISSING: loadAdminSettings must read allow_user_personas into toggle');
});
it('admin settings save writes allow_user_personas', () => {
assert.ok(appSrc.includes("allow_user_personas"),
'MISSING: handleSaveAdminSettings must write allow_user_personas');
});
// ── Settings live-apply ──
it('admin save calls initBanners for policy refresh', () => {
assert.ok(appSrc.includes('await initBanners()'),
'MISSING: initBanners call after admin save — policies won\'t refresh');
});
it('admin save calls checkUserProvidersAllowed', () => {
// Find in handleSaveAdminSettings
const saveFunc = appSrc.slice(appSrc.indexOf('handleSaveAdminSettings'));
assert.ok(saveFunc.includes('checkUserProvidersAllowed'),
'MISSING: checkUserProvidersAllowed call after admin save');
});
it('admin save calls checkUserPresetsAllowed', () => {
const saveFunc = appSrc.slice(appSrc.indexOf('handleSaveAdminSettings'));
assert.ok(saveFunc.includes('checkUserPresetsAllowed'),
'MISSING: checkUserPresetsAllowed call after admin save');
});
it('admin save calls fetchModels to refresh model list', () => {
const saveFunc = appSrc.slice(appSrc.indexOf('handleSaveAdminSettings'));
assert.ok(saveFunc.includes('fetchModels'),
'MISSING: fetchModels call after admin save — model list stale after settings change');
});
// ── Models tab calls preset check ──
it('models tab switch calls checkUserPresetsAllowed', () => {
assert.ok(uiSrc.includes('checkUserPresetsAllowed'),
'MISSING: checkUserPresetsAllowed call on models tab switch');
});
});
// ── Policy evaluation logic ──────────────────
describe('Policy evaluation logic', () => {
function isPolicyEnabled(policies, key) {
return policies?.[key] === 'true';
}
it('policy "true" (string) → enabled', () => {
assert.equal(isPolicyEnabled({ allow_user_byok: 'true' }, 'allow_user_byok'), true);
});
it('policy "false" (string) → disabled', () => {
assert.equal(isPolicyEnabled({ allow_user_byok: 'false' }, 'allow_user_byok'), false);
});
it('policy boolean true → NOT enabled (must be string)', () => {
assert.equal(isPolicyEnabled({ allow_user_byok: true }, 'allow_user_byok'), false,
'DANGER: policies are string "true"/"false", not boolean');
});
it('missing policy → disabled', () => {
assert.equal(isPolicyEnabled({}, 'allow_user_byok'), false);
});
it('null policies → disabled', () => {
assert.equal(isPolicyEnabled(null, 'allow_user_byok'), false);
});
it('undefined policies → disabled', () => {
assert.equal(isPolicyEnabled(undefined, 'allow_user_byok'), false);
});
});
// ── Team member dropdown logic ───────────────
// Bug: Empty dropdown because resp.data used instead of resp.users
describe('Team member dropdown population', () => {
function getAvailableUsers(usersResp, membersResp) {
const users = usersResp.users || usersResp.data || [];
const existingIds = new Set((membersResp.data || []).map(m => m.user_id));
return users.filter(u => u.is_active && !existingIds.has(u.id));
}
it('extracts users from {users:[]} response', () => {
const usersResp = { users: [
{ id: 'u1', username: 'alice', is_active: true },
{ id: 'u2', username: 'bob', is_active: true },
]};
const membersResp = { data: [] };
const available = getAvailableUsers(usersResp, membersResp);
assert.equal(available.length, 2);
});
it('falls back to {data:[]} response (backward compat)', () => {
const usersResp = { data: [
{ id: 'u1', username: 'alice', is_active: true },
]};
const membersResp = { data: [] };
const available = getAvailableUsers(usersResp, membersResp);
assert.equal(available.length, 1);
});
it('excludes existing team members', () => {
const usersResp = { users: [
{ id: 'u1', username: 'alice', is_active: true },
{ id: 'u2', username: 'bob', is_active: true },
]};
const membersResp = { data: [{ user_id: 'u1', role: 'admin' }] };
const available = getAvailableUsers(usersResp, membersResp);
assert.equal(available.length, 1);
assert.equal(available[0].id, 'u2');
});
it('excludes inactive users', () => {
const usersResp = { users: [
{ id: 'u1', username: 'alice', is_active: true },
{ id: 'u2', username: 'disabled', is_active: false },
]};
const membersResp = { data: [] };
const available = getAvailableUsers(usersResp, membersResp);
assert.equal(available.length, 1);
assert.equal(available[0].id, 'u1');
});
it('returns empty for empty users response', () => {
const available = getAvailableUsers({ users: [] }, { data: [] });
assert.equal(available.length, 0);
});
it('handles completely missing response fields', () => {
const available = getAvailableUsers({}, {});
assert.equal(available.length, 0);
});
});
// ── Admin settings field mapping ─────────────
describe('Admin settings field mapping', () => {
// Maps what the frontend sends to what the backend expects
const settingsFieldMap = {
'adminRegToggle': 'allow_registration',
'adminRegDefaultState': 'default_user_active',
'adminUserProvidersToggle': 'allow_user_byok',
'adminUserPresetsToggle': 'allow_user_personas',
'adminBannerEnabled': 'banner',
};
const appSrc = fs.readFileSync(path.join(SRC, 'app.js'), 'utf-8');
const indexSrc = fs.readFileSync(path.join(SRC, '..', 'index.html'), 'utf-8');
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(`app.js writes setting "${settingKey}"`, () => {
assert.ok(appSrc.includes(settingKey),
`MISSING: "${settingKey}" in handleSaveAdminSettings`);
});
}
});
// ── HTML element existence checks ────────────
describe('Critical HTML elements exist', () => {
const indexSrc = fs.readFileSync(path.join(SRC, '..', 'index.html'), 'utf-8');
const requiredElements = [
'adminMemberUser', // Team member user dropdown
'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
];
for (const id of requiredElements) {
it(`#${id} exists in index.html`, () => {
assert.ok(indexSrc.includes(`id="${id}"`),
`MISSING element: #${id} — UI feature will break`);
});
}
});