Add player control-plane and dashboard updates

This commit is contained in:
2026-08-07 22:35:27 +01:00
parent a4f8a807ff
commit 2bd47fb96a
36 changed files with 5081 additions and 263 deletions
+229
View File
@@ -45,4 +45,233 @@ test('multipart uploads accept large text fields used by slide and template form
await new Promise((resolve) => server.close(resolve));
fs.rmSync(uploadDir, { recursive: true, force: true });
}
});
test('fqdn player registration wins over a local configured player target for media sync', async () => {
const uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-signage-upload-sync-'));
fs.mkdirSync(path.join(uploadDir, 'uploads'), { recursive: true });
fs.writeFileSync(path.join(uploadDir, 'uploads', 'sample.bin'), Buffer.from('hello world'));
const activeLastSeenAt = new Date(Date.now() - 10_000).toISOString();
const fetchCalls = [];
const originalFetch = global.fetch;
global.fetch = async function (url, init) {
fetchCalls.push({ url, init });
return {
ok: true,
status: 200,
statusText: 'OK',
headers: {
get() {
return null;
}
},
async text() {
return JSON.stringify({ ok: true });
}
};
};
const uploadSyncService = createUploadSyncService({
common: {},
pool: {
async query() {
return [[
{
identifier: 'player-local',
internal_base_url: 'http://player:8081',
last_seen_at: activeLastSeenAt
},
{
identifier: 'player-remote',
internal_base_url: 'https://pulse-dev-bridge.lzstealth.com',
last_seen_at: activeLastSeenAt
}
]];
}
},
playerSnapshotCache: new Map(),
notifyPlayerScreens: async () => {}
});
try {
const success = await uploadSyncService.pushUploadFileToPlayer('/media/uploads/sample.bin', uploadDir);
assert.equal(success, true);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].url, 'https://pulse-dev-bridge.lzstealth.com/api/media/uploads%2Fsample.bin');
} finally {
global.fetch = originalFetch;
fs.rmSync(uploadDir, { recursive: true, force: true });
}
});
test('media sync retry warnings include the resolved player label', async () => {
const uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-signage-upload-sync-log-'));
fs.mkdirSync(path.join(uploadDir, 'uploads'), { recursive: true });
fs.writeFileSync(path.join(uploadDir, 'uploads', 'sample.bin'), Buffer.from('hello world'));
const activeLastSeenAt = new Date(Date.now() - 10_000).toISOString();
const originalFetch = global.fetch;
const originalWarn = console.warn;
const warned = [];
console.warn = function () {
warned.push(Array.from(arguments).join(' '));
};
global.fetch = async function () {
const error = new Error('getaddrinfo ENOTFOUND player-remote');
error.code = 'ENOTFOUND';
throw error;
};
const uploadSyncService = createUploadSyncService({
common: {},
pool: {
async query() {
return [[
{
identifier: 'player-remote',
internal_base_url: 'https://pulse-dev-bridge.lzstealth.com',
last_seen_at: activeLastSeenAt
}
]];
}
},
playerSnapshotCache: new Map(),
notifyPlayerScreens: async () => {}
});
try {
await uploadSyncService.syncUploadRefsToPlayer(['/media/uploads/sample.bin'], uploadDir);
await uploadSyncService.flushPendingPlayerUploadSyncs();
assert.ok(warned.some(function (message) {
return message.includes('[media-sync] Player unavailable, retry queued for 1 upload for player-remote');
}));
} finally {
global.fetch = originalFetch;
console.warn = originalWarn;
fs.rmSync(uploadDir, { recursive: true, force: true });
}
});
test('media sync treats 503 responses as unavailable without logging a per-upload warning', async () => {
const uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-signage-upload-sync-503-'));
fs.mkdirSync(path.join(uploadDir, 'uploads'), { recursive: true });
fs.writeFileSync(path.join(uploadDir, 'uploads', 'sample.bin'), Buffer.from('hello world'));
const activeLastSeenAt = new Date(Date.now() - 10_000).toISOString();
const originalFetch = global.fetch;
const originalWarn = console.warn;
const warned = [];
console.warn = function () {
warned.push(Array.from(arguments).join(' '));
};
global.fetch = async function () {
return {
ok: false,
status: 503,
statusText: 'Service Unavailable',
headers: {
get() {
return null;
}
},
async text() {
return 'Service Unavailable';
}
};
};
const uploadSyncService = createUploadSyncService({
common: {},
pool: {
async query() {
return [[
{
identifier: 'player-remote',
internal_base_url: 'https://pulse-dev-bridge.lzstealth.com',
last_seen_at: activeLastSeenAt
}
]];
}
},
playerSnapshotCache: new Map(),
notifyPlayerScreens: async () => {}
});
try {
await uploadSyncService.syncUploadRefsToPlayer(['/media/uploads/sample.bin'], uploadDir);
await uploadSyncService.flushPendingPlayerUploadSyncs();
assert.ok(warned.some(function (message) {
return message.includes('[media-sync] Player unavailable, retry queued for 1 upload for player-remote');
}));
assert.ok(warned.every(function (message) {
return !message.includes('Unable to sync upload to player: sample.bin 503 Service Unavailable');
}));
} finally {
global.fetch = originalFetch;
console.warn = originalWarn;
fs.rmSync(uploadDir, { recursive: true, force: true });
}
});
test('stale player registrations stop media sync retries and warnings', async () => {
const uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-signage-upload-sync-stale-'));
fs.mkdirSync(path.join(uploadDir, 'uploads'), { recursive: true });
fs.writeFileSync(path.join(uploadDir, 'uploads', 'sample.bin'), Buffer.from('hello world'));
const originalFetch = global.fetch;
const originalWarn = console.warn;
const fetchCalls = [];
const warned = [];
console.warn = function () {
warned.push(Array.from(arguments).join(' '));
};
global.fetch = async function (url, init) {
fetchCalls.push({ url, init });
return {
ok: false,
status: 503,
statusText: 'Service Unavailable',
headers: {
get() {
return null;
}
},
async text() {
return 'Service Unavailable';
}
};
};
const uploadSyncService = createUploadSyncService({
common: {},
pool: {
async query() {
return [[
{
identifier: 'player-remote',
internal_base_url: 'https://pulse-dev-bridge.lzstealth.com',
last_seen_at: new Date(Date.now() - 5 * 60 * 1000).toISOString()
}
]];
}
},
playerSnapshotCache: new Map(),
notifyPlayerScreens: async () => {}
});
try {
await uploadSyncService.syncUploadRefsToPlayer(['/media/uploads/sample.bin'], uploadDir);
await uploadSyncService.flushPendingPlayerUploadSyncs();
assert.equal(fetchCalls.length, 0);
assert.equal(warned.length, 0);
} finally {
global.fetch = originalFetch;
console.warn = originalWarn;
fs.rmSync(uploadDir, { recursive: true, force: true });
}
});