@@ -54,6 +54,11 @@ test('source authorization returns to its desktop screen and refreshes live', as
5454 const githubInventorySessions : string [ ] = [ ]
5555 let nativeCredentialVisible = false
5656 let installed = false
57+ let holdSlackStart = false
58+ let canceledSlackRequests = 0
59+ const personalAttempts = new Map < string , { session : string ; completed : boolean } > ( )
60+ let personalInventoryFailed = false
61+ let personalInventoryFailures = 0
5762 let javascript = ''
5863 let stylesheet = ''
5964 let origin = ''
@@ -174,9 +179,83 @@ test('source authorization returns to its desktop screen and refreshes live', as
174179 const state = generateShortId ( 32 )
175180 attempts . set ( state , session )
176181 startSessions . push ( session )
182+ if ( holdSlackStart ) {
183+ response . on ( 'close' , ( ) => {
184+ if ( ! response . writableEnded ) canceledSlackRequests ++
185+ } )
186+ return
187+ }
177188 json ( { authorizationUrl : `${ origin } /provider?state=${ state } ` } )
178189 return
179190 }
191+ if ( path === '/api/knowledge/sim-search/personal-integrations' ) {
192+ if ( request . method === 'POST' ) {
193+ const { oauthCompletionId } = await body ( )
194+ personalAttempts . set ( oauthCompletionId , { session, completed : false } )
195+ json ( {
196+ success : true ,
197+ data : { url : `${ origin } /personal-provider?completionId=${ oauthCompletionId } ` } ,
198+ } )
199+ } else if ( personalInventoryFailed ) {
200+ personalInventoryFailures ++
201+ json ( { error : 'Inventory temporarily unavailable' } , 503 )
202+ } else {
203+ const attempt = personalAttempts . get ( url . searchParams . get ( 'completionId' ) ?? '' )
204+ const connected = attempt ?. completed === true
205+ json ( {
206+ success : true ,
207+ data : {
208+ completedCredentialId : connected ? 'fixture-personal-account' : null ,
209+ connections : connected
210+ ? [
211+ {
212+ name : 'Slack' ,
213+ providerId : 'slack' ,
214+ connectorType : 'slack' ,
215+ description : '' ,
216+ accounts : [
217+ {
218+ credentialId : 'fixture-personal-account' ,
219+ displayName : 'Fixture' ,
220+ status : 'connected' ,
221+ action : null ,
222+ } ,
223+ ] ,
224+ connectionStatus : 'connected' ,
225+ action : null ,
226+ } ,
227+ ]
228+ : [ ] ,
229+ available : [
230+ {
231+ name : 'Slack' ,
232+ description : '' ,
233+ target : {
234+ type : 'link' ,
235+ provider : 'slack' ,
236+ connectorType : 'slack' ,
237+ connectionMode : 'live' ,
238+ optionId : 'fixture-option' ,
239+ } ,
240+ } ,
241+ ] ,
242+ nextCursor : null ,
243+ } ,
244+ } )
245+ }
246+ return
247+ }
248+ if ( path === '/personal-callback' ) {
249+ const completionId = url . searchParams . get ( 'completionId' ) ?? ''
250+ const attempt = personalAttempts . get ( completionId )
251+ if ( ! attempt || attempt . session !== session ) {
252+ json ( { error : 'Wrong attempt' } , 403 )
253+ return
254+ }
255+ attempt . completed = true
256+ redirect ( `/credential-groups/complete?completionId=${ completionId } ` )
257+ return
258+ }
180259 if ( path === '/api/knowledge/slack/oauth/callback' ) {
181260 const state = url . searchParams . get ( 'state' ) ?? ''
182261 callbackSessions . push ( session )
@@ -298,6 +377,12 @@ test('source authorization returns to its desktop screen and refreshes live', as
298377 )
299378 return
300379 }
380+ if ( path === '/personal-provider' ) {
381+ response . end (
382+ `<!doctype html><a href="/personal-callback?completionId=${ url . searchParams . get ( 'completionId' ) } ">Authorize personal Search</a>`
383+ )
384+ return
385+ }
301386 if ( path === '/github-provider' ) {
302387 response . end (
303388 `<!doctype html><a href="/github-callback?setupId=${ url . searchParams . get ( 'setupId' ) } ">Authorize GitHub</a>`
@@ -570,6 +655,68 @@ test('source authorization returns to its desktop screen and refreshes live', as
570655 await expect ( web ) . toHaveURL ( `${ origin } /o/fixture-organization/integrations` )
571656 await expect ( web . getByLabel ( 'Account count' ) ) . toHaveText ( '1' )
572657 } )
658+ await check ( 'canceling Slack setup aborts the pending web HTTP request' , async ( ) => {
659+ holdSlackStart = true
660+ const starts = startSessions . length
661+ try {
662+ await web . getByRole ( 'button' , { name : 'Connect Slack' , exact : true } ) . click ( )
663+ await expect . poll ( ( ) => startSessions . length ) . toBe ( starts + 1 )
664+ await web . getByRole ( 'button' , { name : 'Cancel Slack request' , exact : true } ) . click ( )
665+ await expect . poll ( ( ) => canceledSlackRequests ) . toBe ( 1 )
666+ await expect ( web . getByRole ( 'button' , { name : 'Connect Slack' , exact : true } ) ) . toBeEnabled ( )
667+ } finally {
668+ holdSlackStart = false
669+ }
670+ } )
671+ await check (
672+ 'desktop Search preserves pending receipts after inventory failure and allows cancellation/retry' ,
673+ async ( ) => {
674+ const previousOpens = ( await opened ( ) ) . length
675+ await page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } ) . click ( )
676+ await expect . poll ( async ( ) => ( await opened ( ) ) . length ) . toBe ( previousOpens + 1 )
677+ await external . goto ( ( await opened ( ) ) [ previousOpens ] )
678+ await external . getByRole ( 'link' , { name : 'Authorize personal Search' } ) . waitFor ( )
679+ personalInventoryFailed = true
680+ await external . getByRole ( 'link' , { name : 'Authorize personal Search' } ) . click ( )
681+ await expect ( external ) . toHaveURL ( `${ origin } /desktop/done?kind=connect` )
682+ await expect . poll ( ( ) => personalInventoryFailures ) . toBeGreaterThan ( 0 )
683+ await expect (
684+ page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } )
685+ ) . toBeEnabled ( )
686+ const receipt = ( ) =>
687+ page . evaluate ( ( ) => {
688+ const entry = Object . entries ( localStorage ) . find ( ( [ key ] ) =>
689+ key . startsWith ( 'sim.search-connection.' )
690+ )
691+ if ( ! entry ) return null
692+ const attempt : { completionId : string ; status : string ; credentialId ?: string } =
693+ JSON . parse ( entry [ 1 ] )
694+ return attempt
695+ } )
696+ const pendingReceipt = await receipt ( )
697+ expect ( pendingReceipt ) . toMatchObject ( { status : 'pending' } )
698+ await page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } ) . click ( )
699+ expect ( await receipt ( ) ) . toEqual ( pendingReceipt )
700+ await page . getByRole ( 'button' , { name : 'Cancel personal Search' , exact : true } ) . click ( )
701+ await expect
702+ . poll ( receipt )
703+ . toMatchObject ( { completionId : pendingReceipt ?. completionId , status : 'failed' } )
704+ personalInventoryFailed = false
705+ await page . getByRole ( 'button' , { name : 'Retry personal inventory' , exact : true } ) . click ( )
706+ await page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } ) . click ( )
707+ await expect . poll ( async ( ) => ( await opened ( ) ) . length ) . toBe ( previousOpens + 2 )
708+ const retryReceipt = await receipt ( )
709+ expect ( retryReceipt ) . toMatchObject ( { status : 'pending' } )
710+ expect ( retryReceipt ?. completionId ) . not . toBe ( pendingReceipt ?. completionId )
711+ await external . goto ( ( await opened ( ) ) [ previousOpens + 1 ] )
712+ await external . getByRole ( 'link' , { name : 'Authorize personal Search' } ) . click ( )
713+ await expect . poll ( receipt ) . toMatchObject ( {
714+ completionId : retryReceipt ?. completionId ,
715+ status : 'connected' ,
716+ credentialId : 'fixture-personal-account' ,
717+ } )
718+ }
719+ )
573720 await page . screenshot ( { path : test . info ( ) . outputPath ( 'source-connect-desktop.png' ) } )
574721 } finally {
575722 mkdirSync ( dirname ( reportPath ) , { recursive : true } )
0 commit comments