Skip to content

Commit cf39099

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
test(oci): add signing and transport conformance
1 parent fe1e8d5 commit cf39099

16 files changed

Lines changed: 1456 additions & 926 deletions

‎apps/sim/app/api/credentials/route.test.ts‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,4 +550,68 @@ describe('POST /api/credentials', () => {
550550
expect(dbChainMockFns.insert).not.toHaveBeenCalled()
551551
})
552552
})
553+
554+
it('forwards OCI API-key fields without returning secret material', async () => {
555+
mockVerifyAndBuildServiceAccountSecret.mockResolvedValueOnce({
556+
providerId: 'oci-api-key-service-account',
557+
encryptedServiceAccountKey: 'encrypted-oci-blob',
558+
displayName: 'ocid1.user.oc1..principal',
559+
auditMetadata: {
560+
principalKind: 'user',
561+
principalId: 'ocid1.user.oc1..principal',
562+
},
563+
principal: { kind: 'user', id: 'ocid1.user.oc1..principal' },
564+
})
565+
queueTableRows(credential, [])
566+
queueTableRows(credential, [])
567+
queueTableRows(credential, [
568+
{
569+
id: 'credential-oci',
570+
workspaceId: WORKSPACE_ID,
571+
type: 'service_account',
572+
displayName: 'ocid1.user.oc1..principal',
573+
description: null,
574+
unredacted: false,
575+
providerId: 'oci-api-key-service-account',
576+
accountId: null,
577+
envKey: null,
578+
envOwnerUserId: null,
579+
encryptedServiceAccountKey: 'encrypted-oci-blob',
580+
createdBy: 'user-1',
581+
createdAt: new Date('2026-08-11T00:00:00.000Z'),
582+
updatedAt: new Date('2026-08-11T00:00:00.000Z'),
583+
},
584+
])
585+
586+
const response = await POST(
587+
createMockRequest('POST', {
588+
workspaceId: WORKSPACE_ID,
589+
type: 'service_account',
590+
providerId: 'oci-api-key-service-account',
591+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
592+
userOcid: 'ocid1.user.oc1..principal',
593+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
594+
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
595+
privateKeyPassphrase: ' exact passphrase ',
596+
region: 'us-ashburn-1',
597+
})
598+
)
599+
const body = await response.text()
600+
601+
expect(response.status).toBe(201)
602+
expect(mockVerifyAndBuildServiceAccountSecret).toHaveBeenCalledWith(
603+
'oci-api-key-service-account',
604+
expect.objectContaining({
605+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
606+
userOcid: 'ocid1.user.oc1..principal',
607+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
608+
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
609+
privateKeyPassphrase: ' exact passphrase ',
610+
region: 'us-ashburn-1',
611+
})
612+
)
613+
expect(body).not.toContain('PRIVATE KEY')
614+
expect(body).not.toContain('exact passphrase')
615+
expect(body).not.toContain('encrypted-oci-blob')
616+
})
553617
})

‎apps/sim/app/api/v2/credentials/[credentialId]/route.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,33 @@ describe('PATCH /api/v2/credentials/[credentialId]', () => {
127127
expect(body).not.toContain('MUST_NOT_LEAK_CIPHERTEXT')
128128
})
129129

130+
it('forwards a complete OCI rotation tuple and preserves explicit passphrase clearing', async () => {
131+
const request = patchRequest({
132+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
133+
userOcid: 'ocid1.user.oc1..replacement',
134+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
135+
privateKey: '-----BEGIN PRIVATE KEY-----\nreplacement\n-----END PRIVATE KEY-----',
136+
region: 'us-ashburn-1',
137+
})
138+
const response = await PATCH(request, context)
139+
140+
expect(response.status).toBe(200)
141+
expect(mocks.update).toHaveBeenCalledWith({
142+
principal: auth.principal,
143+
input: {
144+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
145+
userOcid: 'ocid1.user.oc1..replacement',
146+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
147+
privateKey: '-----BEGIN PRIVATE KEY-----\nreplacement\n-----END PRIVATE KEY-----',
148+
region: 'us-ashburn-1',
149+
credentialId: CREDENTIAL_ID,
150+
assertedWorkspaceId: WORKSPACE_ID,
151+
},
152+
request,
153+
})
154+
expect(JSON.stringify(await response.json())).not.toContain('PRIVATE KEY')
155+
})
156+
130157
it('asserts the workspace scope and preserves the credential id', async () => {
131158
const request = patchRequest({ displayName: 'Zoom prod' })
132159
await PATCH(request, context)

‎apps/sim/app/api/v2/credentials/route.test.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,55 @@ describe('POST /api/v2/credentials', () => {
288288
authMethod: undefined,
289289
privateKey: undefined,
290290
username: undefined,
291+
tenancyOcid: undefined,
292+
userOcid: undefined,
293+
fingerprint: undefined,
294+
privateKeyPassphrase: undefined,
295+
region: undefined,
291296
},
292297
request,
293298
})
294299
})
295300

