Skip to content

Commit ac8b166

Browse files
committed
chore: tests
1 parent a7e5995 commit ac8b166

File tree

3 files changed

+322
-5
lines changed

3 files changed

+322
-5
lines changed

.github/workflows/node.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -490,11 +490,11 @@ jobs:
490490
- node-version: 22.x
491491
package-name: job-worker
492492
send-coverage: true
493-
# No tests for the gateways yet
493+
# No tests for some gateways yet
494494
# - node-version: 22.x
495495
# package-name: playout-gateway
496-
# - node-version: 22.x
497-
# package-name: mos-gateway
496+
- node-version: 22.x
497+
package-name: mos-gateway
498498
- node-version: 22.x
499499
package-name: live-status-gateway
500500
send-coverage: true
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
import { protectString } from '@sofie-automation/server-core-integration'
2+
import {
3+
IngestPartPlaybackStatus,
4+
IngestRundownActiveStatus,
5+
IngestRundownStatus,
6+
} from '@sofie-automation/shared-lib/dist/ingest/rundownStatus'
7+
import { diffStatuses, ItemStatusEntry, MOS_STATUS_UNKNOWN, StoryStatusEntry } from '../diff'
8+
import type { MosDeviceStatusesConfig } from '../../generated/devices'
9+
import { IMOSObjectStatus } from '@mos-connection/connector'
10+
11+
describe('diffStatuses', () => {
12+
const defaultConfig: MosDeviceStatusesConfig = {
13+
enabled: true,
14+
sendInRehearsal: true,
15+
onlySendPlay: false,
16+
}
17+
const singlePartRundown: IngestRundownStatus = {
18+
_id: protectString('rundown0'),
19+
externalId: 'external0',
20+
active: IngestRundownActiveStatus.ACTIVE,
21+
segments: [
22+
{
23+
externalId: 'segment0',
24+
parts: [
25+
{
26+
externalId: 'part0',
27+
isReady: true,
28+
itemsReady: {},
29+
playbackStatus: IngestPartPlaybackStatus.UNKNOWN,
30+
},
31+
],
32+
},
33+
],
34+
}
35+
36+
test('diff no changes', () => {
37+
const diff = diffStatuses(defaultConfig, singlePartRundown, singlePartRundown)
38+
expect(diff).toHaveLength(0)
39+
})
40+
41+
test('part playback changes', () => {
42+
const partPlayingState = structuredClone(singlePartRundown)
43+
partPlayingState.segments[0].parts[0].playbackStatus = IngestPartPlaybackStatus.PLAY
44+
45+
{
46+
// change to play
47+
const diff = diffStatuses(defaultConfig, singlePartRundown, partPlayingState)
48+
expect(diff).toHaveLength(1)
49+
expect(diff[0]).toEqual({
50+
type: 'story',
51+
rundownExternalId: 'external0',
52+
storyId: 'part0',
53+
mosStatus: IMOSObjectStatus.PLAY,
54+
} satisfies StoryStatusEntry)
55+
}
56+
57+
{
58+
const partStoppedState = structuredClone(partPlayingState)
59+
partStoppedState.segments[0].parts[0].playbackStatus = IngestPartPlaybackStatus.STOP
60+
61+
// change to stop
62+
const diff = diffStatuses(defaultConfig, partPlayingState, partStoppedState)
63+
expect(diff).toHaveLength(1)
64+
expect(diff[0]).toEqual({
65+
type: 'story',
66+
rundownExternalId: 'external0',
67+
storyId: 'part0',
68+
mosStatus: IMOSObjectStatus.STOP,
69+
} satisfies StoryStatusEntry)
70+
}
71+
72+
{
73+
const partClearState = structuredClone(partPlayingState)
74+
partClearState.segments[0].parts[0].playbackStatus = IngestPartPlaybackStatus.UNKNOWN
75+
76+
// change to clear
77+
const diff = diffStatuses(defaultConfig, partPlayingState, partClearState)
78+
expect(diff).toHaveLength(1)
79+
expect(diff[0]).toEqual({
80+
type: 'story',
81+
rundownExternalId: 'external0',
82+
storyId: 'part0',
83+
mosStatus: IMOSObjectStatus.READY,
84+
} satisfies StoryStatusEntry)
85+
}
86+
})
87+
88+
test('part ready changes', () => {
89+
const partNotReadyState = structuredClone(singlePartRundown)
90+
partNotReadyState.segments[0].parts[0].isReady = false
91+
92+
{
93+
// change to not ready
94+
const diff = diffStatuses(defaultConfig, singlePartRundown, partNotReadyState)
95+
expect(diff).toHaveLength(1)
96+
expect(diff[0]).toEqual({
97+
type: 'story',
98+
rundownExternalId: 'external0',
99+
storyId: 'part0',
100+
mosStatus: IMOSObjectStatus.NOT_READY,
101+
} satisfies StoryStatusEntry)
102+
}
103+
104+
{
105+
// change to ready
106+
const diff = diffStatuses(defaultConfig, partNotReadyState, singlePartRundown)
107+
expect(diff).toHaveLength(1)
108+
expect(diff[0]).toEqual({
109+
type: 'story',
110+
rundownExternalId: 'external0',
111+
storyId: 'part0',
112+
mosStatus: IMOSObjectStatus.READY,
113+
} satisfies StoryStatusEntry)
114+
}
115+
116+
{
117+
const partClearState = structuredClone(partNotReadyState)
118+
partClearState.segments[0].parts[0].isReady = null
119+
120+
// change to unknown
121+
const diff = diffStatuses(defaultConfig, partNotReadyState, partClearState)
122+
expect(diff).toHaveLength(1)
123+
expect(diff[0]).toEqual({
124+
type: 'story',
125+
rundownExternalId: 'external0',
126+
storyId: 'part0',
127+
mosStatus: MOS_STATUS_UNKNOWN,
128+
} satisfies StoryStatusEntry)
129+
}
130+
})
131+
132+
test('part added to rundown', () => {
133+
const extraPartState = structuredClone(singlePartRundown)
134+
extraPartState.segments[0].parts.push({
135+
externalId: 'part1',
136+
isReady: false,
137+
itemsReady: {},
138+
playbackStatus: IngestPartPlaybackStatus.UNKNOWN,
139+
})
140+
141+
{
142+
const diff = diffStatuses(defaultConfig, singlePartRundown, extraPartState)
143+
expect(diff).toHaveLength(1)
144+
expect(diff[0]).toEqual({
145+
type: 'story',
146+
rundownExternalId: 'external0',
147+
storyId: 'part1',
148+
mosStatus: IMOSObjectStatus.NOT_READY,
149+
} satisfies StoryStatusEntry)
150+
}
151+
})
152+
153+
test('part removed from rundown', () => {
154+
const extraPartState = structuredClone(singlePartRundown)
155+
extraPartState.segments[0].parts.push({
156+
externalId: 'part1',
157+
isReady: false,
158+
itemsReady: {},
159+
playbackStatus: IngestPartPlaybackStatus.UNKNOWN,
160+
})
161+
162+
{
163+
const diff = diffStatuses(defaultConfig, extraPartState, singlePartRundown)
164+
expect(diff).toHaveLength(1)
165+
expect(diff[0]).toEqual({
166+
type: 'story',
167+
rundownExternalId: 'external0',
168+
storyId: 'part1',
169+
mosStatus: MOS_STATUS_UNKNOWN,
170+
} satisfies StoryStatusEntry)
171+
}
172+
})
173+
174+
test('rundown becomes inactive', () => {
175+
const inactiveState = structuredClone(singlePartRundown)
176+
inactiveState.active = IngestRundownActiveStatus.INACTIVE
177+
178+
{
179+
const diff = diffStatuses(defaultConfig, singlePartRundown, inactiveState)
180+
expect(diff).toHaveLength(1)
181+
expect(diff[0]).toEqual({
182+
type: 'story',
183+
rundownExternalId: 'external0',
184+
storyId: 'part0',
185+
mosStatus: MOS_STATUS_UNKNOWN,
186+
} satisfies StoryStatusEntry)
187+
}
188+
})
189+
190+
test('rundown becomes active', () => {
191+
const inactiveState = structuredClone(singlePartRundown)
192+
inactiveState.active = IngestRundownActiveStatus.INACTIVE
193+
194+
{
195+
const diff = diffStatuses(defaultConfig, inactiveState, singlePartRundown)
196+
expect(diff).toHaveLength(1)
197+
expect(diff[0]).toEqual({
198+
type: 'story',
199+
rundownExternalId: 'external0',
200+
storyId: 'part0',
201+
mosStatus: IMOSObjectStatus.READY,
202+
} satisfies StoryStatusEntry)
203+
}
204+
})
205+
206+
test('rundown becomes rehearsal', () => {
207+
const inactiveState = structuredClone(singlePartRundown)
208+
inactiveState.active = IngestRundownActiveStatus.INACTIVE
209+
const rehearsalState = structuredClone(singlePartRundown)
210+
rehearsalState.active = IngestRundownActiveStatus.REHEARSAL
211+
212+
{
213+
// send during rehearsal
214+
const diff = diffStatuses(defaultConfig, inactiveState, rehearsalState)
215+
expect(diff).toHaveLength(1)
216+
expect(diff[0]).toEqual({
217+
type: 'story',
218+
rundownExternalId: 'external0',
219+
storyId: 'part0',
220+
mosStatus: IMOSObjectStatus.READY,
221+
} satisfies StoryStatusEntry)
222+
}
223+
224+
{
225+
// no send during rehearsal
226+
const disableRehearsalConfig = {
227+
...defaultConfig,
228+
sendInRehearsal: false,
229+
}
230+
const diff = diffStatuses(disableRehearsalConfig, inactiveState, rehearsalState)
231+
expect(diff).toHaveLength(0)
232+
}
233+
})
234+
235+
test('add items', () => {
236+
{
237+
const itemsState = structuredClone(singlePartRundown)
238+
itemsState.segments[0].parts[0].itemsReady.item0 = true
239+
240+
const diff = diffStatuses(defaultConfig, singlePartRundown, itemsState)
241+
expect(diff).toHaveLength(1)
242+
expect(diff[0]).toEqual({
243+
type: 'item',
244+
rundownExternalId: 'external0',
245+
storyId: 'part0',
246+
itemId: 'item0',
247+
mosStatus: IMOSObjectStatus.READY,
248+
} satisfies ItemStatusEntry)
249+
}
250+
251+
{
252+
const itemsState = structuredClone(singlePartRundown)
253+
itemsState.segments[0].parts[0].itemsReady.item0 = false
254+
255+
const diff = diffStatuses(defaultConfig, singlePartRundown, itemsState)
256+
expect(diff).toHaveLength(1)
257+
expect(diff[0]).toEqual({
258+
type: 'item',
259+
rundownExternalId: 'external0',
260+
storyId: 'part0',
261+
itemId: 'item0',
262+
mosStatus: IMOSObjectStatus.NOT_READY,
263+
} satisfies ItemStatusEntry)
264+
}
265+
266+
{
267+
const itemsState = structuredClone(singlePartRundown)
268+
itemsState.segments[0].parts[0].itemsReady.item0 = undefined
269+
270+
const diff = diffStatuses(defaultConfig, singlePartRundown, itemsState)
271+
expect(diff).toHaveLength(0)
272+
}
273+
})
274+
275+
test('remove items', () => {
276+
{
277+
const itemsState = structuredClone(singlePartRundown)
278+
itemsState.segments[0].parts[0].itemsReady.item0 = true
279+
280+
const diff = diffStatuses(defaultConfig, itemsState, singlePartRundown)
281+
expect(diff).toHaveLength(1)
282+
expect(diff[0]).toEqual({
283+
type: 'item',
284+
rundownExternalId: 'external0',
285+
storyId: 'part0',
286+
itemId: 'item0',
287+
mosStatus: MOS_STATUS_UNKNOWN,
288+
} satisfies ItemStatusEntry)
289+
}
290+
291+
{
292+
const itemsState = structuredClone(singlePartRundown)
293+
itemsState.segments[0].parts[0].itemsReady.item0 = undefined
294+
295+
const diff = diffStatuses(defaultConfig, itemsState, singlePartRundown)
296+
expect(diff).toHaveLength(0)
297+
}
298+
})
299+
300+
test('change item state', () => {
301+
const itemsState = structuredClone(singlePartRundown)
302+
itemsState.segments[0].parts[0].itemsReady.item0 = true
303+
304+
const items2State = structuredClone(itemsState)
305+
items2State.segments[0].parts[0].itemsReady.item0 = false
306+
307+
const diff = diffStatuses(defaultConfig, itemsState, items2State)
308+
expect(diff).toHaveLength(1)
309+
expect(diff[0]).toEqual({
310+
type: 'item',
311+
rundownExternalId: 'external0',
312+
storyId: 'part0',
313+
itemId: 'item0',
314+
mosStatus: IMOSObjectStatus.NOT_READY,
315+
} satisfies ItemStatusEntry)
316+
})
317+
})

packages/mos-gateway/src/mosStatus/diff.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ export function diffStatuses(
105105
? buildMosStatus(config, previousStatus.playbackStatus, previousReady, previousStatuses?.active)
106106
: null
107107

108-
if (newMosStatus !== null && previousMosStatus !== newMosStatus) {
108+
if ((newMosStatus !== null || previousMosStatus !== null) && previousMosStatus !== newMosStatus) {
109109
statuses.push({
110110
type: 'item',
111111
rundownExternalId,
112112
storyId,
113113
itemId,
114-
mosStatus: newMosStatus,
114+
mosStatus: newMosStatus ?? MOS_STATUS_UNKNOWN,
115115
})
116116
}
117117
}

0 commit comments

Comments
 (0)