@@ -16,7 +16,7 @@ const mocks = vi.hoisted(() => ({
1616 importProvenance : vi . fn ( ) ,
1717 isComplete : vi . fn ( ) ,
1818 resolveVertexAccessToken : vi . fn ( ) ,
19- resolveBedrockCredentialSession : vi . fn ( ) ,
19+ resolveAuthorizedAwsCredential : vi . fn ( ) ,
2020} ) )
2121
2222vi . mock ( '@/providers' , ( ) => providersMock )
@@ -28,9 +28,10 @@ vi.mock('@/lib/mothership/environment-context', () => mothershipEnvironmentConte
2828vi . mock ( '@/lib/internal/llm/credentials' , ( ) => ( {
2929 resolveVertexAccessToken : mocks . resolveVertexAccessToken ,
3030} ) )
31- vi . mock ( '@/lib/internal/llm/bedrock-credential ' , ( ) => ( {
32- resolveBedrockCredentialSession : mocks . resolveBedrockCredentialSession ,
31+ vi . mock ( '@/lib/internal/aws/credentials ' , ( ) => ( {
32+ resolveAuthorizedAwsCredential : mocks . resolveAuthorizedAwsCredential ,
3333} ) )
34+ vi . mock ( '@/lib/oauth/token-resolution' , ( ) => ( { recordCredentialAccess : vi . fn ( ) } ) )
3435vi . mock ( '@/lib/workspaces/permissions/utils' , ( ) => permissionsMock )
3536vi . mock ( '@/executor/utils/resolved-secret-content-projection' , ( ) => ( {
3637 projectResolvedSecretModelContent : vi . fn ( ) ,
@@ -51,6 +52,20 @@ const BILLING_ATTRIBUTION = {
5152 billingEntity : { type : 'user' as const , id : 'user-1' } ,
5253}
5354
55+ const BEDROCK_CREDENTIAL_INPUT = {
56+ provider : 'bedrock' ,
57+ model : 'bedrock/claude' ,
58+ bedrockCredential : 'aws-cred' ,
59+ workspaceId : 'workspace-1' ,
60+ }
61+
62+ const AUTHORIZED_AWS_CREDENTIAL = {
63+ ok : true ,
64+ credentialType : 'service_account' ,
65+ resolvedCredentialId : 'aws-cred' ,
66+ workspaceId : 'workspace-1' ,
67+ }
68+
5469describe ( 'executeLlmProviderOperation' , ( ) => {
5570 beforeEach ( ( ) => {
5671 permissionsMockFns . mockCheckWorkspaceAccess . mockResolvedValue ( { hasAccess : true } )
@@ -116,60 +131,74 @@ describe('executeLlmProviderOperation', () => {
116131 } )
117132
118133 it ( "signs a Bedrock call with the selected AWS credential's session for the caller" , async ( ) => {
119- const session = {
120- credentialId : 'aws-cred' ,
134+ mocks . authorizeCredential . mockResolvedValueOnce ( AUTHORIZED_AWS_CREDENTIAL )
135+ mocks . resolveAuthorizedAwsCredential . mockResolvedValueOnce ( {
121136 accessKeyId : 'ASIASESSIONKEY000001' ,
122137 secretAccessKey : 'session-secret' ,
123138 sessionToken : 'session-token' ,
124- }
125- mocks . resolveBedrockCredentialSession . mockResolvedValueOnce ( session )
139+ } )
126140
127141 await executeLlmProviderOperation (
128- {
129- provider : 'bedrock' ,
130- model : 'bedrock/claude' ,
131- bedrockCredential : 'aws-cred' ,
132- bedrockRegion : 'eu-west-1' ,
133- workspaceId : 'workspace-1' ,
134- workflowId : 'workflow-1' ,
135- } ,
142+ { ...BEDROCK_CREDENTIAL_INPUT , bedrockRegion : 'eu-west-1' , workflowId : 'workflow-1' } ,
136143 { actorUserId : 'user-1' , headers : new Headers ( ) , requestId : 'request-1' }
137144 )
138145
139- expect ( mocks . resolveBedrockCredentialSession ) . toHaveBeenCalledWith (
146+ expect ( mocks . authorizeCredential ) . toHaveBeenCalledWith (
147+ expect . objectContaining ( { userId : 'user-1' } ) ,
140148 expect . objectContaining ( {
141149 credentialId : 'aws-cred' ,
142- actingUserId : 'user-1' ,
143150 workflowId : 'workflow-1' ,
144- workspaceId : 'workspace -1' ,
151+ callerUserId : 'user -1' ,
145152 } )
146153 )
147154 expect ( mockExecuteProviderRequest ) . toHaveBeenCalledWith (
148155 'bedrock' ,
149- expect . objectContaining ( { bedrockSession : session , bedrockRegion : 'eu-west-1' } ) ,
156+ expect . objectContaining ( {
157+ bedrockSession : {
158+ credentialId : 'aws-cred' ,
159+ accessKeyId : 'ASIASESSIONKEY000001' ,
160+ secretAccessKey : 'session-secret' ,
161+ sessionToken : 'session-token' ,
162+ } ,
163+ bedrockRegion : 'eu-west-1' ,
164+ } ) ,
150165 expect . anything ( )
151166 )
152167 } )
153168
154- it ( 'fails a Bedrock call whose AWS credential cannot be used before provider work ' , async ( ) => {
169+ it ( 'refuses a Bedrock credential the caller may not use with the status a Vertex refusal gets ' , async ( ) => {
155170 mockExecuteProviderRequest . mockClear ( )
156- mocks . resolveBedrockCredentialSession . mockRejectedValueOnce (
157- new Error ( 'Not authorized to use this AWS credential' )
158- )
171+ mocks . authorizeCredential . mockResolvedValue ( { ok : false , error : 'Credential access denied' } )
172+
173+ for ( const input of [
174+ BEDROCK_CREDENTIAL_INPUT ,
175+ { provider : 'vertex' , model : 'vertex/gemini-2.5-pro' , vertexCredential : 'credential-1' } ,
176+ ] ) {
177+ await expect (
178+ executeLlmProviderOperation ( input , {
179+ actorUserId : 'user-1' ,
180+ headers : new Headers ( ) ,
181+ requestId : 'request-1' ,
182+ } )
183+ ) . rejects . toMatchObject ( { status : 401 , body : { error : 'Credential access denied' } } )
184+ }
185+ expect ( mockExecuteProviderRequest ) . not . toHaveBeenCalled ( )
186+ } )
187+
188+ it ( 'fails a Bedrock call whose AWS credential cannot be resolved before provider work' , async ( ) => {
189+ mockExecuteProviderRequest . mockClear ( )
190+ mocks . authorizeCredential . mockResolvedValueOnce ( AUTHORIZED_AWS_CREDENTIAL )
191+ mocks . resolveAuthorizedAwsCredential . mockRejectedValueOnce ( new Error ( 'AccessDenied' ) )
159192
160193 await expect (
161- executeLlmProviderOperation (
162- {
163- provider : 'bedrock' ,
164- model : 'bedrock/claude' ,
165- bedrockCredential : 'aws-cred' ,
166- workspaceId : 'workspace-1' ,
167- } ,
168- { actorUserId : 'user-1' , headers : new Headers ( ) , requestId : 'request-1' }
169- )
194+ executeLlmProviderOperation ( BEDROCK_CREDENTIAL_INPUT , {
195+ actorUserId : 'user-1' ,
196+ headers : new Headers ( ) ,
197+ requestId : 'request-1' ,
198+ } )
170199 ) . rejects . toMatchObject ( {
171200 status : 400 ,
172- body : { error : 'Not authorized to use this AWS credential' } ,
201+ body : { error : 'Could not use the AWS credential: AccessDenied ' } ,
173202 } )
174203 expect ( mockExecuteProviderRequest ) . not . toHaveBeenCalled ( )
175204 } )
0 commit comments