@curl-runner/cli 1.16.0 → 1.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ci-exit.test.ts +0 -216
- package/src/cli.ts +0 -1351
- package/src/commands/upgrade.ts +0 -262
- package/src/diff/baseline-manager.test.ts +0 -181
- package/src/diff/baseline-manager.ts +0 -266
- package/src/diff/diff-formatter.ts +0 -316
- package/src/diff/index.ts +0 -3
- package/src/diff/response-differ.test.ts +0 -330
- package/src/diff/response-differ.ts +0 -489
- package/src/executor/max-concurrency.test.ts +0 -139
- package/src/executor/profile-executor.test.ts +0 -132
- package/src/executor/profile-executor.ts +0 -167
- package/src/executor/request-executor.ts +0 -663
- package/src/parser/yaml.test.ts +0 -480
- package/src/parser/yaml.ts +0 -271
- package/src/snapshot/index.ts +0 -3
- package/src/snapshot/snapshot-differ.test.ts +0 -358
- package/src/snapshot/snapshot-differ.ts +0 -296
- package/src/snapshot/snapshot-formatter.ts +0 -170
- package/src/snapshot/snapshot-manager.test.ts +0 -204
- package/src/snapshot/snapshot-manager.ts +0 -342
- package/src/types/bun-yaml.d.ts +0 -11
- package/src/types/config.ts +0 -638
- package/src/utils/colors.ts +0 -30
- package/src/utils/condition-evaluator.test.ts +0 -415
- package/src/utils/condition-evaluator.ts +0 -327
- package/src/utils/curl-builder.test.ts +0 -165
- package/src/utils/curl-builder.ts +0 -209
- package/src/utils/installation-detector.test.ts +0 -52
- package/src/utils/installation-detector.ts +0 -123
- package/src/utils/logger.ts +0 -856
- package/src/utils/response-store.test.ts +0 -213
- package/src/utils/response-store.ts +0 -108
- package/src/utils/stats.test.ts +0 -161
- package/src/utils/stats.ts +0 -151
- package/src/utils/version-checker.ts +0 -158
- package/src/version.ts +0 -43
- package/src/watcher/file-watcher.test.ts +0 -186
- package/src/watcher/file-watcher.ts +0 -140
package/src/parser/yaml.ts
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import type { RequestConfig, ResponseStoreContext, YamlFile } from '../types/config';
|
|
2
|
-
|
|
3
|
-
// Using class for organization, but could be refactored to functions
|
|
4
|
-
export class YamlParser {
|
|
5
|
-
static async parseFile(filepath: string): Promise<YamlFile> {
|
|
6
|
-
const file = Bun.file(filepath);
|
|
7
|
-
const content = await file.text();
|
|
8
|
-
return Bun.YAML.parse(content) as YamlFile;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static parse(content: string): YamlFile {
|
|
12
|
-
return Bun.YAML.parse(content) as YamlFile;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Interpolates variables in an object, supporting:
|
|
17
|
-
* - Static variables: ${VAR_NAME}
|
|
18
|
-
* - Dynamic variables: ${UUID}, ${TIMESTAMP}, ${DATE:format}, ${TIME:format}
|
|
19
|
-
* - Stored response values: ${store.variableName}
|
|
20
|
-
* - Default values: ${VAR_NAME:default} - uses 'default' if VAR_NAME is not found
|
|
21
|
-
* - Nested defaults: ${VAR1:${VAR2:fallback}} - tries VAR1, then VAR2, then 'fallback'
|
|
22
|
-
*
|
|
23
|
-
* @param obj - The object to interpolate
|
|
24
|
-
* @param variables - Static variables map
|
|
25
|
-
* @param storeContext - Optional stored response values from previous requests
|
|
26
|
-
*/
|
|
27
|
-
static interpolateVariables(
|
|
28
|
-
obj: unknown,
|
|
29
|
-
variables: Record<string, string>,
|
|
30
|
-
storeContext?: ResponseStoreContext,
|
|
31
|
-
): unknown {
|
|
32
|
-
if (typeof obj === 'string') {
|
|
33
|
-
// Extract all variable references with proper brace matching
|
|
34
|
-
const extractedVars = YamlParser.extractVariables(obj);
|
|
35
|
-
|
|
36
|
-
if (extractedVars.length === 0) {
|
|
37
|
-
return obj;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Check if it's a single variable that spans the entire string
|
|
41
|
-
if (
|
|
42
|
-
extractedVars.length === 1 &&
|
|
43
|
-
extractedVars[0].start === 0 &&
|
|
44
|
-
extractedVars[0].end === obj.length
|
|
45
|
-
) {
|
|
46
|
-
const varName = extractedVars[0].name;
|
|
47
|
-
const resolvedValue = YamlParser.resolveVariable(varName, variables, storeContext);
|
|
48
|
-
return resolvedValue !== null ? resolvedValue : obj;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Handle multiple variables in the string
|
|
52
|
-
let result = '';
|
|
53
|
-
let lastEnd = 0;
|
|
54
|
-
for (const varRef of extractedVars) {
|
|
55
|
-
result += obj.slice(lastEnd, varRef.start);
|
|
56
|
-
const resolvedValue = YamlParser.resolveVariable(varRef.name, variables, storeContext);
|
|
57
|
-
result += resolvedValue !== null ? resolvedValue : obj.slice(varRef.start, varRef.end);
|
|
58
|
-
lastEnd = varRef.end;
|
|
59
|
-
}
|
|
60
|
-
result += obj.slice(lastEnd);
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (Array.isArray(obj)) {
|
|
65
|
-
return obj.map((item) => YamlParser.interpolateVariables(item, variables, storeContext));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (obj && typeof obj === 'object') {
|
|
69
|
-
const result: Record<string, unknown> = {};
|
|
70
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
71
|
-
result[key] = YamlParser.interpolateVariables(value, variables, storeContext);
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return obj;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Resolves a single variable reference.
|
|
81
|
-
* Priority: store context > string transforms > dynamic variables > static variables > default values
|
|
82
|
-
*/
|
|
83
|
-
static resolveVariable(
|
|
84
|
-
varName: string,
|
|
85
|
-
variables: Record<string, string>,
|
|
86
|
-
storeContext?: ResponseStoreContext,
|
|
87
|
-
): string | null {
|
|
88
|
-
// Check for store variable (${store.variableName})
|
|
89
|
-
if (varName.startsWith('store.') && storeContext) {
|
|
90
|
-
const storeVarName = varName.slice(6); // Remove 'store.' prefix
|
|
91
|
-
if (storeVarName in storeContext) {
|
|
92
|
-
return storeContext[storeVarName];
|
|
93
|
-
}
|
|
94
|
-
return null; // Store variable not found, return null to keep original
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Check for string transforms: ${VAR:upper} or ${VAR:lower}
|
|
98
|
-
const transformMatch = varName.match(/^([^:]+):(upper|lower)$/);
|
|
99
|
-
if (transformMatch) {
|
|
100
|
-
const baseVarName = transformMatch[1];
|
|
101
|
-
const transform = transformMatch[2];
|
|
102
|
-
const baseValue = variables[baseVarName] || process.env[baseVarName];
|
|
103
|
-
|
|
104
|
-
if (baseValue) {
|
|
105
|
-
return transform === 'upper' ? baseValue.toUpperCase() : baseValue.toLowerCase();
|
|
106
|
-
}
|
|
107
|
-
return null; // Base variable not found
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check for default value syntax: ${VAR:default}
|
|
111
|
-
// Must check before dynamic variables to properly handle defaults
|
|
112
|
-
const colonIndex = varName.indexOf(':');
|
|
113
|
-
if (colonIndex !== -1) {
|
|
114
|
-
const actualVarName = varName.slice(0, colonIndex);
|
|
115
|
-
const defaultValue = varName.slice(colonIndex + 1);
|
|
116
|
-
|
|
117
|
-
// Don't confuse with DATE:, TIME:, UUID:, RANDOM: patterns
|
|
118
|
-
// These are reserved prefixes for dynamic variable generation
|
|
119
|
-
const reservedPrefixes = ['DATE', 'TIME', 'UUID', 'RANDOM'];
|
|
120
|
-
if (!reservedPrefixes.includes(actualVarName)) {
|
|
121
|
-
// Try to resolve the actual variable name
|
|
122
|
-
const resolved = YamlParser.resolveVariable(actualVarName, variables, storeContext);
|
|
123
|
-
if (resolved !== null) {
|
|
124
|
-
return resolved;
|
|
125
|
-
}
|
|
126
|
-
// Variable not found, use the default value
|
|
127
|
-
// The default value might itself be a variable reference like ${OTHER_VAR:fallback}
|
|
128
|
-
// Note: Due to the regex in interpolateVariables using [^}]+, nested braces
|
|
129
|
-
// get truncated (e.g., "${VAR:${OTHER:default}}" captures "VAR:${OTHER:default")
|
|
130
|
-
// So we check for both complete ${...} and truncated ${... patterns
|
|
131
|
-
if (defaultValue.startsWith('${')) {
|
|
132
|
-
// Handle both complete ${VAR} and truncated ${VAR (from nested braces)
|
|
133
|
-
const nestedVarName = defaultValue.endsWith('}')
|
|
134
|
-
? defaultValue.slice(2, -1)
|
|
135
|
-
: defaultValue.slice(2);
|
|
136
|
-
const nestedResolved = YamlParser.resolveVariable(nestedVarName, variables, storeContext);
|
|
137
|
-
return nestedResolved !== null ? nestedResolved : defaultValue;
|
|
138
|
-
}
|
|
139
|
-
return defaultValue;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Check for dynamic variable
|
|
144
|
-
const dynamicValue = YamlParser.resolveDynamicVariable(varName);
|
|
145
|
-
if (dynamicValue !== null) {
|
|
146
|
-
return dynamicValue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check for static variable
|
|
150
|
-
if (varName in variables) {
|
|
151
|
-
return variables[varName];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
static resolveDynamicVariable(varName: string): string | null {
|
|
158
|
-
// UUID generation
|
|
159
|
-
if (varName === 'UUID') {
|
|
160
|
-
return crypto.randomUUID();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// UUID:short - first segment (8 chars) of a UUID
|
|
164
|
-
if (varName === 'UUID:short') {
|
|
165
|
-
return crypto.randomUUID().split('-')[0];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// RANDOM:min-max - random number in range
|
|
169
|
-
const randomRangeMatch = varName.match(/^RANDOM:(\d+)-(\d+)$/);
|
|
170
|
-
if (randomRangeMatch) {
|
|
171
|
-
const min = Number(randomRangeMatch[1]);
|
|
172
|
-
const max = Number(randomRangeMatch[2]);
|
|
173
|
-
return String(Math.floor(Math.random() * (max - min + 1)) + min);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// RANDOM:string:length - random alphanumeric string
|
|
177
|
-
const randomStringMatch = varName.match(/^RANDOM:string:(\d+)$/);
|
|
178
|
-
if (randomStringMatch) {
|
|
179
|
-
const length = Number(randomStringMatch[1]);
|
|
180
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
181
|
-
return Array.from({ length }, () =>
|
|
182
|
-
chars.charAt(Math.floor(Math.random() * chars.length)),
|
|
183
|
-
).join('');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Current timestamp variations
|
|
187
|
-
if (varName === 'CURRENT_TIME' || varName === 'TIMESTAMP') {
|
|
188
|
-
return Date.now().toString();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Date formatting - ${DATE:YYYY-MM-DD}
|
|
192
|
-
if (varName.startsWith('DATE:')) {
|
|
193
|
-
const format = varName.slice(5); // Remove 'DATE:'
|
|
194
|
-
return YamlParser.formatDate(new Date(), format);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Time formatting - ${TIME:HH:mm:ss}
|
|
198
|
-
if (varName.startsWith('TIME:')) {
|
|
199
|
-
const format = varName.slice(5); // Remove 'TIME:'
|
|
200
|
-
return YamlParser.formatTime(new Date(), format);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return null; // Not a dynamic variable
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
static formatDate(date: Date, format: string): string {
|
|
207
|
-
const year = date.getFullYear();
|
|
208
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
209
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
210
|
-
|
|
211
|
-
return format.replace('YYYY', year.toString()).replace('MM', month).replace('DD', day);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
static formatTime(date: Date, format: string): string {
|
|
215
|
-
const hours = String(date.getHours()).padStart(2, '0');
|
|
216
|
-
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
217
|
-
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
218
|
-
|
|
219
|
-
return format.replace('HH', hours).replace('mm', minutes).replace('ss', seconds);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Extracts variable references from a string, properly handling nested braces.
|
|
224
|
-
* For example, "${VAR:${OTHER:default}}" is extracted as a single variable reference.
|
|
225
|
-
*/
|
|
226
|
-
static extractVariables(str: string): Array<{ start: number; end: number; name: string }> {
|
|
227
|
-
const variables: Array<{ start: number; end: number; name: string }> = [];
|
|
228
|
-
let i = 0;
|
|
229
|
-
|
|
230
|
-
while (i < str.length) {
|
|
231
|
-
// Look for ${
|
|
232
|
-
if (str[i] === '$' && str[i + 1] === '{') {
|
|
233
|
-
const start = i;
|
|
234
|
-
i += 2; // Skip past ${
|
|
235
|
-
let braceCount = 1;
|
|
236
|
-
const nameStart = i;
|
|
237
|
-
|
|
238
|
-
// Find the matching closing brace
|
|
239
|
-
while (i < str.length && braceCount > 0) {
|
|
240
|
-
if (str[i] === '{') {
|
|
241
|
-
braceCount++;
|
|
242
|
-
} else if (str[i] === '}') {
|
|
243
|
-
braceCount--;
|
|
244
|
-
}
|
|
245
|
-
i++;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (braceCount === 0) {
|
|
249
|
-
// Found matching closing brace
|
|
250
|
-
const name = str.slice(nameStart, i - 1); // Exclude the closing }
|
|
251
|
-
variables.push({ start, end: i, name });
|
|
252
|
-
}
|
|
253
|
-
// If braceCount > 0, we have unmatched braces - skip this variable
|
|
254
|
-
} else {
|
|
255
|
-
i++;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return variables;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
static mergeConfigs(base: Partial<RequestConfig>, override: RequestConfig): RequestConfig {
|
|
263
|
-
return {
|
|
264
|
-
...base,
|
|
265
|
-
...override,
|
|
266
|
-
headers: { ...base.headers, ...override.headers },
|
|
267
|
-
params: { ...base.params, ...override.params },
|
|
268
|
-
variables: { ...base.variables, ...override.variables },
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
}
|
package/src/snapshot/index.ts
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import type { Snapshot } from '../types/config';
|
|
3
|
-
import { SnapshotDiffer } from './snapshot-differ';
|
|
4
|
-
|
|
5
|
-
describe('SnapshotDiffer', () => {
|
|
6
|
-
describe('basic comparison', () => {
|
|
7
|
-
test('should match identical snapshots', () => {
|
|
8
|
-
const differ = new SnapshotDiffer({});
|
|
9
|
-
const snapshot: Snapshot = {
|
|
10
|
-
status: 200,
|
|
11
|
-
body: { id: 1, name: 'test' },
|
|
12
|
-
hash: 'abc123',
|
|
13
|
-
updatedAt: '2024-01-01',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const result = differ.compare(snapshot, snapshot);
|
|
17
|
-
expect(result.match).toBe(true);
|
|
18
|
-
expect(result.differences).toHaveLength(0);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('should detect status changes', () => {
|
|
22
|
-
const differ = new SnapshotDiffer({});
|
|
23
|
-
const expected: Snapshot = { status: 200, hash: 'a', updatedAt: '' };
|
|
24
|
-
const received: Snapshot = { status: 201, hash: 'b', updatedAt: '' };
|
|
25
|
-
|
|
26
|
-
const result = differ.compare(expected, received);
|
|
27
|
-
expect(result.match).toBe(false);
|
|
28
|
-
expect(result.differences).toHaveLength(1);
|
|
29
|
-
expect(result.differences[0]).toEqual({
|
|
30
|
-
path: 'status',
|
|
31
|
-
expected: 200,
|
|
32
|
-
received: 201,
|
|
33
|
-
type: 'changed',
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('should detect body value changes', () => {
|
|
38
|
-
const differ = new SnapshotDiffer({});
|
|
39
|
-
const expected: Snapshot = {
|
|
40
|
-
body: { name: 'old' },
|
|
41
|
-
hash: 'a',
|
|
42
|
-
updatedAt: '',
|
|
43
|
-
};
|
|
44
|
-
const received: Snapshot = {
|
|
45
|
-
body: { name: 'new' },
|
|
46
|
-
hash: 'b',
|
|
47
|
-
updatedAt: '',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const result = differ.compare(expected, received);
|
|
51
|
-
expect(result.match).toBe(false);
|
|
52
|
-
expect(result.differences[0].path).toBe('body.name');
|
|
53
|
-
expect(result.differences[0].type).toBe('changed');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('should detect added fields', () => {
|
|
57
|
-
const differ = new SnapshotDiffer({});
|
|
58
|
-
const expected: Snapshot = {
|
|
59
|
-
body: { id: 1 },
|
|
60
|
-
hash: 'a',
|
|
61
|
-
updatedAt: '',
|
|
62
|
-
};
|
|
63
|
-
const received: Snapshot = {
|
|
64
|
-
body: { id: 1, newField: 'value' },
|
|
65
|
-
hash: 'b',
|
|
66
|
-
updatedAt: '',
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const result = differ.compare(expected, received);
|
|
70
|
-
expect(result.match).toBe(false);
|
|
71
|
-
expect(result.differences[0].path).toBe('body.newField');
|
|
72
|
-
expect(result.differences[0].type).toBe('added');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('should detect removed fields', () => {
|
|
76
|
-
const differ = new SnapshotDiffer({});
|
|
77
|
-
const expected: Snapshot = {
|
|
78
|
-
body: { id: 1, oldField: 'value' },
|
|
79
|
-
hash: 'a',
|
|
80
|
-
updatedAt: '',
|
|
81
|
-
};
|
|
82
|
-
const received: Snapshot = {
|
|
83
|
-
body: { id: 1 },
|
|
84
|
-
hash: 'b',
|
|
85
|
-
updatedAt: '',
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const result = differ.compare(expected, received);
|
|
89
|
-
expect(result.match).toBe(false);
|
|
90
|
-
expect(result.differences[0].path).toBe('body.oldField');
|
|
91
|
-
expect(result.differences[0].type).toBe('removed');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('array comparison', () => {
|
|
96
|
-
test('should compare array elements', () => {
|
|
97
|
-
const differ = new SnapshotDiffer({});
|
|
98
|
-
const expected: Snapshot = {
|
|
99
|
-
body: { items: [1, 2, 3] },
|
|
100
|
-
hash: 'a',
|
|
101
|
-
updatedAt: '',
|
|
102
|
-
};
|
|
103
|
-
const received: Snapshot = {
|
|
104
|
-
body: { items: [1, 2, 4] },
|
|
105
|
-
hash: 'b',
|
|
106
|
-
updatedAt: '',
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const result = differ.compare(expected, received);
|
|
110
|
-
expect(result.match).toBe(false);
|
|
111
|
-
expect(result.differences[0].path).toBe('body.items[2]');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('should detect added array elements', () => {
|
|
115
|
-
const differ = new SnapshotDiffer({});
|
|
116
|
-
const expected: Snapshot = {
|
|
117
|
-
body: { items: [1, 2] },
|
|
118
|
-
hash: 'a',
|
|
119
|
-
updatedAt: '',
|
|
120
|
-
};
|
|
121
|
-
const received: Snapshot = {
|
|
122
|
-
body: { items: [1, 2, 3] },
|
|
123
|
-
hash: 'b',
|
|
124
|
-
updatedAt: '',
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const result = differ.compare(expected, received);
|
|
128
|
-
expect(result.match).toBe(false);
|
|
129
|
-
expect(result.differences[0].path).toBe('body.items[2]');
|
|
130
|
-
expect(result.differences[0].type).toBe('added');
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe('exclusions', () => {
|
|
135
|
-
test('should exclude exact paths', () => {
|
|
136
|
-
const differ = new SnapshotDiffer({
|
|
137
|
-
exclude: ['body.timestamp'],
|
|
138
|
-
});
|
|
139
|
-
const expected: Snapshot = {
|
|
140
|
-
body: { id: 1, timestamp: '2024-01-01' },
|
|
141
|
-
hash: 'a',
|
|
142
|
-
updatedAt: '',
|
|
143
|
-
};
|
|
144
|
-
const received: Snapshot = {
|
|
145
|
-
body: { id: 1, timestamp: '2024-12-31' },
|
|
146
|
-
hash: 'b',
|
|
147
|
-
updatedAt: '',
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const result = differ.compare(expected, received);
|
|
151
|
-
expect(result.match).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('should exclude wildcard paths (*.field)', () => {
|
|
155
|
-
const differ = new SnapshotDiffer({
|
|
156
|
-
exclude: ['*.createdAt'],
|
|
157
|
-
});
|
|
158
|
-
const expected: Snapshot = {
|
|
159
|
-
body: { user: { createdAt: '2024-01-01' }, post: { createdAt: '2024-01-01' } },
|
|
160
|
-
hash: 'a',
|
|
161
|
-
updatedAt: '',
|
|
162
|
-
};
|
|
163
|
-
const received: Snapshot = {
|
|
164
|
-
body: { user: { createdAt: '2024-12-31' }, post: { createdAt: '2024-12-31' } },
|
|
165
|
-
hash: 'b',
|
|
166
|
-
updatedAt: '',
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const result = differ.compare(expected, received);
|
|
170
|
-
expect(result.match).toBe(true);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('should exclude array wildcard paths (body[*].id)', () => {
|
|
174
|
-
const differ = new SnapshotDiffer({
|
|
175
|
-
exclude: ['body.items[*].id'],
|
|
176
|
-
});
|
|
177
|
-
const expected: Snapshot = {
|
|
178
|
-
body: {
|
|
179
|
-
items: [
|
|
180
|
-
{ id: 1, name: 'a' },
|
|
181
|
-
{ id: 2, name: 'b' },
|
|
182
|
-
],
|
|
183
|
-
},
|
|
184
|
-
hash: 'a',
|
|
185
|
-
updatedAt: '',
|
|
186
|
-
};
|
|
187
|
-
const received: Snapshot = {
|
|
188
|
-
body: {
|
|
189
|
-
items: [
|
|
190
|
-
{ id: 99, name: 'a' },
|
|
191
|
-
{ id: 100, name: 'b' },
|
|
192
|
-
],
|
|
193
|
-
},
|
|
194
|
-
hash: 'b',
|
|
195
|
-
updatedAt: '',
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const result = differ.compare(expected, received);
|
|
199
|
-
expect(result.match).toBe(true);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
describe('match rules', () => {
|
|
204
|
-
test('should accept any value with wildcard (*)', () => {
|
|
205
|
-
const differ = new SnapshotDiffer({
|
|
206
|
-
match: { 'body.id': '*' },
|
|
207
|
-
});
|
|
208
|
-
const expected: Snapshot = {
|
|
209
|
-
body: { id: 1 },
|
|
210
|
-
hash: 'a',
|
|
211
|
-
updatedAt: '',
|
|
212
|
-
};
|
|
213
|
-
const received: Snapshot = {
|
|
214
|
-
body: { id: 999 },
|
|
215
|
-
hash: 'b',
|
|
216
|
-
updatedAt: '',
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const result = differ.compare(expected, received);
|
|
220
|
-
expect(result.match).toBe(true);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test('should match regex patterns', () => {
|
|
224
|
-
const differ = new SnapshotDiffer({
|
|
225
|
-
match: { 'body.version': 'regex:^v\\d+\\.\\d+' },
|
|
226
|
-
});
|
|
227
|
-
const expected: Snapshot = {
|
|
228
|
-
body: { version: 'v1.0.0' },
|
|
229
|
-
hash: 'a',
|
|
230
|
-
updatedAt: '',
|
|
231
|
-
};
|
|
232
|
-
const received: Snapshot = {
|
|
233
|
-
body: { version: 'v2.5.3' },
|
|
234
|
-
hash: 'b',
|
|
235
|
-
updatedAt: '',
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
const result = differ.compare(expected, received);
|
|
239
|
-
expect(result.match).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('should fail on non-matching regex', () => {
|
|
243
|
-
const differ = new SnapshotDiffer({
|
|
244
|
-
match: { 'body.version': 'regex:^v\\d+\\.\\d+' },
|
|
245
|
-
});
|
|
246
|
-
const expected: Snapshot = {
|
|
247
|
-
body: { version: 'v1.0' },
|
|
248
|
-
hash: 'a',
|
|
249
|
-
updatedAt: '',
|
|
250
|
-
};
|
|
251
|
-
const received: Snapshot = {
|
|
252
|
-
body: { version: 'invalid' },
|
|
253
|
-
hash: 'b',
|
|
254
|
-
updatedAt: '',
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const result = differ.compare(expected, received);
|
|
258
|
-
expect(result.match).toBe(false);
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
describe('type mismatches', () => {
|
|
263
|
-
test('should detect type changes', () => {
|
|
264
|
-
const differ = new SnapshotDiffer({});
|
|
265
|
-
const expected: Snapshot = {
|
|
266
|
-
body: { count: 42 },
|
|
267
|
-
hash: 'a',
|
|
268
|
-
updatedAt: '',
|
|
269
|
-
};
|
|
270
|
-
const received: Snapshot = {
|
|
271
|
-
body: { count: '42' },
|
|
272
|
-
hash: 'b',
|
|
273
|
-
updatedAt: '',
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const result = differ.compare(expected, received);
|
|
277
|
-
expect(result.match).toBe(false);
|
|
278
|
-
expect(result.differences[0].type).toBe('type_mismatch');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test('should detect object to array change', () => {
|
|
282
|
-
const differ = new SnapshotDiffer({});
|
|
283
|
-
const expected: Snapshot = {
|
|
284
|
-
body: { data: { key: 'value' } },
|
|
285
|
-
hash: 'a',
|
|
286
|
-
updatedAt: '',
|
|
287
|
-
};
|
|
288
|
-
const received: Snapshot = {
|
|
289
|
-
body: { data: ['value'] },
|
|
290
|
-
hash: 'b',
|
|
291
|
-
updatedAt: '',
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const result = differ.compare(expected, received);
|
|
295
|
-
expect(result.match).toBe(false);
|
|
296
|
-
expect(result.differences[0].type).toBe('type_mismatch');
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe('nested objects', () => {
|
|
301
|
-
test('should compare deeply nested values', () => {
|
|
302
|
-
const differ = new SnapshotDiffer({});
|
|
303
|
-
const expected: Snapshot = {
|
|
304
|
-
body: { level1: { level2: { level3: { value: 'old' } } } },
|
|
305
|
-
hash: 'a',
|
|
306
|
-
updatedAt: '',
|
|
307
|
-
};
|
|
308
|
-
const received: Snapshot = {
|
|
309
|
-
body: { level1: { level2: { level3: { value: 'new' } } } },
|
|
310
|
-
hash: 'b',
|
|
311
|
-
updatedAt: '',
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const result = differ.compare(expected, received);
|
|
315
|
-
expect(result.match).toBe(false);
|
|
316
|
-
expect(result.differences[0].path).toBe('body.level1.level2.level3.value');
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('header comparison', () => {
|
|
321
|
-
test('should compare headers', () => {
|
|
322
|
-
const differ = new SnapshotDiffer({});
|
|
323
|
-
const expected: Snapshot = {
|
|
324
|
-
headers: { 'content-type': 'application/json' },
|
|
325
|
-
hash: 'a',
|
|
326
|
-
updatedAt: '',
|
|
327
|
-
};
|
|
328
|
-
const received: Snapshot = {
|
|
329
|
-
headers: { 'content-type': 'text/html' },
|
|
330
|
-
hash: 'b',
|
|
331
|
-
updatedAt: '',
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
const result = differ.compare(expected, received);
|
|
335
|
-
expect(result.match).toBe(false);
|
|
336
|
-
expect(result.differences[0].path).toBe('headers.content-type');
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test('should exclude headers', () => {
|
|
340
|
-
const differ = new SnapshotDiffer({
|
|
341
|
-
exclude: ['headers.date', 'headers.x-request-id'],
|
|
342
|
-
});
|
|
343
|
-
const expected: Snapshot = {
|
|
344
|
-
headers: { 'content-type': 'application/json', date: '2024-01-01' },
|
|
345
|
-
hash: 'a',
|
|
346
|
-
updatedAt: '',
|
|
347
|
-
};
|
|
348
|
-
const received: Snapshot = {
|
|
349
|
-
headers: { 'content-type': 'application/json', date: '2024-12-31' },
|
|
350
|
-
hash: 'b',
|
|
351
|
-
updatedAt: '',
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
const result = differ.compare(expected, received);
|
|
355
|
-
expect(result.match).toBe(true);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
});
|