11/** @vitest -environment node */
22import { beforeEach , expect , it , vi } from 'vitest'
3+ import { FUNCTION_EXECUTION_DELEGATION_AUDIENCE } from '@/lib/function-execution/application/authorization'
34import { createTrustedOrganizationCopilotPrincipal } from '@/lib/mothership/auth/application-delegation'
4- import { FUNCTION_EXECUTION_DELEGATION_AUDIENCE } from './authorization '
5+ import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry '
56
67const mocks = vi . hoisted ( ( ) => ( { context : vi . fn ( ) , authorize : vi . fn ( ) , execute : vi . fn ( ) } ) )
78vi . mock ( '@/lib/mothership/chat/application/context' , ( ) => ( {
@@ -14,7 +15,7 @@ vi.mock('@/lib/function-execution/execute-request', () => ({
1415 executeFunctionRequest : mocks . execute ,
1516} ) )
1617
17- import { executeChatFunction } from '. /execute-chat-function'
18+ import { executeChatFunction } from '@/lib/function-execution/application /execute-chat-function'
1819
1920const principal = createTrustedOrganizationCopilotPrincipal (
2021 { userId : 'actor' , organizationId : 'org' , chatId : 'chat' , delegationId : 'test' } ,
@@ -40,24 +41,91 @@ beforeEach(() => {
4041 } )
4142 mocks . execute . mockResolvedValue ( Response . json ( { success : true } ) )
4243} )
43- it ( 'runs through the existing Function executor after fresh owner authorization without a workspace' , async ( ) => {
44- await executeChatFunction . execute ( { principal, input } )
45- expect ( mocks . authorize ) . toHaveBeenCalledWith (
44+ it . each ( [ 'agent' , 'plan' ] ) (
45+ 'runs through the existing Function executor after fresh %s owner authorization without a workspace' ,
46+ async ( mode ) => {
47+ mocks . context . mockResolvedValue ( {
48+ organizationId : 'org' ,
49+ chatId : 'chat' ,
50+ userId : 'actor' ,
51+ mode,
52+ } )
53+ await executeChatFunction . execute ( { principal, input } )
54+ expect ( mocks . authorize ) . toHaveBeenCalledWith (
55+ principal ,
56+ expect . objectContaining ( { capability : 'copilot.use' , minimumRole : 'member' } ) ,
57+ { organizationId : 'org' }
58+ )
59+ expect ( mocks . execute ) . toHaveBeenCalledWith (
60+ expect . anything ( ) ,
61+ expect . objectContaining ( { code : 'return 1' , secretScope : 'selected' } ) ,
62+ expect . objectContaining ( {
63+ principal,
64+ attributedUserId : 'actor' ,
65+ sandboxProfile : 'mothership' ,
66+ } )
67+ )
68+ expect ( mocks . execute . mock . calls [ 0 ] [ 1 ] ) . not . toHaveProperty ( 'workspaceId' )
69+ mocks . authorize . mockRejectedValueOnce ( new Error ( 'membership revoked' ) )
70+ await expect ( executeChatFunction . execute ( { principal, input } ) ) . rejects . toThrow (
71+ 'membership revoked'
72+ )
73+ expect ( mocks . execute ) . toHaveBeenCalledTimes ( 1 )
74+ }
75+ )
76+ it . each ( [ 'agent' , 'plan' ] ) ( 'accepts only authorized organization mounts in %s' , async ( mode ) => {
77+ mocks . context . mockResolvedValue ( { organizationId : 'org' , chatId : 'chat' , userId : 'actor' , mode } )
78+ const registry = new ResolvedSecretTraceRegistry ( [
79+ { name : 'TOKEN' , plaintext : 'test-token' , encryptedValue : 'test-cipher' } ,
80+ ] )
81+ await executeChatFunction . execute ( {
4682 principal,
47- expect . objectContaining ( { capability : 'copilot.use' , minimumRole : 'member' } ) ,
48- { organizationId : 'org' }
49- )
83+ input : {
84+ ...input ,
85+ resolvedSecretTraceRegistry : registry ,
86+ body : { ...input . body , mountedSecrets : [ 'TOKEN' ] , envVars : { TOKEN : 'test-token' } } ,
87+ } ,
88+ } )
5089 expect ( mocks . execute ) . toHaveBeenCalledWith (
5190 expect . anything ( ) ,
52- expect . objectContaining ( { code : 'return 1' , secretScope : 'selected' } ) ,
53- expect . objectContaining ( { principal , attributedUserId : 'actor' , sandboxProfile : 'mothership' } )
91+ expect . objectContaining ( { mountedSecrets : [ 'TOKEN' ] , envVars : { TOKEN : 'test-token' } } ) ,
92+ expect . objectContaining ( { resolvedSecretTraceRegistry : registry } )
5493 )
55- expect ( mocks . execute . mock . calls [ 0 ] [ 1 ] ) . not . toHaveProperty ( 'workspaceId' )
56- mocks . authorize . mockRejectedValueOnce ( new Error ( 'membership revoked' ) )
57- await expect ( executeChatFunction . execute ( { principal, input } ) ) . rejects . toThrow (
58- 'membership revoked'
94+ expect ( registry . getActiveMatches ( ) ) . toEqual (
95+ expect . arrayContaining ( [ expect . objectContaining ( { plaintext : 'test-token' } ) ] )
5996 )
60- expect ( mocks . execute ) . toHaveBeenCalledTimes ( 1 )
97+ } )
98+ it . each ( [
99+ { envVars : { TOKEN : 'forged' } , mountedSecrets : [ 'TOKEN' ] } ,
100+ { envVars : { OTHER : 'test-token' } , mountedSecrets : [ 'OTHER' ] } ,
101+ { envVars : { TOKEN : 'test-token' , EXTRA : 'unlisted' } , mountedSecrets : [ 'TOKEN' ] } ,
102+ { envVars : { TOKEN : 'test-token' , EXTRA : 'unlisted' } , mountedSecrets : [ 'TOKEN' , 'TOKEN' ] } ,
103+ ] ) ( 'refuses values or names outside the trusted mount catalog %j' , async ( body ) => {
104+ await expect (
105+ executeChatFunction . execute ( {
106+ principal,
107+ input : {
108+ ...input ,
109+ body : { ...input . body , ...body } ,
110+ resolvedSecretTraceRegistry : new ResolvedSecretTraceRegistry ( [
111+ { name : 'TOKEN' , plaintext : 'test-token' , encryptedValue : 'test-cipher' } ,
112+ ] ) ,
113+ } ,
114+ } )
115+ ) . rejects . toThrow ( 'authorized mount' )
116+ expect ( mocks . execute ) . not . toHaveBeenCalled ( )
117+ } )
118+ it ( 'refuses a mount with no trusted in-process provenance' , async ( ) => {
119+ await expect (
120+ executeChatFunction . execute ( {
121+ principal,
122+ input : {
123+ ...input ,
124+ body : { ...input . body , mountedSecrets : [ 'TOKEN' ] , envVars : { TOKEN : 'test-token' } } ,
125+ } ,
126+ } )
127+ ) . rejects . toThrow ( 'authorized mount' )
128+ expect ( mocks . execute ) . not . toHaveBeenCalled ( )
61129} )
62130it . each ( [
63131 { workspaceId : 'other' } ,
0 commit comments