Files
pulse-signage/test/player-onboarding-bridge.test.js
2026-08-08 13:02:31 +01:00

221 lines
5.7 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
require('../src/common');
const { createPageAuthToken } = require('../src/request-auth');
const { registerPlayerOnboardingRoutes } = require('../src/player/onboarding');
const originalSharedSecret = process.env.PULSE_SIGNAGE_SHARED_SECRET;
const originalFetch = global.fetch;
test.after(() => {
if (originalSharedSecret === undefined) {
delete process.env.PULSE_SIGNAGE_SHARED_SECRET;
} else {
process.env.PULSE_SIGNAGE_SHARED_SECRET = originalSharedSecret;
}
global.fetch = originalFetch;
});
function createAppAndHandlers() {
const handlers = {};
const app = {
use() {},
get(path, ...routeHandlers) {
handlers[path] = function (req, res) {
return new Promise((resolve, reject) => {
const runHandler = (index) => {
const handler = routeHandlers[index];
if (!handler) {
resolve();
return;
}
let nextCalled = false;
const next = function () {
nextCalled = true;
return runHandler(index + 1);
};
let result;
try {
result = handler(req, res, next);
} catch (error) {
reject(error);
return;
}
Promise.resolve(result).then(function (value) {
if (!nextCalled) {
resolve(value);
}
}, reject);
};
runHandler(0);
});
};
},
post() {}
};
return { app, handlers };
}
function createResponse() {
const headers = {};
return {
headers,
statusCode: 200,
body: null,
set(name, value) {
headers[name] = value;
return this;
},
status(code) {
this.statusCode = code;
return this;
},
type(value) {
headers['Content-Type'] = value;
return this;
},
json(value) {
this.body = value;
return this;
},
send(value) {
this.body = value;
return this;
}
};
}
function registerThinClientRoutes(fetchImpl) {
process.env.PULSE_SIGNAGE_SHARED_SECRET = 'bridge-secret';
global.fetch = fetchImpl;
const { app, handlers } = createAppAndHandlers();
registerPlayerOnboardingRoutes(app, {
app,
common: {
renderPlayerOnboardingLandingPage() {
return 'landing';
},
renderPlayerOnboardingFormPage() {
return 'form';
}
},
bridgeBaseUrl: 'http://bridge.test',
playerPublicBaseUrl: 'http://public.test'
});
return handlers;
}
test('remote onboarding status proxies to the bridge and rewrites playerUrl', async () => {
const fetchCalls = [];
const handlers = registerThinClientRoutes(async (url, init) => {
fetchCalls.push({ url, init });
return {
status: 200,
headers: {
get(name) {
return String(name || '').toLowerCase() === 'content-type' ? 'application/json; charset=utf-8' : null;
}
},
async json() {
return {
deviceId: 'device123',
onboarded: true,
clientName: 'Lobby Player',
screenId: 7,
screenSlug: 'main-screen',
screenName: 'Main Screen',
playerUrl: null
};
}
};
});
const token = createPageAuthToken({ scope: 'onboarding', deviceId: 'device123' });
const res = createResponse();
await handlers['/api/onboarding/status']({
headers: {
'x-pulse-page-auth': token
},
query: {
deviceId: 'device 123!?'
},
socket: {}
}, res);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].url, 'http://bridge.test/api/onboarding/status?deviceId=device123');
assert.equal(fetchCalls[0].init.method, 'GET');
assert.equal(fetchCalls[0].init.headers['x-pulse-page-auth'], token);
assert.equal(res.statusCode, 200);
assert.equal(res.body.playerUrl, 'http://public.test/screen/main-screen');
assert.equal(res.body.screenSlug, 'main-screen');
});
test('remote onboarding status returns 502 when the bridge is unavailable', async () => {
const handlers = registerThinClientRoutes(async () => {
return null;
});
const token = createPageAuthToken({ scope: 'player', deviceId: 'device123' });
const res = createResponse();
await handlers['/api/onboarding/status']({
headers: {
'x-pulse-page-auth': token
},
query: {
deviceId: 'device123'
},
socket: {}
}, res);
assert.equal(res.statusCode, 502);
assert.deepEqual(res.body, { error: 'Player bridge unavailable.' });
});
test('remote onboarding screens proxy preserves the bridge response body', async () => {
const fetchCalls = [];
const handlers = registerThinClientRoutes(async (url, init) => {
fetchCalls.push({ url, init });
return {
status: 200,
headers: {
get(name) {
return String(name || '').toLowerCase() === 'content-type' ? 'application/json; charset=utf-8' : null;
}
},
async text() {
return JSON.stringify({ screens: [{ id: 1, name: 'Lobby', slug: 'lobby' }] });
}
};
});
const token = createPageAuthToken({ scope: 'onboarding', deviceId: 'device123' });
const res = createResponse();
await handlers['/api/onboarding/screens']({
headers: {
'x-pulse-page-auth': token
},
query: {},
socket: {}
}, res);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].url, 'http://bridge.test/api/onboarding/screens');
assert.equal(fetchCalls[0].init.method, 'GET');
assert.equal(res.statusCode, 200);
assert.equal(res.headers['Content-Type'], 'application/json; charset=utf-8');
assert.equal(res.body, JSON.stringify({ screens: [{ id: 1, name: 'Lobby', slug: 'lobby' }] }));
});