Add multi-player and remote bridge support
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
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';
|
||||
}
|
||||
},
|
||||
thinClientBaseUrl: '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' }] }));
|
||||
});
|
||||
Reference in New Issue
Block a user