@@ -42,6 +42,8 @@ vi.mock('@/lib/credential-groups/scoped-availability', () => ({
4242
4343/** Each grant carries its own token, as a real exchange does, so a saved grant is identifiable. */
4444let grants = 0
45+ /** Sites the next grant reports, or `undefined` when its site listing failed. */
46+ let grantSites : Array < { name : string ; url : string } > | undefined
4547
4648vi . mock ( '@/lib/credential-groups/provider-registry' , async ( importOriginal ) => {
4749 const { getCredentialGroupProviderId } = await import ( '@/lib/credential-groups/providers' )
@@ -64,7 +66,7 @@ vi.mock('@/lib/credential-groups/provider-registry', async (importOriginal) => {
6466 providerSubjectId : subject ,
6567 providerTenantId : tenant || null ,
6668 displayName : code ,
67- metadata : { email : code } ,
69+ metadata : { email : code , ... ( grantSites ? { sites : grantSites } : { } ) } ,
6870 accessToken : `token-for-${ code } -${ ( grants += 1 ) } ` ,
6971 grantedScopes : POLICY . requiredScopes ,
7072 accessTokenExpiresAt : null ,
@@ -230,6 +232,7 @@ beforeAll(async () => {
230232} )
231233
232234beforeEach ( async ( ) => {
235+ grantSites = undefined
233236 await db . delete ( credential ) . where ( eq ( credential . organizationId , organizationId ) )
234237} )
235238
@@ -383,6 +386,52 @@ describe('adding another site to an Atlassian login', () => {
383386 expect ( ( await viewer ( ) ) ?. sites ) . toEqual ( [ { name : 'Acme' , url : 'https://acme.example.net' } ] )
384387 } )
385388
389+ it ( 'never lets oversized stored sites break the account list' , async ( ) => {
390+ const account = await connect ( 'person@atlassian' , { kind : 'create' } , CONFLUENCE )
391+ const site = ( index : number ) => ( {
392+ name : `Site ${ index } ` ,
393+ url : `https://s${ index } .example.net` ,
394+ } )
395+ await db
396+ . update ( credential )
397+ . set ( {
398+ providerMetadata : sql `${ JSON . stringify ( {
399+ email : 'person@atlassian' ,
400+ sites : [
401+ { name : 'x' . repeat ( 600 ) , url : 'https://long-name.example.net' } ,
402+ { name : 'Long URL' , url : `https://example.net/${ 'x' . repeat ( 2100 ) } ` } ,
403+ ...Array . from ( { length : 60 } , ( _ , index ) => site ( index ) ) ,
404+ ] ,
405+ } ) } ::jsonb`,
406+ } )
407+ . where ( eq ( credential . id , account . credentialId ) )
408+
409+ const [ viewer ] = await listViewerOrganizationAccounts ( { organizationId, userId } )
410+ expect ( viewer ?. sites ) . toHaveLength ( 50 )
411+ expect ( viewer ?. sites [ 0 ] ) . toEqual ( site ( 0 ) )
412+ } )
413+
414+ it ( 'keeps the recorded sites when a reconnect cannot list them' , async ( ) => {
415+ grantSites = [ { name : 'Acme' , url : 'https://acme.example.net' } ]
416+ const account = await connect ( 'person@atlassian' , { kind : 'create' } , CONFLUENCE )
417+ const reconnect = ( ) =>
418+ connect (
419+ 'person@atlassian' ,
420+ { kind : 'reconnect' , credentialId : account . credentialId } ,
421+ CONFLUENCE
422+ )
423+ const sites = async ( ) =>
424+ ( await listViewerOrganizationAccounts ( { organizationId, userId } ) ) [ 0 ] ?. sites
425+
426+ grantSites = undefined
427+ await reconnect ( )
428+ expect ( await sites ( ) ) . toEqual ( [ { name : 'Acme' , url : 'https://acme.example.net' } ] )
429+
430+ grantSites = [ ]
431+ await reconnect ( )
432+ expect ( await sites ( ) ) . toEqual ( [ ] )
433+ } )
434+
386435 it ( 'still adds a different Atlassian login as its own account' , async ( ) => {
387436 await connect ( 'person@atlassian' , { kind : 'create' } , CONFLUENCE )
388437 await connect ( 'other@atlassian' , { kind : 'create' } , CONFLUENCE )
0 commit comments