@@ -354,6 +354,87 @@ describe('Confluence server selector adapters', () => {
354354 ) . rejects . toMatchObject ( { name : 'SelectorConnectionUnavailableError' , status : 403 } )
355355 } )
356356
357+ it . each ( [
358+ { failedStatus : 'current' , status : 401 } ,
359+ { failedStatus : 'current' , status : 403 } ,
360+ { failedStatus : 'current' , status : 429 } ,
361+ { failedStatus : 'archived' , status : 401 } ,
362+ { failedStatus : 'archived' , status : 403 } ,
363+ { failedStatus : 'archived' , status : 429 } ,
364+ ] ) (
365+ 'preserves $status from the $failedStatus lookup when the other status has no matching space' ,
366+ async ( { failedStatus, status } ) => {
367+ mockFetch . mockImplementation ( ( input : URL ) =>
368+ new URL ( input ) . searchParams . get ( 'status' ) === failedStatus
369+ ? new Response ( null , { status } )
370+ : Response . json ( { results : [ { id : '99999' , key : 'OTHER' , name : 'Other space' } ] } )
371+ )
372+
373+ await expect (
374+ confluenceSelectorAttachments [ 'confluence.spaces' ] . execute ( spaceDetailArgs ( ) )
375+ ) . rejects . toMatchObject ( {
376+ name :
377+ status === 429 ? 'SelectorOptionsUnavailableError' : 'SelectorConnectionUnavailableError' ,
378+ status,
379+ } )
380+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 2 )
381+ }
382+ )
383+
384+ it . each ( [ 'current' , 'archived' ] as const ) (
385+ 'returns an exact %s match even when the other status lookup fails' ,
386+ async ( matchingStatus ) => {
387+ mockFetch . mockImplementation ( ( input : URL ) =>
388+ new URL ( input ) . searchParams . get ( 'status' ) === matchingStatus
389+ ? Response . json ( { results : [ { id : '12345' , key : 'ENG' , name : 'Engineering' } ] } )
390+ : new Response ( null , { status : 429 } )
391+ )
392+
393+ await expect (
394+ confluenceSelectorAttachments [ 'confluence.spaces' ] . execute ( spaceDetailArgs ( ) )
395+ ) . resolves . toEqual ( {
396+ kind : 'detail' ,
397+ item : {
398+ id : 'ENG' ,
399+ label :
400+ matchingStatus === 'archived' ? 'Engineering (ENG) — archived' : 'Engineering (ENG)' ,
401+ } ,
402+ } )
403+ }
404+ )
405+
406+ it ( 'reports a space key missing only when both status lookups succeed without a match' , async ( ) => {
407+ mockFetch
408+ . mockResolvedValueOnce (
409+ Response . json ( { results : [ { id : '99999' , key : 'OTHER' , name : 'Other space' } ] } )
410+ )
411+ . mockResolvedValueOnce ( Response . json ( { results : [ ] } ) )
412+
413+ await expect (
414+ confluenceSelectorAttachments [ 'confluence.spaces' ] . execute ( spaceDetailArgs ( ) )
415+ ) . resolves . toEqual ( { kind : 'detail' , item : null } )
416+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 2 )
417+ } )
418+
419+ it ( 'sanitizes an arbitrary partial lookup failure rather than reporting the key missing' , async ( ) => {
420+ const fetchProviderJson = vi
421+ . spyOn ( providerHttp , 'fetchProviderJson' )
422+ . mockRejectedValueOnce ( new Error ( 'raw provider failure' ) )
423+ . mockResolvedValueOnce ( { results : [ ] } )
424+
425+ try {
426+ await expect (
427+ confluenceSelectorAttachments [ 'confluence.spaces' ] . execute ( spaceDetailArgs ( ) )
428+ ) . rejects . toMatchObject ( {
429+ name : 'SelectorOptionsUnavailableError' ,
430+ message : 'Options unavailable' ,
431+ status : 502 ,
432+ } )
433+ } finally {
434+ fetchProviderJson . mockRestore ( )
435+ }
436+ } )
437+
357438 it ( 'preserves the first safe provider failure when both space detail requests fail' , async ( ) => {
358439 mockFetch
359440 . mockResolvedValueOnce ( new Response ( null , { status : 401 } ) )
0 commit comments