const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const fontLibrary = require('../src/web/lib/media/font-library'); const registerFontRoutes = require('../src/web/routes/settings/fonts'); const renderFontsPage = require('../src/web/routes/settings/fonts/list'); function createHandlers() { const handlers = {}; const app = { get(path, ...routeHandlers) { handlers[`GET ${path}`] = routeHandlers; }, post(path, ...routeHandlers) { handlers[`POST ${path}`] = routeHandlers; } }; return { app, handlers }; } test('fonts route filters and paginates managed fonts', async () => { const originalLoadFontLibrary = fontLibrary.loadFontLibrary; const { app, handlers } = createHandlers(); fontLibrary.loadFontLibrary = function () { return { fonts: [ { id: 'alpha', family: 'Alpha Sans', fileName: 'alpha.woff2', format: 'woff2', enabled: true }, { id: 'beta', family: 'Beta Sans', fileName: 'beta.woff2', format: 'woff2', enabled: false }, { id: 'gamma', family: 'Gamma Sans', fileName: 'gamma.woff2', format: 'woff2', enabled: true } ], stylesheetHref: '/media/fonts/fonts.css' }; }; try { registerFontRoutes(app, { pool: { async query() { return [[{ match_count: 0 }]]; } }, common: { getSearchQuery() { return 'beta'; } }, pages: { renderFontsPage(data) { return JSON.stringify(data); } }, upload: { single() { return function (_req, _res, next) { next(); }; } }, uploadDir: 'e:/Projects Git/pulse-signage/media', backgroundTaskQueue: null, setAuthMessageCookie() {} }); const response = { statusCode: 200, body: null, send(value) { this.body = value; return this; } }; await handlers['GET /settings/fonts'][1]({ query: { page: '2' }, currentUser: { id: 1, permissions: ['fonts.read'] } }, response, function () {}); const rendered = JSON.parse(response.body); assert.equal(rendered.fonts.length, 1); assert.equal(rendered.fonts[0].id, 'beta'); assert.equal(rendered.pagination.currentPage, 1); assert.equal(rendered.pagination.totalItems, 1); } finally { fontLibrary.loadFontLibrary = originalLoadFontLibrary; } }); test('fonts route sorts by format and status', async () => { const originalLoadFontLibrary = fontLibrary.loadFontLibrary; const { app, handlers } = createHandlers(); fontLibrary.loadFontLibrary = function () { return { fonts: [ { id: 'alpha', family: 'Alpha Sans', fileName: 'alpha.woff2', format: 'woff2', enabled: true }, { id: 'beta', family: 'Beta Sans', fileName: 'beta.otf', format: 'opentype', enabled: false }, { id: 'gamma', family: 'Gamma Sans', fileName: 'gamma.ttf', format: 'truetype', enabled: true } ], stylesheetHref: '/media/fonts/fonts.css' }; }; try { registerFontRoutes(app, { pool: { async query() { return [[{ match_count: 0 }]]; } }, common: { getSearchQuery() { return ''; }, getSortQuery(req) { return req.query.sort; }, getSortDirectionQuery(req) { return req.query.direction; } }, pages: { renderFontsPage(data) { return JSON.stringify(data); } }, upload: { single() { return function (_req, _res, next) { next(); }; } }, uploadDir: 'e:/Projects Git/pulse-signage/media', backgroundTaskQueue: null, setAuthMessageCookie() {} }); const response = { statusCode: 200, body: null, send(value) { this.body = value; return this; } }; await handlers['GET /settings/fonts'][1]({ query: { sort: 'format', direction: 'asc' }, currentUser: { id: 1, permissions: ['fonts.read'] } }, response, function () {}); let rendered = JSON.parse(response.body); assert.deepEqual(rendered.fonts.map(function (font) { return font.id; }), ['beta', 'gamma', 'alpha']); await handlers['GET /settings/fonts'][1]({ query: { sort: 'status', direction: 'asc' }, currentUser: { id: 1, permissions: ['fonts.read'] } }, response, function () {}); rendered = JSON.parse(response.body); assert.deepEqual(rendered.fonts.map(function (font) { return font.id; }), ['beta', 'alpha', 'gamma']); } finally { fontLibrary.loadFontLibrary = originalLoadFontLibrary; } }); test('template default font references mark fonts in use and block deletion', async () => { const originals = { loadFontLibrary: fontLibrary.loadFontLibrary, deleteFontFile: fontLibrary.deleteFontFile }; const { app, handlers } = createHandlers(); const font = { id: 'alpha.woff2', family: 'Alpha Sans', name: 'Alpha Sans', fileName: 'alpha.woff2', format: 'woff2', enabled: true }; let deleteCount = 0; fontLibrary.loadFontLibrary = function () { return { fonts: [font], stylesheetHref: '' }; }; fontLibrary.deleteFontFile = async function () { deleteCount += 1; return font; }; registerFontRoutes(app, { pool: { async query(sql) { if (sql.includes('FROM c_content_references') && sql.includes('COUNT(DISTINCT')) return [[{ owner_count: 1 }]]; if (sql.includes('FROM c_content_references')) return [[{ reference_value: 'alpha sans' }]]; return [[{ match_count: 0 }]]; } }, pages: { renderFontsPage(data) { return JSON.stringify(data); } }, upload: { single() { return function (_req, _res, next) { next(); }; } }, uploadDir: 'unused', backgroundTaskQueue: null }); try { const response = { body: '', send(value) { this.body = value; return this; }, redirect(value) { this.location = value; return this; } }; const req = { query: {}, currentUser: { permissions: ['fonts.read', 'fonts.delete'] }, params: { id: font.id } }; await handlers['GET /settings/fonts'][1](req, response, error => { throw error; }); assert.equal(JSON.parse(response.body).fonts[0].inUse, true); await handlers['POST /settings/fonts/:id/delete'].at(-1)(req, response, error => { throw error; }); assert.match(decodeURIComponent(response.location), /font is still used by slides or template defaults/); assert.equal(deleteCount, 0); } finally { fontLibrary.loadFontLibrary = originals.loadFontLibrary; fontLibrary.deleteFontFile = originals.deleteFontFile; } }); test('fonts page renders the upload card above the table card', () => { const html = renderFontsPage({ fonts: [ { id: 'alpha', family: 'Alpha Sans', fileName: 'alpha.woff2', format: 'woff2', enabled: true, inUse: false } ], pagination: { hasMultiplePages: true, startItem: 1, endItem: 1, totalItems: 1, itemLabel: 'fonts', ariaLabel: 'Font pages', hasPrevious: false, hasNext: true, previousUrl: '', nextUrl: '?page=2', pages: [{ number: 1, active: true, url: '' }] }, stylesheetHref: '/media/fonts/fonts.css?v=1' }, '', { id: 1 }); assert.ok(html.indexOf('Upload font') < html.indexOf('Managed fonts')); assert.match(html, /data-table-search-container/); assert.match(html, /table-pagination/); assert.match(html, /data-async-command/); assert.match(html, /data-font-toggle-row/); assert.match(html, /data-font-status-badge/); assert.match(html, //); assert.match(html, /Preview<\/th>/); assert.match(html, /font-family: 'Alpha Sans';/); assert.match(html, /data-table-sort-key="family"/); assert.match(html, /data-table-sort-key="file"/); assert.match(html, /data-table-sort-key="format"/); assert.match(html, /data-table-sort-key="status"/); }); test('font upload, status changes, and deletion are audited', async () => { const originals = { loadFontLibrary: fontLibrary.loadFontLibrary, addFontFile: fontLibrary.addFontFile, setFontEnabled: fontLibrary.setFontEnabled, deleteFontFile: fontLibrary.deleteFontFile }; const { app, handlers } = createHandlers(); const events = []; const font = { id: 'alpha.woff2', family: 'Alpha Sans', name: 'Alpha Sans', fileName: 'alpha.woff2', format: 'woff2', enabled: true }; fontLibrary.loadFontLibrary = function () { return { fonts: [font], stylesheetHref: '' }; }; fontLibrary.addFontFile = async function () { return { font: font, fonts: [font] }; }; fontLibrary.setFontEnabled = async function (_dir, _id, enabled) { return Object.assign({}, font, { enabled: enabled }); }; fontLibrary.deleteFontFile = async function () { return font; }; try { registerFontRoutes(app, { pool: { async query() { return [[{ match_count: 0 }]]; } }, pages: { renderFontsPage() { return ''; } }, upload: { single() { return function (_req, _res, next) { next(); }; } }, uploadDir: 'unused', backgroundTaskQueue: null, recordRequestAuditEvent(_pool, _req, event) { events.push(event); } }); const response = { redirect() { return this; } }; const next = function (error) { if (error) throw error; }; const currentUser = { id: 12, permissionKeys: ['fonts.create', 'fonts.update', 'fonts.delete'] }; await handlers['POST /settings/fonts'].at(-1)( { currentUser: currentUser, body: { font_family: 'Alpha Sans' }, file: { path: 'unused' } }, response, next ); await handlers['POST /settings/fonts/:id/toggle'].at(-1)( { currentUser: currentUser, params: { id: font.id }, body: { enabled: '0' } }, response, next ); await handlers['POST /settings/fonts/:id/delete'].at(-1)( { currentUser: currentUser, params: { id: font.id } }, response, next ); assert.deepEqual(events.map(function (event) { return event.eventType; }), ['font.uploaded', 'font.disabled', 'font.deleted']); assert.ok(events.every(function (event) { return event.category === 'media-assets' && event.actorUserId === 12; })); } finally { fontLibrary.loadFontLibrary = originals.loadFontLibrary; fontLibrary.addFontFile = originals.addFontFile; fontLibrary.setFontEnabled = originals.setFontEnabled; fontLibrary.deleteFontFile = originals.deleteFontFile; } });