Skip to content

Commit 0cb7b57

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(oracle-epm-data): preserve provider names and nested errors
1 parent 60179cf commit 0cb7b57

6 files changed

Lines changed: 106 additions & 21 deletions

File tree

‎apps/sim/lib/internal/oracle-epm-data/contracts.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,12 @@ export const oracleEpmDataConnectionsSchema = oracleEpmDataStatusResponseSchema.
177177
.array(z.object({ connectionName: text, refUrl: text }))
178178
.max(ORACLE_EPM_DATA_MAX_ITEMS),
179179
})
180-
export const oracleEpmDataConnectionSchema = oracleEpmDataStatusResponseSchema.extend({
180+
export const oracleEpmDataConnectionStatusResponseSchema = oracleEpmDataStatusResponseSchema.extend(
181+
{
182+
response: z.object({ status: oracleEpmDataStatusSchema }),
183+
}
184+
)
185+
export const oracleEpmDataConnectionSchema = oracleEpmDataConnectionStatusResponseSchema.extend({
181186
response: z.object({
182187
status: oracleEpmDataStatusSchema,
183188
sourceSystemId: id,

‎apps/sim/lib/internal/oracle-epm-data/operations.test.ts‎

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,25 @@ describe('Data Integration provider operations', () => {
9797
})
9898
})
9999

100-
it('does not turn a nested connection failure into success', async () => {
101-
mocks.request.mockResolvedValue({
102-
status: 200,
103-
data: { ...status, response: { ...connection, status: 1 } },
104-
})
105-
expect(
106-
await operations.executeOracleEpmDataGetConnectionOperation({
107-
...auth,
108-
connectionName: 'Source',
100+
it.each([{ ...connection, status: 1 }, { status: 1 }])(
101+
'preserves nested connection errors without requiring success-only fields: %j',
102+
async (response) => {
103+
mocks.request.mockResolvedValue({
104+
status: 200,
105+
data: { ...status, response },
109106
})
110-
).toMatchObject({ success: false })
111-
})
107+
expect(
108+
await operations.executeOracleEpmDataGetConnectionOperation({
109+
...auth,
110+
connectionName: 'Source',
111+
})
112+
).toMatchObject({
113+
success: false,
114+
output: { httpStatus: 200, connection: { status: 1 } },
115+
error: 'Oracle EPM connection returned status 1',
116+
})
117+
}
118+
)
112119

113120
it('updates exactly the documented connection option array', async () => {
114121
mocks.request.mockResolvedValue({
@@ -130,6 +137,29 @@ describe('Data Integration provider operations', () => {
130137
})
131138
})
132139

140+
it('round-trips raw connection and POV names to the declared request unchanged', async () => {
141+
mocks.request.mockResolvedValueOnce({ status: 200, data: { ...status, response: connection } })
142+
await operations.executeOracleEpmDataGetConnectionOperation({
143+
...auth,
144+
connectionName: ' Source ',
145+
})
146+
expect(mocks.request.mock.calls[0][1].pathParams).toEqual({ connectionName: ' Source ' })
147+
mocks.request.mockResolvedValueOnce({ status: 200, data: { ...status, response: [] } })
148+
await operations.executeOracleEpmDataGetPovStatusOperation({
149+
...auth,
150+
application: ' Plan ',
151+
locationName: ' Source ',
152+
period: ' Jan-26 ',
153+
category: ' Actual ',
154+
})
155+
expect(mocks.request.mock.calls[1][1].query).toEqual({
156+
application: ' Plan ',
157+
location: ' Source ',
158+
period: ' Jan-26 ',
159+
category: ' Actual ',
160+
})
161+
})
162+
133163
it('gets a documented pipeline definition with nullable variables', async () => {
134164
const pipeline = {
135165
name: 'Load26',

‎apps/sim/lib/internal/oracle-epm-data/operations/get-connection.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
executeOracleEpmDataOperation,
33
oracleEpmDataConnectionSchema,
4+
oracleEpmDataConnectionStatusResponseSchema,
45
oracleEpmDataEndpoints,
56
oracleEpmDataStatusResponseSchema,
67
projectOracleEpmDataResult,
@@ -19,7 +20,7 @@ export const executeOracleEpmDataGetConnectionOperation: InternalToolOperationIm
1920
})
2021
const envelope = oracleEpmDataStatusResponseSchema.parse(response.data)
2122
if (envelope.status === 0) {
22-
const connection = oracleEpmDataConnectionSchema.parse(response.data).response
23+
const connection = oracleEpmDataConnectionStatusResponseSchema.parse(response.data).response
2324
if (connection.status !== 0) {
2425
return {
2526
success: false,

‎apps/sim/lib/internal/oracle-epm-data/schema.test.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,35 @@ describe('Data Integration inputs', () => {
3939
expect(input).not.toHaveProperty('waitForCompletion')
4040
})
4141

42+
it('preserves nonblank provider names while keeping credentials, IDs and control values normalized', () => {
43+
expect(
44+
schemas.get_connection.parse({
45+
...auth,
46+
oauthCredential: ' credential ',
47+
connectionName: ' Source ',
48+
})
49+
).toMatchObject({ oauthCredential: 'credential', connectionName: ' Source ' })
50+
expect(schemas.get_job_status.parse({ ...auth, jobId: ' 42 ' }).jobId).toBe('42')
51+
expect(
52+
schemas.run_integration.parse({
53+
...auth,
54+
jobName: ' Load ',
55+
periodName: ' {Jan-26} ',
56+
importMode: ' Direct ',
57+
exportMode: ' Merge ',
58+
})
59+
).toMatchObject({
60+
jobName: ' Load ',
61+
periodName: ' {Jan-26} ',
62+
importMode: 'Direct',
63+
exportMode: 'Merge',
64+
})
65+
expect(schemas.get_connection.safeParse({ ...auth, connectionName: ' ' }).success).toBe(false)
66+
expect(
67+
schemas.get_pov_status.safeParse({ ...auth, period: ' ', category: 'Actual' }).success
68+
).toBe(false)
69+
})
70+
4271
it.each(['x', 'pipeline-code', 'a'.repeat(31)])(
4372
'rejects invalid pipeline code %s',
4473
(pipelineCode) => {

‎apps/sim/lib/internal/oracle-epm-data/schema.ts‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@ import { z } from 'zod'
22
import { isUserFileWithMetadata } from '@/lib/core/utils/user-file'
33
import type { UserFile } from '@/executor/types'
44

5-
const name = z.string().trim().min(1).max(255)
6-
const period = z.string().trim().min(1).max(1024)
5+
const identifier = z.string().trim().min(1).max(255)
6+
/** Configured provider names must round-trip unchanged from selectors to requests. */
7+
const name = z
8+
.string()
9+
.min(1)
10+
.max(255)
11+
.refine((value) => value.trim().length > 0, 'Name is required')
12+
const period = z
13+
.string()
14+
.min(1)
15+
.max(1024)
16+
.refine((value) => value.trim().length > 0, 'Period is required')
717
/** Preserve provider filenames, including meaningful spaces and literal percent signs. */
818
const fileName = z
919
.string()
@@ -12,7 +22,7 @@ const fileName = z
1222
.refine((value) => value.trim().length > 0, 'File name is required')
1323
const optionalName = name.optional()
1424
const auth = {
15-
oauthCredential: name,
25+
oauthCredential: identifier,
1626
accessToken: z.string().min(1).max(4096),
1727
instanceUrl: z.string().min(1).max(4096),
1828
}
@@ -31,9 +41,9 @@ export const oracleEpmDataSchemas = {
3141
get_connection: z.object({ ...auth, connectionName: name }),
3242
update_connection: z.object({
3343
...auth,
34-
sourceSystemId: name,
44+
sourceSystemId: identifier,
3545
sourceSystemName: name,
36-
sourceSystemType: name,
46+
sourceSystemType: identifier,
3747
sourceSystemOptions: z
3848
.array(z.object({ optionName: name, optionValue: z.string().max(65_536) }))
3949
.min(1)
@@ -45,8 +55,8 @@ export const oracleEpmDataSchemas = {
4555
...auth,
4656
jobName: name,
4757
periodName: period,
48-
importMode: name,
49-
exportMode: name,
58+
importMode: identifier,
59+
exportMode: identifier,
5060
fileName: fileName.optional(),
5161
executionMode: z.enum(['SYNC', 'ASYNC']).optional(),
5262
sourceFilters: options.optional(),
@@ -74,7 +84,7 @@ export const oracleEpmDataSchemas = {
7484
get_job_status: z.object({
7585
...auth,
7686
...wait,
77-
jobId: name.regex(/^[1-9]\d*$/, 'A positive Oracle job ID is required'),
87+
jobId: identifier.regex(/^[1-9]\d*$/, 'A positive Oracle job ID is required'),
7888
}),
7989
execute_report: z.object({
8090
...auth,

‎apps/sim/lib/selectors/server/providers/oracle-epm-data.test.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ describe('Data Integration server selectors', () => {
188188
)
189189
})
190190

191+
it('preserves exact configured connection names in selector IDs', async () => {
192+
mocks.connections.mockResolvedValue({
193+
success: true,
194+
output: { connections: [{ connectionName: ' Source ', refUrl: '/unused' }] },
195+
})
196+
expect(await attachments['oracle_epm_data.connections'].execute(args(), auth)).toMatchObject({
197+
items: [{ id: ' Source ', label: ' Source ' }],
198+
})
199+
})
200+
191201
it.each([
192202
[401, new SelectorConnectionUnavailableError(401)],
193203
[403, new SelectorConnectionUnavailableError(403)],

0 commit comments

Comments
 (0)