Skip to content

Commit c60e5b5

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
feat(oci-functions): add native Functions integration
1 parent 3fa59e7 commit c60e5b5

44 files changed

Lines changed: 3724 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ import {
177177
OktaIcon,
178178
OnePasswordIcon,
179179
OpenAIIcon,
180+
OracleIcon,
180181
OutlookIcon,
181182
PackageSearchIcon,
182183
PagerDutyIcon,
@@ -482,6 +483,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
482483
notion: NotionIcon,
483484
notion_v2: NotionIcon,
484485
obsidian: ObsidianIcon,
486+
oci_functions: OracleIcon,
485487
okta: OktaIcon,
486488
onedrive: MicrosoftOneDriveIcon,
487489
onepassword: OnePasswordIcon,

apps/docs/content/docs/integrations/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
"notion",
186186
"notion-service-account",
187187
"obsidian",
188+
"oci_functions",
188189
"okta",
189190
"onedrive",
190191
"onepassword",

apps/docs/content/docs/integrations/oci_functions.mdx

Lines changed: 554 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/** @vitest-environment node */
2+
import { describe, expect, it } from 'vitest'
3+
import { OciFunctionsBlock } from '@/blocks/blocks/oci_functions'
4+
5+
function map(input: Record<string, unknown>) {
6+
return { ...input, ...OciFunctionsBlock.tools.config?.params?.(input) }
7+
}
8+
9+
/** Raw block values are merged underneath the mapper patch by the executor. */
10+
describe('OCI Functions execution-time parameter mapping', () => {
11+
it('clears stale invocation fields when changing operations', () => {
12+
const result = map({
13+
operation: 'get_function',
14+
oauthCredential: 'credential-1',
15+
functionId: 'function-1',
16+
payload: '{broken',
17+
file: { key: 'old-file' },
18+
dryRun: true,
19+
invocationType: 'detached',
20+
configuration: '{broken',
21+
applicationId: 'stale-app',
22+
})
23+
expect(result.functionId).toBe('function-1')
24+
for (const key of [
25+
'payload',
26+
'file',
27+
'dryRun',
28+
'invocationType',
29+
'configuration',
30+
'applicationId',
31+
]) {
32+
expect(result[key]).toBeUndefined()
33+
}
34+
})
35+
36+
it('preserves a manual canonical function OCID without discovery-only scope', () => {
37+
const result = map({
38+
operation: 'invoke',
39+
oauthCredential: 'credential-1',
40+
functionId: 'known-function',
41+
ociRegion: 'credential',
42+
})
43+
expect(result.functionId).toBe('known-function')
44+
expect(result.applicationId).toBeUndefined()
45+
expect(result.compartmentId).toBeUndefined()
46+
expect(result.region).toBeUndefined()
47+
})
48+
49+
it('maps the selector region into the service request region', () => {
50+
expect(map({ operation: 'get_function', ociRegion: 'us-phoenix-1' }).region).toBe(
51+
'us-phoenix-1'
52+
)
53+
})
54+
55+
it('coerces resolved numeric, boolean, and JSON values only at execution time', () => {
56+
const result = map({
57+
operation: 'invoke',
58+
payloadType: 'json',
59+
payload: 'false',
60+
dryRun: 'false',
61+
timeoutMs: '120000',
62+
})
63+
expect(result).toMatchObject({ payload: false, dryRun: false, timeoutMs: 120000 })
64+
expect(
65+
map({ operation: 'create_function', memoryInMBs: '512', configuration: '{"config":{}}' })
66+
).toMatchObject({ memoryInMBs: 512, configuration: { config: {} } })
67+
expect(map({ operation: 'create_application', subnetIds: '["subnet-1"]' }).subnetIds).toEqual([
68+
'subnet-1',
69+
])
70+
})
71+
72+
it.each(['false', '0', 'null', '""'])('preserves JSON value %s', (payload) => {
73+
expect(map({ operation: 'invoke', payloadType: 'json', payload }).payload).toEqual(
74+
JSON.parse(payload)
75+
)
76+
})
77+
78+
it('keeps text unencoded and clears file data in text mode', () => {
79+
const result = map({
80+
operation: 'invoke',
81+
payloadType: 'text',
82+
payload: 'plain text',
83+
file: { key: 'stale' },
84+
})
85+
expect(result.payload).toBe('plain text')
86+
expect(result.file).toBeUndefined()
87+
})
88+
89+
it('normalizes a single file and rejects multiple files', () => {
90+
const file = { key: 'file-1', name: 'input.bin', size: 2 }
91+
const result = map({ operation: 'invoke', payloadType: 'file', file: [file], payload: 'stale' })
92+
expect(result.file).toEqual(file)
93+
expect(result.payload).toBeUndefined()
94+
expect(() => map({ operation: 'invoke', payloadType: 'file', file: [file, file] })).toThrow()
95+
})
96+
97+
it('rejects malformed JSON and numbers before provider execution', () => {
98+
expect(() => map({ operation: 'invoke', payloadType: 'json', payload: '{' })).toThrow(
99+
'Payload must be valid JSON'
100+
)
101+
expect(() => map({ operation: 'list_applications', limit: '51' })).toThrow()
102+
expect(() => map({ operation: 'invoke', dryRun: 'maybe' })).toThrow(
103+
'Dry run must be true or false'
104+
)
105+
expect(() => map({ operation: 'unknown' })).toThrow('Invalid OCI Functions operation')
106+
})
107+
})

0 commit comments

Comments
 (0)