11/**
22 * @vitest -environment node
33 */
4- import { resetEnvFlagsMock , setEnvFlags } from '@sim/testing/mocks/env-flags.mock'
5- import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
4+ import { beforeEach , describe , expect , it , vi } from 'vitest'
65import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
76
87const {
98 mockCheckInternalApiKey,
109 mockPrepareEnvironmentContext,
1110 mockHandler,
12- mockToolRequiresApproval ,
11+ mockToolRequiresApprovalLane ,
1312} = vi . hoisted ( ( ) => ( {
1413 mockCheckInternalApiKey : vi . fn ( ) ,
1514 mockPrepareEnvironmentContext : vi . fn ( ) ,
1615 mockHandler : vi . fn ( ) ,
17- mockToolRequiresApproval : vi . fn ( ) . mockReturnValue ( false ) ,
16+ mockToolRequiresApprovalLane : vi . fn ( ) . mockReturnValue ( false ) ,
1817} ) )
1918
2019vi . mock ( '@/lib/copilot/request/http' , ( ) => ( {
@@ -27,7 +26,7 @@ vi.mock('@/lib/copilot/environment-context', () => ({
2726
2827vi . mock ( '@/lib/copilot/tool-executor' , ( ) => ( {
2928 ensureHandlersRegistered : vi . fn ( ) ,
30- toolRequiresApproval : mockToolRequiresApproval ,
29+ toolRequiresApprovalLane : mockToolRequiresApprovalLane ,
3130} ) )
3231
3332vi . mock ( '@/lib/copilot/tool-executor/executor' , ( ) => ( {
@@ -75,7 +74,7 @@ describe('POST /api/copilot/tools/execute (in-band)', () => {
7574 beforeEach ( ( ) => {
7675 vi . clearAllMocks ( )
7776 mockCheckInternalApiKey . mockReturnValue ( { success : true } )
78- mockToolRequiresApproval . mockReturnValue ( false )
77+ mockToolRequiresApprovalLane . mockReturnValue ( false )
7978 // A fresh, complete registry per test: the module-level turn cache is keyed
8079 // by messageId, so each test uses a distinct messageId to avoid cross-test
8180 // cache hits.
@@ -170,17 +169,20 @@ describe('POST /api/copilot/tools/execute (in-band)', () => {
170169 } )
171170 } )
172171
172+ /**
173+ * Whether a tool needs an approval-capable lane is decided by
174+ * `toolRequiresApprovalLane` (covered against the real flag and catalog in
175+ * the tool-executor router tests). What matters here is what the route does
176+ * with that answer.
177+ */
173178 describe ( 'approval-gated tools' , ( ) => {
174- afterEach ( resetEnvFlagsMock )
175-
176179 /**
177180 * This lane cannot hold an approval prompt: the dispatch handler owns the gate and
178181 * declines to dispatch in-band calls, so a gated tool arriving here has no waiter behind
179182 * it. Refuse before running anything rather than execute on consent nobody gave.
180183 */
181- it ( 'refuses an approval-gated tool without executing it when permissions are enabled' , async ( ) => {
182- setEnvFlags ( { isCopilotToolPermissionsEnabled : true } )
183- mockToolRequiresApproval . mockReturnValue ( true )
184+ it ( 'refuses a tool that needs an approval-capable lane, without executing it' , async ( ) => {
185+ mockToolRequiresApprovalLane . mockReturnValue ( true )
184186 mockHandler . mockResolvedValue ( { success : true , output : { ran : true } } )
185187
186188 const res = await POST (
@@ -200,36 +202,14 @@ describe('POST /api/copilot/tools/execute (in-band)', () => {
200202 expect ( body . error ) . toContain ( 'checkpoint lane' )
201203 } )
202204
203- it ( 'still runs a tool the catalog does not gate when permissions are enabled' , async ( ) => {
204- setEnvFlags ( { isCopilotToolPermissionsEnabled : true } )
205+ it ( 'still runs a tool that does not need an approval-capable lane' , async ( ) => {
205206 mockHandler . mockResolvedValue ( { success : true , output : { content : 'hello' } } )
206207
207208 const res = await POST ( makeRequest ( { ...BASE_BODY , messageId : 'msg-ungated' } ) as never )
208209
209210 expect ( mockHandler ) . toHaveBeenCalledTimes ( 1 )
210211 await expect ( res . json ( ) ) . resolves . toEqual ( { success : true , output : { content : 'hello' } } )
211212 } )
212-
213- /**
214- * The guard is inert while the feature is off, which is the state this ships in — enabling
215- * the flag is what makes it bite, so it cannot change in-band behavior today.
216- */
217- it ( 'runs an approval-gated tool unchanged while permissions are disabled' , async ( ) => {
218- mockToolRequiresApproval . mockReturnValue ( true )
219- mockHandler . mockResolvedValue ( { success : true , output : { ran : true } } )
220-
221- const res = await POST (
222- makeRequest ( {
223- ...BASE_BODY ,
224- toolName : 'run_function' ,
225- params : { code : 'return 1' } ,
226- messageId : 'msg-gated-flag-off' ,
227- } ) as never
228- )
229-
230- expect ( mockHandler ) . toHaveBeenCalledTimes ( 1 )
231- await expect ( res . json ( ) ) . resolves . toEqual ( { success : true , output : { ran : true } } )
232- } )
233213 } )
234214
235215 it ( 'passes a failed generate_api_key call through with its error' , async ( ) => {
0 commit comments