Skip to content

Commit 5ec4e63

Browse files
committed
added tests
1 parent 628a313 commit 5ec4e63

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/integrations/integrations.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,11 @@ export function Integrations({ onOpenChange, registerCloseHandler }: Integration
227227
const groupedServices = services.reduce(
228228
(acc, service) => {
229229
// Filter based on allowedIntegrations
230+
// Normalize hyphens to underscores since service IDs use hyphens (e.g., "google-drive")
231+
// but block types in the allowlist use underscores (e.g., "google_drive")
230232
if (
231233
permissionConfig.allowedIntegrations !== null &&
232-
!permissionConfig.allowedIntegrations.includes(service.id)
234+
!permissionConfig.allowedIntegrations.includes(service.id.replace(/-/g, '_'))
233235
) {
234236
return acc
235237
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { databaseMock, drizzleOrmMock, loggerMock } from '@sim/testing'
5+
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
7+
const DEFAULT_PERMISSION_GROUP_CONFIG = {
8+
allowedIntegrations: null,
9+
allowedModelProviders: null,
10+
hideTraceSpans: false,
11+
hideKnowledgeBaseTab: false,
12+
hideCopilot: false,
13+
hideApiKeysTab: false,
14+
hideEnvironmentTab: false,
15+
hideFilesTab: false,
16+
disableMcpTools: false,
17+
disableCustomTools: false,
18+
disableSkills: false,
19+
hideTemplates: false,
20+
disableInvitations: false,
21+
hideDeployApi: false,
22+
hideDeployMcp: false,
23+
hideDeployA2a: false,
24+
hideDeployChatbot: false,
25+
hideDeployTemplate: false,
26+
}
27+
28+
const mockGetAllowedIntegrationsFromEnv = vi.fn<() => string[] | null>()
29+
const mockIsOrganizationOnEnterprisePlan = vi.fn<() => Promise<boolean>>()
30+
const mockGetProviderFromModel = vi.fn<(model: string) => string>()
31+
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', () => ({
37+
isOrganizationOnEnterprisePlan: mockIsOrganizationOnEnterprisePlan,
38+
}))
39+
vi.doMock('@/lib/core/config/feature-flags', () => ({
40+
getAllowedIntegrationsFromEnv: mockGetAllowedIntegrationsFromEnv,
41+
isAccessControlEnabled: false,
42+
isHosted: false,
43+
}))
44+
vi.doMock('@/lib/permission-groups/types', () => ({
45+
DEFAULT_PERMISSION_GROUP_CONFIG,
46+
parsePermissionGroupConfig: (config: unknown) => {
47+
if (!config || typeof config !== 'object') return DEFAULT_PERMISSION_GROUP_CONFIG
48+
return { ...DEFAULT_PERMISSION_GROUP_CONFIG, ...config }
49+
},
50+
}))
51+
vi.doMock('@/providers/utils', () => ({
52+
getProviderFromModel: mockGetProviderFromModel,
53+
}))
54+
55+
const { IntegrationNotAllowedError, validateBlockType } = await import('./permission-check')
56+
57+
describe('validateBlockType', () => {
58+
beforeEach(() => {
59+
vi.clearAllMocks()
60+
})
61+
62+
describe('when no env allowlist is configured', () => {
63+
beforeEach(() => {
64+
mockGetAllowedIntegrationsFromEnv.mockReturnValue(null)
65+
})
66+
67+
it('allows any block type', async () => {
68+
await expect(validateBlockType(undefined, 'google_drive')).resolves.not.toThrow()
69+
})
70+
71+
it('allows multi-word block types', async () => {
72+
await expect(validateBlockType(undefined, 'microsoft_excel')).resolves.not.toThrow()
73+
})
74+
75+
it('always allows start_trigger', async () => {
76+
await expect(validateBlockType(undefined, 'start_trigger')).resolves.not.toThrow()
77+
})
78+
})
79+
80+
describe('when env allowlist is configured', () => {
81+
beforeEach(() => {
82+
mockGetAllowedIntegrationsFromEnv.mockReturnValue([
83+
'slack',
84+
'google_drive',
85+
'microsoft_excel',
86+
])
87+
})
88+
89+
it('allows block types on the allowlist', async () => {
90+
await expect(validateBlockType(undefined, 'slack')).resolves.not.toThrow()
91+
await expect(validateBlockType(undefined, 'google_drive')).resolves.not.toThrow()
92+
await expect(validateBlockType(undefined, 'microsoft_excel')).resolves.not.toThrow()
93+
})
94+
95+
it('rejects block types not on the allowlist', async () => {
96+
await expect(validateBlockType(undefined, 'discord')).rejects.toThrow(
97+
IntegrationNotAllowedError
98+
)
99+
})
100+
101+
it('always allows start_trigger regardless of allowlist', async () => {
102+
await expect(validateBlockType(undefined, 'start_trigger')).resolves.not.toThrow()
103+
})
104+
105+
it('matches case-insensitively', async () => {
106+
await expect(validateBlockType(undefined, 'Slack')).resolves.not.toThrow()
107+
await expect(validateBlockType(undefined, 'GOOGLE_DRIVE')).resolves.not.toThrow()
108+
})
109+
110+
it('includes reason in error for env-only enforcement', async () => {
111+
await expect(validateBlockType(undefined, 'discord')).rejects.toThrow(/ALLOWED_INTEGRATIONS/)
112+
})
113+
})
114+
})
115+
116+
describe('service ID to block type normalization', () => {
117+
it('hyphenated service IDs match underscore block types after normalization', () => {
118+
const allowedBlockTypes = [
119+
'google_drive',
120+
'microsoft_excel',
121+
'microsoft_teams',
122+
'google_sheets',
123+
]
124+
const serviceIds = ['google-drive', 'microsoft-excel', 'microsoft-teams', 'google-sheets']
125+
126+
for (const serviceId of serviceIds) {
127+
const normalized = serviceId.replace(/-/g, '_')
128+
expect(allowedBlockTypes).toContain(normalized)
129+
}
130+
})
131+
132+
it('single-word service IDs are unaffected by normalization', () => {
133+
const serviceIds = ['slack', 'gmail', 'notion', 'discord']
134+
135+
for (const serviceId of serviceIds) {
136+
const normalized = serviceId.replace(/-/g, '_')
137+
expect(normalized).toBe(serviceId)
138+
}
139+
})
140+
})

0 commit comments

Comments
 (0)