From ac8b1660ea5a1b0d9cf3224f2195c9a653ba019e Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 17 Dec 2024 15:39:14 +0000 Subject: [PATCH] chore: tests --- .github/workflows/node.yaml | 6 +- .../src/mosStatus/__tests__/diff.spec.ts | 317 ++++++++++++++++++ packages/mos-gateway/src/mosStatus/diff.ts | 4 +- 3 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 packages/mos-gateway/src/mosStatus/__tests__/diff.spec.ts diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index c616662038..3c98b6b9d7 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -490,11 +490,11 @@ jobs: - node-version: 22.x package-name: job-worker send-coverage: true - # No tests for the gateways yet + # No tests for some gateways yet # - node-version: 22.x # package-name: playout-gateway - # - node-version: 22.x - # package-name: mos-gateway + - node-version: 22.x + package-name: mos-gateway - node-version: 22.x package-name: live-status-gateway send-coverage: true diff --git a/packages/mos-gateway/src/mosStatus/__tests__/diff.spec.ts b/packages/mos-gateway/src/mosStatus/__tests__/diff.spec.ts new file mode 100644 index 0000000000..624d7eb9c1 --- /dev/null +++ b/packages/mos-gateway/src/mosStatus/__tests__/diff.spec.ts @@ -0,0 +1,317 @@ +import { protectString } from '@sofie-automation/server-core-integration' +import { + IngestPartPlaybackStatus, + IngestRundownActiveStatus, + IngestRundownStatus, +} from '@sofie-automation/shared-lib/dist/ingest/rundownStatus' +import { diffStatuses, ItemStatusEntry, MOS_STATUS_UNKNOWN, StoryStatusEntry } from '../diff' +import type { MosDeviceStatusesConfig } from '../../generated/devices' +import { IMOSObjectStatus } from '@mos-connection/connector' + +describe('diffStatuses', () => { + const defaultConfig: MosDeviceStatusesConfig = { + enabled: true, + sendInRehearsal: true, + onlySendPlay: false, + } + const singlePartRundown: IngestRundownStatus = { + _id: protectString('rundown0'), + externalId: 'external0', + active: IngestRundownActiveStatus.ACTIVE, + segments: [ + { + externalId: 'segment0', + parts: [ + { + externalId: 'part0', + isReady: true, + itemsReady: {}, + playbackStatus: IngestPartPlaybackStatus.UNKNOWN, + }, + ], + }, + ], + } + + test('diff no changes', () => { + const diff = diffStatuses(defaultConfig, singlePartRundown, singlePartRundown) + expect(diff).toHaveLength(0) + }) + + test('part playback changes', () => { + const partPlayingState = structuredClone(singlePartRundown) + partPlayingState.segments[0].parts[0].playbackStatus = IngestPartPlaybackStatus.PLAY + + { + // change to play + const diff = diffStatuses(defaultConfig, singlePartRundown, partPlayingState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.PLAY, + } satisfies StoryStatusEntry) + } + + { + const partStoppedState = structuredClone(partPlayingState) + partStoppedState.segments[0].parts[0].playbackStatus = IngestPartPlaybackStatus.STOP + + // change to stop + const diff = diffStatuses(defaultConfig, partPlayingState, partStoppedState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.STOP, + } satisfies StoryStatusEntry) + } + + { + const partClearState = structuredClone(partPlayingState) + partClearState.segments[0].parts[0].playbackStatus = IngestPartPlaybackStatus.UNKNOWN + + // change to clear + const diff = diffStatuses(defaultConfig, partPlayingState, partClearState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.READY, + } satisfies StoryStatusEntry) + } + }) + + test('part ready changes', () => { + const partNotReadyState = structuredClone(singlePartRundown) + partNotReadyState.segments[0].parts[0].isReady = false + + { + // change to not ready + const diff = diffStatuses(defaultConfig, singlePartRundown, partNotReadyState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.NOT_READY, + } satisfies StoryStatusEntry) + } + + { + // change to ready + const diff = diffStatuses(defaultConfig, partNotReadyState, singlePartRundown) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.READY, + } satisfies StoryStatusEntry) + } + + { + const partClearState = structuredClone(partNotReadyState) + partClearState.segments[0].parts[0].isReady = null + + // change to unknown + const diff = diffStatuses(defaultConfig, partNotReadyState, partClearState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: MOS_STATUS_UNKNOWN, + } satisfies StoryStatusEntry) + } + }) + + test('part added to rundown', () => { + const extraPartState = structuredClone(singlePartRundown) + extraPartState.segments[0].parts.push({ + externalId: 'part1', + isReady: false, + itemsReady: {}, + playbackStatus: IngestPartPlaybackStatus.UNKNOWN, + }) + + { + const diff = diffStatuses(defaultConfig, singlePartRundown, extraPartState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part1', + mosStatus: IMOSObjectStatus.NOT_READY, + } satisfies StoryStatusEntry) + } + }) + + test('part removed from rundown', () => { + const extraPartState = structuredClone(singlePartRundown) + extraPartState.segments[0].parts.push({ + externalId: 'part1', + isReady: false, + itemsReady: {}, + playbackStatus: IngestPartPlaybackStatus.UNKNOWN, + }) + + { + const diff = diffStatuses(defaultConfig, extraPartState, singlePartRundown) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part1', + mosStatus: MOS_STATUS_UNKNOWN, + } satisfies StoryStatusEntry) + } + }) + + test('rundown becomes inactive', () => { + const inactiveState = structuredClone(singlePartRundown) + inactiveState.active = IngestRundownActiveStatus.INACTIVE + + { + const diff = diffStatuses(defaultConfig, singlePartRundown, inactiveState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: MOS_STATUS_UNKNOWN, + } satisfies StoryStatusEntry) + } + }) + + test('rundown becomes active', () => { + const inactiveState = structuredClone(singlePartRundown) + inactiveState.active = IngestRundownActiveStatus.INACTIVE + + { + const diff = diffStatuses(defaultConfig, inactiveState, singlePartRundown) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.READY, + } satisfies StoryStatusEntry) + } + }) + + test('rundown becomes rehearsal', () => { + const inactiveState = structuredClone(singlePartRundown) + inactiveState.active = IngestRundownActiveStatus.INACTIVE + const rehearsalState = structuredClone(singlePartRundown) + rehearsalState.active = IngestRundownActiveStatus.REHEARSAL + + { + // send during rehearsal + const diff = diffStatuses(defaultConfig, inactiveState, rehearsalState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'story', + rundownExternalId: 'external0', + storyId: 'part0', + mosStatus: IMOSObjectStatus.READY, + } satisfies StoryStatusEntry) + } + + { + // no send during rehearsal + const disableRehearsalConfig = { + ...defaultConfig, + sendInRehearsal: false, + } + const diff = diffStatuses(disableRehearsalConfig, inactiveState, rehearsalState) + expect(diff).toHaveLength(0) + } + }) + + test('add items', () => { + { + const itemsState = structuredClone(singlePartRundown) + itemsState.segments[0].parts[0].itemsReady.item0 = true + + const diff = diffStatuses(defaultConfig, singlePartRundown, itemsState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'item', + rundownExternalId: 'external0', + storyId: 'part0', + itemId: 'item0', + mosStatus: IMOSObjectStatus.READY, + } satisfies ItemStatusEntry) + } + + { + const itemsState = structuredClone(singlePartRundown) + itemsState.segments[0].parts[0].itemsReady.item0 = false + + const diff = diffStatuses(defaultConfig, singlePartRundown, itemsState) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'item', + rundownExternalId: 'external0', + storyId: 'part0', + itemId: 'item0', + mosStatus: IMOSObjectStatus.NOT_READY, + } satisfies ItemStatusEntry) + } + + { + const itemsState = structuredClone(singlePartRundown) + itemsState.segments[0].parts[0].itemsReady.item0 = undefined + + const diff = diffStatuses(defaultConfig, singlePartRundown, itemsState) + expect(diff).toHaveLength(0) + } + }) + + test('remove items', () => { + { + const itemsState = structuredClone(singlePartRundown) + itemsState.segments[0].parts[0].itemsReady.item0 = true + + const diff = diffStatuses(defaultConfig, itemsState, singlePartRundown) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'item', + rundownExternalId: 'external0', + storyId: 'part0', + itemId: 'item0', + mosStatus: MOS_STATUS_UNKNOWN, + } satisfies ItemStatusEntry) + } + + { + const itemsState = structuredClone(singlePartRundown) + itemsState.segments[0].parts[0].itemsReady.item0 = undefined + + const diff = diffStatuses(defaultConfig, itemsState, singlePartRundown) + expect(diff).toHaveLength(0) + } + }) + + test('change item state', () => { + const itemsState = structuredClone(singlePartRundown) + itemsState.segments[0].parts[0].itemsReady.item0 = true + + const items2State = structuredClone(itemsState) + items2State.segments[0].parts[0].itemsReady.item0 = false + + const diff = diffStatuses(defaultConfig, itemsState, items2State) + expect(diff).toHaveLength(1) + expect(diff[0]).toEqual({ + type: 'item', + rundownExternalId: 'external0', + storyId: 'part0', + itemId: 'item0', + mosStatus: IMOSObjectStatus.NOT_READY, + } satisfies ItemStatusEntry) + }) +}) diff --git a/packages/mos-gateway/src/mosStatus/diff.ts b/packages/mos-gateway/src/mosStatus/diff.ts index 3952b1fbcb..9eaa6904f7 100644 --- a/packages/mos-gateway/src/mosStatus/diff.ts +++ b/packages/mos-gateway/src/mosStatus/diff.ts @@ -105,13 +105,13 @@ export function diffStatuses( ? buildMosStatus(config, previousStatus.playbackStatus, previousReady, previousStatuses?.active) : null - if (newMosStatus !== null && previousMosStatus !== newMosStatus) { + if ((newMosStatus !== null || previousMosStatus !== null) && previousMosStatus !== newMosStatus) { statuses.push({ type: 'item', rundownExternalId, storyId, itemId, - mosStatus: newMosStatus, + mosStatus: newMosStatus ?? MOS_STATUS_UNKNOWN, }) } }