99 mockIsOrganizationOnEnterprisePlan,
1010 mockGetWorkspaceWithOwner,
1111 mockGetProviderFromModel,
12+ mockGetBlock,
1213 mockExplicitGroup,
1314 mockDefaultGroup,
1415} = vi . hoisted ( ( ) => ( {
@@ -40,6 +41,7 @@ const {
4041 mockIsOrganizationOnEnterprisePlan : vi . fn < ( ) => Promise < boolean > > ( ) ,
4142 mockGetWorkspaceWithOwner : vi . fn < ( ) => Promise < { organizationId : string | null } | null > > ( ) ,
4243 mockGetProviderFromModel : vi . fn < ( model : string ) => string > ( ) ,
44+ mockGetBlock : vi . fn < ( type : string ) => { hideFromToolbar ?: boolean } | undefined > ( ) ,
4345 // The explicit-group query joins permission_group_member -> permission_group;
4446 // the org-default query selects permission_group directly. The db mock returns
4547 // the explicit rows when `innerJoin` was called and the default rows otherwise.
@@ -108,6 +110,11 @@ vi.mock('@/providers/utils', () => ({
108110 getProviderFromModel : mockGetProviderFromModel ,
109111} ) )
110112
113+ vi . mock ( '@/blocks/registry' , ( ) => ( {
114+ getBlock : mockGetBlock ,
115+ getAllBlocks : vi . fn ( ( ) => [ ] ) ,
116+ } ) )
117+
111118import {
112119 assertPermissionsAllowed ,
113120 CustomToolsNotAllowedError ,
@@ -128,6 +135,15 @@ function setEnterpriseOrgWorkspace() {
128135 mockIsOrganizationOnEnterprisePlan . mockResolvedValue ( true )
129136}
130137
138+ /**
139+ * Default every block to non-legacy. `vi.clearAllMocks()` (used by the
140+ * describe-level hooks) keeps implementations, so reset here to stop a legacy
141+ * `getBlock` implementation set in one test from leaking into later ones.
142+ */
143+ beforeEach ( ( ) => {
144+ mockGetBlock . mockImplementation ( ( ) => undefined )
145+ } )
146+
131147describe ( 'IntegrationNotAllowedError' , ( ) => {
132148 it . concurrent ( 'creates error with correct name and message' , ( ) => {
133149 const error = new IntegrationNotAllowedError ( 'discord' )
@@ -280,6 +296,14 @@ describe('validateBlockType', () => {
280296 await validateBlockType ( undefined , undefined , 'start_trigger' )
281297 } )
282298
299+ it ( 'always allows legacy blocks hidden from the toolbar' , async ( ) => {
300+ mockGetBlock . mockImplementation ( ( type ) =>
301+ type === 'notion' ? { hideFromToolbar : true } : undefined
302+ )
303+
304+ await validateBlockType ( undefined , undefined , 'notion' )
305+ } )
306+
283307 it ( 'matches case-insensitively' , async ( ) => {
284308 await validateBlockType ( undefined , undefined , 'Slack' )
285309 await validateBlockType ( undefined , undefined , 'GOOGLE_DRIVE' )
@@ -445,6 +469,19 @@ describe('assertPermissionsAllowed', () => {
445469 ) . rejects . toBeInstanceOf ( IntegrationNotAllowedError )
446470 } )
447471
472+ it ( 'exempts legacy blocks from the integration allowlist' , async ( ) => {
473+ mockExplicitGroup . value = [ { config : { allowedIntegrations : [ 'slack' ] } } ]
474+ mockGetBlock . mockImplementation ( ( type ) =>
475+ type === 'notion' ? { hideFromToolbar : true } : undefined
476+ )
477+
478+ await assertPermissionsAllowed ( {
479+ userId : 'user-123' ,
480+ workspaceId : 'workspace-1' ,
481+ blockType : 'notion' ,
482+ } )
483+ } )
484+
448485 it ( 'throws CustomToolsNotAllowedError when custom tools are disabled' , async ( ) => {
449486 mockExplicitGroup . value = [ { config : { disableCustomTools : true } } ]
450487
0 commit comments