301+
it('forwards OCI credential fields from the write-only credentials envelope', async () => {
302+
const request = new NextRequest('http://localhost:3000/api/v2/credentials', {
303+
method: 'POST',
304+
headers: { 'content-type': 'application/json' },
305+
body: JSON.stringify({
306+
workspaceId: WORKSPACE_ID,
307+
type: 'service_account',
308+
providerId: 'oci-api-key-service-account',
309+
credentials: JSON.stringify({
310+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
311+
userOcid: 'ocid1.user.oc1..user',
312+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
313+
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
314+
privateKeyPassphrase: ' exact passphrase ',
315+
region: 'us-ashburn-1',
316+
}),
317+
}),
318+
})
319+
const response = await POST(request)
320+
const body = await response.text()
321+
322+
expect(response.status).toBe(201)
323+
expect(mocks.create).toHaveBeenCalledWith({
324+
principal: { kind: 'personal_api_key', userId: 'user-1', keyId: 'key-1' },
325+
input: expect.objectContaining({
326+
providerId: 'oci-api-key-service-account',
327+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
328+
userOcid: 'ocid1.user.oc1..user',
329+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
330+
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
331+
privateKeyPassphrase: ' exact passphrase ',
332+
region: 'us-ashburn-1',
333+
}),
334+
request,
335+
})
336+
expect(body).not.toContain('PRIVATE KEY')
337+
expect(body).not.toContain('exact passphrase')
338+
})
339+
296340
it('rejects an unknown service-account provider before the use case', async () => {
297341
const response = await POST(
298342
new NextRequest('http://localhost:3000/api/v2/credentials', {

‎apps/sim/lib/api/contracts/credentials.test.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
*/
44
import { describe, expect, it } from 'vitest'
55
import {
6+
createCredentialBodySchema,
67
createCredentialDraftBodySchema,
78
updateCredentialByIdBodySchema,
89
workspaceCredentialSchema,
910
} from '@/lib/api/contracts/credentials'
11+
import {
12+
v2CreateServiceAccountCredentialBodySchema,
13+
v2UpdateCredentialBodySchema,
14+
} from '@/lib/api/contracts/v2/credentials'
1015

1116
const credential = {
1217
id: 'credential-1',
@@ -92,3 +97,43 @@ describe('createCredentialDraftBodySchema OAuth client configuration', () => {
9297
).toBe(false)
9398
})
9499
})
100+
101+
describe('OCI API-key credential fields', () => {
102+
const fields = {
103+
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
104+
userOcid: 'ocid1.user.oc1..user',
105+
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
106+
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
107+
privateKeyPassphrase: ' exact passphrase ',
108+
region: 'us-ashburn-1',
109+
}
110+
111+
it('accepts the stable web create field names and preserves the passphrase exactly', () => {
112+
const parsed = createCredentialBodySchema.parse({
113+
workspaceId: '11111111-2222-4333-8444-555555555555',
114+
type: 'service_account',
115+
providerId: 'oci-api-key-service-account',
116+
...fields,
117+
})
118+
119+
expect(parsed.privateKeyPassphrase).toBe(' exact passphrase ')
120+
expect(parsed).toMatchObject(fields)
121+
})
122+
123+
it('accepts the same fields in the V2 write-only envelope', () => {
124+
const parsed = v2CreateServiceAccountCredentialBodySchema.parse({
125+
workspaceId: '11111111-2222-4333-8444-555555555555',
126+
type: 'service_account',
127+
providerId: 'oci-api-key-service-account',
128+
credentials: JSON.stringify(fields),
129+
})
130+
131+
expect(parsed.credentials).toMatchObject(fields)
132+
})
133+
134+
it('accepts an omitted passphrase on rotation as an unencrypted replacement key', () => {
135+
const { privateKeyPassphrase: _omitted, ...replacement } = fields
136+
expect(v2UpdateCredentialBodySchema.parse(replacement)).toEqual(replacement)
137+
expect(updateCredentialByIdBodySchema.parse(replacement)).toEqual(replacement)
138+
})
139+
})

‎apps/sim/lib/credentials/application/provider-catalog.test.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,46 @@ describe('listCredentialProviderCatalog', () => {
287287
})
288288
})
289289

290+
it('publishes the OCI setup contract but keeps it unavailable without product metadata', async () => {
291+
mocks.getAllOAuthServices.mockReturnValue([
292+
{
293+
serviceId: 'oci',
294+
providerId: 'oci-api-key-service-account',
295+
serviceAccountProviderId: 'oci-api-key-service-account',
296+
name: 'Oracle Cloud Infrastructure',
297+
description: 'Connect to Oracle Cloud Infrastructure services.',
298+
baseProvider: 'oci',
299+
authType: 'service_account',
300+
},
301+
])
302+
mocks.createVisibility.mockReturnValue({
303+
isOAuthServiceVisible: vi.fn(),
304+
isCredentialVisible: vi.fn().mockReturnValue(false),
305+
})
306+
307+
const catalog = await listCredentialProviderCatalog(personalPrincipal, context)
308+
309+
expect(catalog).toEqual([
310+
expect.objectContaining({
311+
type: 'service_account',
312+
serviceId: 'oci-api-key-service-account',
313+
providerId: 'oci-api-key-service-account',
314+
name: 'OCI API key',
315+
providerFamily: 'oci',
316+
available: false,
317+
requiresClientGeneratedCredentialId: false,
318+
fields: [
319+
expect.objectContaining({ id: 'tenancyOcid', required: true, secret: false }),
320+
expect.objectContaining({ id: 'userOcid', required: true, secret: false }),
321+
expect.objectContaining({ id: 'fingerprint', required: true, secret: false }),
322+
expect.objectContaining({ id: 'privateKey', required: true, secret: true }),
323+
expect.objectContaining({ id: 'privateKeyPassphrase', required: false, secret: true }),
324+
expect.objectContaining({ id: 'region', required: true, secret: false }),
325+
],
326+
}),
327+
])
328+
})
329+
290330
it('fails fast when a multi-server provider lacks complete labels', async () => {
291331
mocks.getServiceConfigByServiceId.mockImplementation((serviceId: string) => {
292332
if (serviceId === 'salesforce') {

0 commit comments

Comments
 (0)