@@ -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} ) ;
0 commit comments