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: 'Stored Client Name', 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('SELECT identifier, public_base_url FROM d_players WHERE public_base_url = ?')) { return [[{ identifier: 'player-a', public_base_url: 'http://remote-player.example' }]]; } if (sql.includes('INSERT INTO d_onboarding_devices')) { return [{ affectedRows: 1 }]; } return [[]]; } }, common: { fetchPlayerRegistrations: async () => ([ { 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(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' } ] }; } if (slug === 'target-screen') { return { connections: [ { id: 'target-conn-1', clientId: 'target-conn-1', deviceId: 'target-device-1', playerPublicBaseUrl: 'https://remote-target.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', clientId: 'tab-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.playerUrl, 'http://remote-player.example/screen/target-screen'); assert.equal(calls.some((entry) => entry.kind === 'query' && entry.sql.includes('SET screen_id = ?, modified_at = CURRENT_TIMESTAMP') && entry.params[0] === 27 && entry.params[1] === 'device-123'), false); assert.equal(calls.some((entry) => entry.kind === 'query' && entry.sql.includes('SET client_name = ?, screen_id = ?, modified_at = CURRENT_TIMESTAMP') && entry.params[0] === 'Lobby Client' && entry.params[1] === 27 && entry.params[2] === 'tab-123'), true); assert.equal(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand' && entry.slug === 'source-screen' && entry.payload && entry.payload.command === 'redirect'), false); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl' && entry.baseUrl === 'http://remote-player.example'), true); }); test('move client requires a registered player identity', async () => { const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql) { if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?')) { return [[{ id: 12, name: 'Source Screen', slug: 'source-screen' }]]; } return [[]]; } }, common: { fetchPlayerRegistrations: async () => [] }, forwardPlayerCommand: async () => ({ ok: true }), forwardPlayerCommandToBaseUrl: async () => ({ ok: true }), getScreenConnections: async () => ({ connections: [] }), isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => {}, requirePermission() { return function (_req, _res, next) { next(); }; } }); const response = { statusCode: 200, body: null, status(code) { this.statusCode = code; return this; }, json(value) { this.body = value; return this; } }; await handlers['/clients/:slug/commands'][1]({ params: { slug: 'source-screen' }, body: { command: 'moveclient', deviceId: 'unregistered-device', clientName: 'Lobby Client', targetScreenSlug: 'target-screen', playerBaseUrl: 'http://unregistered-player.example' }, query: {} }, response, () => {}); assert.equal(response.statusCode, 400); assert.equal(response.body.error, 'Registered player identity is required'); }); test('move client routes a remote connection through its bridge player identity', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql, params) { if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && 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: 'Remote Client', current_screen_slug: 'source-screen' }]]; } if (sql.includes('SELECT id, name, slug FROM d_screens WHERE slug = ?') && params[0] === 'target-screen') { return [[{ id: 27, name: 'Target Screen', slug: 'target-screen' }]]; } return [[]]; } }, common: { fetchPlayerRegistrations: async () => [] }, forwardPlayerCommand() { throw new Error('direct player fallback should not be used'); }, forwardPlayerCommandToBaseUrl() { throw new Error('public URL routing should not be used'); }, forwardPlayerCommandToDevice(deviceId, payload) { calls.push({ deviceId, payload }); return { ok: true }; }, getScreenConnections: async () => ({ connections: [{ id: 'connection-1', clientId: 'connection-1', deviceId: 'browser-tab-1', playerDeviceId: 'remote-player-1', playerPublicBaseUrl: null }] }), isClientNameAvailable: async () => true, withClientNameReservation: async (_pool, _name, callback) => callback(), broadcastDashboardState: async () => {}, requirePermission() { return function (_req, _res, next) { next(); }; } }); const response = { statusCode: 200, body: null, status(code) { this.statusCode = code; return this; }, json(value) { this.body = value; return this; } }; await handlers['/clients/:slug/commands'][1]({ params: { slug: 'source-screen' }, body: { command: 'moveclient', clientId: 'browser-tab-1', clientName: 'Remote Client', targetScreenSlug: 'target-screen', connectionId: 'connection-1' }, query: {} }, response, (error) => { throw error; }); assert.equal(response.statusCode, 200); assert.equal(response.body.ok, true); assert.deepEqual(calls, [{ deviceId: 'remote-player-1', payload: { screenSlug: 'source-screen', command: 'redirect', url: '/screen/target-screen', moveToken: calls[0] && calls[0].payload.moveToken, connectionId: 'connection-1' } }]); }); 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('FROM d_screens') && String(sql || '').includes('ORDER BY slug ASC')) { return [[ { slug: 'alpha' }, { slug: 'beta' } ]]; } return [[]]; } }, common: { fetchPlayerRegistrations: async () => ([ { 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(slug, payload) { calls.push({ kind: 'forwardPlayerCommand', slug, payload }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload, connectionId }); 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', 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.equal(response.body.targetPlayerCount, 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); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand'), false); }); test('screen control commands use the bridge for live connections', async () => { const calls = []; const { app, handlers } = createHandlers(); registerScreenCommandRoutes(app, { pool: { async query(sql, params) { calls.push({ kind: 'query', sql, params }); if (String(sql || '').includes('SELECT s.slug, s.player_id, p.public_base_url, p.internal_base_url')) { return [[ { slug: 'source-screen', player_id: 'player-a', public_base_url: 'http://player-a.example', internal_base_url: 'http://player-a.internal' } ]]; } 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: { fetchPlayerRegistrations: async () => ([ { 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(slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommand', 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 === 'forwardPlayerCommand' && entry.slug === 'source-screen'), true); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl' && entry.baseUrl === 'http://local-player.example'), false); }); test('screen control commands fan out through the bridge 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 (String(sql || '').includes('SELECT s.slug, s.player_id, p.public_base_url, p.internal_base_url')) { return [[{ slug: 'source-screen', player_id: 'player-a', public_base_url: 'http://player-a.example', internal_base_url: 'http://player-a.internal' }]]; } 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: { fetchPlayerRegistrations: async () => ([ { 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(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' }, { 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.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand' && entry.slug === 'source-screen'), false); assert.deepEqual( calls.filter((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl').map((entry) => entry.baseUrl), ['http://local-player.example', 'http://local-player-b.example'] ); }); test('all screens commands fan out through the bridge 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('FROM d_screens') && String(sql || '').includes('ORDER BY slug ASC')) { return [[ { slug: 'alpha' }, { slug: 'beta' } ]]; } return [[]]; } }, common: { fetchPlayerRegistrations: async () => ([ { 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(slug, payload) { calls.push({ kind: 'forwardPlayerCommand', slug, payload }); return { ok: true }; }, forwardPlayerCommandToBaseUrl(baseUrl, slug, payload, connectionId) { calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload, connectionId }); 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(response.body.targetPlayerCount, 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.internal', 'http://player-b.internal'] ); assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand'), false); });