Skip to content

Commit fef7b16

Browse files
committed
chore: add unit test for getNowInPlayout
1 parent 4c20059 commit fef7b16

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
import { JSONBlobStringify, PieceLifespan, StatusCode } from '@sofie-automation/blueprints-integration'
2+
import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece'
3+
import { PartInstanceId, RundownId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
4+
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
5+
import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
6+
import {
7+
PeripheralDevice,
8+
PeripheralDeviceCategory,
9+
PeripheralDeviceType,
10+
} from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice'
11+
import { EmptyPieceTimelineObjectsBlob, Piece } from '@sofie-automation/corelib/dist/dataModel/Piece'
12+
import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
13+
import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
14+
import { RundownBaselineAdLibItem } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineAdLibPiece'
15+
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
16+
import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString'
17+
import { ReadonlyDeep } from 'type-fest'
18+
import { MockJobContext, setupDefaultJobEnvironment } from '../../../../__mocks__/context.js'
19+
import {
20+
defaultAdLibPiece,
21+
defaultPart,
22+
defaultPiece,
23+
defaultRundown,
24+
defaultRundownPlaylist,
25+
defaultSegment,
26+
} from '../../../../__mocks__/defaultCollectionObjects.js'
27+
import { setupMockShowStyleCompound } from '../../../../__mocks__/presetCollections.js'
28+
import { ProcessedShowStyleCompound } from '../../../../jobs/index.js'
29+
import { runWithPlaylistLock } from '../../../../playout/lock.js'
30+
import { PlayoutModelImpl } from '../PlayoutModelImpl.js'
31+
import { PlayoutRundownModelImpl } from '../PlayoutRundownModelImpl.js'
32+
33+
describe('PlayoutModelImpl', () => {
34+
let context: MockJobContext
35+
let showStyleCompound: ReadonlyDeep<ProcessedShowStyleCompound>
36+
37+
beforeAll(async () => {
38+
context = setupDefaultJobEnvironment()
39+
showStyleCompound = await setupMockShowStyleCompound(context)
40+
})
41+
42+
describe('nowInPlayout', () => {
43+
beforeEach(async () => {
44+
jest.useFakeTimers()
45+
})
46+
47+
afterEach(async () =>
48+
Promise.all([
49+
context.mockCollections.RundownBaselineAdLibPieces.remove({}),
50+
context.mockCollections.RundownBaselineAdLibActions.remove({}),
51+
context.mockCollections.RundownBaselineObjects.remove({}),
52+
context.mockCollections.AdLibActions.remove({}),
53+
context.mockCollections.AdLibPieces.remove({}),
54+
context.mockCollections.Pieces.remove({}),
55+
context.mockCollections.Parts.remove({}),
56+
context.mockCollections.Segments.remove({}),
57+
context.mockCollections.Rundowns.remove({}),
58+
context.mockCollections.RundownPlaylists.remove({}),
59+
])
60+
)
61+
62+
it('returns the current time', async () => {
63+
const { playlistId: playlistId0 } = await setupRundownWithAutoplayPart0(
64+
context,
65+
protectString('rundown00'),
66+
showStyleCompound
67+
)
68+
69+
const playlist = await context.mockCollections.RundownPlaylists.findOne(playlistId0)
70+
71+
const TIME_FAR_PAST = 1000
72+
const TIME_CONNECTED = 2000
73+
const TIME_PING = 3000
74+
75+
const TIME_NOW = 5000
76+
77+
const peripheralDevices: PeripheralDevice[] = [
78+
{
79+
_id: protectString('playoutGateway0'),
80+
category: PeripheralDeviceCategory.PLAYOUT,
81+
type: PeripheralDeviceType.PLAYOUT,
82+
subType: '',
83+
connected: true,
84+
configManifest: {
85+
deviceConfigSchema: JSONBlobStringify({}),
86+
subdeviceManifest: {},
87+
},
88+
connectionId: 'connectionId0',
89+
created: TIME_FAR_PAST,
90+
deviceName: 'Dummy Playout Gateway 1',
91+
lastConnected: TIME_CONNECTED,
92+
lastSeen: TIME_PING,
93+
name: 'Dummy Playout Gateway 1',
94+
organizationId: null,
95+
status: {
96+
statusCode: StatusCode.GOOD,
97+
messages: [],
98+
},
99+
token: '',
100+
},
101+
{
102+
_id: protectString('playoutGateway1'),
103+
category: PeripheralDeviceCategory.PLAYOUT,
104+
type: PeripheralDeviceType.PLAYOUT,
105+
subType: '',
106+
connected: true,
107+
configManifest: {
108+
deviceConfigSchema: JSONBlobStringify({}),
109+
subdeviceManifest: {},
110+
},
111+
connectionId: 'connectionId1',
112+
created: TIME_FAR_PAST,
113+
deviceName: 'Dummy Playout Gateway 2',
114+
lastConnected: TIME_CONNECTED,
115+
lastSeen: TIME_PING,
116+
name: 'Dummy Playout Gateway 2',
117+
organizationId: null,
118+
status: {
119+
statusCode: StatusCode.GOOD,
120+
messages: [],
121+
},
122+
token: '',
123+
},
124+
]
125+
const partInstances: DBPartInstance[] = []
126+
const groupedPieceInstances: Map<PartInstanceId, PieceInstance[]> = new Map()
127+
const rundowns: PlayoutRundownModelImpl[] = []
128+
129+
if (!playlist) throw new Error('Playlist not found!')
130+
131+
jest.setSystemTime(TIME_NOW)
132+
133+
await runWithPlaylistLock(context, playlistId0, async (lock) => {
134+
const model = new PlayoutModelImpl(
135+
context,
136+
lock,
137+
playlistId0,
138+
peripheralDevices,
139+
playlist,
140+
partInstances,
141+
groupedPieceInstances,
142+
rundowns,
143+
undefined
144+
)
145+
146+
const TIME_DELTA = 1000
147+
148+
peripheralDevices[0].latencies = [20, 30, 50, 10]
149+
peripheralDevices[1].latencies = [20, 30, 50, 10]
150+
151+
jest.advanceTimersByTime(TIME_DELTA)
152+
153+
const now0 = model.getNowInPlayout()
154+
expect(now0).toBeGreaterThanOrEqual(TIME_NOW)
155+
156+
peripheralDevices[0].latencies = [0]
157+
peripheralDevices[1].latencies = [0]
158+
159+
const now1 = model.getNowInPlayout()
160+
expect(now1).toBeGreaterThanOrEqual(now0)
161+
162+
jest.advanceTimersByTime(TIME_DELTA)
163+
164+
const now2 = model.getNowInPlayout()
165+
expect(now2).toBeGreaterThanOrEqual(now1)
166+
167+
peripheralDevices[0].latencies = [100, 200, 100, 50]
168+
peripheralDevices[1].latencies = [100, 200, 100, 50]
169+
170+
const now3 = model.getNowInPlayout()
171+
expect(now3).toBeGreaterThanOrEqual(now2)
172+
})
173+
})
174+
})
175+
})
176+
177+
async function setupRundownWithAutoplayPart0(
178+
context: MockJobContext,
179+
rundownId: RundownId,
180+
showStyle: ReadonlyDeep<ProcessedShowStyleCompound>
181+
): Promise<{ playlistId: RundownPlaylistId; rundownId: RundownId }> {
182+
const outputLayerIds = Object.keys(showStyle.outputLayers)
183+
const sourceLayerIds = Object.keys(showStyle.sourceLayers)
184+
185+
const playlistId = await context.mockCollections.RundownPlaylists.insertOne(
186+
defaultRundownPlaylist(protectString(`playlist_${rundownId}`), context.studioId)
187+
)
188+
189+
const rundown: DBRundown = defaultRundown(
190+
unprotectString(rundownId),
191+
context.studioId,
192+
null,
193+
playlistId,
194+
showStyle._id,
195+
showStyle.showStyleVariantId
196+
)
197+
rundown._id = rundownId
198+
await context.mockCollections.Rundowns.insertOne(rundown)
199+
200+
const segment0: DBSegment = {
201+
...defaultSegment(protectString(rundownId + '_segment0'), rundown._id),
202+
_rank: 0,
203+
externalId: 'MOCK_SEGMENT_0',
204+
name: 'Segment 0',
205+
}
206+
await context.mockCollections.Segments.insertOne(segment0)
207+
208+
const part00: DBPart = {
209+
...defaultPart(protectString(rundownId + '_part0_0'), rundown._id, segment0._id),
210+
externalId: 'MOCK_PART_0_0',
211+
title: 'Part 0 0',
212+
213+
expectedDuration: 20000,
214+
autoNext: true,
215+
}
216+
await context.mockCollections.Parts.insertOne(part00)
217+
218+
const piece000: Piece = {
219+
...defaultPiece(protectString(rundownId + '_piece000'), rundown._id, part00.segmentId, part00._id),
220+
externalId: 'MOCK_PIECE_000',
221+
name: 'Piece 000',
222+
sourceLayerId: sourceLayerIds[0],
223+
outputLayerId: outputLayerIds[0],
224+
}
225+
await context.mockCollections.Pieces.insertOne(piece000)
226+
227+
const piece001: Piece = {
228+
...defaultPiece(protectString(rundownId + '_piece001'), rundown._id, part00.segmentId, part00._id),
229+
externalId: 'MOCK_PIECE_001',
230+
name: 'Piece 001',
231+
sourceLayerId: sourceLayerIds[1],
232+
outputLayerId: outputLayerIds[0],
233+
}
234+
await context.mockCollections.Pieces.insertOne(piece001)
235+
236+
const adLibPiece000: AdLibPiece = {
237+
...defaultAdLibPiece(protectString(rundownId + '_adLib000'), segment0.rundownId, part00._id),
238+
expectedDuration: 1000,
239+
externalId: 'MOCK_ADLIB_000',
240+
name: 'AdLib 0',
241+
sourceLayerId: sourceLayerIds[1],
242+
outputLayerId: outputLayerIds[0],
243+
}
244+
245+
await context.mockCollections.AdLibPieces.insertOne(adLibPiece000)
246+
247+
const part01: DBPart = {
248+
...defaultPart(protectString(rundownId + '_part0_1'), rundown._id, segment0._id),
249+
_rank: 1,
250+
externalId: 'MOCK_PART_0_1',
251+
title: 'Part 0 1',
252+
}
253+
await context.mockCollections.Parts.insertOne(part01)
254+
255+
const piece010: Piece = {
256+
...defaultPiece(protectString(rundownId + '_piece010'), rundown._id, part01.segmentId, part01._id),
257+
externalId: 'MOCK_PIECE_010',
258+
name: 'Piece 010',
259+
sourceLayerId: sourceLayerIds[0],
260+
outputLayerId: outputLayerIds[0],
261+
}
262+
await context.mockCollections.Pieces.insertOne(piece010)
263+
264+
const segment1: DBSegment = {
265+
...defaultSegment(protectString(rundownId + '_segment1'), rundown._id),
266+
_rank: 1,
267+
externalId: 'MOCK_SEGMENT_2',
268+
name: 'Segment 1',
269+
}
270+
await context.mockCollections.Segments.insertOne(segment1)
271+
272+
const part10: DBPart = {
273+
...defaultPart(protectString(rundownId + '_part1_0'), rundown._id, segment1._id),
274+
_rank: 0,
275+
externalId: 'MOCK_PART_1_0',
276+
title: 'Part 1 0',
277+
}
278+
await context.mockCollections.Parts.insertOne(part10)
279+
280+
const piece100: Piece = {
281+
...defaultPiece(protectString(rundownId + '_piece100'), rundown._id, part10.segmentId, part10._id),
282+
}
283+
await context.mockCollections.Pieces.insertOne(piece100)
284+
285+
const part11: DBPart = {
286+
...defaultPart(protectString(rundownId + '_part1_1'), rundown._id, segment1._id),
287+
_rank: 1,
288+
}
289+
await context.mockCollections.Parts.insertOne(part11)
290+
291+
const piece110: Piece = {
292+
...defaultPiece(protectString(rundownId + '_piece110'), rundown._id, part11.segmentId, part11._id),
293+
}
294+
await context.mockCollections.Pieces.insertOne(piece110)
295+
296+
const part12: DBPart = {
297+
...defaultPart(protectString(rundownId + '_part1_2'), rundown._id, segment1._id),
298+
_rank: 2,
299+
}
300+
await context.mockCollections.Parts.insertOne(part12)
301+
302+
const piece120: Piece = {
303+
...defaultPiece(protectString(rundownId + '_piece120'), rundown._id, part12.segmentId, part12._id),
304+
}
305+
await context.mockCollections.Pieces.insertOne(piece120)
306+
307+
const segment2: DBSegment = {
308+
...defaultSegment(protectString(rundownId + '_segment2'), rundown._id),
309+
_rank: 2,
310+
}
311+
await context.mockCollections.Segments.insertOne(segment2)
312+
313+
const part20: DBPart = {
314+
...defaultPart(protectString(rundownId + '_part2_0'), rundown._id, segment2._id),
315+
_rank: 0,
316+
}
317+
await context.mockCollections.Parts.insertOne(part20)
318+
319+
const piece200: Piece = {
320+
...defaultPiece(protectString(rundownId + '_piece200'), rundown._id, part20.segmentId, part20._id),
321+
}
322+
await context.mockCollections.Pieces.insertOne(piece200)
323+
324+
const part21: DBPart = {
325+
...defaultPart(protectString(rundownId + '_part2_1'), rundown._id, segment2._id),
326+
_rank: 1,
327+
}
328+
await context.mockCollections.Parts.insertOne(part21)
329+
330+
const piece210: Piece = {
331+
...defaultPiece(protectString(rundownId + '_piece210'), rundown._id, part21.segmentId, part21._id),
332+
}
333+
await context.mockCollections.Pieces.insertOne(piece210)
334+
335+
const part22: DBPart = {
336+
...defaultPart(protectString(rundownId + '_part2_2'), rundown._id, segment2._id),
337+
_rank: 2,
338+
}
339+
await context.mockCollections.Parts.insertOne(part22)
340+
341+
const segment3: DBSegment = {
342+
...defaultSegment(protectString(rundownId + '_segment3'), rundown._id),
343+
_rank: 3,
344+
}
345+
await context.mockCollections.Segments.insertOne(segment3)
346+
347+
const part30: DBPart = {
348+
...defaultPart(protectString(rundownId + '_part3_0'), rundown._id, segment2._id),
349+
_rank: 0,
350+
}
351+
await context.mockCollections.Parts.insertOne(part30)
352+
353+
const globalAdLib0: RundownBaselineAdLibItem = {
354+
_id: protectString(rundownId + '_globalAdLib0'),
355+
_rank: 0,
356+
externalId: 'MOCK_GLOBAL_ADLIB_0',
357+
lifespan: PieceLifespan.OutOnRundownChange,
358+
rundownId: segment0.rundownId,
359+
name: 'Global AdLib 0',
360+
sourceLayerId: sourceLayerIds[0],
361+
outputLayerId: outputLayerIds[0],
362+
content: {},
363+
timelineObjectsString: EmptyPieceTimelineObjectsBlob,
364+
}
365+
366+
const globalAdLib1: RundownBaselineAdLibItem = {
367+
_id: protectString(rundownId + '_globalAdLib1'),
368+
_rank: 0,
369+
externalId: 'MOCK_GLOBAL_ADLIB_1',
370+
lifespan: PieceLifespan.OutOnRundownChange,
371+
rundownId: segment0.rundownId,
372+
name: 'Global AdLib 1',
373+
sourceLayerId: sourceLayerIds[1],
374+
outputLayerId: outputLayerIds[0],
375+
content: {},
376+
timelineObjectsString: EmptyPieceTimelineObjectsBlob,
377+
}
378+
379+
await context.mockCollections.RundownBaselineAdLibPieces.insertOne(globalAdLib0)
380+
await context.mockCollections.RundownBaselineAdLibPieces.insertOne(globalAdLib1)
381+
382+
return { playlistId, rundownId }
383+
}

0 commit comments

Comments
 (0)