Skip to content

Commit b8cf11c

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 b8cf11c

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { 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) => `${context.deviceName} could not reach ${context.url as string}`,
101+
},
102+
} as any)
103+
104+
const resolved = await resolveActionResult(deviceId, makeErrorResult())
105+
106+
expect(resolved.response).toEqual({
107+
key: 'Playout Gateway could not reach http://graphics/api',
108+
})
109+
})
110+
111+
it('clears the response when the blueprint suppresses the message', async () => {
112+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
113+
name: 'Playout Gateway',
114+
studioAndConfigId: { studioId, configId: 'config0' },
115+
} as any)
116+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue({ blueprintId: 'blueprint0' } as any)
117+
jest.spyOn(Blueprints, 'findOneAsync').mockResolvedValue({
118+
_id: 'blueprint0',
119+
name: 'test',
120+
code: '',
121+
} as any)
122+
mockEvalBlueprint.mockReturnValue({
123+
deviceActionMessages: {
124+
[ACTION_ERROR_CODE]: '',
125+
},
126+
} as any)
127+
128+
const original = makeErrorResult()
129+
const resolved = await resolveActionResult(deviceId, original)
130+
131+
expect(resolved).toMatchObject({
132+
result: TSR.ActionExecutionResultCode.Error,
133+
code: ACTION_ERROR_CODE,
134+
context: original.context,
135+
response: { key: '' },
136+
})
137+
})
138+
139+
it('returns the original result when there is no matching deviceActionMessages entry', async () => {
140+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
141+
name: 'Playout Gateway',
142+
studioAndConfigId: { studioId, configId: 'config0' },
143+
} as any)
144+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue({ blueprintId: 'blueprint0' } as any)
145+
jest.spyOn(Blueprints, 'findOneAsync').mockResolvedValue({
146+
_id: 'blueprint0',
147+
name: 'test',
148+
code: '',
149+
} as any)
150+
mockEvalBlueprint.mockReturnValue({
151+
deviceActionMessages: {},
152+
} as any)
153+
154+
const result = makeErrorResult()
155+
const resolved = await resolveActionResult(deviceId, result)
156+
157+
expect(resolved).toBe(result)
158+
})
159+
160+
it('returns the original result when the device has no studio', async () => {
161+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
162+
name: 'Playout Gateway',
163+
} as any)
164+
165+
const result = makeErrorResult()
166+
const resolved = await resolveActionResult(deviceId, result)
167+
168+
expect(resolved).toBe(result)
169+
expect(Studios.findOneAsync).not.toHaveBeenCalled()
170+
})
171+
172+
it('returns the original result when blueprint lookup fails', async () => {
173+
jest.spyOn(PeripheralDevices, 'findOneAsync').mockResolvedValue({
174+
name: 'Playout Gateway',
175+
studioAndConfigId: { studioId, configId: 'config0' },
176+
} as any)
177+
jest.spyOn(Studios, 'findOneAsync').mockResolvedValue(undefined)
178+
179+
const result = makeErrorResult()
180+
const resolved = await resolveActionResult(deviceId, result)
181+
182+
expect(resolved).toBe(result)
183+
})
184+
})

0 commit comments

Comments
 (0)