const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const registerScreensRoutes = require('../src/web/routes/signage/screens/routes'); const { renderScreenEditPage } = require('../src/web/pages'); test('screen edit page includes shared launcher downloads and base player url', async () => { const handlers = {}; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; let renderedArgs = null; const pages = { renderScreenEditPage(screen, data, message, currentUser) { renderedArgs = { screen, data, message, currentUser }; return 'ok'; } }; registerScreensRoutes(app, { pool: { async query() { return [[]]; } }, common: { async fetchScreenById() { return { id: 7, name: 'Demo Conference', slug: 'demo-conference', playlist_id: null }; }, async fetchScreenEditData() { return { playlists: [] }; }, async fetchPlayerRegistrations() { return [ { identifier: 'player-alpha', public_base_url: 'http://alpha.example', last_seen_at: new Date().toISOString() }, { identifier: 'player-beta', public_base_url: 'http://beta.example/', last_seen_at: new Date().toISOString() } ]; }, async fetchPlayerPublicBaseUrl() { return 'http://player.example'; } }, pages, buildDashboardState: async () => ({ screens: [] }), getScreenDeleteBlockMessage: async () => '', getScreenConnections: async () => [], playerPublicBaseUrl: 'http://player.example', requirePermission() { return function (_req, _res, next) { next(); }; } }); const handler = handlers['/screens/:id/edit']; assert.equal(Array.isArray(handler), true); const res = { send(value) { this.body = value; }, status(code) { this.statusCode = code; return this; } }; await handler[1]({ params: { id: '7' }, query: {}, currentUser: { id: 1 } }, res, () => {}); assert.equal(res.body, 'ok'); assert.deepEqual(renderedArgs.screen.player_urls, [ { identifier: 'player-alpha', public_base_url: 'http://alpha.example', player_url: 'http://alpha.example/screen/demo-conference' }, { identifier: 'player-beta', public_base_url: 'http://beta.example', player_url: 'http://beta.example/screen/demo-conference' } ]); assert.deepEqual(renderedArgs.screen.launcher_downloads, { windows: '/downloads/kiosk/pulse-signage-kiosk.bat', linux: '/downloads/kiosk/pulse-signage-kiosk.sh' }); }); test('screen edit query route includes player urls for every registration', async () => { const handlers = {}; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; let renderedArgs = null; const pages = { renderScreenEditPage(screen, data, message, currentUser) { renderedArgs = { screen, data, message, currentUser }; return 'ok'; } }; registerScreensRoutes(app, { pool: { async query() { return [[]]; } }, common: { async fetchScreenById() { return { id: 7, name: 'Demo Conference', slug: 'demo-conference', playlist_id: null }; }, async fetchScreenEditData() { return { playlists: [] }; }, async fetchPlayerRegistrations() { return [ { identifier: 'player-alpha', public_base_url: 'http://alpha.example', last_seen_at: new Date().toISOString() } ]; }, async fetchPlayerPublicBaseUrl() { return 'http://player.example'; } }, pages, buildDashboardState: async () => ({ screens: [] }), getScreenDeleteBlockMessage: async () => '', getScreenConnections: async () => [], playerPublicBaseUrl: 'http://player.example', requirePermission() { return function (_req, _res, next) { next(); }; } }); const handler = handlers['/screens']; assert.equal(Array.isArray(handler), true); const res = { send(value) { this.body = value; }, status(code) { this.statusCode = code; return this; } }; await handler[1]({ query: { edit: '7' }, currentUser: { id: 1 } }, res, () => {}); assert.equal(res.body, 'ok'); assert.deepEqual(renderedArgs.screen.player_urls, [ { identifier: 'player-alpha', public_base_url: 'http://alpha.example', player_url: 'http://alpha.example/screen/demo-conference' } ]); assert.deepEqual(renderedArgs.screen.launcher_downloads, { windows: '/downloads/kiosk/pulse-signage-kiosk.bat', linux: '/downloads/kiosk/pulse-signage-kiosk.sh' }); }); test('screen edit page hides stale player registrations', async () => { const handlers = {}; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; let renderedArgs = null; const now = Date.now(); const recentSeenAt = new Date(now - 10 * 1000).toISOString(); const staleSeenAt = new Date(now - 5 * 60 * 1000).toISOString(); const pages = { renderScreenEditPage(screen, data, message, currentUser) { renderedArgs = { screen, data, message, currentUser }; return 'ok'; } }; registerScreensRoutes(app, { pool: { async query() { return [[]]; } }, common: { async fetchScreenById() { return { id: 7, name: 'Demo Conference', slug: 'demo-conference', playlist_id: null }; }, async fetchScreenEditData() { return { playlists: [] }; }, async fetchPlayerRegistrations() { return [ { identifier: 'player-stale', public_base_url: 'http://stale.example', last_seen_at: staleSeenAt }, { identifier: 'player-recent', public_base_url: 'http://recent.example', last_seen_at: recentSeenAt } ]; }, async fetchPlayerPublicBaseUrl() { return 'http://player.example'; } }, pages, buildDashboardState: async () => ({ screens: [] }), getScreenDeleteBlockMessage: async () => '', getScreenConnections: async () => [], formatDashboardDate(value) { return `formatted:${String(value)}`; }, playerPublicBaseUrl: 'http://player.example', requirePermission() { return function (_req, _res, next) { next(); }; } }); const handler = handlers['/screens/:id/edit']; assert.equal(Array.isArray(handler), true); const res = { send(value) { this.body = value; }, status(code) { this.statusCode = code; return this; } }; await handler[1]({ params: { id: '7' }, query: {}, currentUser: { id: 1 } }, res, () => {}); assert.equal(res.body, 'ok'); assert.deepEqual(renderedArgs.screen.player_urls.map(function (player) { return { identifier: player.identifier, public_base_url: player.public_base_url, player_url: player.player_url }; }), [ { identifier: 'player-recent', public_base_url: 'http://recent.example', player_url: 'http://recent.example/screen/demo-conference' } ]); }); test('screen edit page renders player urls as an adminlte table', async () => { const html = renderScreenEditPage( { id: 7, name: 'Demo Conference', slug: 'demo-conference', playlist_id: null, player_urls: [ { identifier: 'player-alpha', public_base_url: 'http://alpha.example', player_url: 'http://alpha.example/screen/demo-conference' } ] }, { playlists: [] }, '', { id: 1 } ); assert.match(html, /Player URLs/); assert.match(html, / { const handlers = {}; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; registerScreensRoutes(app, { pool: { async query() { return [[]]; } }, common: { async fetchScreenById() { return { id: 7, name: 'Demo Conference', slug: 'demo-conference', playlist_id: null }; }, async fetchScreenEditData() { return { playlists: [] }; }, async fetchScreenPlayerUrls() { return { 'demo-conference': 'http://player.example/screen/demo-conference' }; }, async fetchPlayerPublicBaseUrl() { return 'http://player.example'; } }, pages: { renderScreenEditPage() { return 'ok'; } }, buildDashboardState: async () => ({ screens: [] }), getScreenDeleteBlockMessage: async () => '', getScreenConnections: async () => [], playerPublicBaseUrl: 'http://player.example', requirePermission() { return function (_req, _res, next) { next(); }; } }); const windowsHandler = handlers['/downloads/kiosk/pulse-signage-kiosk.bat']; assert.equal(Array.isArray(windowsHandler), true); const res = { headers: {}, statusCode: 200, set(name, value) { this.headers[name.toLowerCase()] = value; }, attachment(filename) { this.headers['content-disposition'] = `attachment; filename="${filename}"`; }, type(value) { this.headers['content-type'] = value; }, send(value) { this.body = value; }, status(code) { this.statusCode = code; return this; } }; await windowsHandler[1]({ params: { id: '7' } }, res, () => {}); assert.equal(res.statusCode, 200); assert.match(res.headers['content-disposition'], /pulse-signage-kiosk\.bat/); assert.match(res.body, /TARGET_URL=http:\/\/player\.example/); assert.match(res.body, /--kiosk/); }); test('screen launcher downloads can target an explicit player url', async () => { const handlers = {}; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; registerScreensRoutes(app, { pool: { async query() { return [[]]; } }, common: { async fetchScreenById() { return { id: 7, name: 'Demo Conference', slug: 'demo-conference', playlist_id: null }; }, async fetchScreenEditData() { return { playlists: [] }; }, async fetchPlayerPublicBaseUrl() { return 'http://player.example'; } }, pages: { renderScreenEditPage() { return 'ok'; } }, buildDashboardState: async () => ({ screens: [] }), getScreenDeleteBlockMessage: async () => '', getScreenConnections: async () => [], playerPublicBaseUrl: 'http://player.example', requirePermission() { return function (_req, _res, next) { next(); }; } }); const windowsHandler = handlers['/downloads/kiosk/pulse-signage-kiosk.bat']; assert.equal(Array.isArray(windowsHandler), true); const res = { headers: {}, statusCode: 200, set(name, value) { this.headers[name.toLowerCase()] = value; }, attachment(filename) { this.headers['content-disposition'] = `attachment; filename="${filename}"`; }, type(value) { this.headers['content-type'] = value; }, send(value) { this.body = value; }, status(code) { this.statusCode = code; return this; } }; await windowsHandler[1]({ query: { playerUrl: 'http://player-b.example/screen/demo-conference' } }, res, () => {}); assert.equal(res.statusCode, 200); assert.match(res.body, /TARGET_URL=http:\/\/player-b\.example\/screen\/demo-conference/); assert.doesNotMatch(res.body, /TARGET_URL=http:\/\/player\.example/); });