@@ -54,6 +54,10 @@ 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
5761 let javascript = ''
5862 let stylesheet = ''
5963 let origin = ''
@@ -174,9 +178,82 @@ test('source authorization returns to its desktop screen and refreshes live', as
174178 const state = generateShortId ( 32 )
175179 attempts . set ( state , session )
176180 startSessions . push ( session )
181+ if ( holdSlackStart ) {
182+ response . on ( 'close' , ( ) => {
183+ if ( ! response . writableEnded ) canceledSlackRequests ++
184+ } )
185+ return
186+ }
177187 json ( { authorizationUrl : `${ origin } /provider?state=${ state } ` } )
178188 return
179189 }
190+ if ( path === '/api/knowledge/sim-search/personal-integrations' ) {
191+ if ( request . method === 'POST' ) {
192+ const { oauthCompletionId } = await body ( )
193+ personalAttempts . set ( oauthCompletionId , { session, completed : false } )
194+ json ( {
195+ success : true ,
196+ data : { url : `${ origin } /personal-provider?completionId=${ oauthCompletionId } ` } ,
197+ } )
198+ } else if ( personalInventoryFailed ) {
199+ json ( { error : 'Inventory temporarily unavailable' } , 503 )
200+ } else {
201+ const attempt = personalAttempts . get ( url . searchParams . get ( 'completionId' ) ?? '' )
202+ const connected = attempt ?. completed === true
203+ json ( {
204+ success : true ,
205+ data : {
206+ completedCredentialId : connected ? 'fixture-personal-account' : null ,
207+ connections : connected
208+ ? [
209+ {
210+ name : 'Slack' ,
211+ providerId : 'slack' ,
212+ connectorType : 'slack' ,
213+ description : '' ,
214+ accounts : [
215+ {
216+ credentialId : 'fixture-personal-account' ,
217+ displayName : 'Fixture' ,
218+ status : 'connected' ,
219+ action : null ,
220+ } ,
221+ ] ,
222+ connectionStatus : 'connected' ,
223+ action : null ,
224+ } ,
225+ ]
226+ : [ ] ,
227+ available : [
228+ {
229+ name : 'Slack' ,
230+ description : '' ,
231+ target : {
232+ type : 'link' ,
233+ provider : 'slack' ,
234+ connectorType : 'slack' ,
235+ connectionMode : 'live' ,
236+ optionId : 'fixture-option' ,
237+ } ,
238+ } ,
239+ ] ,
240+ nextCursor : null ,
241+ } ,
242+ } )
243+ }
244+ return
245+ }
246+ if ( path === '/personal-callback' ) {
247+ const completionId = url . searchParams . get ( 'completionId' ) ?? ''
248+ const attempt = personalAttempts . get ( completionId )
249+ if ( ! attempt || attempt . session !== session ) {
250+ json ( { error : 'Wrong attempt' } , 403 )
251+ return
252+ }
253+ attempt . completed = true
254+ redirect ( `/credential-groups/complete?completionId=${ completionId } ` )
255+ return
256+ }
180257 if ( path === '/api/knowledge/slack/oauth/callback' ) {
181258 const state = url . searchParams . get ( 'state' ) ?? ''
182259 callbackSessions . push ( session )
@@ -298,6 +375,12 @@ test('source authorization returns to its desktop screen and refreshes live', as
298375 )
299376 return
300377 }
378+ if ( path === '/personal-provider' ) {
379+ response . end (
380+ `<!doctype html><a href="/personal-callback?completionId=${ url . searchParams . get ( 'completionId' ) } ">Authorize personal Search</a>`
381+ )
382+ return
383+ }
301384 if ( path === '/github-provider' ) {
302385 response . end (
303386 `<!doctype html><a href="/github-callback?setupId=${ url . searchParams . get ( 'setupId' ) } ">Authorize GitHub</a>`
@@ -570,6 +653,61 @@ test('source authorization returns to its desktop screen and refreshes live', as
570653 await expect ( web ) . toHaveURL ( `${ origin } /o/fixture-organization/integrations` )
571654 await expect ( web . getByLabel ( 'Account count' ) ) . toHaveText ( '1' )
572655 } )
656+ await check ( 'canceling Slack setup aborts the pending web HTTP request' , async ( ) => {
657+ holdSlackStart = true
658+ const starts = startSessions . length
659+ try {
660+ await web . getByRole ( 'button' , { name : 'Connect Slack' , exact : true } ) . click ( )
661+ await expect . poll ( ( ) => startSessions . length ) . toBe ( starts + 1 )
662+ await web . getByRole ( 'button' , { name : 'Cancel Slack request' , exact : true } ) . click ( )
663+ await expect . poll ( ( ) => canceledSlackRequests ) . toBe ( 1 )
664+ await expect ( web . getByLabel ( 'Connection' , { exact : true } ) ) . toHaveText ( 'error' )
665+ } finally {
666+ holdSlackStart = false
667+ }
668+ } )
669+ await check (
670+ 'desktop Search preserves pending receipts after inventory failure and allows cancellation/retry' ,
671+ async ( ) => {
672+ const previousOpens = ( await opened ( ) ) . length
673+ await page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } ) . click ( )
674+ await expect . poll ( async ( ) => ( await opened ( ) ) . length ) . toBe ( previousOpens + 1 )
675+ await external . goto ( ( await opened ( ) ) [ previousOpens ] )
676+ await external . getByRole ( 'link' , { name : 'Authorize personal Search' } ) . waitFor ( )
677+ personalInventoryFailed = true
678+ await external . getByRole ( 'link' , { name : 'Authorize personal Search' } ) . click ( )
679+ await expect ( external ) . toHaveURL ( `${ origin } /desktop/done?kind=connect` )
680+ await expect ( page . getByLabel ( 'Personal Search inventory error' ) ) . toHaveText (
681+ 'Inventory temporarily unavailable'
682+ )
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+ return entry ? ( JSON . parse ( entry [ 1 ] ) . completionId as string ) : null
692+ } )
693+ const pendingReceipt = await receipt ( )
694+ expect ( pendingReceipt ) . toBeTruthy ( )
695+ await page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } ) . click ( )
696+ expect ( await receipt ( ) ) . toBe ( pendingReceipt )
697+ await expect ( page . getByLabel ( 'Personal Search pending' ) ) . toHaveText ( 'true' )
698+ await page . getByRole ( 'button' , { name : 'Cancel personal Search' , exact : true } ) . click ( )
699+ await expect ( page . getByLabel ( 'Personal Search pending' ) ) . toHaveText ( 'false' )
700+ personalInventoryFailed = false
701+ await page . getByRole ( 'button' , { name : 'Retry personal inventory' , exact : true } ) . click ( )
702+ await page . getByRole ( 'button' , { name : 'Connect personal Search' , exact : true } ) . click ( )
703+ await expect . poll ( async ( ) => ( await opened ( ) ) . length ) . toBe ( previousOpens + 2 )
704+ expect ( await receipt ( ) ) . not . toBe ( pendingReceipt )
705+ await external . goto ( ( await opened ( ) ) [ previousOpens + 1 ] )
706+ await external . getByRole ( 'link' , { name : 'Authorize personal Search' } ) . click ( )
707+ await expect ( page . getByLabel ( 'Personal Search connected' ) ) . toHaveText ( 'true' )
708+ await expect ( page . getByLabel ( 'Personal Search pending' ) ) . toHaveText ( 'false' )
709+ }
710+ )
573711 await page . screenshot ( { path : test . info ( ) . outputPath ( 'source-connect-desktop.png' ) } )
574712 } finally {
575713 mkdirSync ( dirname ( reportPath ) , { recursive : true } )
0 commit comments