const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const registerScreenCommandRoutes = require('../src/web/routes/admin/client-commands'); function createAppHarness() { const handlers = {}; const app = { post(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; return { app, handlers }; } test('client command route forwards all screens commands', async () => { const { app, handlers } = createAppHarness(); const calls = []; registerScreenCommandRoutes(app, { pool: { async query(sql) { calls.push({ kind: 'query', sql }); if (String(sql).includes('FROM d_screens') && String(sql).includes('ORDER BY slug ASC')) { return [[{ slug: 'alpha' }, { slug: 'beta' }]]; } return [[[]]]; } }, common: { async fetchPlayerRegistrations() { return [ { public_base_url: 'http://player-a.example', internal_base_url: 'http://player-a.internal' }, { public_base_url: 'http://player-b.example', internal_base_url: 'http://player-b.internal' } ]; } }, forwardPlayerCommand(screenSlug, commandPayload) { calls.push({ kind: 'forwardPlayerCommand', screenSlug, commandPayload }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, screenSlug, commandPayload) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, screenSlug, commandPayload }); return { ok: true }; }, getScreenConnections: async (slug) => { if (slug === 'alpha') { return { connections: [{ playerPublicBaseUrl: 'http://player-a.example' }] }; } if (slug === 'beta') { return { connections: [{ playerPublicBaseUrl: 'http://player-b.example' }] }; } return { connections: [] }; }, isClientNameAvailable() { return true; }, withClientNameReservation() { throw new Error('withClientNameReservation should not be called for __all__'); }, broadcastDashboardState() { calls.push({ kind: 'broadcastDashboardState' }); }, requirePermission() { return function (_req, _res, next) { next(); }; } }); const routeHandlers = handlers['/clients/:slug/commands']; assert.equal(Array.isArray(routeHandlers), true); const response = { statusCode: 200, body: null, status(code) { this.statusCode = code; return this; }, json(payload) { this.body = payload; return this; } }; await routeHandlers[1]({ params: { slug: '__all__' }, body: { command: 'reload' }, query: {}, currentUser: { id: 1 } }, response, () => {}); assert.equal(response.statusCode, 200); assert.equal(response.body.ok, true); assert.equal(response.body.allScreens, true); assert.equal(response.body.targetScreenCount, 2); assert.equal(response.body.sent, 2); assert.deepEqual(calls.filter((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl').map((entry) => entry.baseUrl), ['http://player-a.internal', 'http://player-b.internal']); assert.equal(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true); }); test('client command route forwards a single screen command', async () => { const { app, handlers } = createAppHarness(); const calls = []; registerScreenCommandRoutes(app, { pool: { async query(sql) { if (String(sql).includes('SELECT id, name, slug FROM d_screens WHERE slug = ?')) { return [[{ id: 7, name: 'Demo Lobby', slug: 'demo-lobby' }]]; } if (String(sql).includes('SELECT client_name')) { return [[null]]; } if (String(sql).includes('SELECT slug FROM d_screens ORDER BY slug ASC')) { return [[[]]]; } if (String(sql).includes('UPDATE d_onboarding_devices')) { return [{ affectedRows: 0 }]; } if (String(sql).includes('FROM d_onboarding_devices d')) { return [[[]]]; } return [[[]]]; } }, common: { async fetchPlayerRegistrations() { return []; } }, forwardPlayerCommand(screenSlug, commandPayload, connectionId) { calls.push({ kind: 'forward', screenSlug, commandPayload, connectionId }); return Promise.resolve({ ok: true, sent: 1 }); }, forwardPlayerCommandToBaseUrl() { throw new Error('forwardPlayerCommandToBaseUrl should not be called for this path'); }, getScreenConnections() { return Promise.resolve({ connections: [] }); }, isClientNameAvailable() { return true; }, withClientNameReservation() { throw new Error('withClientNameReservation should not be called for reload'); }, broadcastDashboardState() {}, requirePermission() { return function (_req, _res, next) { next(); }; } }); const routeHandlers = handlers['/clients/:slug/commands']; assert.equal(Array.isArray(routeHandlers), true); const response = { statusCode: 200, body: null, status(code) { this.statusCode = code; return this; }, json(payload) { this.body = payload; return this; } }; await routeHandlers[1]({ params: { slug: 'demo-lobby' }, body: { command: 'reload' }, query: {}, currentUser: { id: 1 } }, response, () => {}); assert.equal(response.statusCode, 200); assert.equal(calls.length, 1); assert.equal(calls[0].kind, 'forward'); assert.equal(calls[0].screenSlug, 'demo-lobby'); assert.deepEqual(calls[0].commandPayload, { command: 'reload' }); assert.equal(calls[0].connectionId, undefined); assert.equal(response.body.ok, true); }); test('client command route uses the bridge device route without a public player URL', async () => { const { app, handlers } = createAppHarness(); const calls = []; registerScreenCommandRoutes(app, { pool: { async query(sql) { if (String(sql).includes('SELECT id, name, slug FROM d_screens WHERE slug = ?')) { return [[{ id: 1, name: 'Lobby', slug: 'lobby' }]]; } return [[]]; } }, common: { async fetchPlayerRegistrations() { return []; } }, forwardPlayerCommand() { throw new Error('direct player fallback should not be used'); }, forwardPlayerCommandToBaseUrl() { throw new Error('public URL fallback should not be used'); }, forwardPlayerCommandToDevice(deviceId, payload) { calls.push({ deviceId, payload }); return { ok: true }; }, getScreenConnections: async () => ({ connections: [{ id: 'connection-1', deviceId: 'browser-tab-1', playerDeviceId: 'remote-player-1', playerPublicBaseUrl: null }] }), isClientNameAvailable() { return true; }, withClientNameReservation() {}, broadcastDashboardState() {}, requirePermission() { return function (_req, _res, next) { next(); }; } }); const response = { statusCode: 200, body: null, status(code) { this.statusCode = code; return this; }, json(payload) { this.body = payload; return this; } }; await handlers['/clients/:slug/commands'][1]({ params: { slug: 'lobby' }, body: { command: 'pause', connectionId: 'connection-1' }, query: {} }, response, () => {}); assert.equal(response.statusCode, 200); assert.deepEqual(calls, [{ deviceId: 'remote-player-1', payload: { screenSlug: 'lobby', command: 'pause', connectionId: 'connection-1' } }]); });