Skip to content

Commit 6b64789

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
feat(integrations): add Oracle Fusion Subscription Management
1 parent 15294a2 commit 6b64789

95 files changed

Lines changed: 16673 additions & 8 deletions

File tree

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
480480
onedrive: MicrosoftOneDriveIcon,
481481
onepassword: OnePasswordIcon,
482482
openai: OpenAIIcon,
483+
oracle_fusion_subscription_management: NetSuiteIcon,
483484
outlook: OutlookIcon,
484485
pagerduty: PagerDutyIcon,
485486
parallel_ai: ParallelIcon,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
"onedrive",
188188
"onepassword",
189189
"openai",
190+
"oracle_fusion_subscription_management",
190191
"outlook",
191192
"pagerduty",
192193
"parallel_ai",

apps/docs/content/docs/integrations/oracle_fusion_subscription_management.mdx

Lines changed: 2505 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it, vi } from 'vitest'
5+
6+
vi.mock('@/components/icons', () => ({ NetSuiteIcon: () => null }))
7+
vi.mock('@/lib/oauth/utils', () => ({ getScopesForService: () => [] }))
8+
9+
import { OracleFusionSubscriptionManagementBlock } from '@/blocks/blocks/oracle_fusion_subscription_management'
10+
import { parseOracleFusionSubscriptionInput } from '@/lib/internal/oracle-fusion-subscription-management/schema'
11+
import { buildSelectorContextFromValues } from '@/lib/selectors/context'
12+
import {
13+
buildCanonicalIndex,
14+
isSubBlockVisibleForMode,
15+
resolveActiveCanonicalValue,
16+
} from '@/lib/workflows/subblocks/visibility'
17+
18+
const block = OracleFusionSubscriptionManagementBlock
19+
function mapped(name: string, values: Record<string, unknown>) {
20+
return {
21+
...block.tools.config.params?.({
22+
operation: 'oracle_fusion_subscription_management_' + name,
23+
oauthCredential: 'credential-1',
24+
...values,
25+
}),
26+
oauthCredential: 'credential-1',
27+
accessToken: 'test-token',
28+
instanceUrl: 'https://vision.fa.us2.oraclecloud.com',
29+
}
30+
}
31+
32+
describe('Subscription Management canonical block mapping', () => {
33+
it('keeps existing-key pickers visible through the unfiltered canonical index', () => {
34+
const index = buildCanonicalIndex(block.subBlocks)
35+
for (const canonical of ['subscriptionNumber', 'subscriptionProductPuid', 'coveredLevelPuid']) {
36+
const selector = block.subBlocks.find((field) => field.id === canonical + 'Selector')!
37+
expect(index.groupsById[canonical].basicId).toBe(selector.id)
38+
expect(index.groupsById[canonical].advancedIds).toEqual([canonical + 'Manual'])
39+
expect(isSubBlockVisibleForMode(selector, false, index, {}, { [canonical]: 'basic' })).toBe(true)
40+
}
41+
const input = mapped('create_covered_level', {
42+
subscriptionNumber: 'SUB-001',
43+
subscriptionProductPuid: 'P-001',
44+
newCoveredLevelPuid: 'NEW-C-001',
45+
coveredLevelPuid: 'STALE-C-001',
46+
type: 'TENANT_COVERAGE_CODE',
47+
startDate: '2026-09-01',
48+
})
49+
expect(parseOracleFusionSubscriptionInput('create_covered_level', input)).toMatchObject({
50+
newCoveredLevelPuid: 'NEW-C-001',
51+
})
52+
expect(input).not.toHaveProperty('coveredLevelPuid')
53+
})
54+
55+
it.each([
56+
'update_product',
57+
'create_covered_level',
58+
'update_covered_level',
59+
'create_associated_asset',
60+
'update_associated_asset',
61+
])('keeps organization available only as item-picker context for %s', (name) => {
62+
const values = {
63+
operation: 'oracle_fusion_subscription_management_' + name,
64+
credential: 'credential-1',
65+
definitionOrganizationIdSelector: '123',
66+
}
67+
expect(
68+
buildSelectorContextFromValues({
69+
selectorKey: 'oracleFusionSubscriptionManagement.subscriptionItems',
70+
contextConfigs: block.subBlocks,
71+
values,
72+
dependsOn: ['oauthCredential', 'definitionOrganizationId'],
73+
})
74+
).toEqual({ oauthCredential: 'credential-1', orgId: '123' })
75+
expect(mapped(name, { definitionOrganizationId: '123' })).not.toHaveProperty(
76+
'definitionOrganizationId'
77+
)
78+
})
79+
80+
it('preserves exact resolved identifiers and drops stale lifecycle fields after operation changes', () => {
81+
const input = mapped('update_product', {
82+
subscriptionNumber: 'SUB / 001',
83+
subscriptionProductPuid: 'P-001',
84+
inventoryItemId: '9007199254740993',
85+
quantity: '0',
86+
suspendedDate: '2026-09-01',
87+
autoExtendFlag: 'true',
88+
})
89+
expect(parseOracleFusionSubscriptionInput('update_product', input)).toMatchObject({
90+
subscriptionNumber: 'SUB / 001',
91+
inventoryItemId: '9007199254740993',
92+
quantity: 0,
93+
})
94+
expect(input).not.toHaveProperty('suspendedDate')
95+
expect(input).not.toHaveProperty('autoExtendFlag')
96+
})
97+
98+
it('keeps the selected/manual public identifier canonical and out of numeric coercion', () => {
99+
const index = buildCanonicalIndex(block.subBlocks)
100+
const members = block.subBlocks.filter((field) => field.canonicalParamId === 'subscriptionNumber')
101+
expect(members.map((field) => field.id)).toEqual([
102+
'subscriptionNumberSelector',
103+
'subscriptionNumberManual',
104+
])
105+
expect(
106+
resolveActiveCanonicalValue(
107+
index.groupsById.subscriptionNumber,
108+
{
109+
subscriptionNumberSelector: 'SUB-OLD',
110+
subscriptionNumberManual: '<upstream.subscriptionNumber>',
111+
operation: 'oracle_fusion_subscription_management_get_subscription',
112+
},
113+
{ subscriptionNumber: 'advanced' }
114+
)
115+
).toBe('<upstream.subscriptionNumber>')
116+
})
117+
118+
it('maps billing scope independently and removes inactive covered-level context', () => {
119+
const values = {
120+
subscriptionNumber: 'SUB-001',
121+
subscriptionProductPuid: 'P-001',
122+
coveredLevelPuid: 'C-001',
123+
limit: '25',
124+
totalResults: 'false',
125+
}
126+
const product = mapped('list_bill_lines', { ...values, billingScope: 'product' })
127+
expect(product).not.toHaveProperty('coveredLevelPuid')
128+
expect(parseOracleFusionSubscriptionInput('list_bill_lines', product)).toMatchObject({
129+
billingScope: 'product',
130+
limit: 25,
131+
totalResults: false,
132+
})
133+
const covered = mapped('list_bill_lines', { ...values, billingScope: 'covered_level' })
134+
expect(parseOracleFusionSubscriptionInput('list_bill_lines', covered)).toMatchObject({
135+
billingScope: 'covered_level',
136+
coveredLevelPuid: 'C-001',
137+
})
138+
})
139+
140+
it('projects item selector organization dependencies from the active canonical field', () => {
141+
expect(
142+
buildSelectorContextFromValues({
143+
selectorKey: 'oracleFusionSubscriptionManagement.subscriptionItems',
144+
contextConfigs: block.subBlocks,
145+
values: {
146+
operation: 'oracle_fusion_subscription_management_create_product',
147+
credential: 'credential-1',
148+
definitionOrganizationIdSelector: '123',
149+
definitionOrganizationIdManual: '9007199254740993',
150+
instanceUrl: 'https://attacker.example',
151+
},
152+
canonicalModes: { definitionOrganizationId: 'advanced' },
153+
dependsOn: ['oauthCredential', 'definitionOrganizationId'],
154+
})
155+
).toEqual({ oauthCredential: 'credential-1', orgId: '9007199254740993' })
156+
})
157+
158+
it('distinguishes explicit false and invalid numbers from unset controls', () => {
159+
const values = { subscriptionNumber: 'SUB-001', subscriptionProductPuid: 'P-001' }
160+
expect(
161+
parseOracleFusionSubscriptionInput(
162+
'suspend_product',
163+
mapped('suspend_product', { ...values, autoExtendFlag: 'false', resumeDuration: '0' })
164+
)
165+
).toMatchObject({ autoExtendFlag: false, resumeDuration: 0 })
166+
expect(() =>
167+
parseOracleFusionSubscriptionInput(
168+
'suspend_product',
169+
mapped('suspend_product', { ...values, resumeDuration: 'invalid' })
170+
)
171+
).toThrow()
172+
})
173+
})

0 commit comments

Comments
 (0)