|
| 1 | +/** @vitest-environment node */ |
| 2 | +import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 3 | + |
| 4 | +const mocks = vi.hoisted(() => ({ authorize: vi.fn(), create: vi.fn(), prepare: vi.fn(), request: vi.fn() })) |
| 5 | +vi.mock('@/lib/auth/credential-access', () => ({ authorizeCredentialUseForAuth: mocks.authorize })) |
| 6 | +vi.mock('@/lib/internal/oci/client.server', () => ({ createOciClient: mocks.create })) |
| 7 | + |
| 8 | +import { executeOciLoggingTool } from '@/lib/internal/oci-logging/execute-tool' |
| 9 | +import { OCI_LOGGING_INGESTION_POLICY, OCI_LOGGING_MANAGEMENT_POLICY } from '@/lib/internal/oci-logging/operations' |
| 10 | +import type { InternalToolOperationCall } from '@/lib/internal/tool-operations/types' |
| 11 | + |
| 12 | +function call(overrides: Partial<InternalToolOperationCall> = {}): InternalToolOperationCall { |
| 13 | + return { |
| 14 | + toolId: 'oci_logging_list_log_groups', input: { ociCredential: 'supplied', compartmentId: 'compartment' }, |
| 15 | + headers: new Headers(), context: { workflowId: 'workflow', workspaceId: 'workspace', userId: 'actor' }, |
| 16 | + requestId: 'request', ...overrides, |
| 17 | + } |
| 18 | +} |
| 19 | + |
| 20 | +describe('OCI Logging tool authorization and execution', () => { |
| 21 | + beforeEach(() => { |
| 22 | + vi.clearAllMocks() |
| 23 | + mocks.authorize.mockResolvedValue({ ok: true, credentialType: 'service_account', resolvedCredentialId: 'resolved', workspaceId: 'workspace' }) |
| 24 | + mocks.create.mockResolvedValue({ prepareStaticEndpoint: mocks.prepare, request: mocks.request }) |
| 25 | + mocks.prepare.mockResolvedValue({ origin: 'https://logging.us-phoenix-1.oci.oraclecloud.com' }) |
| 26 | + mocks.request.mockResolvedValue({ status: 200, headers: {}, body: new TextEncoder().encode('[]') }) |
| 27 | + }) |
| 28 | + |
| 29 | + it('binds the authorized resolved ID and trusted scope before provider work', async () => { |
| 30 | + const response = await executeOciLoggingTool(call({ input: { ociCredential: 'supplied', compartmentId: 'compartment', region: 'us-phoenix-1', workspaceId: 'forged', userId: 'forged' } })) |
| 31 | + expect(response.status).toBe(200) |
| 32 | + expect(mocks.authorize).toHaveBeenCalledWith(expect.objectContaining({ userId: 'actor' }), { |
| 33 | + credentialId: 'supplied', workspaceId: 'workspace', workflowId: 'workflow', callerUserId: 'actor', |
| 34 | + }) |
| 35 | + expect(mocks.create).toHaveBeenCalledWith({ credentialId: 'resolved', workspaceId: 'workspace', serviceId: 'oci-logging', region: 'us-phoenix-1' }) |
| 36 | + expect(mocks.prepare).toHaveBeenCalledWith(OCI_LOGGING_MANAGEMENT_POLICY) |
| 37 | + expect(mocks.authorize.mock.invocationCallOrder[0]).toBeLessThan(mocks.create.mock.invocationCallOrder[0]!) |
| 38 | + }) |
| 39 | + |
| 40 | + it.each([ |
| 41 | + { ok: false }, |
| 42 | + { ok: true, credentialType: 'oauth', resolvedCredentialId: 'resolved', workspaceId: 'workspace' }, |
| 43 | + { ok: true, credentialType: 'service_account', workspaceId: 'workspace' }, |
| 44 | + { ok: true, credentialType: 'service_account', resolvedCredentialId: 'resolved', workspaceId: 'other' }, |
| 45 | + ])('rejects unavailable or unbound credentials before creating a client: %j', async (access) => { |
| 46 | + mocks.authorize.mockResolvedValue(access) |
| 47 | + expect((await executeOciLoggingTool(call())).status).toBe(403) |
| 48 | + expect(mocks.create).not.toHaveBeenCalled() |
| 49 | + }) |
| 50 | + |
| 51 | + it.each([{ workflowId: 'workflow' }, { workflowId: '', userId: 'actor' }])('requires trusted user and workspace context', async (context) => { |
| 52 | + expect((await executeOciLoggingTool(call({ context }))).status).toBe(401) |
| 53 | + expect(mocks.authorize).not.toHaveBeenCalled() |
| 54 | + }) |
| 55 | + |
| 56 | + it('rejects invalid ingestion before transmission and uses the ingestion policy on success', async () => { |
| 57 | + const input = { ociCredential: 'supplied', logId: 'custom', logEntryBatches: [{ source: 'app', type: 'events', defaultlogentrytime: '2026-09-01T00:00:00.000Z', entries: [{ id: 'stable', data: 'event' }] }] } |
| 58 | + mocks.request.mockResolvedValue({ status: 200, headers: {}, body: new Uint8Array() }) |
| 59 | + const invalid = await executeOciLoggingTool(call({ toolId: 'oci_logging_put_logs', input: { ...input, logEntryBatches: [] } })) |
| 60 | + expect(invalid.status).toBe(400) |
| 61 | + expect(mocks.create).not.toHaveBeenCalled() |
| 62 | + const response = await executeOciLoggingTool(call({ toolId: 'oci_logging_put_logs', input })) |
| 63 | + expect(await response.json()).toEqual({ success: true, output: { accepted: true } }) |
| 64 | + expect(mocks.prepare).toHaveBeenCalledWith(OCI_LOGGING_INGESTION_POLICY) |
| 65 | + }) |
| 66 | + |
| 67 | + it('does not expose unexpected errors or invite replay after a write failure', async () => { |
| 68 | + mocks.request.mockRejectedValue(new Error('private-key-canary')) |
| 69 | + const response = await executeOciLoggingTool(call({ toolId: 'oci_logging_delete_log_group', input: { ociCredential: 'supplied', logGroupId: 'group' } })) |
| 70 | + expect(await response.json()).toEqual({ success: false, error: 'OCI Logging operation failed', retryable: false }) |
| 71 | + }) |
| 72 | + |
| 73 | + it('stops after authorization when canceled, and forwards active cancellation to the client', async () => { |
| 74 | + const controller = new AbortController() |
| 75 | + mocks.authorize.mockImplementationOnce(async () => { controller.abort(); return { ok: true } }) |
| 76 | + await expect(executeOciLoggingTool(call({ signal: controller.signal }))).rejects.toThrow() |
| 77 | + expect(mocks.create).not.toHaveBeenCalled() |
| 78 | + const active = new AbortController() |
| 79 | + await executeOciLoggingTool(call({ signal: active.signal })) |
| 80 | + expect(mocks.request).toHaveBeenCalledWith(expect.objectContaining({ signal: active.signal })) |
| 81 | + }) |
| 82 | +}) |
0 commit comments