Skip to content

Commit a3b7068

Browse files
authored
fix(api): tests (#148)
* fix(api): tests * docs(api): enhance CalculatorNode documentation with detailed operations and examples
1 parent 5f0c520 commit a3b7068

19 files changed

Lines changed: 304 additions & 158 deletions

apps/api/src/nodes/json/json-flatten-node.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ export class JsonFlattenNode extends ExecutableNode {
8080

8181
public async execute(context: NodeContext): Promise<NodeExecution> {
8282
try {
83-
const { value, separator = ".", includeArrays = true } = context.inputs;
83+
const { value, separator = ".", includeArrays = false } = context.inputs;
8484

8585
// Handle null or undefined inputs
8686
if (value === null || value === undefined) {
8787
return this.createSuccessResult({
88-
result: {},
88+
result: null,
8989
keyCount: 0,
9090
success: true,
9191
});
@@ -94,12 +94,23 @@ export class JsonFlattenNode extends ExecutableNode {
9494
// Handle primitive values
9595
if (typeof value !== "object") {
9696
return this.createSuccessResult({
97-
result: { value },
97+
result: value,
9898
keyCount: 1,
9999
success: true,
100100
});
101101
}
102102

103+
// Handle top-level arrays explicitly
104+
if (Array.isArray(value)) {
105+
if (!includeArrays) {
106+
return this.createSuccessResult({
107+
result: value,
108+
keyCount: Array.isArray(value) ? value.length : 0,
109+
success: true,
110+
});
111+
}
112+
}
113+
103114
// Flatten the JSON structure
104115
const flattened = this.flattenObject(value, separator, includeArrays);
105116
const keyCount = Object.keys(flattened).length;
@@ -125,41 +136,57 @@ export class JsonFlattenNode extends ExecutableNode {
125136

126137
if (Array.isArray(obj)) {
127138
if (includeArrays) {
128-
// Handle arrays by including indices
129139
for (let i = 0; i < obj.length; i++) {
130-
const key = prefix ? `${prefix}${separator}${i}` : `${i}`;
131-
const value = obj[i];
132-
133-
if (value !== null && typeof value === "object") {
140+
const idxKey = prefix ? `${prefix}[${i}]` : `[${i}]`;
141+
const valueAtIndex = obj[i];
142+
if (valueAtIndex !== null && typeof valueAtIndex === "object") {
134143
Object.assign(
135144
result,
136-
this.flattenObject(value, separator, includeArrays, key)
145+
this.flattenObject(valueAtIndex, separator, includeArrays, idxKey)
137146
);
138147
} else {
139-
result[key] = value;
148+
result[idxKey] = valueAtIndex;
140149
}
141150
}
142151
} else {
143-
// Skip arrays if includeArrays is false
144-
result[prefix || "value"] = obj;
152+
if (prefix) {
153+
result[prefix] = obj;
154+
}
145155
}
146156
} else if (typeof obj === "object" && obj !== null) {
147157
// Handle objects
148158
for (const [key, value] of Object.entries(obj)) {
149159
const newKey = prefix ? `${prefix}${separator}${key}` : key;
150160

151-
if (value !== null && typeof value === "object") {
161+
if (
162+
value !== null &&
163+
typeof value === "object" &&
164+
!Array.isArray(value)
165+
) {
152166
Object.assign(
153167
result,
154168
this.flattenObject(value, separator, includeArrays, newKey)
155169
);
156170
} else {
157-
result[newKey] = value;
171+
if (Array.isArray(value)) {
172+
if (includeArrays) {
173+
Object.assign(
174+
result,
175+
this.flattenObject(value, separator, includeArrays, newKey)
176+
);
177+
} else {
178+
result[newKey] = value;
179+
}
180+
} else {
181+
result[newKey] = value;
182+
}
158183
}
159184
}
160185
} else {
161186
// Handle primitives
162-
result[prefix || "value"] = obj;
187+
if (prefix) {
188+
result[prefix] = obj;
189+
}
163190
}
164191

165192
return result;

apps/api/src/nodes/json/json-insert-node.ts

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,8 @@ export class JsonInsertNode extends ExecutableNode {
8282
try {
8383
const { json, path, value } = context.inputs;
8484

85-
// Handle null or undefined inputs
86-
if (json === null || json === undefined) {
87-
return this.createSuccessResult({
88-
result: null,
89-
success: false,
90-
inserted: false,
91-
});
92-
}
85+
// Initialize empty object when input is null/undefined
86+
const base = json === null || json === undefined ? {} : json;
9387

9488
if (path === null || path === undefined || path === "") {
9589
return this.createSuccessResult({
@@ -100,27 +94,15 @@ export class JsonInsertNode extends ExecutableNode {
10094
}
10195

10296
// Deep clone the input JSON to avoid modifying the original
103-
const result = JSON.parse(JSON.stringify(json));
104-
105-
// Check if path already exists
106-
const pathExists = this.pathExists(result, path);
107-
108-
if (pathExists) {
109-
// Path exists, don't insert
110-
return this.createSuccessResult({
111-
result,
112-
success: true,
113-
inserted: false,
114-
});
115-
}
97+
const result = JSON.parse(JSON.stringify(base));
11698

117-
// Path doesn't exist, insert the value
99+
// Insert or overwrite the value at path (upsert semantics)
118100
const success = this.insertValueAtPath(result, path, value);
119101

120102
return this.createSuccessResult({
121-
result: success ? result : json,
103+
result: success ? result : base,
122104
success,
123-
inserted: success,
105+
inserted: true,
124106
});
125107
} catch (err) {
126108
const error = err as Error;
@@ -152,10 +134,11 @@ export class JsonInsertNode extends ExecutableNode {
152134
if (!Array.isArray(current)) {
153135
return false;
154136
}
155-
if (part < 0 || part >= current.length) {
137+
const actualIndex = part < 0 ? current.length + part : part;
138+
if (actualIndex < 0 || actualIndex >= current.length) {
156139
return false;
157140
}
158-
current = current[part];
141+
current = current[actualIndex];
159142
}
160143
}
161144

@@ -211,29 +194,25 @@ export class JsonInsertNode extends ExecutableNode {
211194
}
212195
}
213196

214-
// Insert the value at the final path part
197+
// Insert the value at the final path part (overwrite if exists)
215198
const finalPart = pathParts[pathParts.length - 1];
216199
if (typeof finalPart === "string") {
217200
if (typeof current !== "object" || current === null) {
218201
return false;
219202
}
220-
// Only insert if the key doesn't exist
221-
if (!(finalPart in current)) {
222-
current[finalPart] = value;
223-
return true;
224-
}
203+
current[finalPart] = value;
204+
return true;
225205
} else if (typeof finalPart === "number") {
226206
if (!Array.isArray(current)) {
227207
return false;
228208
}
229-
// Only insert if the index doesn't exist
230-
if (finalPart >= current.length) {
231-
while (current.length <= finalPart) {
232-
current.push(null);
233-
}
234-
current[finalPart] = value;
235-
return true;
209+
const actualIndex =
210+
finalPart < 0 ? current.length + finalPart : finalPart;
211+
while (current.length <= actualIndex) {
212+
current.push(null);
236213
}
214+
current[actualIndex] = value;
215+
return true;
237216
}
238217

239218
return false;
@@ -265,7 +244,7 @@ export class JsonInsertNode extends ExecutableNode {
265244
}
266245

267246
// Check for object property
268-
const propMatch = remaining.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
247+
const propMatch = remaining.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
269248
if (propMatch) {
270249
parts.push(propMatch[1]);
271250
remaining = remaining.substring(propMatch[1].length);

apps/api/src/nodes/json/json-merge-node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class JsonMergeNode extends ExecutableNode {
7373

7474
public async execute(context: NodeContext): Promise<NodeExecution> {
7575
try {
76-
const { objects, deep = false } = context.inputs;
76+
const { objects, deep = true } = context.inputs;
7777

7878
// Handle null or undefined inputs
7979
if (objects === null || objects === undefined) {
@@ -158,8 +158,8 @@ export class JsonMergeNode extends ExecutableNode {
158158
const sourceValue = source[key];
159159
const targetValue = target[key];
160160

161-
if (sourceValue === null || sourceValue === undefined) {
162-
// Skip null/undefined values
161+
if (sourceValue === undefined) {
162+
// Skip undefined values; allow null to overwrite
163163
continue;
164164
}
165165

apps/api/src/nodes/json/json-remove-node.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ export class JsonRemoveNode extends ExecutableNode {
130130
if (!Array.isArray(current)) {
131131
return { success: false, removed: false };
132132
}
133-
// Treat negative indices as out of bounds
134-
if (part < 0 || part >= current.length) {
133+
const actualIndex = part < 0 ? current.length + part : part;
134+
if (actualIndex < 0 || actualIndex >= current.length) {
135135
return { success: true, removed: false };
136136
}
137-
current = current[part];
137+
current = current[actualIndex];
138138
}
139139
}
140140

@@ -154,9 +154,10 @@ export class JsonRemoveNode extends ExecutableNode {
154154
if (!Array.isArray(current)) {
155155
return { success: false, removed: false };
156156
}
157-
// Treat negative indices as out of bounds
158-
if (finalPart >= 0 && finalPart < current.length) {
159-
current.splice(finalPart, 1);
157+
const actualIndex =
158+
finalPart < 0 ? current.length + finalPart : finalPart;
159+
if (actualIndex >= 0 && actualIndex < current.length) {
160+
current.splice(actualIndex, 1);
160161
return { success: true, removed: true };
161162
} else {
162163
return { success: true, removed: false };
@@ -187,7 +188,7 @@ export class JsonRemoveNode extends ExecutableNode {
187188
}
188189

189190
// Check for object property
190-
const propMatch = remaining.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
191+
const propMatch = remaining.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
191192
if (propMatch) {
192193
parts.push(propMatch[1]);
193194
remaining = remaining.substring(propMatch[1].length);

0 commit comments

Comments
 (0)