From f8cde5d627774925e1d11fcf0bbc51c6bbff0103 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sat, 10 Jan 2026 09:42:16 +0100 Subject: [PATCH] fix: restore process sets currentDay --- .../__tests__/restore.parser.test.ts | 50 +++++++++++++++++++ .../__tests__/restore.service.test.ts | 37 ++++++++++++++ .../restore-service/restore.parser.ts | 5 ++ .../services/restore-service/restore.type.ts | 1 + .../runtime-service/runtime.service.ts | 1 + .../src/stores/__tests__/runtimeState.test.ts | 40 +++++++++++++++ apps/server/src/stores/runtimeState.ts | 7 +++ 7 files changed, 141 insertions(+) diff --git a/apps/server/src/services/restore-service/__tests__/restore.parser.test.ts b/apps/server/src/services/restore-service/__tests__/restore.parser.test.ts index 1963d335e..07bf3dc10 100644 --- a/apps/server/src/services/restore-service/__tests__/restore.parser.test.ts +++ b/apps/server/src/services/restore-service/__tests__/restore.parser.test.ts @@ -13,6 +13,7 @@ describe('isRestorePoint()', () => { pausedAt: 3, firstStart: 1, startEpoch: 1, + currentDay: 0, }; expect(isRestorePoint(restorePoint)).toBe(true); @@ -24,6 +25,21 @@ describe('isRestorePoint()', () => { pausedAt: null, firstStart: 1, startEpoch: 1, + currentDay: 2, + }; + expect(isRestorePoint(restorePoint)).toBe(true); + }); + + it('accepts null start fields', () => { + const restorePoint: RestorePoint = { + playback: Playback.Stop, + selectedEventId: null, + startedAt: null, + addedTime: 0, + pausedAt: null, + firstStart: null, + startEpoch: null, + currentDay: null, }; expect(isRestorePoint(restorePoint)).toBe(true); }); @@ -63,5 +79,39 @@ describe('isRestorePoint()', () => { }; expect(isRestorePoint(restorePoint)).toBe(false); }); + it('with missing firstStart', () => { + const restorePoint = { + playback: Playback.Roll, + selectedEventId: '123', + startedAt: null, + addedTime: 0, + pausedAt: null, + startEpoch: 1, + }; + expect(isRestorePoint(restorePoint)).toBe(false); + }); + it('with missing startEpoch', () => { + const restorePoint = { + playback: Playback.Roll, + selectedEventId: '123', + startedAt: null, + addedTime: 0, + pausedAt: null, + firstStart: 1, + }; + expect(isRestorePoint(restorePoint)).toBe(false); + }); + it('with missing currentDay', () => { + const restorePoint = { + playback: Playback.Roll, + selectedEventId: '123', + startedAt: null, + addedTime: 0, + pausedAt: null, + firstStart: 1, + startEpoch: 1, + }; + expect(isRestorePoint(restorePoint)).toBe(false); + }); }); }); diff --git a/apps/server/src/services/restore-service/__tests__/restore.service.test.ts b/apps/server/src/services/restore-service/__tests__/restore.service.test.ts index 81afc392c..aab07cb98 100644 --- a/apps/server/src/services/restore-service/__tests__/restore.service.test.ts +++ b/apps/server/src/services/restore-service/__tests__/restore.service.test.ts @@ -17,6 +17,7 @@ describe('restoreService', () => { pausedAt: 9087, firstStart: 1234, startEpoch: 1234, + currentDay: 0, }; const mockRead = vi.fn().mockResolvedValue(expected); @@ -35,6 +36,7 @@ describe('restoreService', () => { pausedAt: null, firstStart: 1234, startEpoch: 1234, + currentDay: null, }; const mockRead = vi.fn().mockResolvedValue(expected); @@ -82,6 +84,7 @@ describe('restoreService', () => { pausedAt: 1234, firstStart: 1234, startEpoch: 1234, + currentDay: 0, }; const mockWrite = vi.fn().mockResolvedValue(undefined); @@ -90,6 +93,39 @@ describe('restoreService', () => { expect(mockWrite).toHaveBeenCalledWith(testData); }); + it('writes updated data when values change', async () => { + const firstData: RestorePoint = { + playback: Playback.Play, + selectedEventId: '2345', + startedAt: 2345, + addedTime: 2345, + pausedAt: 2345, + firstStart: 2345, + startEpoch: 2345, + currentDay: 0, + }; + + const updatedData: RestorePoint = { + playback: Playback.Pause, + selectedEventId: '3456', + startedAt: 3456, + addedTime: 3456, + pausedAt: 3456, + firstStart: 3456, + startEpoch: 3456, + currentDay: 1, + }; + + const mockWrite = vi.fn().mockResolvedValue(undefined); + + await restoreService.save(firstData, mockWrite); + await restoreService.save(updatedData, mockWrite); + + expect(mockWrite).toHaveBeenCalledTimes(2); + expect(mockWrite).toHaveBeenNthCalledWith(1, firstData); + expect(mockWrite).toHaveBeenNthCalledWith(2, updatedData); + }); + it('handles write failures gracefully', async () => { const testData: RestorePoint = { playback: Playback.Pause, @@ -99,6 +135,7 @@ describe('restoreService', () => { pausedAt: 5678, firstStart: 5678, startEpoch: 5678, + currentDay: 0, }; const mockWrite = vi.fn().mockRejectedValue(new Error('Write failed')); diff --git a/apps/server/src/services/restore-service/restore.parser.ts b/apps/server/src/services/restore-service/restore.parser.ts index 550ba0c76..86462db91 100644 --- a/apps/server/src/services/restore-service/restore.parser.ts +++ b/apps/server/src/services/restore-service/restore.parser.ts @@ -21,6 +21,7 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo 'pausedAt', 'firstStart', 'startEpoch', + 'currentDay', ]) ) { return false; @@ -54,5 +55,9 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo return false; } + if (!is.number(restorePoint.currentDay) && restorePoint.currentDay !== null) { + return false; + } + return true; } diff --git a/apps/server/src/services/restore-service/restore.type.ts b/apps/server/src/services/restore-service/restore.type.ts index d01979ce6..6b7bfa526 100644 --- a/apps/server/src/services/restore-service/restore.type.ts +++ b/apps/server/src/services/restore-service/restore.type.ts @@ -8,4 +8,5 @@ export type RestorePoint = { pausedAt: MaybeNumber; firstStart: MaybeNumber; startEpoch: MaybeNumber; + currentDay: MaybeNumber; }; diff --git a/apps/server/src/services/runtime-service/runtime.service.ts b/apps/server/src/services/runtime-service/runtime.service.ts index 4d10e67cb..bf404ef70 100644 --- a/apps/server/src/services/runtime-service/runtime.service.ts +++ b/apps/server/src/services/runtime-service/runtime.service.ts @@ -759,6 +759,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert pausedAt: state._timer.pausedAt, firstStart: state.rundown.actualStart, startEpoch: state._startEpoch, + currentDay: state.rundown.currentDay, }) .catch((_e) => { //we don't do anything with the error here diff --git a/apps/server/src/stores/__tests__/runtimeState.test.ts b/apps/server/src/stores/__tests__/runtimeState.test.ts index dc249e84c..4227ca6a5 100644 --- a/apps/server/src/stores/__tests__/runtimeState.test.ts +++ b/apps/server/src/stores/__tests__/runtimeState.test.ts @@ -11,9 +11,11 @@ import { load, loadGroupFlagAndEnd, pause, + resume, roll, start, stop, + update, } from '../runtimeState.js'; import { rundownCache } from '../../api-data/rundown/rundown.dao.js'; import { RundownMetadata } from '../../api-data/rundown/rundown.types.js'; @@ -244,6 +246,44 @@ describe('mutation on runtimeState', () => { expect(newState.offset.absolute).toBe(0); expect(newState.offset.expectedRundownEnd).toBeNull(); }); + + test('resume restores currentDay from restore point', async () => { + clearState(); + const mockRundown = makeRundown({ + entries: { + event1: { ...mockEvent, id: 'event1', timeStart: 0, timeEnd: 1000, duration: 1000, dayOffset: 0 }, + }, + order: ['event1'], + }); + + const startEpoch = new Date('2024-01-01T00:00:00Z').getTime(); + vi.setSystemTime(new Date('2024-01-03T00:00:00Z')); + + await initRundown(mockRundown, {}); + vi.runAllTimers(); + + const { rundown, metadata } = rundownCache.get(); + const restorePoint = { + playback: Playback.Play, + selectedEventId: 'event1', + startedAt: 0, + addedTime: 0, + pausedAt: null, + firstStart: 60 * 1000, + startEpoch, + currentDay: 2, + }; + + resume(restorePoint, mockRundown.entries.event1 as PlayableEvent, rundown, metadata); + + const newState = getState(); + expect(newState.rundown.actualStart).toBe(60 * 1000); + expect(newState.rundown.currentDay).toBe(2); + + vi.setSystemTime(new Date('2024-01-04T00:00:00Z')); + update(); + expect(getState().rundown.currentDay).toBe(3); + }); }); describe('roll mode', () => { diff --git a/apps/server/src/stores/runtimeState.ts b/apps/server/src/stores/runtimeState.ts index f54f5b5fa..8a37f6b6e 100644 --- a/apps/server/src/stores/runtimeState.ts +++ b/apps/server/src/stores/runtimeState.ts @@ -246,12 +246,19 @@ export function load( patchTimer(initialData); const startEpoch = initialData?.startEpoch; const firstStart = initialData?.firstStart; + const currentDay = initialData?.currentDay; if ( (firstStart === null || typeof firstStart === 'number') && (startEpoch === null || typeof startEpoch === 'number') ) { runtimeState.rundown.actualStart = firstStart; runtimeState._startEpoch = startEpoch; + if (firstStart !== null && runtimeState.rundown.plannedStart !== null) { + runtimeState._startDayOffset = findDayOffset(runtimeState.rundown.plannedStart, firstStart); + } + if (currentDay !== undefined) { + runtimeState.rundown.currentDay = currentDay; + } const { absolute, relative } = getRuntimeOffset(runtimeState); runtimeState.offset.absolute = absolute; runtimeState.offset.relative = relative;