const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const registerScreenCommandRoutes = require('../src/web/routes/admin/client-commands'); function createHandlers() { const handlers = {}; const app = { post(path, ...routeHandlers) { handlers[path] = routeHandlers; } }; return { app, handlers }; } test('move client rebinding redirects the live player to the target screen', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql, params) { calls.push({ kind: 'query', sql, params }); if (sql.includes('SELECT slug FROM d_screens ORDER BY slug ASC')) { return [[{ slug: 'source-screen' }, { slug: 'target-screen' }]]; } if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params && params[0] === 'source-screen') { return [[{ id: 12, name: 'Source Screen', slug: 'source-screen' }]]; } if (sql.includes('SELECT d.client_name, s.slug AS current_screen_slug')) { return [[{ client_name: 'Lobby Client', current_screen_slug: 'source-screen' }]]; } if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params && params[0] === 'target-screen') { return [[{ id: 27, name: 'Target Screen', slug: 'target-screen' }]]; } if (sql.includes('INSERT INTO d_onboarding_devices')) { return [{ affectedRows: 1 }]; } return [[]]; } }, common: {}, forwardPlayerCommand(slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommand', slug, payload, connectionId }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload, connectionId }); return { ok: true }; }, getScreenConnections: async (slug) => { if (slug === 'source-screen') { return { connections: [ { id: 'conn-1', clientId: 'conn-1', deviceId: 'device-123', playerPublicBaseUrl: 'http://remote-player.example' } ] }; } return { connections: [] }; }, isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => { 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(value) { this.body = value; return this; } }; await routeHandlers[1]({ params: { slug: 'source-screen' }, body: { command: 'moveclient', deviceId: 'device-123', clientName: 'Lobby Client', targetScreenSlug: 'target-screen', connectionId: 'conn-1', playerBaseUrl: 'http://remote-player.example' }, query: {} }, response, () => {}); assert.equal(response.statusCode, 200); assert.equal(response.body.ok, true); assert.equal(response.body.targetScreenSlug, 'target-screen'); assert.equal(response.body.playerUrl, 'http://remote-player.example/screen/target-screen'); assert.equal(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl' && entry.baseUrl === 'http://remote-player.example' && entry.payload && entry.payload.command === 'redirect'), true); }); test('screen control commands can target all screens', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql) { calls.push({ kind: 'query', sql }); if (String(sql || '').includes('SELECT id, name, slug FROM d_screens WHERE slug IS NOT NULL ORDER BY slug ASC')) { return [[ { id: 1, name: 'Alpha', slug: 'alpha' }, { id: 2, name: 'Beta', slug: 'beta' } ]]; } return [[]]; } }, common: {}, forwardPlayerCommand(slug, payload) { calls.push({ kind: 'forwardPlayerCommand', slug, payload }); return { ok: true }; }, getScreenConnections: async () => ({ connections: [] }), isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => { 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(value) { this.body = value; return this; } }; await routeHandlers[1]({ params: { slug: '__all__' }, body: { command: 'pause', paused: 'true' }, query: {} }, 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.deepEqual(calls.filter((entry) => entry.kind === 'forwardPlayerCommand').map((entry) => entry.slug), ['alpha', 'beta']); assert.equal(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true); }); test('screen control commands use the live connection player url when available', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql, params) { calls.push({ kind: 'query', sql, params }); if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params && params[0] === 'source-screen') { return [[{ id: 12, name: 'Source Screen', slug: 'source-screen' }]]; } return [[]]; } }, common: {}, forwardPlayerCommand(slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommand', slug, payload, connectionId }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload, connectionId }); return { ok: true }; }, getScreenConnections: async (slug) => { if (slug === 'source-screen') { return { connections: [ { id: 'conn-1', clientId: 'conn-1', deviceId: 'device-123', playerPublicBaseUrl: 'http://local-player.example' } ] }; } return { connections: [] }; }, isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => { 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(value) { this.body = value; return this; } }; await routeHandlers[1]({ params: { slug: 'source-screen' }, body: { command: 'pause', connectionId: 'conn-1' }, query: {} }, response, () => {}); assert.equal(response.statusCode, 200); assert.equal(response.body.ok, true); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl' && entry.baseUrl === 'http://local-player.example'), true); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand'), false); }); test('screen control commands fan out to every live player for the selected screen', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql, params) { calls.push({ kind: 'query', sql, params }); if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params && params[0] === 'source-screen') { return [[{ id: 12, name: 'Source Screen', slug: 'source-screen' }]]; } return [[]]; } }, common: {}, forwardPlayerCommand(slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommand', slug, payload, connectionId }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload, connectionId }); return { ok: true }; }, getScreenConnections: async (slug) => { if (slug === 'source-screen') { return { connections: [ { id: 'conn-1', clientId: 'conn-1', deviceId: 'device-123', playerPublicBaseUrl: 'http://local-player-a.example' }, { id: 'conn-2', clientId: 'conn-2', deviceId: 'device-456', playerPublicBaseUrl: 'http://local-player-b.example' } ] }; } return { connections: [] }; }, isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => { 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(value) { this.body = value; return this; } }; await routeHandlers[1]({ params: { slug: 'source-screen' }, body: { command: 'pause' }, query: {} }, response, () => {}); assert.equal(response.statusCode, 200); assert.equal(response.body.ok, true); assert.deepEqual( calls.filter((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl').map((entry) => entry.baseUrl), ['http://local-player-a.example', 'http://local-player-b.example'] ); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand'), false); }); test('all screens commands fan out across the live player for each screen', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql) { calls.push({ kind: 'query', sql }); if (String(sql || '').includes('SELECT id, name, slug FROM d_screens WHERE slug IS NOT NULL ORDER BY slug ASC')) { return [[ { id: 1, name: 'Alpha', slug: 'alpha' }, { id: 2, name: 'Beta', slug: 'beta' } ]]; } return [[]]; } }, common: {}, forwardPlayerCommand(slug, payload) { calls.push({ kind: 'forwardPlayerCommand', slug, payload }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, slug, payload) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload }); return { ok: true }; }, getScreenConnections: async (slug) => { if (slug === 'alpha') { return { connections: [ { id: 'alpha-1', clientId: 'alpha-1', deviceId: 'alpha-device', playerPublicBaseUrl: 'http://player-a.example' } ] }; } if (slug === 'beta') { return { connections: [ { id: 'beta-1', clientId: 'beta-1', deviceId: 'beta-device', playerPublicBaseUrl: 'http://player-b.example' } ] }; } return { connections: [] }; }, isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => { 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(value) { this.body = value; return this; } }; await routeHandlers[1]({ params: { slug: '__all__' }, body: { command: 'pause' }, query: {} }, 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(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true); assert.deepEqual( calls.filter((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl').map((entry) => entry.baseUrl), ['http://player-a.example', 'http://player-b.example'] ); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand'), false); });