Skip to content

Commit 39dceca

Browse files
Add error handling tests and improve exhaustive type checking
1 parent a29b5d3 commit 39dceca

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

packages/bubble-core/src/bubbles/service-bubble/olostep.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,96 @@ describe('OlostepBubble', () => {
351351
expect(res.data.answer).toBeDefined();
352352
});
353353
});
354+
355+
//
356+
// ERROR HANDLING
357+
//
358+
describe('Error Handling', () => {
359+
it('should handle API request failures (non-2xx responses)', async () => {
360+
mockFetch.mockResolvedValueOnce({
361+
ok: false,
362+
status: 401,
363+
text: async () => JSON.stringify({ error: 'Invalid API key' }),
364+
});
365+
366+
const bubble = new OlostepBubble({
367+
operation: 'scrape',
368+
url: 'https://example.com',
369+
credentials: createTestCredentials(),
370+
});
371+
372+
const res = await bubble.action();
373+
374+
expect(res.success).toBe(false);
375+
expect(res.error).toBeDefined();
376+
expect(res.error).not.toBe('');
377+
});
378+
379+
it('should handle network errors (fetch throws exception)', async () => {
380+
mockFetch.mockRejectedValueOnce(
381+
new Error('Network error: Failed to fetch')
382+
);
383+
384+
const bubble = new OlostepBubble({
385+
operation: 'scrape',
386+
url: 'https://example.com',
387+
credentials: createTestCredentials(),
388+
});
389+
390+
const res = await bubble.action();
391+
392+
expect(res.success).toBe(false);
393+
expect(res.error).toContain('Network error');
394+
});
395+
396+
it('should handle malformed response data gracefully', async () => {
397+
mockFetch.mockResolvedValueOnce({
398+
ok: true,
399+
text: async () => 'not valid json {{{',
400+
});
401+
402+
const bubble = new OlostepBubble({
403+
operation: 'scrape',
404+
url: 'https://example.com',
405+
credentials: createTestCredentials(),
406+
});
407+
408+
const res = await bubble.action();
409+
410+
// Implementation gracefully handles malformed JSON by returning raw text
411+
expect(res.data.operation).toBe('scrape');
412+
});
413+
414+
it('should handle missing credentials gracefully', async () => {
415+
const bubble = new OlostepBubble({
416+
operation: 'scrape',
417+
url: 'https://example.com',
418+
// No credentials provided
419+
});
420+
421+
const res = await bubble.action();
422+
423+
expect(res.success).toBe(false);
424+
expect(res.error).toContain('OLOSTEP_API_KEY');
425+
});
426+
427+
it('should handle rate limiting responses', async () => {
428+
mockFetch.mockResolvedValueOnce({
429+
ok: false,
430+
status: 429,
431+
text: async () => JSON.stringify({ error: 'Rate limit exceeded' }),
432+
});
433+
434+
const bubble = new OlostepBubble({
435+
operation: 'scrape',
436+
url: 'https://example.com',
437+
credentials: createTestCredentials(),
438+
});
439+
440+
const res = await bubble.action();
441+
442+
expect(res.success).toBe(false);
443+
expect(res.error).toBeDefined();
444+
});
445+
});
354446
});

packages/bubble-core/src/bubbles/service-bubble/olostep.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,8 +571,11 @@ export class OlostepBubble extends ServiceBubble<OlostepParams, OlostepResult> {
571571
return { operation: 'map', ...base };
572572
case 'answer':
573573
return { operation: 'answer', ...base };
574-
default:
574+
default: {
575+
// Exhaustive check: TypeScript will error if a case is missing
576+
const _exhaustiveCheck: never = operation;
575577
return { operation: 'scrape', ...base };
578+
}
576579
}
577580
}
578581
}

0 commit comments

Comments
 (0)