Files
pulse-signage/test/client-commands-route.test.js
T
lzstealth ed58da4416
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s
Release v2.10.4
2026-08-29 12:12:39 +01:00

251 lines
7.4 KiB
JavaScript

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' }
}]);
});