11/**
22 * @vitest -environment node
33 */
4+ import { dbChainMockFns , resetDbChainMock } from '@sim/testing'
45import { beforeEach , describe , expect , it , vi } from 'vitest'
56
6- const { mockEnsureWorkflowAccess, mockPerformFullDeploy, mockPerformFullUndeploy } = vi . hoisted (
7- ( ) => ( {
8- mockEnsureWorkflowAccess : vi . fn ( ) ,
9- mockPerformFullDeploy : vi . fn ( ) ,
10- mockPerformFullUndeploy : vi . fn ( ) ,
11- } )
12- )
7+ const {
8+ mockCheckChatAccess,
9+ mockEnsureWorkflowAccess,
10+ mockPerformChatUndeploy,
11+ mockPerformDeleteWorkflowMcpTool,
12+ mockPerformFullDeploy,
13+ mockPerformFullUndeploy,
14+ } = vi . hoisted ( ( ) => ( {
15+ mockCheckChatAccess : vi . fn ( ) ,
16+ mockEnsureWorkflowAccess : vi . fn ( ) ,
17+ mockPerformChatUndeploy : vi . fn ( ) ,
18+ mockPerformDeleteWorkflowMcpTool : vi . fn ( ) ,
19+ mockPerformFullDeploy : vi . fn ( ) ,
20+ mockPerformFullUndeploy : vi . fn ( ) ,
21+ } ) )
1322
1423vi . mock ( '@/lib/workflows/orchestration' , ( ) => ( {
1524 performChatDeploy : vi . fn ( ) ,
16- performChatUndeploy : vi . fn ( ) ,
25+ performChatUndeploy : mockPerformChatUndeploy ,
1726 performFullDeploy : mockPerformFullDeploy ,
1827 performFullUndeploy : mockPerformFullUndeploy ,
1928} ) )
2029
2130vi . mock ( '@/lib/mcp/orchestration' , ( ) => ( {
2231 performCreateWorkflowMcpTool : vi . fn ( ) ,
23- performDeleteWorkflowMcpTool : vi . fn ( ) ,
32+ performDeleteWorkflowMcpTool : mockPerformDeleteWorkflowMcpTool ,
2433 performUpdateWorkflowMcpTool : vi . fn ( ) ,
2534} ) )
2635
@@ -35,7 +44,7 @@ vi.mock('@/lib/mcp/workflow-tool-schema', () => ({
3544} ) )
3645
3746vi . mock ( '@/app/api/chat/utils' , ( ) => ( {
38- checkChatAccess : vi . fn ( ) ,
47+ checkChatAccess : mockCheckChatAccess ,
3948 checkWorkflowAccessForChatCreation : vi . fn ( ) ,
4049} ) )
4150
@@ -55,30 +64,16 @@ import {
5564 executeRedeploy ,
5665} from '@/lib/copilot/tools/handlers/deployment/deploy'
5766
58- describe ( 'executeDeployApi ' , ( ) => {
67+ describe ( 'deployment handlers ' , ( ) => {
5968 beforeEach ( ( ) => {
6069 vi . clearAllMocks ( )
70+ resetDbChainMock ( )
6171 mockEnsureWorkflowAccess . mockResolvedValue ( {
6272 workflow : { id : 'workflow-1' , workspaceId : 'workspace-1' } ,
6373 } )
6474 } )
6575
66- it ( 'refuses undeploy without approval for the exact tool call' , async ( ) => {
67- const result = await executeDeployApi (
68- { workflowId : 'workflow-1' , action : 'undeploy' } ,
69- {
70- userId : 'user-1' ,
71- workflowId : 'workflow-1' ,
72- toolCallId : 'call-1' ,
73- userApprovedToolCall : false ,
74- }
75- )
76-
77- expect ( result ) . toMatchObject ( { success : false , error : expect . stringContaining ( 'approval' ) } )
78- expect ( mockPerformFullUndeploy ) . not . toHaveBeenCalled ( )
79- } )
80-
81- it ( 'allows an explicitly approved undeploy' , async ( ) => {
76+ it ( 'undeploys the API without approval context when permission gating is disabled' , async ( ) => {
8277 mockPerformFullUndeploy . mockResolvedValue ( { success : true } )
8378
8479 const result = await executeDeployApi (
@@ -87,7 +82,6 @@ describe('executeDeployApi', () => {
8782 userId : 'user-1' ,
8883 workflowId : 'workflow-1' ,
8984 toolCallId : 'call-1' ,
90- userApprovedToolCall : true ,
9185 }
9286 )
9387
@@ -182,31 +176,62 @@ describe('executeDeployApi', () => {
182176 } )
183177 } )
184178
185- it ( 'refuses chat undeploy without exact-call approval' , async ( ) => {
179+ it ( 'undeploys chat without approval context when permission gating is disabled' , async ( ) => {
180+ dbChainMockFns . limit . mockResolvedValueOnce ( [
181+ {
182+ id : 'chat-1' ,
183+ identifier : 'production-helper' ,
184+ title : 'Production Helper' ,
185+ description : null ,
186+ authType : 'public' ,
187+ allowedEmails : [ ] ,
188+ outputConfigs : [ ] ,
189+ includeThinking : false ,
190+ includeToolCalls : false ,
191+ customizations : null ,
192+ } ,
193+ ] )
194+ mockCheckChatAccess . mockResolvedValue ( { hasAccess : true , workspaceId : 'workspace-1' } )
195+ mockPerformChatUndeploy . mockResolvedValue ( { success : true } )
196+
186197 const result = await executeDeployChat (
187198 { workflowId : 'workflow-1' , action : 'undeploy' } ,
188199 {
189200 userId : 'user-1' ,
190201 workflowId : 'workflow-1' ,
191202 toolCallId : 'call-1' ,
192- userApprovedToolCall : false ,
193203 }
194204 )
195205
196- expect ( result ) . toMatchObject ( { success : false , error : expect . stringContaining ( 'approval' ) } )
206+ expect ( result . success ) . toBe ( true )
207+ expect ( mockPerformChatUndeploy ) . toHaveBeenCalledWith ( {
208+ chatId : 'chat-1' ,
209+ userId : 'user-1' ,
210+ workspaceId : 'workspace-1' ,
211+ } )
197212 } )
198213
199- it ( 'refuses MCP undeploy without exact-call approval' , async ( ) => {
214+ it ( 'undeploys MCP without approval context when permission gating is disabled' , async ( ) => {
215+ dbChainMockFns . limit
216+ . mockResolvedValueOnce ( [ { id : 'server-1' , name : 'Production MCP' } ] )
217+ . mockResolvedValueOnce ( [ { id : 'tool-1' } ] )
218+ mockPerformDeleteWorkflowMcpTool . mockResolvedValue ( { success : true } )
219+
200220 const result = await executeDeployMcp (
201221 { workflowId : 'workflow-1' , serverId : 'server-1' , action : 'undeploy' } ,
202222 {
203223 userId : 'user-1' ,
204224 workflowId : 'workflow-1' ,
205225 toolCallId : 'call-1' ,
206- userApprovedToolCall : false ,
207226 }
208227 )
209228
210- expect ( result ) . toMatchObject ( { success : false , error : expect . stringContaining ( 'approval' ) } )
229+ expect ( result . success ) . toBe ( true )
230+ expect ( mockPerformDeleteWorkflowMcpTool ) . toHaveBeenCalledWith ( {
231+ serverId : 'server-1' ,
232+ toolId : 'tool-1' ,
233+ workspaceId : 'workspace-1' ,
234+ userId : 'user-1' ,
235+ } )
211236 } )
212237} )
0 commit comments