'use strict'; // Issue #15: instance-level default branding. Tests the resolver order // (workspace row -> custom-domain -> platform default -> hardcoded) and the // platform-admin GET/PUT /api/admin/branding endpoints. const test = require('node:test'); const assert = require('node:assert/strict'); const Database = require('better-sqlite3'); process.env.JWT_SECRET = 'test-secret-branding'; const db = new Database(':memory:'); db.pragma('foreign_keys = ON'); db.exec(` CREATE TABLE users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, name TEXT DEFAULT '', password_hash TEXT, auth_provider TEXT NOT NULL DEFAULT 'local', avatar_url TEXT, role TEXT NOT NULL DEFAULT 'user', plan_id TEXT DEFAULT 'free', email_alerts INTEGER DEFAULT 1, must_change_password INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE white_labels ( id TEXT PRIMARY KEY, user_id TEXT, brand_name TEXT NOT NULL DEFAULT 'ScreenTinker', logo_url TEXT, favicon_url TEXT, primary_color TEXT DEFAULT '#3B82F6', secondary_color TEXT DEFAULT '#1E293B', bg_color TEXT DEFAULT '#111827', custom_domain TEXT, custom_css TEXT, hide_branding INTEGER DEFAULT 0, workspace_id TEXT, created_at INTEGER DEFAULT (strftime('%s','now')), updated_at INTEGER DEFAULT (strftime('%s','now')) ); CREATE TABLE activity_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT, device_id TEXT, action TEXT NOT NULL, details TEXT, ip_address TEXT, workspace_id TEXT, created_at INTEGER DEFAULT (strftime('%s','now')) ); `); const dbModulePath = require.resolve('../db/database'); require.cache[dbModulePath] = { id: dbModulePath, filename: dbModulePath, loaded: true, exports: { db, pruneTelemetry() {}, pruneScreenshots() {} } }; const express = require('express'); const { generateToken, requireAuth } = require('../middleware/auth'); const { resolveBranding } = require('../lib/branding'); const adminRouter = require('../routes/admin'); db.prepare("INSERT INTO users (id, email, role, password_hash) VALUES ('u-admin','admin@test.local','platform_admin','x')").run(); db.prepare("INSERT INTO users (id, email, role, password_hash) VALUES ('u-reg','reg@test.local','user','x')").run(); const tokens = { admin: generateToken({ id: 'u-admin', email: 'admin@test.local', role: 'platform_admin' }, null), reg: generateToken({ id: 'u-reg', email: 'reg@test.local', role: 'user' }, null), }; const app = express(); app.use(express.json()); app.use('/api/admin', requireAuth, adminRouter); const server = app.listen(0); let base; test.before(async () => { await new Promise(r => server.listening ? r() : server.once('listening', r)); base = `http://127.0.0.1:${server.address().port}`; }); test.after(() => { server.close(); db.close(); }); const wl = (id, fields) => db.prepare( `INSERT INTO white_labels (id, user_id, brand_name, custom_domain, workspace_id) VALUES (?, 'u-admin', ?, ?, ?)` ).run(id, fields.brand_name, fields.custom_domain || null, fields.workspace_id || null); test('resolver order: workspace row > domain > platform default > hardcoded', () => { db.prepare('DELETE FROM white_labels').run(); wl('w1', { brand_name: 'WS One', workspace_id: 'ws1' }); // a custom-domain row belongs to a workspace (realistic); also seed a legacy // null-workspace row to prove the fixed-id sentinel ignores it. wl('dom', { brand_name: 'Domain Brand', custom_domain: 'cust.example', workspace_id: 'ws-dom' }); wl('legacy', { brand_name: 'Legacy Null WS', workspace_id: null }); wl('platform-default', { brand_name: 'Global Default', workspace_id: null }); // fixed-id platform default assert.equal(resolveBranding(db, { workspaceId: 'ws1' }).brand_name, 'WS One', 'workspace row wins'); assert.equal(resolveBranding(db, { domain: 'cust.example' }).brand_name, 'Domain Brand', 'domain match'); assert.equal(resolveBranding(db, { workspaceId: 'ws-none' }).brand_name, 'Global Default', 'unbranded workspace inherits platform default (not the legacy null-ws row)'); assert.equal(resolveBranding(db, {}).brand_name, 'Global Default', 'no context -> platform default'); db.prepare("DELETE FROM white_labels WHERE id='platform-default'").run(); assert.equal(resolveBranding(db, {}).brand_name, 'ScreenTinker', 'no platform default -> hardcoded (legacy null-ws row not used)'); }); test('GET /api/admin/branding returns hardcoded default when none set', async () => { db.prepare('DELETE FROM white_labels').run(); const res = await fetch(base + '/api/admin/branding', { headers: { Authorization: `Bearer ${tokens.admin}` } }); assert.equal(res.status, 200); assert.equal((await res.json()).brand_name, 'ScreenTinker'); }); test('PUT /api/admin/branding creates then updates the single platform-default row', async () => { const put = (body) => fetch(base + '/api/admin/branding', { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${tokens.admin}` }, body: JSON.stringify(body), }); let res = await put({ brand_name: 'Acme Signage', primary_color: '#10b981', hide_branding: true }); assert.equal(res.status, 200); // exactly one platform-default row, with workspace_id NULL let rows = db.prepare("SELECT * FROM white_labels WHERE id = 'platform-default'").all(); assert.equal(rows.length, 1); assert.equal(rows[0].brand_name, 'Acme Signage'); assert.equal(rows[0].primary_color, '#10b981'); assert.equal(rows[0].hide_branding, 1); // second PUT updates the same row (no second row) res = await put({ brand_name: 'Acme Displays' }); assert.equal(res.status, 200); rows = db.prepare("SELECT * FROM white_labels WHERE id = 'platform-default'").all(); assert.equal(rows.length, 1, 'still a single platform-default row'); assert.equal(rows[0].brand_name, 'Acme Displays'); // and now an unbranded workspace resolves to it assert.equal(resolveBranding(db, { workspaceId: 'whatever' }).brand_name, 'Acme Displays'); }); test('branding endpoints are platform-admin only (403 for a regular user)', async () => { const get = await fetch(base + '/api/admin/branding', { headers: { Authorization: `Bearer ${tokens.reg}` } }); assert.equal(get.status, 403); const put = await fetch(base + '/api/admin/branding', { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${tokens.reg}` }, body: JSON.stringify({ brand_name: 'Hacker' }), }); assert.equal(put.status, 403); });