148 lines
5.8 KiB
JavaScript
148 lines
5.8 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
|
|
const { createPlayerPlaylistService } = require('../src/player/playlist');
|
|
|
|
function createFsStub() {
|
|
const original = {
|
|
readFile: fs.promises.readFile,
|
|
writeFile: fs.promises.writeFile,
|
|
mkdir: fs.promises.mkdir
|
|
};
|
|
const calls = [];
|
|
|
|
fs.promises.readFile = async function (filePath) {
|
|
calls.push({ method: 'readFile', filePath });
|
|
if (filePath.endsWith('fallback.json')) {
|
|
return JSON.stringify({ fromSnapshot: true, screen: { slug: 'test2' } });
|
|
}
|
|
const error = new Error('missing');
|
|
error.code = 'ENOENT';
|
|
throw error;
|
|
};
|
|
|
|
fs.promises.writeFile = async function (filePath, content) {
|
|
calls.push({ method: 'writeFile', filePath, content });
|
|
};
|
|
|
|
fs.promises.mkdir = async function (dirPath, options) {
|
|
calls.push({ method: 'mkdir', dirPath, options });
|
|
};
|
|
|
|
return {
|
|
calls,
|
|
restore() {
|
|
fs.promises.readFile = original.readFile;
|
|
fs.promises.writeFile = original.writeFile;
|
|
fs.promises.mkdir = original.mkdir;
|
|
}
|
|
};
|
|
}
|
|
|
|
test('buildScreenPlaylist assembles slides, templates, and derived values', async () => {
|
|
const pool = {
|
|
async query(sql, params) {
|
|
if (sql.includes('FROM d_screens')) {
|
|
assert.deepEqual(params, ['test2']);
|
|
return [[{ id: 7, name: 'Screen 7', slug: 'test2', playlist_id: 22, modified_at: '2026-08-03T00:00:00.000Z' }]];
|
|
}
|
|
if (sql.includes('FROM c_playlists')) {
|
|
assert.deepEqual(params, [22]);
|
|
return [[{ id: 22, name: 'Playlist 22', fade_between_slides: 1, skip_unavailable_rtmp: 0, modified_at: '2026-08-02T00:00:00.000Z' }]];
|
|
}
|
|
if (sql.includes('FROM c_playlist_slides ps')) {
|
|
return [[{
|
|
id: 101,
|
|
title: 'Intro',
|
|
template_id: 33,
|
|
content_json: '{"videoRegion":{"type":"video","duration_seconds":12.3456},"textRegion":{"type":"text","value":"Hello"}}',
|
|
modified_at: '2026-08-03T00:00:01.000Z',
|
|
position: 1,
|
|
duration_seconds: 9,
|
|
use_video_duration: 1,
|
|
disable_audio: null,
|
|
template_name: 'Template 33',
|
|
canvas_size_name: 'HD',
|
|
canvas_size_width: 1920,
|
|
canvas_size_height: 1080,
|
|
canvas_width: 1920,
|
|
canvas_height: 1080
|
|
}]];
|
|
}
|
|
if (sql.includes('FROM c_playlist_slide_schedule_rules')) {
|
|
return [[
|
|
{ id: 500, playlist_slide_id: 101, position: 1, start_datetime: null, end_datetime: null, start_time: '08:00', end_time: '12:00', schedule_days_json: '[1,2,3]' },
|
|
{ id: 501, playlist_slide_id: 101, position: 2, start_datetime: null, end_datetime: null, start_time: '13:00', end_time: '17:00', schedule_days_json: '[4,5]' }
|
|
]];
|
|
}
|
|
if (sql.includes('FROM c_templates st')) {
|
|
assert.deepEqual(params, [[33]]);
|
|
return [[{ id: 33, name: 'Template 33', canvas_size_id: 4, canvas_size_width: 1920, canvas_size_height: 1080, background_image_path: '/media/bg.png', background_color: '#111111', modified_at: '2026-08-03T00:00:02.000Z' }]];
|
|
}
|
|
if (sql.includes('FROM c_template_regions')) {
|
|
assert.deepEqual(params, [[33]]);
|
|
return [[{ id: 900, template_id: 33, region_key: 'textRegion', region_type: 'text', label: 'Text Region', font_family: 'Arial', x: 10, y: 20, width: 300, height: 200, z_index: 1, modified_at: '2026-08-03T00:00:03.000Z' }]];
|
|
}
|
|
throw new Error(`unexpected query: ${sql}`);
|
|
}
|
|
};
|
|
const common = {
|
|
parseJsonSafe(value) {
|
|
return JSON.parse(value);
|
|
},
|
|
fetchRssFeedsData: async () => ({ rssFeeds: [{ id: 1, title: 'Feed 1' }] }),
|
|
fetchRssFeedItemsByFeedId: async () => ([{ title: 'Item 1', link: 'https://example.com' }]),
|
|
normalizeRssFeedItem(item) {
|
|
return Object.assign({}, item, { normalized: true });
|
|
},
|
|
fetchApiSourcesData: async () => ({ apiSources: [{ id: 2, name: 'Source 2', last_response_json: '{"ok":true}' }] }),
|
|
fetchTimetablesData: async () => ({ timetableGroups: [{ id: 3, name: 'Group 3' }] })
|
|
};
|
|
const fsStub = createFsStub();
|
|
|
|
try {
|
|
const service = createPlayerPlaylistService({ pool, common, snapshotDir: 'C:\\tmp\\snapshots' });
|
|
const payload = await service.buildScreenPlaylist('test2');
|
|
|
|
assert.equal(payload.screen.slug, 'test2');
|
|
assert.equal(payload.playlist.name, 'Playlist 22');
|
|
assert.equal(payload.slides[0].duration_seconds, 12.346);
|
|
assert.equal(payload.slides[0].disable_audio, true);
|
|
assert.equal(payload.slides[0].content.videoRegion.disable_audio, true);
|
|
assert.equal(payload.slides[0].content.videoRegion.cache_bust, '2026-08-03T00:00:01.000Z');
|
|
assert.equal(payload.slides[0].scheduleRules.length, 2);
|
|
assert.equal(payload.slides[0].template.regions[0].label, 'Text Region');
|
|
assert.equal(payload.rssFeeds[0].items[0].normalized, true);
|
|
assert.deepEqual(payload.apiSources[0].responseJson, { ok: true });
|
|
assert.deepEqual(payload.timetableGroups, [{ id: 3, name: 'Group 3' }]);
|
|
assert.match(payload.revision, /^[a-f0-9]{40}$/);
|
|
assert.ok(fsStub.calls.some((call) => call.method === 'writeFile'));
|
|
} finally {
|
|
fsStub.restore();
|
|
}
|
|
});
|
|
|
|
test('buildScreenPlaylist falls back to a snapshot when queries fail', async () => {
|
|
const pool = {
|
|
async query() {
|
|
throw new Error('database unavailable');
|
|
}
|
|
};
|
|
const common = {
|
|
parseJsonSafe() {
|
|
throw new Error('not expected');
|
|
}
|
|
};
|
|
const fsStub = createFsStub();
|
|
|
|
try {
|
|
const service = createPlayerPlaylistService({ pool, common, snapshotDir: 'C:\\tmp\\snapshots' });
|
|
const payload = await service.buildScreenPlaylist('fallback');
|
|
|
|
assert.deepEqual(payload, { fromSnapshot: true, screen: { slug: 'test2' } });
|
|
assert.ok(fsStub.calls.some((call) => call.method === 'readFile'));
|
|
} finally {
|
|
fsStub.restore();
|
|
}
|
|
}); |