Skip to content

Commit 83e7bcb

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(integrations): tighten Oracle HCM selectors
1 parent 20724c1 commit 83e7bcb

5 files changed

Lines changed: 434 additions & 322 deletions

File tree

apps/sim/lib/selectors/server/providers/oracle-fusion-hcm.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { OracleFusionHcmOperationError } from '@/lib/internal/oracle-fusion-hcm/errors'
23
import { selectorManifest } from '@/lib/selectors/manifest'
34
import {
45
SelectorConnectionUnavailableError,
@@ -116,6 +117,7 @@ describe('Oracle Fusion HCM selectors', () => {
116117
absences: [
117118
{
118119
absenceId: '3',
120+
personId: '1',
119121
absenceType: 'Vacation',
120122
startDate: '2026-01-01',
121123
endDate: '2026-01-02',
@@ -311,6 +313,76 @@ describe('Oracle Fusion HCM selectors', () => {
311313
).rejects.toBeInstanceOf(SelectorContextUnavailableError)
312314
})
313315

316+
it.each([undefined, '2'])('rejects a listed absence with personId %s', async (personId) => {
317+
mocks.listAbsences.mockResolvedValueOnce({
318+
success: true,
319+
output: {
320+
absences: [{ absenceId: '3', personId, absenceType: 'Vacation' }],
321+
count: 1,
322+
hasMore: false,
323+
limit: 50,
324+
offset: 0,
325+
},
326+
})
327+
const attachment = oracleFusionHcmSelectorAttachments['oracle_fusion_hcm.absences']
328+
const input = args({
329+
selectorKey: 'oracle_fusion_hcm.absences',
330+
context: {
331+
domain: 'https://acme.fa.ocs.oraclecloud.com',
332+
username: 'reader',
333+
password: 'secret',
334+
personId: '1',
335+
},
336+
})
337+
338+
await expect(
339+
attachment.execute(input, await prepare(attachment, input))
340+
).rejects.toBeInstanceOf(SelectorContextUnavailableError)
341+
})
342+
343+
it('returns a missing detail when Oracle reports 404', async () => {
344+
const context = {
345+
domain: 'https://acme.fa.ocs.oraclecloud.com',
346+
username: 'reader',
347+
password: 'secret',
348+
personId: '1',
349+
}
350+
for (const [key, id, mock] of [
351+
['workers', '1', mocks.getWorker],
352+
['assignments', '2', mocks.getAssignment],
353+
['absences', '3', mocks.getAbsence],
354+
] as const) {
355+
mock.mockRejectedValueOnce(new OracleFusionHcmOperationError('not found', 404))
356+
const attachment = oracleFusionHcmSelectorAttachments[`oracle_fusion_hcm.${key}`]
357+
const input = args({
358+
selectorKey: `oracle_fusion_hcm.${key}`,
359+
context,
360+
request: { kind: 'detail', id },
361+
})
362+
363+
await expect(attachment.execute(input, await prepare(attachment, input))).resolves.toEqual({
364+
kind: 'detail',
365+
item: null,
366+
})
367+
}
368+
})
369+
370+
it('keeps malformed detail requests and list 404s as context errors', async () => {
371+
const attachment = oracleFusionHcmSelectorAttachments['oracle_fusion_hcm.workers']
372+
for (const [status, request] of [
373+
[400, { kind: 'detail', id: '1' }],
374+
[404, { kind: 'list' }],
375+
] as const) {
376+
const mock = request.kind === 'detail' ? mocks.getWorker : mocks.listWorkers
377+
mock.mockRejectedValueOnce(new OracleFusionHcmOperationError('invalid context', status))
378+
const input = args({ request })
379+
380+
await expect(
381+
attachment.execute(input, await prepare(attachment, input))
382+
).rejects.toBeInstanceOf(SelectorContextUnavailableError)
383+
}
384+
})
385+
314386
it.each(['../workers/1', '1,absenceTypeId=2', '0', '9223372036854775808'])(
315387
'rejects invalid dependent person ID %s before provider preparation',
316388
async (invalidId) => {

apps/sim/lib/selectors/server/providers/oracle-fusion-hcm.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ function publicSelectorError(error: unknown): never {
5656
throw new SelectorOptionsUnavailableError(error.status === 429 ? 429 : 502)
5757
}
5858

59+
function isMissingDetail(error: unknown): boolean {
60+
return error instanceof OracleFusionHcmOperationError && error.status === 404
61+
}
62+
5963
const destination = {
6064
kind: 'user-controlled' as const,
6165
async prepare(args: ExecuteServerSelectorArgs): Promise<PreparedOracleFusionHcm> {
@@ -119,6 +123,9 @@ export const oracleFusionHcmSelectorAttachments = {
119123
)
120124
} catch (error) {
121125
args.signal?.throwIfAborted()
126+
if (args.request.kind === 'detail' && isMissingDetail(error)) {
127+
return detailSelectorResult(null)
128+
}
122129
publicSelectorError(error)
123130
}
124131
},
@@ -162,6 +169,9 @@ export const oracleFusionHcmSelectorAttachments = {
162169
)
163170
} catch (error) {
164171
args.signal?.throwIfAborted()
172+
if (args.request.kind === 'detail' && isMissingDetail(error)) {
173+
return detailSelectorResult(null)
174+
}
165175
publicSelectorError(error)
166176
}
167177
},
@@ -200,22 +210,30 @@ export const oracleFusionHcmSelectorAttachments = {
200210
args.signal
201211
)
202212
return listSelectorResult(
203-
result.output.absences.map((absence) => ({
204-
id: absence.absenceId,
205-
label:
206-
[absence.absenceType, absence.startDate, absence.endDate]
207-
.filter(Boolean)
208-
.join(' · ') || absence.absenceId,
209-
meta: {
210-
status: absence.displayStatusMeaning || absence.displayStatus,
211-
startDate: absence.startDate,
212-
endDate: absence.endDate,
213-
},
214-
})),
213+
result.output.absences.map((absence) => {
214+
if (absence.personId !== prepared.personId) {
215+
throw new SelectorContextUnavailableError()
216+
}
217+
return {
218+
id: absence.absenceId,
219+
label:
220+
[absence.absenceType, absence.startDate, absence.endDate]
221+
.filter(Boolean)
222+
.join(' · ') || absence.absenceId,
223+
meta: {
224+
status: absence.displayStatusMeaning || absence.displayStatus,
225+
startDate: absence.startDate,
226+
endDate: absence.endDate,
227+
},
228+
}
229+
}),
215230
nextCursor(result.output.hasMore, offset, result.output.limit)
216231
)
217232
} catch (error) {
218233
args.signal?.throwIfAborted()
234+
if (args.request.kind === 'detail' && isMissingDetail(error)) {
235+
return detailSelectorResult(null)
236+
}
219237
publicSelectorError(error)
220238
}
221239
},

0 commit comments

Comments
 (0)