Skip to content

Commit fbabd9c

Browse files
committed
test(meteor): add unit tests for resolveActionResult
- Cover success, template interpolation, function messages, and suppression - Cover missing blueprint, missing studio, and unmatched error codes
1 parent fd8362c commit fbabd9c

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { DeviceStatusContext, TSR } from '@sofie-automation/blueprints-integration'
2+
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
3+
import { PeripheralDeviceId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids'
4+
import { Blueprints, PeripheralDevices, Studios } from '../../collections'
5+
import { evalBlueprint } from '../blueprints/cache'
6+
import { resolveActionResult } from '../peripheralDevice'
7+
8+
jest.mock('../deviceTriggers/observer')
9+
jest.mock('../blueprints/cache')
10+
11+
const mockEvalBlueprint = evalBlueprint as jest.MockedFunction<typeof evalBlueprint>
12+
13+
const ACTION_ERROR_CODE = 'ACTION_HTTP_REQUEST_FAILED'
14+
const deviceId = protectString<PeripheralDeviceId>('device0')
15+
const studioId = protectString<StudioId>('studio0')
16+
17+
function makeErrorResult(overrides: Partial<TSR.ActionExecutionResult> = {}): TSR.ActionExecutionResult {
18+
return {
19+
result: TSR.ActionExecutionResultCode.Error,
20+
response: { key: 'HTTP request to {{url}} failed: {{errorMessage}}' },
21+
code: ACTION_ERROR_CODE,
22+
context: {
23+
url: 'http://graphics/api',
24+
errorMessage: 'connection refused',
25+
},
26+
...overrides,
27+
}
28+
}
29+
30+
describe('resolveActionResult', () => {
31+
beforeEach(() => {
32+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockReset()
33+
jest.spyOn(Studios, 'findOneAsync').mockReset()
34+
jest.spyOn(Blueprints, 'findOneAsync').mockReset()
35+
mockEvalBlueprint.mockReset()
36+
})
37+
38+
afterEach(() => {
39+
jest.restoreAllMocks()
40+
})
41+
42+
it('returns Ok results unchanged', async () => {
43+
const result: TSR.ActionExecutionResult = {
44+
result: TSR.ActionExecutionResultCode.Ok,
45+
response: { key: 'Action completed' },
46+
}
47+
48+
const resolved = await resolveActionResult(deviceId, result)
49+
50+
expect(resolved).toBe(result)
51+
expect(PeripheralDevices.findOneAsync).not.toHaveBeenCalled()
52+
})
53+
54+
it('returns non-Ok results without a code unchanged', async () => {
55+
const result = makeErrorResult({ code: undefined })
56+
57+
const resolved = await resolveActionResult(deviceId, result)
58+
59+
expect(resolved).toBe(result)
60+
expect(PeripheralDevices.findOneAsync).not.toHaveBeenCalled()
61+
})
62+
63+
it('interpolates a matching string template from deviceActionMessages', async () => {
64+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
65+
name: 'Playout Gateway',
66+
studioAndConfigId: { studioId, configId: 'config0' },
67+
} as any)
68+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue({ blueprintId: 'blueprint0' } as any)
69+
jest.spyOn(Blueprints, 'findOneAsync').mockResolvedValue({
70+
_id: 'blueprint0',
71+
name: 'test',
72+
code: '',
73+
} as any)
74+
mockEvalBlueprint.mockReturnValue({
75+
deviceActionMessages: {
76+
[ACTION_ERROR_CODE]: 'Failed to trigger graphics at {{url}}: {{errorMessage}}',
77+
},
78+
} as any)
79+
80+
const resolved = await resolveActionResult(deviceId, makeErrorResult())
81+
82+
expect(resolved.response).toEqual({
83+
key: 'Failed to trigger graphics at http://graphics/api: connection refused',
84+
})
85+
})
86+
87+
it('uses a DeviceStatusMessageFunction from deviceActionMessages', async () => {
88+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
89+
name: 'Playout Gateway',
90+
studioAndConfigId: { studioId, configId: 'config0' },
91+
} as any)
92+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue({ blueprintId: 'blueprint0' } as any)
93+
jest.spyOn(Blueprints, 'findOneAsync').mockResolvedValue({
94+
_id: 'blueprint0',
95+
name: 'test',
96+
code: '',
97+
} as any)
98+
mockEvalBlueprint.mockReturnValue({
99+
deviceActionMessages: {
100+
[ACTION_ERROR_CODE]: (context: DeviceStatusContext) =>
101+
`${context.deviceName} could not reach ${context.url as string}`,
102+
},
103+
} as any)
104+
105+
const resolved = await resolveActionResult(deviceId, makeErrorResult())
106+
107+
expect(resolved.response).toEqual({
108+
key: 'Playout Gateway could not reach http://graphics/api',
109+
})
110+
})
111+
112+
it('clears the response when the blueprint suppresses the message', async () => {
113+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
114+
name: 'Playout Gateway',
115+
studioAndConfigId: { studioId, configId: 'config0' },
116+
} as any)
117+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue({ blueprintId: 'blueprint0' } as any)
118+
jest.spyOn(Blueprints, 'findOneAsync').mockResolvedValue({
119+
_id: 'blueprint0',
120+
name: 'test',
121+
code: '',
122+
} as any)
123+
mockEvalBlueprint.mockReturnValue({
124+
deviceActionMessages: {
125+
[ACTION_ERROR_CODE]: '',
126+
},
127+
} as any)
128+
129+
const original = makeErrorResult()
130+
const resolved = await resolveActionResult(deviceId, original)
131+
132+
expect(resolved).toMatchObject({
133+
result: TSR.ActionExecutionResultCode.Error,
134+
code: ACTION_ERROR_CODE,
135+
context: original.context,
136+
response: { key: '' },
137+
})
138+
})
139+
140+
it('returns the original result when there is no matching deviceActionMessages entry', async () => {
141+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
142+
name: 'Playout Gateway',
143+
studioAndConfigId: { studioId, configId: 'config0' },
144+
} as any)
145+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue({ blueprintId: 'blueprint0' } as any)
146+
jest.spyOn(Blueprints, 'findOneAsync').mockResolvedValue({
147+
_id: 'blueprint0',
148+
name: 'test',
149+
code: '',
150+
} as any)
151+
mockEvalBlueprint.mockReturnValue({
152+
deviceActionMessages: {},
153+
} as any)
154+
155+
const result = makeErrorResult()
156+
const resolved = await resolveActionResult(deviceId, result)
157+
158+
expect(resolved).toBe(result)
159+
})
160+
161+
it('returns the original result when the device has no studio', async () => {
162+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
163+
name: 'Playout Gateway',
164+
} as any)
165+
166+
const result = makeErrorResult()
167+
const resolved = await resolveActionResult(deviceId, result)
168+
169+
expect(resolved).toBe(result)
170+
expect(Studios.findOneAsync).not.toHaveBeenCalled()
171+
})
172+
173+
it('returns the original result when blueprint lookup fails', async () => {
174+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
175+
name: 'Playout Gateway',
176+
studioAndConfigId: { studioId, configId: 'config0' },
177+
} as any)
178+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue(undefined)
179+
180+
const result = makeErrorResult()
181+
const resolved = await resolveActionResult(deviceId, result)
182+
183+
expect(resolved).toBe(result)
184+
})
185+
})

0 commit comments

Comments
 (0)