Skip to content

Commit 8bc18a7

Browse files
committed
wip: some tests
1 parent 4c00959 commit 8bc18a7

File tree

2 files changed

+303
-8
lines changed

2 files changed

+303
-8
lines changed

packages/job-worker/src/ingest/__tests__/syncChangesToPartInstance.test.ts

Lines changed: 294 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
12
/* eslint-disable @typescript-eslint/unbound-method */
2-
import { setupDefaultJobEnvironment } from '../../__mocks__/context'
3+
import { MockJobContext, setupDefaultJobEnvironment } from '../../__mocks__/context'
34
import { setupMockShowStyleCompound } from '../../__mocks__/presetCollections'
45
import { findInstancesToSync, PartInstanceToSync, SyncChangesToPartInstancesWorker } from '../syncChangesToPartInstance'
56
import { mock } from 'jest-mock-extended'
@@ -9,6 +10,17 @@ import type { PlayoutRundownModel } from '../../playout/model/PlayoutRundownMode
910
import type { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel'
1011
import type { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
1112
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
13+
import { PlayoutModelImpl } from '../../playout/model/implementation/PlayoutModelImpl'
14+
import { PlaylistTimingType, ShowStyleBlueprintManifest } from '@sofie-automation/blueprints-integration'
15+
import { RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
16+
import { DBRundownPlaylist, SelectedPartInstance } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
17+
import { PlayoutRundownModelImpl } from '../../playout/model/implementation/PlayoutRundownModelImpl'
18+
import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
19+
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
20+
import { PlayoutSegmentModelImpl } from '../../playout/model/implementation/PlayoutSegmentModelImpl'
21+
import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
22+
import { ProcessedShowStyleCompound } from '../../jobs'
23+
import { PartialDeep, ReadonlyDeep } from 'type-fest'
1224

1325
jest.mock('../../playout/adlibTesting')
1426
import { validateAdlibTestingPartInstanceProperties } from '../../playout/adlibTesting'
@@ -67,14 +79,16 @@ describe('SyncChangesToPartInstancesWorker', () => {
6779
})
6880

6981
describe('syncChangesToPartInstance', () => {
70-
function createMockPlayoutModel(): PlayoutModel {
82+
function createMockPlayoutModel(partialModel?: Partial<Pick<PlayoutModel, 'nextPartInstance'>>) {
7183
return mock<PlayoutModel>(
7284
{
7385
currentPartInstance: null,
74-
nextPartInstance: null,
86+
nextPartInstance: partialModel?.nextPartInstance ?? null,
7587
previousPartInstance: null,
7688

7789
clearAllNotifications: jest.fn(),
90+
// setPartInstanceAsNext: jest.fn(),
91+
// removeUntakenPartInstances: jest.fn(),
7892
},
7993
mockOptions
8094
)
@@ -117,6 +131,10 @@ describe('SyncChangesToPartInstancesWorker', () => {
117131
)
118132
}
119133

134+
beforeEach(() => {
135+
jest.clearAllMocks()
136+
})
137+
120138
test('successful with empty blueprint method', async () => {
121139
const context = setupDefaultJobEnvironment()
122140
const showStyleCompound = await setupMockShowStyleCompound(context)
@@ -158,5 +176,278 @@ describe('SyncChangesToPartInstancesWorker', () => {
158176
expect(syncIngestUpdateToPartInstanceFn).toHaveBeenCalledTimes(1)
159177
expect(validateAdlibTestingPartInstanceProperties).toHaveBeenCalledTimes(1)
160178
})
179+
180+
test('removePartInstance for next calls recreateNextPartInstance', async () => {
181+
const context = setupDefaultJobEnvironment()
182+
const showStyleCompound = await setupMockShowStyleCompound(context)
183+
184+
type TsyncIngestUpdateToPartInstanceFn = jest.MockedFunction<
185+
Required<ShowStyleBlueprintManifest>['syncIngestUpdateToPartInstance']
186+
>
187+
const syncIngestUpdateToPartInstanceFn: TsyncIngestUpdateToPartInstanceFn = jest.fn()
188+
context.updateShowStyleBlueprint({
189+
syncIngestUpdateToPartInstance: syncIngestUpdateToPartInstanceFn,
190+
})
191+
const blueprint = await context.getShowStyleBlueprint(showStyleCompound._id)
192+
193+
const partInstance = createMockPartInstance('mockPartInstanceId')
194+
const part = createMockPart('mockPartId')
195+
196+
const playoutModel = createMockPlayoutModel({ nextPartInstance: partInstance })
197+
const ingestModel = createMockIngestModelReadonly()
198+
const rundownModel = createMockPlayoutRundownModel()
199+
200+
const worker = new SyncChangesToPartInstancesWorker(
201+
context,
202+
playoutModel,
203+
ingestModel,
204+
showStyleCompound,
205+
blueprint
206+
)
207+
worker.recreateNextPartInstance = jest.fn()
208+
209+
const instanceToSync: PartInstanceToSync = {
210+
playoutRundownModel: rundownModel,
211+
existingPartInstance: partInstance,
212+
previousPartInstance: null,
213+
playStatus: 'next',
214+
newPart: part,
215+
proposedPieceInstances: Promise.resolve([]),
216+
}
217+
218+
syncIngestUpdateToPartInstanceFn.mockImplementationOnce((context) => {
219+
// Remove the partInstance
220+
context.removePartInstance()
221+
})
222+
223+
await worker.syncChangesToPartInstance(instanceToSync)
224+
225+
expect(partInstance.snapshotMakeCopy).toHaveBeenCalledTimes(1)
226+
expect(partInstance.snapshotRestore).toHaveBeenCalledTimes(0)
227+
expect(syncIngestUpdateToPartInstanceFn).toHaveBeenCalledTimes(1)
228+
expect(validateAdlibTestingPartInstanceProperties).toHaveBeenCalledTimes(0)
229+
expect(worker.recreateNextPartInstance).toHaveBeenCalledTimes(1)
230+
})
231+
})
232+
233+
describe('recreateNextPartInstance', () => {
234+
async function createSimplePlayoutModel(
235+
context: MockJobContext,
236+
showStyleCompound: ReadonlyDeep<ProcessedShowStyleCompound>
237+
) {
238+
const playlistId = protectString<RundownPlaylistId>('mockPlaylistId')
239+
const playlistLock = await context.lockPlaylist(playlistId)
240+
241+
const rundown: DBRundown = {
242+
_id: protectString('mockRundownId'),
243+
externalId: 'mockExternalId',
244+
playlistId: playlistId,
245+
showStyleBaseId: showStyleCompound._id,
246+
showStyleVariantId: showStyleCompound.showStyleVariantId,
247+
name: 'mockName',
248+
organizationId: null,
249+
studioId: context.studioId,
250+
source: {
251+
type: 'http',
252+
},
253+
created: 0,
254+
modified: 0,
255+
importVersions: {
256+
blueprint: '',
257+
core: '',
258+
showStyleBase: '',
259+
showStyleVariant: '',
260+
studio: '',
261+
},
262+
timing: { type: PlaylistTimingType.None },
263+
}
264+
265+
const segment: DBSegment = {
266+
_id: protectString('mockSegmentId'),
267+
rundownId: rundown._id,
268+
name: 'mockSegmentName',
269+
externalId: 'mockSegmentExternalId',
270+
_rank: 0,
271+
}
272+
273+
const part0: DBPart = {
274+
_id: protectString('mockPartId0'),
275+
segmentId: segment._id,
276+
rundownId: rundown._id,
277+
title: 'mockPartTitle0',
278+
_rank: 0,
279+
expectedDuration: 0,
280+
expectedDurationWithTransition: 0,
281+
externalId: 'mockPartExternalId0',
282+
}
283+
284+
const nextPartInstance: DBPartInstance = {
285+
_id: protectString('mockPartInstanceId'),
286+
part: part0,
287+
segmentId: segment._id,
288+
rundownId: rundown._id,
289+
takeCount: 0,
290+
rehearsal: false,
291+
playlistActivationId: protectString('mockPlaylistActivationId'),
292+
segmentPlayoutId: protectString('mockSegmentPlayoutId'),
293+
}
294+
295+
const playlist: DBRundownPlaylist = {
296+
_id: playlistId,
297+
externalId: 'mockExternalId',
298+
activationId: protectString('mockActivationId'),
299+
currentPartInfo: null,
300+
nextPartInfo: {
301+
rundownId: nextPartInstance.rundownId,
302+
partInstanceId: nextPartInstance._id,
303+
manuallySelected: false,
304+
consumesQueuedSegmentId: false,
305+
},
306+
previousPartInfo: null,
307+
studioId: context.studioId,
308+
name: 'mockName',
309+
created: 0,
310+
modified: 0,
311+
timing: { type: PlaylistTimingType.None },
312+
rundownIdsInOrder: [],
313+
}
314+
315+
const segmentModel = new PlayoutSegmentModelImpl(segment, [part0])
316+
const rundownModel = new PlayoutRundownModelImpl(rundown, [segmentModel], [])
317+
const playoutModel = new PlayoutModelImpl(
318+
context,
319+
playlistLock,
320+
playlistId,
321+
[],
322+
playlist,
323+
[nextPartInstance],
324+
new Map(),
325+
[rundownModel],
326+
undefined
327+
)
328+
329+
return { playlistId, playoutModel, part0, nextPartInstance }
330+
}
331+
332+
function createMockIngestModelReadonly(): IngestModelReadonly {
333+
return mock<IngestModelReadonly>(
334+
{
335+
findPart: jest.fn(() => undefined),
336+
getGlobalPieces: jest.fn(() => []),
337+
},
338+
mockOptions
339+
)
340+
}
341+
342+
test('clear auto chosen partInstance', async () => {
343+
const context = setupDefaultJobEnvironment()
344+
const showStyleCompound = await setupMockShowStyleCompound(context)
345+
const blueprint = await context.getShowStyleBlueprint(showStyleCompound._id)
346+
347+
const { playoutModel } = await createSimplePlayoutModel(context, showStyleCompound)
348+
349+
const ingestModel = createMockIngestModelReadonly()
350+
351+
const worker = new SyncChangesToPartInstancesWorker(
352+
context,
353+
playoutModel,
354+
ingestModel,
355+
showStyleCompound,
356+
blueprint
357+
)
358+
359+
expect(playoutModel.nextPartInstance).toBeTruthy()
360+
expect(playoutModel.playlist.nextPartInfo).toEqual({
361+
partInstanceId: playoutModel.nextPartInstance!.partInstance._id,
362+
rundownId: playoutModel.nextPartInstance!.partInstance.rundownId,
363+
consumesQueuedSegmentId: false,
364+
manuallySelected: false,
365+
} satisfies SelectedPartInstance)
366+
367+
await worker.recreateNextPartInstance(undefined)
368+
369+
expect(playoutModel.nextPartInstance).toBeFalsy()
370+
})
371+
372+
test('clear manually chosen partInstance', async () => {
373+
const context = setupDefaultJobEnvironment()
374+
const showStyleCompound = await setupMockShowStyleCompound(context)
375+
const blueprint = await context.getShowStyleBlueprint(showStyleCompound._id)
376+
377+
const { playoutModel } = await createSimplePlayoutModel(context, showStyleCompound)
378+
379+
const ingestModel = createMockIngestModelReadonly()
380+
381+
const worker = new SyncChangesToPartInstancesWorker(
382+
context,
383+
playoutModel,
384+
ingestModel,
385+
showStyleCompound,
386+
blueprint
387+
)
388+
389+
expect(playoutModel.nextPartInstance).toBeTruthy()
390+
// Force the next part to be manually selected, and verify
391+
playoutModel.setPartInstanceAsNext(playoutModel.nextPartInstance, true, false)
392+
expect(playoutModel.playlist.nextPartInfo).toEqual({
393+
partInstanceId: playoutModel.nextPartInstance!.partInstance._id,
394+
rundownId: playoutModel.nextPartInstance!.partInstance.rundownId,
395+
consumesQueuedSegmentId: false,
396+
manuallySelected: true,
397+
} satisfies SelectedPartInstance)
398+
399+
await worker.recreateNextPartInstance(undefined)
400+
401+
expect(playoutModel.nextPartInstance).toBeFalsy()
402+
})
403+
404+
test('clear manually chosen partInstance with replacement part', async () => {
405+
const context = setupDefaultJobEnvironment()
406+
const showStyleCompound = await setupMockShowStyleCompound(context)
407+
const blueprint = await context.getShowStyleBlueprint(showStyleCompound._id)
408+
409+
const { playoutModel, part0 } = await createSimplePlayoutModel(context, showStyleCompound)
410+
411+
const ingestModel = createMockIngestModelReadonly()
412+
413+
const worker = new SyncChangesToPartInstancesWorker(
414+
context,
415+
playoutModel,
416+
ingestModel,
417+
showStyleCompound,
418+
blueprint
419+
)
420+
421+
expect(playoutModel.nextPartInstance).toBeTruthy()
422+
const partInstanceIdBefore = playoutModel.nextPartInstance!.partInstance._id
423+
424+
// Force the next part to be manually selected, and verify
425+
playoutModel.setPartInstanceAsNext(playoutModel.nextPartInstance, true, false)
426+
expect(playoutModel.playlist.nextPartInfo).toEqual({
427+
partInstanceId: playoutModel.nextPartInstance!.partInstance._id,
428+
rundownId: playoutModel.nextPartInstance!.partInstance.rundownId,
429+
consumesQueuedSegmentId: false,
430+
manuallySelected: true,
431+
} satisfies SelectedPartInstance)
432+
433+
await worker.recreateNextPartInstance(part0)
434+
435+
expect(playoutModel.nextPartInstance).toBeTruthy()
436+
// Must have been regenerated
437+
expect(playoutModel.nextPartInstance!.partInstance._id).not.toEqual(partInstanceIdBefore)
438+
expect(playoutModel.nextPartInstance!.partInstance).toMatchObject({
439+
part: {
440+
_id: part0._id,
441+
},
442+
} satisfies PartialDeep<DBPartInstance>)
443+
444+
// Make sure the part is still manually selected
445+
expect(playoutModel.playlist.nextPartInfo).toEqual({
446+
partInstanceId: playoutModel.nextPartInstance!.partInstance._id,
447+
rundownId: playoutModel.nextPartInstance!.partInstance.rundownId,
448+
consumesQueuedSegmentId: false,
449+
manuallySelected: true,
450+
} satisfies SelectedPartInstance)
451+
})
161452
})
162453
})

packages/job-worker/src/ingest/syncChangesToPartInstance.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,21 +218,25 @@ export class SyncChangesToPartInstancesWorker {
218218
}
219219

220220
async recreateNextPartInstance(newPart: ReadonlyDeep<DBPart> | undefined): Promise<void> {
221-
if (this.#playoutModel.playlist.nextPartInfo?.manuallySelected && newPart) {
221+
const originalNextPartInfo = this.#playoutModel.playlist.nextPartInfo
222+
223+
// Clear the next part
224+
await setNextPart(this.#context, this.#playoutModel, null, false, 0)
225+
226+
if (originalNextPartInfo?.manuallySelected && newPart) {
227+
// If the next part was manually selected, we need to force it to be re-created
222228
await setNextPart(
223229
this.#context,
224230
this.#playoutModel,
225231
{
226232
part: newPart,
227-
consumesQueuedSegmentId: this.#playoutModel.playlist.nextPartInfo.consumesQueuedSegmentId,
233+
consumesQueuedSegmentId: originalNextPartInfo.consumesQueuedSegmentId,
228234
},
229235
true,
230236
this.#playoutModel.playlist.nextTimeOffset || 0
231237
)
232238
} else {
233-
// Clear the next part
234-
await setNextPart(this.#context, this.#playoutModel, null, false, 0)
235-
await ensureNextPartIsValid(this.#context, this.#playoutModel)
239+
// A new next part will be selected automatically during the commit
236240
}
237241
}
238242

0 commit comments

Comments
 (0)