Skip to content

Commit 5f0fa6c

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(oracle-fusion-recruiting): address independent review and CI findings
1 parent 42dadb3 commit 5f0fa6c

40 files changed

Lines changed: 1373 additions & 418 deletions

apps/sim/blocks/blocks/oracle_fusion_recruiting.ts

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import type { BlockConfig, BlockMeta } from '@/blocks/types'
2-
import { AuthMode, IntegrationType } from '@/blocks/types'
31
import { NetSuiteIcon } from '@/components/icons'
42
import { getScopesForService } from '@/lib/oauth/utils'
3+
import type { BlockConfig, BlockMeta } from '@/blocks/types'
4+
import { AuthMode, IntegrationType } from '@/blocks/types'
55

66
export const OracleFusionRecruitingBlock: BlockConfig = {
77
type: 'oracle_fusion_recruiting',
88
name: 'Oracle Fusion Recruiting',
9-
description: 'Manage candidates and requisitions, and read applications, offers, and recruiting lookups',
10-
longDescription: 'Use a saved Oracle Fusion integration-user credential to manage candidates, candidate phones, and job requisitions. Read candidate education, experience, skills, attachment metadata, published jobs, applications, offers, interview schedule lookups, requisition templates, and recruiting representatives. Lists return one bounded page. Interview schedules contain lookup metadata, not appointments. Oracle privileges and data security govern access; use a dedicated least-privilege integration user. Write bodies accept only the documented scalar fields listed by each tool; nested child mutations and custom fields are not supported.',
9+
description:
10+
'Manage candidates and requisitions, and read applications, offers, and recruiting lookups',
11+
longDescription:
12+
'Use a saved Oracle Fusion integration-user credential to manage candidates, candidate phones, and job requisitions. Read candidate education, experience, skills, attachment metadata, published jobs, applications, offers, interview schedule lookups, requisition templates, and recruiting representatives. Lists return one bounded page. Interview schedules contain lookup metadata, not appointments. Oracle privileges and data security govern access; use a dedicated least-privilege integration user. Write bodies accept only the documented scalar fields listed by each tool; nested child mutations and custom fields are not supported.',
1113
docsLink: 'https://docs.sim.ai/integrations/oracle_fusion_recruiting',
1214
category: 'tools',
1315
integrationType: IntegrationType.HR,
14-
tags: ['automation'],
1516
authMode: AuthMode.ApiKey,
1617
bgColor: '#F80000',
1718
icon: NetSuiteIcon,
@@ -20,27 +21,125 @@ export const OracleFusionRecruitingBlock: BlockConfig = {
2021
sentences: {
2122
byOperation: {
2223
list_candidates: ['List candidates'],
23-
get_candidate: [{ text: 'Get candidate for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
24+
get_candidate: [
25+
{
26+
text: 'Get candidate for',
27+
field: ['candidateNumberPicker', 'candidateNumberInput'],
28+
core: true,
29+
},
30+
],
2431
create_candidate: ['Create candidate'],
25-
update_candidate: [{ text: 'Update candidate for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
26-
delete_candidate: [{ text: 'Delete candidate for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
27-
list_candidate_phones: [{ text: 'List candidate phones for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
28-
get_candidate_phone: [{ text: 'Get candidate phone for', field: ['phoneIdPicker', 'phoneIdInput'], core: true }],
29-
create_candidate_phone: [{ text: 'Create candidate phone for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
30-
update_candidate_phone: [{ text: 'Update candidate phone for', field: ['phoneIdPicker', 'phoneIdInput'], core: true }],
31-
delete_candidate_phone: [{ text: 'Delete candidate phone for', field: ['phoneIdPicker', 'phoneIdInput'], core: true }],
32-
list_candidate_education: [{ text: 'List candidate education for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
33-
list_candidate_experience: [{ text: 'List candidate experience for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
34-
list_candidate_skills: [{ text: 'List candidate skills for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
35-
list_candidate_attachments: [{ text: 'List candidate attachments for', field: ['candidateNumberPicker', 'candidateNumberInput'], core: true }],
32+
update_candidate: [
33+
{
34+
text: 'Update candidate for',
35+
field: ['candidateNumberPicker', 'candidateNumberInput'],
36+
core: true,
37+
},
38+
],
39+
delete_candidate: [
40+
{
41+
text: 'Delete candidate for',
42+
field: ['candidateNumberPicker', 'candidateNumberInput'],
43+
core: true,
44+
},
45+
],
46+
list_candidate_phones: [
47+
{
48+
text: 'List candidate phones for',
49+
field: ['candidateNumberPicker', 'candidateNumberInput'],
50+
core: true,
51+
},
52+
],
53+
get_candidate_phone: [
54+
{ text: 'Get candidate phone for', field: ['phoneIdPicker', 'phoneIdInput'], core: true },
55+
],
56+
create_candidate_phone: [
57+
{
58+
text: 'Create candidate phone for',
59+
field: ['candidateNumberPicker', 'candidateNumberInput'],
60+
core: true,
61+
},
62+
],
63+
update_candidate_phone: [
64+
{
65+
text: 'Update candidate phone for',
66+
field: ['phoneIdPicker', 'phoneIdInput'],
67+
core: true,
68+
},
69+
],
70+
delete_candidate_phone: [
71+
{
72+
text: 'Delete candidate phone for',
73+
field: ['phoneIdPicker', 'phoneIdInput'],
74+
core: true,
75+
},
76+
],
77+
list_candidate_education: [
78+
{
79+
text: 'List candidate education for',
80+
field: ['candidateNumberPicker', 'candidateNumberInput'],
81+
core: true,
82+
},
83+
],
84+
list_candidate_experience: [
85+
{
86+
text: 'List candidate experience for',
87+
field: ['candidateNumberPicker', 'candidateNumberInput'],
88+
core: true,
89+
},
90+
],
91+
list_candidate_skills: [
92+
{
93+
text: 'List candidate skills for',
94+
field: ['candidateNumberPicker', 'candidateNumberInput'],
95+
core: true,
96+
},
97+
],
98+
list_candidate_attachments: [
99+
{
100+
text: 'List candidate attachments for',
101+
field: ['candidateNumberPicker', 'candidateNumberInput'],
102+
core: true,
103+
},
104+
],
36105
list_requisitions: ['List requisitions'],
37-
get_requisition: [{ text: 'Get requisition for', field: ['requisitionIdPicker', 'requisitionIdInput'], core: true }],
106+
get_requisition: [
107+
{
108+
text: 'Get requisition for',
109+
field: ['requisitionIdPicker', 'requisitionIdInput'],
110+
core: true,
111+
},
112+
],
38113
create_requisition: ['Create requisition'],
39-
update_requisition: [{ text: 'Update requisition for', field: ['requisitionIdPicker', 'requisitionIdInput'], core: true }],
40-
delete_requisition: [{ text: 'Delete requisition for', field: ['requisitionIdPicker', 'requisitionIdInput'], core: true }],
41-
list_requisition_postings: [{ text: 'List requisition postings for', field: ['requisitionIdPicker', 'requisitionIdInput'], core: true }],
114+
update_requisition: [
115+
{
116+
text: 'Update requisition for',
117+
field: ['requisitionIdPicker', 'requisitionIdInput'],
118+
core: true,
119+
},
120+
],
121+
delete_requisition: [
122+
{
123+
text: 'Delete requisition for',
124+
field: ['requisitionIdPicker', 'requisitionIdInput'],
125+
core: true,
126+
},
127+
],
128+
list_requisition_postings: [
129+
{
130+
text: 'List requisition postings for',
131+
field: ['requisitionIdPicker', 'requisitionIdInput'],
132+
core: true,
133+
},
134+
],
42135
list_applications: ['List applications'],
43-
get_application: [{ text: 'Get application for', field: ['applicationIdPicker', 'applicationIdInput'], core: true }],
136+
get_application: [
137+
{
138+
text: 'Get application for',
139+
field: ['applicationIdPicker', 'applicationIdInput'],
140+
core: true,
141+
},
142+
],
44143
list_offers: ['List offers'],
45144
get_offer: [{ text: 'Get offer for', field: ['offerIdPicker', 'offerIdInput'], core: true }],
46145
list_interview_schedules: ['List interview schedules'],

apps/sim/lib/integrations/credential-display.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,18 @@ describe('service-account coverage', () => {
112112
})
113113

114114
it('registers Recruiting on the existing Fusion credential family', () => {
115-
expect(INTEGRATIONS.find((integration) => integration.type === 'oracle_fusion_recruiting')?.authType).toBe('api-key')
115+
expect(
116+
INTEGRATIONS.find((integration) => integration.type === 'oracle_fusion_recruiting')?.authType
117+
).toBe('api-key')
116118
expect(OAUTH_PROVIDERS.oracle_fusion.services.oracle_fusion_recruiting).toMatchObject({
117-
providerId: 'oracle_fusion_recruiting', serviceAccountProviderId: 'oracle-fusion-service-account', authType: 'service_account',
119+
providerId: 'oracle_fusion_recruiting',
120+
serviceAccountProviderId: 'oracle-fusion-service-account',
121+
authType: 'service_account',
118122
})
119-
expect(credentialProviderMatchesService('oracle-fusion-service-account', 'oracle_fusion_recruiting')).toBe(true)
123+
expect(credentialProviderMatchesService(
124+
'oracle-fusion-service-account',
125+
OAUTH_PROVIDERS.oracle_fusion.services.oracle_fusion_recruiting
126+
)).toBe(true)
120127
})
121128

122129
it('pins the table to exactly the registered service-account provider ids', () => {

apps/sim/lib/internal/oracle-fusion-recruiting/execute-tool.test.ts

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ vi.mock('@/lib/internal/oracle-fusion-recruiting/operations', () => ({
7070
const auth = { instanceUrl: 'https://example.fa.ocs.oraclecloud.com', accessToken: 'test-token' }
7171
function invoke(overrides: Partial<InternalToolOperationCall> = {}) {
7272
return executeOracleFusionRecruitingTool({
73-
toolId: 'oracle_fusion_recruiting_list_candidates', input: auth,
74-
headers: new Headers(), requestId: 'test-request',
73+
toolId: 'oracle_fusion_recruiting_list_candidates',
74+
input: auth,
75+
headers: new Headers(),
76+
requestId: 'test-request',
7577
context: { workflowId: 'workflow-1', workspaceId: 'workspace-1', userId: 'user-1' },
7678
...overrides,
7779
})
@@ -83,35 +85,74 @@ describe('Recruiting dispatch', () => {
8385
})
8486
it.each([
8587
['list_candidates', mocks.list_candidates, {}],
86-
['get_candidate', mocks.get_candidate, {"candidateNumber": "1"}],
87-
['create_candidate', mocks.create_candidate, {"body": {"FirstName": "Taylor"}}],
88-
['update_candidate', mocks.update_candidate, {"candidateNumber": "1", "body": {"FirstName": "Taylor"}}],
89-
['delete_candidate', mocks.delete_candidate, {"candidateNumber": "1"}],
90-
['list_candidate_phones', mocks.list_candidate_phones, {"candidateNumber": "1"}],
91-
['get_candidate_phone', mocks.get_candidate_phone, {"candidateNumber": "1", "phoneId": "1"}],
92-
['create_candidate_phone', mocks.create_candidate_phone, {"candidateNumber": "1", "body": {"PhoneNumber": "5550100"}}],
93-
['update_candidate_phone', mocks.update_candidate_phone, {"candidateNumber": "1", "phoneId": "1", "body": {"PhoneNumber": "5550100"}}],
94-
['delete_candidate_phone', mocks.delete_candidate_phone, {"candidateNumber": "1", "phoneId": "1"}],
95-
['list_candidate_education', mocks.list_candidate_education, {"candidateNumber": "1"}],
96-
['list_candidate_experience', mocks.list_candidate_experience, {"candidateNumber": "1"}],
97-
['list_candidate_skills', mocks.list_candidate_skills, {"candidateNumber": "1"}],
98-
['list_candidate_attachments', mocks.list_candidate_attachments, {"candidateNumber": "1"}],
88+
['get_candidate', mocks.get_candidate, { candidateNumber: '1' }],
89+
['create_candidate', mocks.create_candidate, { body: { FirstName: 'Taylor' } }],
90+
[
91+
'update_candidate',
92+
mocks.update_candidate,
93+
{ candidateNumber: '1', body: { FirstName: 'Taylor' } },
94+
],
95+
['delete_candidate', mocks.delete_candidate, { candidateNumber: '1' }],
96+
['list_candidate_phones', mocks.list_candidate_phones, { candidateNumber: '1' }],
97+
['get_candidate_phone', mocks.get_candidate_phone, { candidateNumber: '1', phoneId: '1' }],
98+
[
99+
'create_candidate_phone',
100+
mocks.create_candidate_phone,
101+
{ candidateNumber: '1', body: { PhoneNumber: '5550100' } },
102+
],
103+
[
104+
'update_candidate_phone',
105+
mocks.update_candidate_phone,
106+
{ candidateNumber: '1', phoneId: '1', body: { PhoneNumber: '5550100' } },
107+
],
108+
[
109+
'delete_candidate_phone',
110+
mocks.delete_candidate_phone,
111+
{ candidateNumber: '1', phoneId: '1' },
112+
],
113+
['list_candidate_education', mocks.list_candidate_education, { candidateNumber: '1' }],
114+
['list_candidate_experience', mocks.list_candidate_experience, { candidateNumber: '1' }],
115+
['list_candidate_skills', mocks.list_candidate_skills, { candidateNumber: '1' }],
116+
['list_candidate_attachments', mocks.list_candidate_attachments, { candidateNumber: '1' }],
99117
['list_requisitions', mocks.list_requisitions, {}],
100-
['get_requisition', mocks.get_requisition, {"requisitionId": "1"}],
101-
['create_requisition', mocks.create_requisition, {"body": {"Title": "Engineer", "RecruitingType": "ORA_PROFESSIONAL", "HiringManagerId": "9007199254740993", "RecruiterId": "2", "PrimaryLocationId": "3", "PhaseId": "1", "StateId": "21", "UnlimitedOpenings": "N", "NumberOfOpenings": 1}}],
102-
['update_requisition', mocks.update_requisition, {"requisitionId": "1", "body": {"Title": "Engineer"}}],
103-
['delete_requisition', mocks.delete_requisition, {"requisitionId": "1"}],
104-
['list_requisition_postings', mocks.list_requisition_postings, {"requisitionId": "1"}],
118+
['get_requisition', mocks.get_requisition, { requisitionId: '1' }],
119+
[
120+
'create_requisition',
121+
mocks.create_requisition,
122+
{
123+
body: {
124+
Title: 'Engineer',
125+
RecruitingType: 'ORA_PROFESSIONAL',
126+
HiringManagerId: '9007199254740993',
127+
RecruiterId: '2',
128+
PrimaryLocationId: '3',
129+
PhaseId: '1',
130+
StateId: '21',
131+
UnlimitedOpenings: 'N',
132+
NumberOfOpenings: 1,
133+
},
134+
},
135+
],
136+
[
137+
'update_requisition',
138+
mocks.update_requisition,
139+
{ requisitionId: '1', body: { Title: 'Engineer' } },
140+
],
141+
['delete_requisition', mocks.delete_requisition, { requisitionId: '1' }],
142+
['list_requisition_postings', mocks.list_requisition_postings, { requisitionId: '1' }],
105143
['list_applications', mocks.list_applications, {}],
106-
['get_application', mocks.get_application, {"applicationId": "1"}],
144+
['get_application', mocks.get_application, { applicationId: '1' }],
107145
['list_offers', mocks.list_offers, {}],
108-
['get_offer', mocks.get_offer, {"offerId": "1"}],
146+
['get_offer', mocks.get_offer, { offerId: '1' }],
109147
['list_interview_schedules', mocks.list_interview_schedules, {}],
110-
['get_interview_schedule', mocks.get_interview_schedule, {"scheduleId": "1"}],
148+
['get_interview_schedule', mocks.get_interview_schedule, { scheduleId: '1' }],
111149
['list_requisition_templates', mocks.list_requisition_templates, {}],
112150
['list_recruiting_representatives', mocks.list_recruiting_representatives, {}],
113151
])('dispatches %s', async (action, operation, input) => {
114-
const response = await invoke({ toolId: `oracle_fusion_recruiting_${action}`, input: { ...auth, ...input } })
152+
const response = await invoke({
153+
toolId: `oracle_fusion_recruiting_${action}`,
154+
input: { ...auth, ...input },
155+
})
115156
expect(response.status).toBe(200)
116157
expect(operation).toHaveBeenCalledOnce()
117158
})
@@ -120,7 +161,9 @@ describe('Recruiting dispatch', () => {
120161
expect(mocks.list_candidates).not.toHaveBeenCalled()
121162
})
122163
it.each([401, 403, 404, 429, 502, 504])('preserves safe provider status %s', async (status) => {
123-
mocks.list_candidates.mockRejectedValue(new OracleFusionProviderError('Provider unavailable', status))
164+
mocks.list_candidates.mockRejectedValue(
165+
new OracleFusionProviderError('Provider unavailable', status)
166+
)
124167
expect((await invoke()).status).toBe(status)
125168
})
126169
it('does not expose unknown internal exceptions', async () => {

0 commit comments

Comments
 (0)