const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const registerPlaylistRoutes = require('../src/web/routes/admin/playlists'); function createAppAndHandlers() { const handlers = {}; const app = { post(path, ...routeHandlers) { handlers[path] = routeHandlers; }, get() {} }; return { app, handlers }; } function createResponse() { return { redirectedTo: '', statusCode: 200, redirect(url) { this.redirectedTo = url; }, status(code) { this.statusCode = code; return this; }, send(body) { this.body = body; return this; } }; } test('playlist create and update redirect to the canonical edit path', async () => { const calls = []; let groupInsertParams = null; let canvasSizeEnabled = 1; const connection = { async beginTransaction() {}, async commit() {}, async rollback() {}, release() {}, async query(sql, params) { if (sql.startsWith('INSERT INTO c_playlists')) { return [{ insertId: 4 }]; } if (sql.startsWith('UPDATE c_playlists SET')) { calls.push('update-playlist'); return [{ affectedRows: 1 }]; } if (sql.startsWith('DELETE FROM c_playlist_slides')) { calls.push('delete-slides'); return [{ affectedRows: 1 }]; } if (sql.startsWith('SELECT sl.id, st.canvas_size_id')) { return [[{ id: 11, canvas_size_id: 7 }]]; } if (sql.startsWith('INSERT INTO c_playlist_slides')) { groupInsertParams = params; calls.push('insert-slide'); return [{ insertId: 101 }]; } return [[]]; } }; const { app, handlers } = createAppAndHandlers(); registerPlaylistRoutes(app, { pool: { async getConnection() { return connection; } }, common: { async fetchDuplicateName() { return null; }, async fetchCanvasSizeById() { return { id: 7, enabled: canvasSizeEnabled }; }, async fetchPlaylistById() { return { id: 4, canvas_id: 7 }; } }, pages: {}, fetchOrderedPlaylistSlides: async () => [], fetchScreensByPlaylistId: async () => [], fetchPlaylistCanvasId: async () => null, readArrayField(body, keys) { for (const key of keys) { if (Object.prototype.hasOwnProperty.call(body, key)) { return Array.isArray(body[key]) ? body[key] : [body[key]]; } } return []; }, getAuditUserId: () => 12, redirectAfterSave(req, res, url) { calls.push({ kind: 'redirectAfterSave', url, path: req.path }); res.redirect(url); }, notifyPlayerScreens: async () => {}, broadcastDashboardState: async () => {}, getPlaylistDeleteBlockMessage: async () => '', requirePermission() { return function (_req, _res, next) { next(); }; } }); const createHandlers = handlers['/playlists']; const updateHandlers = handlers['/playlists/:id']; assert.equal(Array.isArray(createHandlers), true); assert.equal(Array.isArray(updateHandlers), true); const createRes = createResponse(); await createHandlers[1]({ path: '/playlists', body: { name: 'Lunch Loop', canvas_size_id: '7', slide_id: [] } }, createRes, () => {}); const updateRes = createResponse(); await updateHandlers[1]({ path: '/playlists/4', params: { id: '4' }, body: { name: 'Lunch Loop', slide_id: ['11'], row_key: ['row-11'], duration_seconds: ['10'], use_video_duration: ['0'], disable_audio: ['1'], group_key: ['news'], group_name: ['News'], group_pick_count: ['1'], group_shuffle: ['1'], group_mode: ['repeat'], group_repeat_count: ['2'], group_schedule_rules_json: [JSON.stringify([{ start_time: '08:00', end_time: '17:00', days: [1, 2, 3] }])] } }, updateRes, () => {}); assert.equal(createRes.redirectedTo, '/playlists/4/edit'); assert.equal(updateRes.redirectedTo, '/playlists/4/edit', updateRes.statusCode + ': ' + String(updateRes.body || '')); assert.deepEqual(groupInsertParams.slice(6, 10), [ 'news', 'News', 1, JSON.stringify([{ start_time: '08:00', end_time: '17:00', days: [1, 2, 3] }]) ]); assert.equal(groupInsertParams[10], 1); assert.equal(groupInsertParams[11], 'repeat'); assert.equal(groupInsertParams[12], 2); assert.deepEqual(calls, [ 'delete-slides', { kind: 'redirectAfterSave', url: '/playlists/4/edit', path: '/playlists' }, 'update-playlist', 'delete-slides', 'insert-slide', { kind: 'redirectAfterSave', url: '/playlists/4/edit', path: '/playlists/4' } ]); canvasSizeEnabled = 0; const disabledCreateRes = createResponse(); await createHandlers[1]({ path: '/playlists', body: { name: 'Disabled size playlist', canvas_size_id: '7', slide_id: [] } }, disabledCreateRes, (error) => { throw error; }); assert.equal(disabledCreateRes.statusCode, 400); assert.equal(disabledCreateRes.body, 'Canvas size is disabled.'); const retainDisabledRes = createResponse(); await updateHandlers[1]({ path: '/playlists/4', params: { id: '4' }, body: { name: 'Lunch Loop', canvas_size_id: '7', slide_id: [] } }, retainDisabledRes, (error) => { throw error; }); assert.equal(retainDisabledRes.redirectedTo, '/playlists/4/edit'); for (const pickCount of ['0', '2']) { const invalidRes = createResponse(); await updateHandlers[1]({ path: '/playlists/4', params: { id: '4' }, body: { name: 'Lunch Loop', slide_id: ['11'], group_key: ['news'], group_name: ['News'], group_pick_count: [pickCount] } }, invalidRes, () => {}); assert.equal(invalidRes.statusCode, 400); } for (const settings of [{ group_mode: ['unknown'] }, { group_repeat_count: ['0'] }, { group_repeat_count: ['101'] }, { group_repeat_count: ['1.5'] }]) { const invalidRes = createResponse(); await updateHandlers[1]({ path: '/playlists/4', params: { id: '4' }, body: Object.assign({ name: 'Lunch Loop', slide_id: ['11'], group_key: ['news'], group_name: ['News'], group_pick_count: ['1'] }, settings) }, invalidRes, () => {}); assert.equal(invalidRes.statusCode, 400); } });