Skip to content

Commit be66cc4

Browse files
committed
refactor compute-parameters, do not stringify at for each step
1 parent f3383a9 commit be66cc4

File tree

5 files changed

+172
-151
lines changed

5 files changed

+172
-151
lines changed

packages/backend/src/apps/toolbox/__tests__/actions/for-each/schema.test.ts

Lines changed: 48 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { inputSchema } from '../../../actions/for-each/schema'
44

55
describe('inputSchema', () => {
66
describe('table input format', () => {
7-
it('should parse valid table input', () => {
8-
const validTableInput = JSON.stringify({
7+
it('should handle valid table input', () => {
8+
const validTableInput = {
99
rows: [
1010
{
1111
data: { name: 'John', age: 25 },
@@ -19,7 +19,7 @@ describe('inputSchema', () => {
1919
{ id: 'name', name: 'Name', value: 'name' },
2020
{ id: 'age', name: 'Age', value: 'age' },
2121
],
22-
})
22+
}
2323

2424
const result = inputSchema.safeParse(validTableInput)
2525

@@ -37,8 +37,8 @@ describe('inputSchema', () => {
3737
}
3838
})
3939

40-
it('should parse table input without rowId', () => {
41-
const validTableInput = JSON.stringify({
40+
it('should handle table input without rowId', () => {
41+
const validTableInput = {
4242
rows: [
4343
{
4444
data: { name: 'Alice', score: 95.5 },
@@ -48,7 +48,7 @@ describe('inputSchema', () => {
4848
{ id: 'name', name: 'Name', value: 'name' },
4949
{ id: 'score', name: 'Score', value: 'score' },
5050
],
51-
})
51+
}
5252

5353
const result = inputSchema.safeParse(validTableInput)
5454

@@ -61,76 +61,56 @@ describe('inputSchema', () => {
6161
}
6262
})
6363

64-
it('should trim whitespace before processing', () => {
65-
const validTableInput = JSON.stringify({
66-
rows: [{ data: { name: 'John' } }],
67-
columns: [{ id: 'name', name: 'Name', value: 'name' }],
68-
})
69-
70-
const result = inputSchema.safeParse(` ${validTableInput} `)
71-
72-
expect(result.success).toBe(true)
73-
if (result.success) {
74-
expect(result.data.type).toBe('table')
75-
}
76-
})
77-
7864
it('should reject table input with missing rows', () => {
79-
const invalidTableInput = JSON.stringify({
65+
const invalidTableInput = {
8066
columns: [{ id: 'name', name: 'Name', value: 'name' }],
81-
})
67+
}
8268

8369
const result = inputSchema.safeParse(invalidTableInput)
8470

8571
expect(result.success).toBe(false)
8672
if (result.success === false) {
87-
expect(result.error.issues[0].message).toBe(
88-
'Invalid table format: must have rows and columns',
89-
)
73+
expect(result.error.issues[0].message).toBe('Invalid input')
9074
}
9175
})
9276

9377
it('should reject table input with missing columns', () => {
94-
const invalidTableInput = JSON.stringify({
78+
const invalidTableInput = {
9579
rows: [
9680
{
9781
data: { name: 'John' },
9882
},
9983
],
100-
})
84+
}
10185

10286
const result = inputSchema.safeParse(invalidTableInput)
10387

10488
expect(result.success).toBe(false)
10589
if (result.success === false) {
106-
expect((result as any).error.issues[0].message).toBe(
107-
'Invalid table format: must have rows and columns',
108-
)
90+
expect(result.error.issues[0].message).toBe('Invalid input')
10991
}
11092
})
11193

11294
it('should reject table input with invalid row structure', () => {
113-
const invalidTableInput = JSON.stringify({
95+
const invalidTableInput = {
11496
rows: [
11597
{
11698
invalidField: 'test',
11799
},
118100
],
119101
columns: [{ id: 'name', name: 'Name', value: 'name' }],
120-
})
102+
}
121103

122104
const result = inputSchema.safeParse(invalidTableInput)
123105

124106
expect(result.success).toBe(false)
125107
if (result.success === false) {
126-
expect(result.error.issues[0].message).toBe(
127-
'Invalid table format: must have rows and columns',
128-
)
108+
expect(result.error.issues[0].message).toBe('Invalid input')
129109
}
130110
})
131111

132112
it('should reject table input with invalid column structure', () => {
133-
const invalidTableInput = JSON.stringify({
113+
const invalidTableInput = {
134114
rows: [
135115
{
136116
data: { name: 'John' },
@@ -139,35 +119,42 @@ describe('inputSchema', () => {
139119
columns: [
140120
{ id: 'name', name: 'Name' }, // missing 'value' field
141121
],
142-
})
122+
}
143123

144124
const result = inputSchema.safeParse(invalidTableInput)
145125

146126
expect(result.success).toBe(false)
147127
if (result.success === false) {
148-
expect(result.error.issues[0].message).toBe(
149-
'Invalid table format: must have rows and columns',
150-
)
128+
expect(result.error.issues[0].message).toBe('Invalid input')
151129
}
152130
})
153131

154132
it.each([
155-
'{ "rows": [invalid json',
156-
'{"item1": value1, "item2": "value2"}',
157-
'item1,item2}',
158-
])('should reject malformed JSON', (malformedJson) => {
159-
const result = inputSchema.safeParse(malformedJson)
133+
{
134+
input: '{ "rows": [invalid json',
135+
error: 'Invalid input',
136+
},
137+
{
138+
input: '{"item1": value1, "item2": "value2"}',
139+
error: 'Invalid input',
140+
},
141+
{
142+
input: 'item1,item2}',
143+
error: 'Invalid input',
144+
},
145+
])('should reject malformed JSON', ({ input, error }) => {
146+
const result = inputSchema.safeParse(input)
160147

161148
expect(result.success).toBe(false)
162149
if (result.success === false) {
163-
expect(result.error.issues[0].message).toBe('Invalid JSON format')
150+
expect(result.error.issues[0].message).toBe(error)
164151
}
165152
})
166153
})
167154

168155
describe('checkbox input format', () => {
169-
it('should parse single item', () => {
170-
const result = inputSchema.safeParse('item1')
156+
it('should handle single item', () => {
157+
const result = inputSchema.safeParse(['item1'])
171158

172159
expect(result.success).toBe(true)
173160
if (result.success) {
@@ -176,8 +163,8 @@ describe('inputSchema', () => {
176163
}
177164
})
178165

179-
it('should parse multiple comma-separated items', () => {
180-
const result = inputSchema.safeParse('item1,item2,item3')
166+
it('should handle multiple comma-separated items', () => {
167+
const result = inputSchema.safeParse(['item1', 'item2', 'item3'])
181168

182169
expect(result.success).toBe(true)
183170
if (result.success) {
@@ -187,7 +174,7 @@ describe('inputSchema', () => {
187174
})
188175

189176
it('should handle items with spaces', () => {
190-
const result = inputSchema.safeParse('item 1, item 2 , item 3')
177+
const result = inputSchema.safeParse(['item 1', ' item 2 ', ' item 3'])
191178

192179
expect(result.success).toBe(true)
193180
if (result.success) {
@@ -197,7 +184,7 @@ describe('inputSchema', () => {
197184
})
198185

199186
it('should handle empty items in comma-separated list', () => {
200-
const result = inputSchema.safeParse('item1,,item3')
187+
const result = inputSchema.safeParse(['item1', '', 'item3'])
201188

202189
expect(result.success).toBe(true)
203190
if (result.success) {
@@ -206,23 +193,22 @@ describe('inputSchema', () => {
206193
}
207194
})
208195

209-
it('should trim whitespace for checkbox input', () => {
210-
const result = inputSchema.safeParse(' item1,item2 ')
196+
it('should not trim whitespace for checkbox items', () => {
197+
const result = inputSchema.safeParse([' item1', 'item2 '])
211198

212199
expect(result.success).toBe(true)
213200
if (result.success) {
214201
expect(result.data.type).toBe('checkbox')
215-
expect(result.data.items).toEqual(['item1', 'item2'])
202+
expect(result.data.items).toEqual([' item1', 'item2 '])
216203
}
217204
})
218205

219-
it('should handle single comma (results in two empty items)', () => {
206+
it('should reject single comma', () => {
220207
const result = inputSchema.safeParse(',')
221208

222-
expect(result.success).toBe(true)
223-
if (result.success) {
224-
expect(result.data.type).toBe('checkbox')
225-
expect(result.data.items).toEqual(['', ''])
209+
expect(result.success).toBe(false)
210+
if (result.success == false) {
211+
expect(result.error.issues[0].message).toBe('Invalid input')
226212
}
227213
})
228214
})
@@ -233,7 +219,7 @@ describe('inputSchema', () => {
233219

234220
expect(result.success).toBe(false)
235221
if (result.success === false) {
236-
expect(result.error.issues[0].message).toBe('Input cannot be empty')
222+
expect(result.error.issues[0].message).toBe('Invalid input')
237223
}
238224
})
239225

@@ -242,7 +228,7 @@ describe('inputSchema', () => {
242228

243229
expect(result.success).toBe(false)
244230
if (result.success === false) {
245-
expect(result.error.issues[0].message).toBe('Input cannot be empty')
231+
expect(result.error.issues[0].message).toBe('Invalid input')
246232
}
247233
})
248234
})

packages/backend/src/apps/toolbox/actions/for-each/schema.ts

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,49 +19,25 @@ const tableInputSchema = z.object({
1919

2020
// Create a discriminated union based on input format
2121
export const inputSchema = z
22-
.string()
23-
.min(1, 'Input cannot be empty')
24-
.transform((str, ctx) => {
25-
const trimmed = str.trim()
26-
27-
if (trimmed.length === 0) {
22+
.union([z.array(z.any()), tableInputSchema])
23+
.transform((data, ctx) => {
24+
if (!data || (Array.isArray(data) && data.length === 0)) {
2825
ctx.addIssue({
2926
code: z.ZodIssueCode.custom,
3027
message: 'Input cannot be empty',
3128
})
3229
return z.NEVER
3330
}
3431

35-
// Handle table input (JSON object)
36-
if (trimmed.startsWith('{') || trimmed.endsWith('}')) {
37-
try {
38-
const parsed = JSON.parse(trimmed)
39-
const result = tableInputSchema.safeParse(parsed)
40-
41-
if (!result.success) {
42-
ctx.addIssue({
43-
code: z.ZodIssueCode.custom,
44-
message: 'Invalid table format: must have rows and columns',
45-
})
46-
return z.NEVER
47-
}
48-
49-
return {
50-
type: 'table' as const,
51-
items: result.data,
52-
}
53-
} catch (error) {
54-
ctx.addIssue({
55-
code: z.ZodIssueCode.custom,
56-
message: 'Invalid JSON format',
57-
})
58-
return z.NEVER
32+
if (Array.isArray(data)) {
33+
return {
34+
type: 'checkbox' as const,
35+
items: data,
36+
}
37+
} else {
38+
return {
39+
type: 'table' as const,
40+
items: data,
5941
}
60-
}
61-
62-
// Handle checkbox input (comma-separated values)
63-
return {
64-
type: 'checkbox' as const,
65-
items: trimmed.split(','),
6642
}
6743
})

packages/backend/src/helpers/__tests__/compute-parameters.test.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ const executionSteps = [
1515
numberProp: 123,
1616
'space separated prop': 'space separated value',
1717
arrayProp: ['array value 1', 'hehe', 'array value 3'], // for-each intro
18+
arrayPropWithCommas: [
19+
'array value 1, with, commas',
20+
'hehe',
21+
'array value 3',
22+
'array value 4, with comma',
23+
],
1824
tableProp: {
1925
rows: [{ data: { name: 'John' } }],
2026
columns: [{ id: 'name', name: 'Name', value: 'name' }],
@@ -153,19 +159,52 @@ describe('compute parameters', () => {
153159
},
154160
},
155161
},
162+
])(
163+
'performs variable substitution on $testDescription',
164+
({ params, expected }) => {
165+
const result = computeParameters(params, executionSteps)
166+
expect(result).toEqual(expected)
167+
},
168+
)
169+
170+
it.each([
156171
{
157172
testDescription: 'entire object',
158173
params: {
159174
param1: `{{step.${randomStepID}.tableProp}}`,
160175
},
161176
expected: {
162-
param1: JSON.stringify(executionSteps[0].dataOut.tableProp),
177+
param1: executionSteps[0].dataOut.tableProp,
178+
},
179+
},
180+
{
181+
testDescription: 'entire array',
182+
params: {
183+
param2: `{{step.${randomStepID}.arrayProp}}`,
184+
},
185+
expected: {
186+
param2: executionSteps[0].dataOut.arrayProp,
187+
},
188+
},
189+
{
190+
testDescription: 'array with values containing commas',
191+
params: {
192+
param3: `{{step.${randomStepID}.arrayPropWithCommas}}`,
193+
},
194+
expected: {
195+
param3: executionSteps[0].dataOut.arrayPropWithCommas,
163196
},
164197
},
165198
])(
166-
'performs variable substitution on $testDescription',
167-
({ params, expected }) => {
168-
const result = computeParameters(params, executionSteps)
199+
'performs for-each variable substitution on $testDescription ',
200+
({
201+
params,
202+
expected,
203+
}: {
204+
params: Record<string, any>
205+
expected: Record<string, any>
206+
}) => {
207+
const result = computeParameters(params, executionSteps, undefined, true)
169208
expect(result).toEqual(expected)
170209
},
171210
)

0 commit comments

Comments
 (0)