Release 2.6.7

This commit is contained in:
2026-08-08 13:02:31 +01:00
parent b20beb250b
commit 7e46fffc34
41 changed files with 2489 additions and 1290 deletions
+277 -5
View File
@@ -14,12 +14,33 @@ test('screen update redirects and forwards redirect when the slug changes', asyn
const pool = {
async query(sql) {
if (sql.includes('SELECT slug FROM d_screens ORDER BY slug ASC')) {
return [[{ slug: 'alpha' }, { slug: 'beta' }]];
if (sql.includes('SELECT s.slug, s.player_id, p.public_base_url, p.internal_base_url') && sql.includes('WHERE s.slug = ?')) {
return [[{
slug: 'alpha',
player_id: 'player-a',
public_base_url: 'http://player.local',
internal_base_url: 'http://player.internal'
}]];
}
if (sql.includes('SELECT id, name, slug, playlist_id')) {
return [[{ id: 42, name: 'Old Screen', slug: 'alpha', playlist_id: null }]];
}
if (sql.includes('SELECT s.slug, s.player_id, p.public_base_url, p.internal_base_url')) {
return [[
{
slug: 'alpha',
player_id: 'player-a',
public_base_url: 'http://player-a.example',
internal_base_url: 'http://player-a.internal'
},
{
slug: 'beta',
player_id: 'player-b',
public_base_url: 'http://player-b.example',
internal_base_url: 'http://player-b.internal'
}
]];
}
if (sql.includes('UPDATE d_screens SET name = ?, slug = ?, playlist_id = ?, player_id = ?, modified_by = ? WHERE id = ?')) {
return [{ affectedRows: 1 }];
}
@@ -37,7 +58,13 @@ test('screen update redirects and forwards redirect when the slug changes', asyn
fetchDuplicateName: async () => null,
fetchScreenById: async () => ({ id: 42, name: 'Old Screen', slug: 'alpha', playlist_id: null }),
fetchScreenPlayerRecord: async () => ({ public_base_url: 'http://player.local' }),
fetchPlayerPublicBaseUrl: async () => 'http://player.local'
fetchPlayerPublicBaseUrl: async () => 'http://player.local',
fetchPlayerRegistrations: async () => ([
{
public_base_url: 'http://player.local',
internal_base_url: 'http://player.internal'
}
])
},
pages,
getAuditUserId() { return 7; },
@@ -53,11 +80,30 @@ test('screen update redirects and forwards redirect when the slug changes', asyn
calls.push({ kind: 'broadcastDashboardState' });
},
getScreenDeleteBlockMessage: async () => '',
getScreenConnections: async () => [],
getScreenConnections: async (slug) => {
if (slug === 'alpha') {
return {
connections: [
{
id: 'alpha-1',
clientId: 'alpha-1',
deviceId: 'alpha-device',
playerPublicBaseUrl: 'http://player.local/'
}
]
};
}
return { connections: [] };
},
forwardPlayerCommand: async (slug, payload) => {
calls.push({ kind: 'forwardPlayerCommand', slug, payload });
return { ok: true };
},
forwardPlayerCommandToBaseUrl: async (baseUrl, slug, payload) => {
calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload });
return { ok: true };
},
playerPublicBaseUrl: 'http://player.example',
requirePermission() {
return function (_req, _res, next) {
@@ -91,7 +137,233 @@ test('screen update redirects and forwards redirect when the slug changes', asyn
assert.equal(res.redirectedTo, '/screens?edit=42');
assert.deepEqual(calls, [
{ kind: 'forwardPlayerCommand', slug: 'alpha', payload: { command: 'redirect', url: 'http://player.local/screen/beta' } },
{ kind: 'forwardPlayerCommandToBaseUrl', baseUrl: 'http://player.internal', slug: 'alpha', payload: { command: 'redirect', url: 'http://player.local/screen/beta' } },
{ kind: 'redirectAfterSave', url: '/screens?edit=42' }
]);
});
test('dashboard commands fan out to each connected bridge player', async () => {
const handlers = {};
const app = {
post(path, ...routeHandlers) {
handlers[path] = routeHandlers;
},
get() {}
};
const calls = [];
registerManageRoutes(app, {
pool: {
async query(sql) {
if (sql.includes('SELECT s.slug') && sql.includes('FROM d_screens s') && sql.includes('ORDER BY s.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'
}
])
},
pages: { renderScreenFormPage() {}, renderScreenEditPage() {} },
getAuditUserId() { return 7; },
redirectAfterSave() {},
notifyPlayerScreens: async () => 0,
broadcastDashboardState: async () => {
calls.push({ kind: 'broadcastDashboardState' });
},
getScreenDeleteBlockMessage: async () => '',
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: [] };
},
forwardPlayerCommand: async (slug, payload) => {
calls.push({ kind: 'forwardPlayerCommand', slug, payload });
return { ok: true };
},
forwardPlayerCommandToBaseUrl: async (baseUrl, slug, payload) => {
calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload });
return { ok: true };
},
requirePermission() {
return function (_req, _res, next) {
next();
};
}
});
const routeHandlers = handlers['/commands'];
assert.equal(Array.isArray(routeHandlers), true);
const req = {
body: { command: 'pause' },
query: {}
};
const res = {
statusCode: 200,
body: null,
status(code) {
this.statusCode = code;
return this;
},
json(value) {
this.body = value;
return this;
}
};
await routeHandlers[1](req, res, () => {});
assert.equal(res.statusCode, 200);
assert.equal(res.body.ok, true);
assert.equal(res.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 === 'forwardPlayerCommand'), false);
assert.equal(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true);
});
test('dashboard commands use live bridge connections when available', async () => {
const handlers = {};
const app = {
post(path, ...routeHandlers) {
handlers[path] = routeHandlers;
},
get() {}
};
const calls = [];
registerManageRoutes(app, {
pool: {
async query(sql) {
if (sql.includes('SELECT s.slug') && sql.includes('FROM d_screens s') && sql.includes('ORDER BY s.slug ASC')) {
return [[
{
slug: 'alpha',
}
]];
}
return [[]];
}
},
common: {
fetchPlayerRegistrations: async () => ([
{
public_base_url: 'http://player-a.example',
internal_base_url: 'http://player-a.internal'
}
])
},
pages: { renderScreenFormPage() {}, renderScreenEditPage() {} },
getAuditUserId() { return 7; },
redirectAfterSave() {},
notifyPlayerScreens: async () => 0,
broadcastDashboardState: async () => {
calls.push({ kind: 'broadcastDashboardState' });
},
getScreenDeleteBlockMessage: async () => '',
getScreenConnections: async (slug) => {
if (slug !== 'alpha') {
return { connections: [] };
}
return {
connections: [
{
id: 'alpha-1',
clientId: 'alpha-1',
deviceId: 'alpha-device',
playerPublicBaseUrl: 'http://player-a.example/'
}
]
};
},
forwardPlayerCommand: async (slug, payload) => {
calls.push({ kind: 'forwardPlayerCommand', slug, payload });
return { ok: true };
},
forwardPlayerCommandToBaseUrl: async (baseUrl, slug, payload) => {
calls.push({ kind: 'forwardPlayerCommandToBaseUrl', baseUrl, slug, payload });
return { ok: true };
},
requirePermission() {
return function (_req, _res, next) {
next();
};
}
});
const routeHandlers = handlers['/commands'];
assert.equal(Array.isArray(routeHandlers), true);
const req = {
body: { command: 'reload' },
query: {}
};
const res = {
statusCode: 200,
body: null,
status(code) {
this.statusCode = code;
return this;
},
json(value) {
this.body = value;
return this;
}
};
await routeHandlers[1](req, res, () => {});
assert.equal(res.statusCode, 200);
assert.equal(res.body.ok, true);
assert.equal(res.body.sent, 1);
assert.deepEqual(
calls.filter((entry) => entry.kind === 'forwardPlayerCommandToBaseUrl').map((entry) => entry.baseUrl),
['http://player-a.internal']
);
assert.equal(calls.some((entry) => entry.kind === 'forwardPlayerCommand'), false);
assert.equal(calls.some((entry) => entry.kind === 'broadcastDashboardState'), true);
});