Skip to content

Commit 39882c8

Browse files
authored
✅ Test probe error handling when addProbe fails (#4754)
1 parent 3672f41 commit 39882c8

2 files changed

Lines changed: 121 additions & 10 deletions

File tree

packages/browser-debugger/src/domain/deliveryApi.spec.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,106 @@ describe('deliveryApi', () => {
256256
expect(errorSpy).toHaveBeenCalledWith('Failed to add probe invalid-capture-expression:', jasmine.any(Error))
257257
})
258258

259+
it('should keep adding the other probes when one probe in the same response fails to initialize', async () => {
260+
respondWith({
261+
nextCursor: 'cursor-1',
262+
updates: [
263+
createProbe({ id: 'valid-before', where: { typeName: 'test.js', methodName: 'before' } }),
264+
createProbe({
265+
id: 'invalid',
266+
where: { typeName: 'test.js', methodName: 'invalid' },
267+
captureSnapshot: false,
268+
captureExpressions: [
269+
{
270+
name: 'invalid expr',
271+
expr: { dsl: 'not a valid identifier!', json: { ref: 'not a valid identifier!' } },
272+
},
273+
],
274+
}),
275+
createProbe({ id: 'valid-after', where: { typeName: 'test.js', methodName: 'after' } }),
276+
],
277+
deletions: [],
278+
})
279+
280+
startDeliveryApiPolling(makeConfig())
281+
await flushPromises()
282+
283+
// The failing probe must not prevent the surrounding probes from being registered.
284+
expect(getProbes('test.js;before')).toBeDefined()
285+
expect(getProbes('test.js;after')).toBeDefined()
286+
expect(getProbes('test.js;invalid')).toBeUndefined()
287+
expect(errorSpy).toHaveBeenCalledOnceWith('Failed to add probe invalid:', jasmine.any(Error))
288+
})
289+
290+
it('should not track a probe that failed to initialize, so a later deletion is a no-op', async () => {
291+
respondWith({
292+
nextCursor: 'cursor-1',
293+
updates: [
294+
createProbe({
295+
id: 'invalid',
296+
captureSnapshot: false,
297+
captureExpressions: [
298+
{
299+
name: 'invalid expr',
300+
expr: { dsl: 'not a valid identifier!', json: { ref: 'not a valid identifier!' } },
301+
},
302+
],
303+
}),
304+
],
305+
deletions: [],
306+
})
307+
308+
startDeliveryApiPolling(makeConfig())
309+
await flushPromises()
310+
expect(errorSpy).toHaveBeenCalledOnceWith('Failed to add probe invalid:', jasmine.any(Error))
311+
312+
errorSpy.calls.reset()
313+
314+
// The failed probe was never tracked, so deleting it does nothing and must
315+
// not attempt a removal (which would log a "Failed to remove probe" error).
316+
respondWith({
317+
nextCursor: 'cursor-2',
318+
updates: [],
319+
deletions: ['invalid'],
320+
})
321+
clock.tick(5000)
322+
await flushPromises()
323+
324+
expect(errorSpy).not.toHaveBeenCalled()
325+
})
326+
327+
it('should keep polling after a probe fails to initialize', async () => {
328+
respondWith({
329+
nextCursor: 'cursor-1',
330+
updates: [
331+
createProbe({
332+
id: 'invalid',
333+
captureSnapshot: false,
334+
captureExpressions: [
335+
{
336+
name: 'invalid expr',
337+
expr: { dsl: 'not a valid identifier!', json: { ref: 'not a valid identifier!' } },
338+
},
339+
],
340+
}),
341+
],
342+
deletions: [],
343+
})
344+
345+
startDeliveryApiPolling(makeConfig({ pollInterval: 5000 }))
346+
await flushPromises()
347+
expect(errorSpy).toHaveBeenCalledOnceWith('Failed to add probe invalid:', jasmine.any(Error))
348+
349+
// A per-probe compilation error is a data error, not a transport failure:
350+
// it must not trip the circuit breaker or stop polling.
351+
const callsBefore = fetchSpy.calls.count()
352+
clock.tick(5000)
353+
await flushPromises()
354+
355+
expect(fetchSpy.calls.count()).toBe(callsBefore + 1)
356+
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringMatching(/circuit breaker/i))
357+
})
358+
259359
it('should ignore log probes without a where clause', async () => {
260360
respondWith({
261361
nextCursor: 'cursor-1',

packages/browser-debugger/src/domain/probes.spec.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ describe('probes', () => {
5353
const retrieved = getProbes('non-existent')
5454
expect(retrieved).toBeUndefined()
5555
})
56+
57+
it('should not register a probe when initialization throws', () => {
58+
const probe = createProbe({
59+
when: {
60+
dsl: 'invalid',
61+
json: { invalidOp: 'bad' } as any,
62+
},
63+
})
64+
65+
expect(() => addProbe(probe)).toThrow()
66+
expect(getProbes(DEFAULT_PROBE_FUNCTION_ID)).toBeUndefined()
67+
})
5668
})
5769

5870
describe('removeProbe', () => {
@@ -156,25 +168,24 @@ describe('probes', () => {
156168
)
157169
})
158170

159-
it('should not add probe when condition compilation fails', () => {
171+
it('should throw when condition compilation fails', () => {
172+
const probe = createProbe({
173+
when: {
174+
dsl: 'invalid',
175+
json: { invalidOp: 'bad' } as any,
176+
},
177+
})
178+
160179
let error: unknown
161180
try {
162-
addProbe(
163-
createProbe({
164-
when: {
165-
dsl: 'invalid',
166-
json: { invalidOp: 'bad' } as any,
167-
},
168-
})
169-
)
181+
initializeProbe(probe)
170182
} catch (err) {
171183
error = err
172184
}
173185

174186
expect(error).toEqual(jasmine.any(Error))
175187
expect((error as Error).message).toContain('Cannot compile condition')
176188
expect((error as ErrorWithCause).cause).toEqual(jasmine.any(TypeError))
177-
expect(getProbes(DEFAULT_PROBE_FUNCTION_ID)).toBeUndefined()
178189
})
179190

180191
it('should calculate msBetweenSampling for snapshot probes', () => {

0 commit comments

Comments
 (0)