|
2 | 2 | * @vitest-environment node |
3 | 3 | */ |
4 | 4 | import { |
| 5 | + environmentUtilsMockFns, |
5 | 6 | MockV2ApiKeyUnauthenticatedError, |
6 | 7 | resetDbChainMock, |
| 8 | + resetEnvironmentUtilsMock, |
7 | 9 | resetEnvMock, |
8 | 10 | setEnv, |
9 | 11 | V2_OPERATION_RATE_LIMIT_ALLOWED, |
@@ -72,6 +74,8 @@ vi.mock('@/ee/access-control/utils/permission-check', () => ({ |
72 | 74 | vi.mock('@/lib/api/server/routes/v2-api-key-auth', () => v2ApiKeyAuthModuleMock) |
73 | 75 | vi.mock('@/lib/core/rate-limiter', () => v2RateLimiterModuleMock) |
74 | 76 |
|
| 77 | +import { markCopilotRequest } from '@/lib/api/server/routes/copilot-request' |
| 78 | +import { performChatDeploy as realPerformChatDeploy } from '@/lib/workflows/orchestration/chat-deploy' |
75 | 79 | import { DELETE, GET, PUT } from '@/app/api/v2/workflows/[workflowId]/deployments/chat/route' |
76 | 80 |
|
77 | 81 | const WORKSPACE_ID = 'workspace-1' |
@@ -481,6 +485,91 @@ describe('/api/v2/workflows/[workflowId]/deployments/chat', () => { |
481 | 485 |
|
482 | 486 | expect(mocks.validateChatDeployAuth).not.toHaveBeenCalled() |
483 | 487 | }) |
| 488 | + |
| 489 | + describe('password references', () => { |
| 490 | + const passwordBody = (password: string) => ({ ...validBody, authType: 'password', password }) |
| 491 | + |
| 492 | + /** Admitted exactly as the Sim agent's in-process CLI transport admits its calls. */ |
| 493 | + const agentPut = (body: unknown) => { |
| 494 | + const request = new NextRequest(PATH, { |
| 495 | + method: 'PUT', |
| 496 | + headers: { 'content-type': 'application/json' }, |
| 497 | + body: JSON.stringify(body), |
| 498 | + }) |
| 499 | + markCopilotRequest(request, { userId: 'user-1', workspaceId: WORKSPACE_ID, chatId: 'c-1' }) |
| 500 | + return PUT(request, routeContext) |
| 501 | + } |
| 502 | + |
| 503 | + const environment = (variables: Record<string, string>) => |
| 504 | + environmentUtilsMockFns.mockResolveEffectiveEnvironmentVariables.mockResolvedValueOnce( |
| 505 | + Object.fromEntries( |
| 506 | + Object.entries(variables).map(([name, value]) => [ |
| 507 | + name, |
| 508 | + { value, scope: 'workspace', visible: false }, |
| 509 | + ]) |
| 510 | + ) |
| 511 | + ) |
| 512 | + |
| 513 | + afterEach(resetEnvironmentUtilsMock) |
| 514 | + |
| 515 | + it("deploys with the value of the agent's referenced variable", async () => { |
| 516 | + environment({ CHAT_PW: 'resolved-chat-password' }) |
| 517 | + |
| 518 | + const response = await agentPut(passwordBody('{{CHAT_PW}}')) |
| 519 | + |
| 520 | + expect(response.status).toBe(200) |
| 521 | + expect( |
| 522 | + environmentUtilsMockFns.mockResolveEffectiveEnvironmentVariables |
| 523 | + ).toHaveBeenCalledWith('user-1', WORKSPACE_ID, ['CHAT_PW']) |
| 524 | + expect(mocks.performChatDeploy.mock.calls[0][0].password).toBe('resolved-chat-password') |
| 525 | + }) |
| 526 | + |
| 527 | + it('refuses an unset variable by name instead of deploying the placeholder', async () => { |
| 528 | + const response = await agentPut(passwordBody('{{CHAT_PW}}')) |
| 529 | + |
| 530 | + expect(response.status).toBe(400) |
| 531 | + expect((await response.json()).error.message).toBe( |
| 532 | + 'Environment variable "CHAT_PW" referenced by password is not set for this workspace or user. Set it first, or pass the raw value.' |
| 533 | + ) |
| 534 | + expect(mocks.performChatDeploy).not.toHaveBeenCalled() |
| 535 | + }) |
| 536 | + |
| 537 | + it('holds the resolved value to the chat password rules', async () => { |
| 538 | + mocks.performChatDeploy.mockImplementation(realPerformChatDeploy) |
| 539 | + environment({ CHAT_PW: 'short' }) |
| 540 | + |
| 541 | + const response = await agentPut(passwordBody('{{CHAT_PW}}')) |
| 542 | + |
| 543 | + expect(response.status).toBe(400) |
| 544 | + expect((await response.json()).error.message).toBe( |
| 545 | + 'Password must be at least 15 characters' |
| 546 | + ) |
| 547 | + }) |
| 548 | + |
| 549 | + it('keeps a reference literal for an API key caller, under the same rules', async () => { |
| 550 | + mocks.performChatDeploy.mockImplementation(realPerformChatDeploy) |
| 551 | + |
| 552 | + const response = await put(passwordBody('{{SHORT}}')) |
| 553 | + |
| 554 | + expect(response.status).toBe(400) |
| 555 | + expect((await response.json()).error.message).toBe( |
| 556 | + 'Password must be at least 15 characters' |
| 557 | + ) |
| 558 | + expect( |
| 559 | + environmentUtilsMockFns.mockResolveEffectiveEnvironmentVariables |
| 560 | + ).not.toHaveBeenCalled() |
| 561 | + }) |
| 562 | + |
| 563 | + it('stores a long literal reference verbatim for an API key caller', async () => { |
| 564 | + const response = await put(passwordBody('{{A_LONG_LITERAL_NAME}}')) |
| 565 | + |
| 566 | + expect(response.status).toBe(200) |
| 567 | + expect(mocks.performChatDeploy.mock.calls[0][0].password).toBe('{{A_LONG_LITERAL_NAME}}') |
| 568 | + expect( |
| 569 | + environmentUtilsMockFns.mockResolveEffectiveEnvironmentVariables |
| 570 | + ).not.toHaveBeenCalled() |
| 571 | + }) |
| 572 | + }) |
484 | 573 | }) |
485 | 574 |
|
486 | 575 | describe('DELETE', () => { |
|
0 commit comments