@@ -31,6 +31,8 @@ const mocks = vi.hoisted(() => ({
3131 isForkingAvailableForWorkspace : vi . fn ( ) ,
3232 isOrganizationOnEnterprisePlan : vi . fn ( ) ,
3333 isOrganizationSettingsSectionAvailable : vi . fn ( ) ,
34+ isScopedCredentialGroupsAvailable : vi . fn ( ) ,
35+ isKnowledgeMemberAccessAvailable : vi . fn ( ) ,
3436 isPlatformAdmin : vi . fn ( ) ,
3537 resolveVerifiedUserAccessControlContext : vi . fn ( ) ,
3638 resolveWorkspaceNavigation : vi . fn ( ) ,
@@ -43,6 +45,7 @@ vi.mock('@/components/settings/navigation', () => ({
4345 UNIFIED_TO_ORGANIZATION_SECTION : {
4446 organization : 'members' ,
4547 billing : 'billing' ,
48+ 'connected-accounts' : 'connected-accounts' ,
4649 'access-control' : 'access-control' ,
4750 } ,
4851 UNIFIED_TO_WORKSPACE_SECTION : {
@@ -60,6 +63,12 @@ vi.mock('@/lib/billing/core/subscription', () => ({
6063vi . mock ( '@/lib/core/config/deployment-shape' , ( ) => ( {
6164 getDeploymentShape : ( ) => mocks . deploymentShape ,
6265} ) )
66+ vi . mock ( '@/lib/credential-groups/scoped-availability' , ( ) => ( {
67+ isScopedCredentialGroupsAvailable : mocks . isScopedCredentialGroupsAvailable ,
68+ } ) )
69+ vi . mock ( '@/lib/knowledge/access/availability' , ( ) => ( {
70+ isKnowledgeMemberAccessAvailable : mocks . isKnowledgeMemberAccessAvailable ,
71+ } ) )
6372vi . mock ( '@/lib/organizations/settings-access' , ( ) => ( {
6473 canOpenOrganizationSettingsSection : mocks . canOpenOrganizationSettingsSection ,
6574} ) )
@@ -114,6 +123,8 @@ describe('authorizeWorkspaceSettingsSection', () => {
114123 mocks . isForkingAvailableForWorkspace . mockResolvedValue ( true )
115124 mocks . isOrganizationOnEnterprisePlan . mockResolvedValue ( true )
116125 mocks . isOrganizationSettingsSectionAvailable . mockReturnValue ( true )
126+ mocks . isScopedCredentialGroupsAvailable . mockResolvedValue ( true )
127+ mocks . isKnowledgeMemberAccessAvailable . mockResolvedValue ( false )
117128 mocks . isPlatformAdmin . mockResolvedValue ( true )
118129 mocks . canOpenOrganizationSettingsSection . mockResolvedValue ( true )
119130 mocks . resolveVerifiedUserAccessControlContext . mockResolvedValue ( { config : { } } )
@@ -238,6 +249,65 @@ describe('authorizeWorkspaceSettingsSection', () => {
238249 expect ( mocks . canOpenOrganizationSettingsSection ) . not . toHaveBeenCalled ( )
239250 } )
240251
252+ it . each ( [
253+ { groups : true , search : false , allowed : true } ,
254+ { groups : false , search : false , allowed : false } ,
255+ { groups : true , search : true , allowed : false } ,
256+ { groups : false , search : true , allowed : false } ,
257+ ] ) (
258+ 'gates Connected accounts with organization groups=$groups and search=$search' ,
259+ async ( { groups, search, allowed } ) => {
260+ mocks . checkWorkspaceAccess . mockResolvedValue ( ORGANIZATION_ACCESS )
261+ mocks . isScopedCredentialGroupsAvailable . mockResolvedValue ( groups )
262+ mocks . isKnowledgeMemberAccessAvailable . mockResolvedValue ( search )
263+
264+ await expect ( authorize ( 'connected-accounts' ) ) . resolves . toEqual (
265+ allowed ? { allowed : true } : { allowed : false , disposition : 'redirect-general' }
266+ )
267+ expect ( mocks . canOpenOrganizationSettingsSection ) . toHaveBeenCalledWith (
268+ 'organization-1' ,
269+ 'viewer-1' ,
270+ 'connected-accounts'
271+ )
272+ expect ( mocks . isScopedCredentialGroupsAvailable ) . toHaveBeenCalledWith ( {
273+ kind : 'organization' ,
274+ organizationId : 'organization-1' ,
275+ } )
276+ if ( groups ) {
277+ expect ( mocks . isKnowledgeMemberAccessAvailable ) . toHaveBeenCalledWith ( {
278+ organizationId : 'organization-1' ,
279+ } )
280+ }
281+ expect ( mocks . isOrganizationOnEnterprisePlan ) . not . toHaveBeenCalled ( )
282+ }
283+ )
284+
285+ it ( 'does not infer organization admin access from workspace admin access' , async ( ) => {
286+ mocks . checkWorkspaceAccess . mockResolvedValue ( ORGANIZATION_ACCESS )
287+ mocks . canOpenOrganizationSettingsSection . mockResolvedValue ( false )
288+
289+ await expect ( authorize ( 'connected-accounts' ) ) . resolves . toEqual ( {
290+ allowed : false ,
291+ disposition : 'redirect-general' ,
292+ } )
293+ expect ( mocks . isScopedCredentialGroupsAvailable ) . not . toHaveBeenCalled ( )
294+ } )
295+
296+ it ( 'requires a host organization for Connected accounts' , async ( ) => {
297+ await expect ( authorize ( 'connected-accounts' ) ) . resolves . toEqual ( {
298+ allowed : false ,
299+ disposition : 'redirect-general' ,
300+ } )
301+ expect ( mocks . canOpenOrganizationSettingsSection ) . not . toHaveBeenCalled ( )
302+ } )
303+
304+ it ( 'propagates feature lookup failures instead of opening Connected accounts' , async ( ) => {
305+ mocks . checkWorkspaceAccess . mockResolvedValue ( ORGANIZATION_ACCESS )
306+ mocks . isKnowledgeMemberAccessAvailable . mockRejectedValue ( new Error ( 'Feature lookup failed' ) )
307+
308+ await expect ( authorize ( 'connected-accounts' ) ) . rejects . toThrow ( 'Feature lookup failed' )
309+ } )
310+
241311 it ( 'requires current organization access and plan availability for enterprise sections' , async ( ) => {
242312 mocks . checkWorkspaceAccess . mockResolvedValue ( ORGANIZATION_ACCESS )
243313 mocks . canOpenOrganizationSettingsSection . mockResolvedValue ( false )
0 commit comments