@@ -29,32 +29,35 @@ const mockGetAllowedIntegrationsFromEnv = vi.fn<() => string[] | null>()
2929const mockIsOrganizationOnEnterprisePlan = vi . fn < ( ) => Promise < boolean > > ( )
3030const mockGetProviderFromModel = vi . fn < ( model : string ) => string > ( )
3131
32- vi . doMock ( '@sim/db' , ( ) => databaseMock )
33- vi . doMock ( '@sim/db/schema' , ( ) => ( { } ) )
34- vi . doMock ( '@sim/logger' , ( ) => loggerMock )
35- vi . doMock ( 'drizzle-orm' , ( ) => drizzleOrmMock )
36- vi . doMock ( '@/lib/billing' , ( ) => ( {
32+ vi . mock ( '@sim/db' , ( ) => databaseMock )
33+ vi . mock ( '@sim/db/schema' , ( ) => ( { } ) )
34+ vi . mock ( '@sim/logger' , ( ) => loggerMock )
35+ vi . mock ( 'drizzle-orm' , ( ) => drizzleOrmMock )
36+ vi . mock ( '@/lib/billing' , ( ) => ( {
3737 isOrganizationOnEnterprisePlan : mockIsOrganizationOnEnterprisePlan ,
3838} ) )
39- vi . doMock ( '@/lib/core/config/feature-flags' , ( ) => ( {
39+ vi . mock ( '@/lib/core/config/feature-flags' , ( ) => ( {
4040 getAllowedIntegrationsFromEnv : mockGetAllowedIntegrationsFromEnv ,
4141 isAccessControlEnabled : false ,
4242 isHosted : false ,
4343} ) )
44- vi . doMock ( '@/lib/permission-groups/types' , ( ) => ( {
44+ vi . mock ( '@/lib/permission-groups/types' , ( ) => ( {
4545 DEFAULT_PERMISSION_GROUP_CONFIG ,
4646 parsePermissionGroupConfig : ( config : unknown ) => {
4747 if ( ! config || typeof config !== 'object' ) return DEFAULT_PERMISSION_GROUP_CONFIG
4848 return { ...DEFAULT_PERMISSION_GROUP_CONFIG , ...config }
4949 } ,
5050} ) )
51- vi . doMock ( '@/providers/utils' , ( ) => ( {
51+ vi . mock ( '@/providers/utils' , ( ) => ( {
5252 getProviderFromModel : mockGetProviderFromModel ,
5353} ) )
5454
55- const { IntegrationNotAllowedError, getUserPermissionConfig, validateBlockType } = await import (
56- './permission-check'
57- )
55+ import { getAllowedIntegrationsFromEnv } from '@/lib/core/config/feature-flags'
56+ import {
57+ getUserPermissionConfig ,
58+ IntegrationNotAllowedError ,
59+ validateBlockType ,
60+ } from './permission-check'
5861
5962describe ( 'IntegrationNotAllowedError' , ( ) => {
6063 it . concurrent ( 'creates error with correct name and message' , ( ) => {
@@ -104,6 +107,56 @@ describe('getUserPermissionConfig', () => {
104107 } )
105108} )
106109
110+ describe ( 'env allowlist fallback when userId is absent' , ( ) => {
111+ beforeEach ( ( ) => {
112+ vi . clearAllMocks ( )
113+ } )
114+
115+ it ( 'returns null config when no userId and no env allowlist' , async ( ) => {
116+ mockGetAllowedIntegrationsFromEnv . mockReturnValue ( null )
117+
118+ const userId : string | undefined = undefined
119+ const permissionConfig = userId ? await getUserPermissionConfig ( userId ) : null
120+ const allowedIntegrations =
121+ permissionConfig ?. allowedIntegrations ?? getAllowedIntegrationsFromEnv ( )
122+
123+ expect ( allowedIntegrations ) . toBeNull ( )
124+ } )
125+
126+ it ( 'falls back to env allowlist when no userId is provided' , async ( ) => {
127+ mockGetAllowedIntegrationsFromEnv . mockReturnValue ( [ 'slack' , 'gmail' ] )
128+
129+ const userId : string | undefined = undefined
130+ const permissionConfig = userId ? await getUserPermissionConfig ( userId ) : null
131+ const allowedIntegrations =
132+ permissionConfig ?. allowedIntegrations ?? getAllowedIntegrationsFromEnv ( )
133+
134+ expect ( allowedIntegrations ) . toEqual ( [ 'slack' , 'gmail' ] )
135+ } )
136+
137+ it ( 'env allowlist filters block types when userId is absent' , async ( ) => {
138+ mockGetAllowedIntegrationsFromEnv . mockReturnValue ( [ 'slack' , 'gmail' ] )
139+
140+ const userId : string | undefined = undefined
141+ const permissionConfig = userId ? await getUserPermissionConfig ( userId ) : null
142+ const allowedIntegrations =
143+ permissionConfig ?. allowedIntegrations ?? getAllowedIntegrationsFromEnv ( )
144+
145+ expect ( allowedIntegrations ) . not . toBeNull ( )
146+ expect ( allowedIntegrations ! . includes ( 'slack' ) ) . toBe ( true )
147+ expect ( allowedIntegrations ! . includes ( 'discord' ) ) . toBe ( false )
148+ } )
149+
150+ it ( 'uses permission config when userId is present, ignoring env fallback' , async ( ) => {
151+ mockGetAllowedIntegrationsFromEnv . mockReturnValue ( [ 'slack' , 'gmail' ] )
152+
153+ const config = await getUserPermissionConfig ( 'user-123' )
154+
155+ expect ( config ) . not . toBeNull ( )
156+ expect ( config ! . allowedIntegrations ) . toEqual ( [ 'slack' , 'gmail' ] )
157+ } )
158+ } )
159+
107160describe ( 'validateBlockType' , ( ) => {
108161 beforeEach ( ( ) => {
109162 vi . clearAllMocks ( )
@@ -115,15 +168,15 @@ describe('validateBlockType', () => {
115168 } )
116169
117170 it ( 'allows any block type' , async ( ) => {
118- await expect ( validateBlockType ( undefined , 'google_drive' ) ) . resolves . not . toThrow ( )
171+ await validateBlockType ( undefined , 'google_drive' )
119172 } )
120173
121174 it ( 'allows multi-word block types' , async ( ) => {
122- await expect ( validateBlockType ( undefined , 'microsoft_excel' ) ) . resolves . not . toThrow ( )
175+ await validateBlockType ( undefined , 'microsoft_excel' )
123176 } )
124177
125178 it ( 'always allows start_trigger' , async ( ) => {
126- await expect ( validateBlockType ( undefined , 'start_trigger' ) ) . resolves . not . toThrow ( )
179+ await validateBlockType ( undefined , 'start_trigger' )
127180 } )
128181 } )
129182
@@ -137,9 +190,9 @@ describe('validateBlockType', () => {
137190 } )
138191
139192 it ( 'allows block types on the allowlist' , async ( ) => {
140- await expect ( validateBlockType ( undefined , 'slack' ) ) . resolves . not . toThrow ( )
141- await expect ( validateBlockType ( undefined , 'google_drive' ) ) . resolves . not . toThrow ( )
142- await expect ( validateBlockType ( undefined , 'microsoft_excel' ) ) . resolves . not . toThrow ( )
193+ await validateBlockType ( undefined , 'slack' )
194+ await validateBlockType ( undefined , 'google_drive' )
195+ await validateBlockType ( undefined , 'microsoft_excel' )
143196 } )
144197
145198 it ( 'rejects block types not on the allowlist' , async ( ) => {
@@ -149,12 +202,12 @@ describe('validateBlockType', () => {
149202 } )
150203
151204 it ( 'always allows start_trigger regardless of allowlist' , async ( ) => {
152- await expect ( validateBlockType ( undefined , 'start_trigger' ) ) . resolves . not . toThrow ( )
205+ await validateBlockType ( undefined , 'start_trigger' )
153206 } )
154207
155208 it ( 'matches case-insensitively' , async ( ) => {
156- await expect ( validateBlockType ( undefined , 'Slack' ) ) . resolves . not . toThrow ( )
157- await expect ( validateBlockType ( undefined , 'GOOGLE_DRIVE' ) ) . resolves . not . toThrow ( )
209+ await validateBlockType ( undefined , 'Slack' )
210+ await validateBlockType ( undefined , 'GOOGLE_DRIVE' )
158211 } )
159212
160213 it ( 'includes reason in error for env-only enforcement' , async ( ) => {
0 commit comments