@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
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import type { DiffCompareResult, DiffSummary, ResponseDiff } from '../types/config';
|
|
2
|
-
|
|
3
|
-
const COLORS = {
|
|
4
|
-
reset: '\x1b[0m',
|
|
5
|
-
red: '\x1b[31m',
|
|
6
|
-
green: '\x1b[32m',
|
|
7
|
-
yellow: '\x1b[33m',
|
|
8
|
-
cyan: '\x1b[36m',
|
|
9
|
-
dim: '\x1b[2m',
|
|
10
|
-
bright: '\x1b[1m',
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Formats diff comparison results for various outputs.
|
|
15
|
-
*/
|
|
16
|
-
export class DiffFormatter {
|
|
17
|
-
private outputFormat: 'terminal' | 'json' | 'markdown';
|
|
18
|
-
|
|
19
|
-
constructor(outputFormat: 'terminal' | 'json' | 'markdown' = 'terminal') {
|
|
20
|
-
this.outputFormat = outputFormat;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private color(text: string, color: keyof typeof COLORS): string {
|
|
24
|
-
if (this.outputFormat !== 'terminal') {
|
|
25
|
-
return text;
|
|
26
|
-
}
|
|
27
|
-
return `${COLORS[color]}${text}${COLORS.reset}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Formats the complete diff summary.
|
|
32
|
-
*/
|
|
33
|
-
formatSummary(summary: DiffSummary, baselineLabel: string, currentLabel: string): string {
|
|
34
|
-
switch (this.outputFormat) {
|
|
35
|
-
case 'json':
|
|
36
|
-
return this.formatSummaryJson(summary);
|
|
37
|
-
case 'markdown':
|
|
38
|
-
return this.formatSummaryMarkdown(summary, baselineLabel, currentLabel);
|
|
39
|
-
default:
|
|
40
|
-
return this.formatSummaryTerminal(summary, baselineLabel, currentLabel);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Terminal format for summary.
|
|
46
|
-
*/
|
|
47
|
-
private formatSummaryTerminal(
|
|
48
|
-
summary: DiffSummary,
|
|
49
|
-
baselineLabel: string,
|
|
50
|
-
currentLabel: string,
|
|
51
|
-
): string {
|
|
52
|
-
const lines: string[] = [];
|
|
53
|
-
|
|
54
|
-
lines.push('');
|
|
55
|
-
lines.push(
|
|
56
|
-
`${this.color('Response Diff:', 'bright')} ${this.color(baselineLabel, 'cyan')} → ${this.color(currentLabel, 'cyan')}`,
|
|
57
|
-
);
|
|
58
|
-
lines.push('');
|
|
59
|
-
|
|
60
|
-
for (const result of summary.results) {
|
|
61
|
-
lines.push(this.formatResultTerminal(result));
|
|
62
|
-
lines.push('');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
lines.push(this.formatStatsSummary(summary));
|
|
66
|
-
|
|
67
|
-
return lines.join('\n');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* JSON format for summary.
|
|
72
|
-
*/
|
|
73
|
-
private formatSummaryJson(summary: DiffSummary): string {
|
|
74
|
-
return JSON.stringify(
|
|
75
|
-
{
|
|
76
|
-
summary: {
|
|
77
|
-
total: summary.totalRequests,
|
|
78
|
-
unchanged: summary.unchanged,
|
|
79
|
-
changed: summary.changed,
|
|
80
|
-
newBaselines: summary.newBaselines,
|
|
81
|
-
},
|
|
82
|
-
results: summary.results.map((r) => ({
|
|
83
|
-
request: r.requestName,
|
|
84
|
-
status: r.hasDifferences ? 'changed' : r.isNewBaseline ? 'new' : 'unchanged',
|
|
85
|
-
differences: r.differences,
|
|
86
|
-
timingDiff: r.timingDiff,
|
|
87
|
-
})),
|
|
88
|
-
},
|
|
89
|
-
null,
|
|
90
|
-
2,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Markdown format for summary.
|
|
96
|
-
*/
|
|
97
|
-
private formatSummaryMarkdown(
|
|
98
|
-
summary: DiffSummary,
|
|
99
|
-
baselineLabel: string,
|
|
100
|
-
currentLabel: string,
|
|
101
|
-
): string {
|
|
102
|
-
const lines: string[] = [];
|
|
103
|
-
|
|
104
|
-
lines.push(`# Response Diff: ${baselineLabel} → ${currentLabel}`);
|
|
105
|
-
lines.push('');
|
|
106
|
-
lines.push(`| Metric | Count |`);
|
|
107
|
-
lines.push(`|--------|-------|`);
|
|
108
|
-
lines.push(`| Total Requests | ${summary.totalRequests} |`);
|
|
109
|
-
lines.push(`| Unchanged | ${summary.unchanged} |`);
|
|
110
|
-
lines.push(`| Changed | ${summary.changed} |`);
|
|
111
|
-
lines.push(`| New Baselines | ${summary.newBaselines} |`);
|
|
112
|
-
lines.push('');
|
|
113
|
-
|
|
114
|
-
if (summary.changed > 0) {
|
|
115
|
-
lines.push('## Changes');
|
|
116
|
-
lines.push('');
|
|
117
|
-
|
|
118
|
-
for (const result of summary.results) {
|
|
119
|
-
if (result.hasDifferences) {
|
|
120
|
-
lines.push(this.formatResultMarkdown(result));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (summary.newBaselines > 0) {
|
|
126
|
-
lines.push('## New Requests');
|
|
127
|
-
lines.push('');
|
|
128
|
-
for (const result of summary.results) {
|
|
129
|
-
if (result.isNewBaseline) {
|
|
130
|
-
lines.push(`- \`${result.requestName}\``);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
lines.push('');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return lines.join('\n');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Formats a single result for terminal.
|
|
141
|
-
*/
|
|
142
|
-
formatResultTerminal(result: DiffCompareResult): string {
|
|
143
|
-
const lines: string[] = [];
|
|
144
|
-
|
|
145
|
-
if (result.isNewBaseline) {
|
|
146
|
-
lines.push(` ${this.color('NEW', 'cyan')} ${this.color(result.requestName, 'bright')}`);
|
|
147
|
-
return lines.join('\n');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!result.hasDifferences) {
|
|
151
|
-
lines.push(
|
|
152
|
-
` ${this.color('✓', 'green')} ${this.color(result.requestName, 'bright')} ${this.color('(no changes)', 'dim')}`,
|
|
153
|
-
);
|
|
154
|
-
return lines.join('\n');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
lines.push(` ${this.color('✗', 'red')} ${this.color(result.requestName, 'bright')}`);
|
|
158
|
-
|
|
159
|
-
for (const diff of result.differences) {
|
|
160
|
-
lines.push(this.formatDifferenceTerminal(diff));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (result.timingDiff) {
|
|
164
|
-
const { baseline, current, changePercent } = result.timingDiff;
|
|
165
|
-
const sign = changePercent >= 0 ? '+' : '';
|
|
166
|
-
const color = changePercent > 20 ? 'red' : changePercent < -20 ? 'green' : 'yellow';
|
|
167
|
-
lines.push(
|
|
168
|
-
` ${this.color('timing:', 'cyan')} ${baseline}ms → ${current}ms ${this.color(`(${sign}${changePercent.toFixed(0)}%)`, color)}`,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return lines.join('\n');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Formats a single difference for terminal.
|
|
177
|
-
*/
|
|
178
|
-
private formatDifferenceTerminal(diff: ResponseDiff): string {
|
|
179
|
-
const lines: string[] = [];
|
|
180
|
-
const path = diff.path || '(root)';
|
|
181
|
-
|
|
182
|
-
switch (diff.type) {
|
|
183
|
-
case 'added':
|
|
184
|
-
lines.push(` ${this.color(path, 'cyan')}:`);
|
|
185
|
-
lines.push(` ${this.color(`+ ${this.stringify(diff.current)}`, 'green')}`);
|
|
186
|
-
break;
|
|
187
|
-
|
|
188
|
-
case 'removed':
|
|
189
|
-
lines.push(` ${this.color(path, 'cyan')}:`);
|
|
190
|
-
lines.push(` ${this.color(`- ${this.stringify(diff.baseline)}`, 'red')}`);
|
|
191
|
-
break;
|
|
192
|
-
|
|
193
|
-
case 'changed':
|
|
194
|
-
lines.push(` ${this.color(path, 'cyan')}:`);
|
|
195
|
-
lines.push(` ${this.color(`- ${this.stringify(diff.baseline)}`, 'red')}`);
|
|
196
|
-
lines.push(` ${this.color(`+ ${this.stringify(diff.current)}`, 'green')}`);
|
|
197
|
-
break;
|
|
198
|
-
|
|
199
|
-
case 'type_mismatch':
|
|
200
|
-
lines.push(` ${this.color(path, 'cyan')} (type mismatch):`);
|
|
201
|
-
lines.push(
|
|
202
|
-
` ${this.color(`- ${this.stringify(diff.baseline)} (${typeof diff.baseline})`, 'red')}`,
|
|
203
|
-
);
|
|
204
|
-
lines.push(
|
|
205
|
-
` ${this.color(`+ ${this.stringify(diff.current)} (${typeof diff.current})`, 'green')}`,
|
|
206
|
-
);
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return lines.join('\n');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Formats a single result for markdown.
|
|
215
|
-
*/
|
|
216
|
-
private formatResultMarkdown(result: DiffCompareResult): string {
|
|
217
|
-
const lines: string[] = [];
|
|
218
|
-
|
|
219
|
-
lines.push(`### \`${result.requestName}\``);
|
|
220
|
-
lines.push('');
|
|
221
|
-
lines.push('```diff');
|
|
222
|
-
|
|
223
|
-
for (const diff of result.differences) {
|
|
224
|
-
lines.push(this.formatDifferenceMarkdown(diff));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
lines.push('```');
|
|
228
|
-
|
|
229
|
-
if (result.timingDiff) {
|
|
230
|
-
const { baseline, current, changePercent } = result.timingDiff;
|
|
231
|
-
const sign = changePercent >= 0 ? '+' : '';
|
|
232
|
-
lines.push('');
|
|
233
|
-
lines.push(`**Timing:** ${baseline}ms → ${current}ms (${sign}${changePercent.toFixed(0)}%)`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
lines.push('');
|
|
237
|
-
|
|
238
|
-
return lines.join('\n');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Formats a single difference for markdown.
|
|
243
|
-
*/
|
|
244
|
-
private formatDifferenceMarkdown(diff: ResponseDiff): string {
|
|
245
|
-
const lines: string[] = [];
|
|
246
|
-
const path = diff.path || '(root)';
|
|
247
|
-
|
|
248
|
-
switch (diff.type) {
|
|
249
|
-
case 'added':
|
|
250
|
-
lines.push(`# ${path}:`);
|
|
251
|
-
lines.push(`+ ${this.stringify(diff.current)}`);
|
|
252
|
-
break;
|
|
253
|
-
|
|
254
|
-
case 'removed':
|
|
255
|
-
lines.push(`# ${path}:`);
|
|
256
|
-
lines.push(`- ${this.stringify(diff.baseline)}`);
|
|
257
|
-
break;
|
|
258
|
-
|
|
259
|
-
case 'changed':
|
|
260
|
-
lines.push(`# ${path}:`);
|
|
261
|
-
lines.push(`- ${this.stringify(diff.baseline)}`);
|
|
262
|
-
lines.push(`+ ${this.stringify(diff.current)}`);
|
|
263
|
-
break;
|
|
264
|
-
|
|
265
|
-
case 'type_mismatch':
|
|
266
|
-
lines.push(`# ${path} (type mismatch):`);
|
|
267
|
-
lines.push(`- ${this.stringify(diff.baseline)} (${typeof diff.baseline})`);
|
|
268
|
-
lines.push(`+ ${this.stringify(diff.current)} (${typeof diff.current})`);
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return lines.join('\n');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Formats stats summary for terminal.
|
|
277
|
-
*/
|
|
278
|
-
private formatStatsSummary(summary: DiffSummary): string {
|
|
279
|
-
const parts: string[] = [];
|
|
280
|
-
|
|
281
|
-
if (summary.unchanged > 0) {
|
|
282
|
-
parts.push(this.color(`${summary.unchanged} unchanged`, 'green'));
|
|
283
|
-
}
|
|
284
|
-
if (summary.changed > 0) {
|
|
285
|
-
parts.push(this.color(`${summary.changed} changed`, 'red'));
|
|
286
|
-
}
|
|
287
|
-
if (summary.newBaselines > 0) {
|
|
288
|
-
parts.push(this.color(`${summary.newBaselines} new`, 'cyan'));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return `Summary: ${parts.join(', ')} (${summary.totalRequests} total)`;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Converts value to display string.
|
|
296
|
-
*/
|
|
297
|
-
private stringify(value: unknown): string {
|
|
298
|
-
if (value === undefined) {
|
|
299
|
-
return 'undefined';
|
|
300
|
-
}
|
|
301
|
-
if (value === null) {
|
|
302
|
-
return 'null';
|
|
303
|
-
}
|
|
304
|
-
if (typeof value === 'string') {
|
|
305
|
-
return `"${value}"`;
|
|
306
|
-
}
|
|
307
|
-
if (typeof value === 'object') {
|
|
308
|
-
const str = JSON.stringify(value);
|
|
309
|
-
if (str.length > 80) {
|
|
310
|
-
return `${str.slice(0, 77)}...`;
|
|
311
|
-
}
|
|
312
|
-
return str;
|
|
313
|
-
}
|
|
314
|
-
return String(value);
|
|
315
|
-
}
|
|
316
|
-
}
|
package/src/diff/index.ts
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import type { Baseline } from '../types/config';
|
|
3
|
-
import { ResponseDiffer } from './response-differ';
|
|
4
|
-
|
|
5
|
-
describe('ResponseDiffer', () => {
|
|
6
|
-
describe('compare', () => {
|
|
7
|
-
test('should detect no differences for identical baselines', () => {
|
|
8
|
-
const differ = new ResponseDiffer({});
|
|
9
|
-
const baseline: Baseline = {
|
|
10
|
-
status: 200,
|
|
11
|
-
body: { id: 1, name: 'Test' },
|
|
12
|
-
hash: 'abc123',
|
|
13
|
-
capturedAt: '2024-01-01',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const result = differ.compare(baseline, baseline, 'staging', 'production', 'Get User');
|
|
17
|
-
|
|
18
|
-
expect(result.hasDifferences).toBe(false);
|
|
19
|
-
expect(result.differences).toHaveLength(0);
|
|
20
|
-
expect(result.requestName).toBe('Get User');
|
|
21
|
-
expect(result.baselineLabel).toBe('staging');
|
|
22
|
-
expect(result.currentLabel).toBe('production');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('should detect status change', () => {
|
|
26
|
-
const differ = new ResponseDiffer({});
|
|
27
|
-
const baseline: Baseline = { status: 200, hash: 'a', capturedAt: '' };
|
|
28
|
-
const current: Baseline = { status: 201, hash: 'b', capturedAt: '' };
|
|
29
|
-
|
|
30
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Create User');
|
|
31
|
-
|
|
32
|
-
expect(result.hasDifferences).toBe(true);
|
|
33
|
-
expect(result.differences).toHaveLength(1);
|
|
34
|
-
expect(result.differences[0]).toEqual({
|
|
35
|
-
path: 'status',
|
|
36
|
-
baseline: 200,
|
|
37
|
-
current: 201,
|
|
38
|
-
type: 'changed',
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('should detect body field changes', () => {
|
|
43
|
-
const differ = new ResponseDiffer({});
|
|
44
|
-
const baseline: Baseline = {
|
|
45
|
-
body: { id: 1, name: 'Old' },
|
|
46
|
-
hash: 'a',
|
|
47
|
-
capturedAt: '',
|
|
48
|
-
};
|
|
49
|
-
const current: Baseline = {
|
|
50
|
-
body: { id: 1, name: 'New' },
|
|
51
|
-
hash: 'b',
|
|
52
|
-
capturedAt: '',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
56
|
-
|
|
57
|
-
expect(result.hasDifferences).toBe(true);
|
|
58
|
-
expect(result.differences).toContainEqual({
|
|
59
|
-
path: 'body.name',
|
|
60
|
-
baseline: 'Old',
|
|
61
|
-
current: 'New',
|
|
62
|
-
type: 'changed',
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('should detect added fields', () => {
|
|
67
|
-
const differ = new ResponseDiffer({});
|
|
68
|
-
const baseline: Baseline = {
|
|
69
|
-
body: { id: 1 },
|
|
70
|
-
hash: 'a',
|
|
71
|
-
capturedAt: '',
|
|
72
|
-
};
|
|
73
|
-
const current: Baseline = {
|
|
74
|
-
body: { id: 1, newField: 'value' },
|
|
75
|
-
hash: 'b',
|
|
76
|
-
capturedAt: '',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
80
|
-
|
|
81
|
-
expect(result.hasDifferences).toBe(true);
|
|
82
|
-
expect(result.differences).toContainEqual({
|
|
83
|
-
path: 'body.newField',
|
|
84
|
-
baseline: undefined,
|
|
85
|
-
current: 'value',
|
|
86
|
-
type: 'added',
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('should detect removed fields', () => {
|
|
91
|
-
const differ = new ResponseDiffer({});
|
|
92
|
-
const baseline: Baseline = {
|
|
93
|
-
body: { id: 1, oldField: 'value' },
|
|
94
|
-
hash: 'a',
|
|
95
|
-
capturedAt: '',
|
|
96
|
-
};
|
|
97
|
-
const current: Baseline = {
|
|
98
|
-
body: { id: 1 },
|
|
99
|
-
hash: 'b',
|
|
100
|
-
capturedAt: '',
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
104
|
-
|
|
105
|
-
expect(result.hasDifferences).toBe(true);
|
|
106
|
-
expect(result.differences).toContainEqual({
|
|
107
|
-
path: 'body.oldField',
|
|
108
|
-
baseline: 'value',
|
|
109
|
-
current: undefined,
|
|
110
|
-
type: 'removed',
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('should detect type mismatch', () => {
|
|
115
|
-
const differ = new ResponseDiffer({});
|
|
116
|
-
const baseline: Baseline = {
|
|
117
|
-
body: { count: '10' },
|
|
118
|
-
hash: 'a',
|
|
119
|
-
capturedAt: '',
|
|
120
|
-
};
|
|
121
|
-
const current: Baseline = {
|
|
122
|
-
body: { count: 10 },
|
|
123
|
-
hash: 'b',
|
|
124
|
-
capturedAt: '',
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
128
|
-
|
|
129
|
-
expect(result.hasDifferences).toBe(true);
|
|
130
|
-
expect(result.differences[0].type).toBe('type_mismatch');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('should include timing diff when configured', () => {
|
|
134
|
-
const differ = new ResponseDiffer({ includeTimings: true });
|
|
135
|
-
const baseline: Baseline = {
|
|
136
|
-
body: {},
|
|
137
|
-
timing: 100,
|
|
138
|
-
hash: 'a',
|
|
139
|
-
capturedAt: '',
|
|
140
|
-
};
|
|
141
|
-
const current: Baseline = {
|
|
142
|
-
body: {},
|
|
143
|
-
timing: 150,
|
|
144
|
-
hash: 'b',
|
|
145
|
-
capturedAt: '',
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
149
|
-
|
|
150
|
-
expect(result.timingDiff).toBeDefined();
|
|
151
|
-
expect(result.timingDiff?.baseline).toBe(100);
|
|
152
|
-
expect(result.timingDiff?.current).toBe(150);
|
|
153
|
-
expect(result.timingDiff?.changePercent).toBe(50);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('deepCompare', () => {
|
|
158
|
-
test('should handle nested objects', () => {
|
|
159
|
-
const differ = new ResponseDiffer({});
|
|
160
|
-
const baseline = { user: { profile: { age: 25 } } };
|
|
161
|
-
const current = { user: { profile: { age: 26 } } };
|
|
162
|
-
|
|
163
|
-
const diffs = differ.deepCompare(baseline, current, 'body');
|
|
164
|
-
|
|
165
|
-
expect(diffs).toHaveLength(1);
|
|
166
|
-
expect(diffs[0].path).toBe('body.user.profile.age');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('should handle arrays', () => {
|
|
170
|
-
const differ = new ResponseDiffer({});
|
|
171
|
-
const baseline = { items: [1, 2, 3] };
|
|
172
|
-
const current = { items: [1, 2, 4] };
|
|
173
|
-
|
|
174
|
-
const diffs = differ.deepCompare(baseline, current, 'body');
|
|
175
|
-
|
|
176
|
-
expect(diffs).toHaveLength(1);
|
|
177
|
-
expect(diffs[0].path).toBe('body.items[2]');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('should detect array length changes', () => {
|
|
181
|
-
const differ = new ResponseDiffer({});
|
|
182
|
-
const baseline = { items: [1, 2] };
|
|
183
|
-
const current = { items: [1, 2, 3] };
|
|
184
|
-
|
|
185
|
-
const diffs = differ.deepCompare(baseline, current, 'body');
|
|
186
|
-
|
|
187
|
-
expect(diffs).toHaveLength(1);
|
|
188
|
-
expect(diffs[0].type).toBe('added');
|
|
189
|
-
expect(diffs[0].path).toBe('body.items[2]');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('should handle null values', () => {
|
|
193
|
-
const differ = new ResponseDiffer({});
|
|
194
|
-
const baseline = { value: null };
|
|
195
|
-
const current = { value: null };
|
|
196
|
-
|
|
197
|
-
const diffs = differ.deepCompare(baseline, current, 'body');
|
|
198
|
-
|
|
199
|
-
expect(diffs).toHaveLength(0);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
describe('isExcluded', () => {
|
|
204
|
-
test('should exclude exact paths', () => {
|
|
205
|
-
const differ = new ResponseDiffer({ exclude: ['body.timestamp'] });
|
|
206
|
-
|
|
207
|
-
expect(differ.isExcluded('body.timestamp')).toBe(true);
|
|
208
|
-
expect(differ.isExcluded('body.id')).toBe(false);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test('should exclude wildcard paths', () => {
|
|
212
|
-
const differ = new ResponseDiffer({ exclude: ['*.createdAt'] });
|
|
213
|
-
|
|
214
|
-
expect(differ.isExcluded('body.createdAt')).toBe(true);
|
|
215
|
-
expect(differ.isExcluded('body.user.createdAt')).toBe(true);
|
|
216
|
-
expect(differ.isExcluded('body.id')).toBe(false);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('should exclude array wildcards', () => {
|
|
220
|
-
const differ = new ResponseDiffer({ exclude: ['body.items[*].id'] });
|
|
221
|
-
|
|
222
|
-
expect(differ.isExcluded('body.items[0].id')).toBe(true);
|
|
223
|
-
expect(differ.isExcluded('body.items[99].id')).toBe(true);
|
|
224
|
-
expect(differ.isExcluded('body.items[0].name')).toBe(false);
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe('matchesRule', () => {
|
|
229
|
-
test('should match wildcard rule', () => {
|
|
230
|
-
const differ = new ResponseDiffer({ match: { 'body.requestId': '*' } });
|
|
231
|
-
|
|
232
|
-
expect(differ.matchesRule('body.requestId', 'any-value')).toBe(true);
|
|
233
|
-
expect(differ.matchesRule('body.requestId', 12345)).toBe(true);
|
|
234
|
-
expect(differ.matchesRule('body.otherId', 'value')).toBe(false);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test('should match regex rule', () => {
|
|
238
|
-
const differ = new ResponseDiffer({
|
|
239
|
-
match: { 'body.uuid': 'regex:^[a-f0-9-]{36}$' },
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
expect(differ.matchesRule('body.uuid', '550e8400-e29b-41d4-a716-446655440000')).toBe(true);
|
|
243
|
-
expect(differ.matchesRule('body.uuid', 'invalid')).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test('should return false for unmatched paths', () => {
|
|
247
|
-
const differ = new ResponseDiffer({ match: { 'body.id': '*' } });
|
|
248
|
-
|
|
249
|
-
expect(differ.matchesRule('body.name', 'value')).toBe(false);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe('compare with exclusions', () => {
|
|
254
|
-
test('should ignore excluded paths in comparison', () => {
|
|
255
|
-
const differ = new ResponseDiffer({ exclude: ['body.timestamp', 'body.requestId'] });
|
|
256
|
-
const baseline: Baseline = {
|
|
257
|
-
body: { id: 1, timestamp: '2024-01-01', requestId: 'abc' },
|
|
258
|
-
hash: 'a',
|
|
259
|
-
capturedAt: '',
|
|
260
|
-
};
|
|
261
|
-
const current: Baseline = {
|
|
262
|
-
body: { id: 1, timestamp: '2024-01-02', requestId: 'xyz' },
|
|
263
|
-
hash: 'b',
|
|
264
|
-
capturedAt: '',
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
268
|
-
|
|
269
|
-
expect(result.hasDifferences).toBe(false);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test('should ignore matched paths with wildcard', () => {
|
|
273
|
-
const differ = new ResponseDiffer({ match: { 'body.token': '*' } });
|
|
274
|
-
const baseline: Baseline = {
|
|
275
|
-
body: { id: 1, token: 'old-token' },
|
|
276
|
-
hash: 'a',
|
|
277
|
-
capturedAt: '',
|
|
278
|
-
};
|
|
279
|
-
const current: Baseline = {
|
|
280
|
-
body: { id: 1, token: 'new-token' },
|
|
281
|
-
hash: 'b',
|
|
282
|
-
capturedAt: '',
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
286
|
-
|
|
287
|
-
expect(result.hasDifferences).toBe(false);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
describe('header comparison', () => {
|
|
292
|
-
test('should detect header changes', () => {
|
|
293
|
-
const differ = new ResponseDiffer({});
|
|
294
|
-
const baseline: Baseline = {
|
|
295
|
-
headers: { 'content-type': 'application/json' },
|
|
296
|
-
hash: 'a',
|
|
297
|
-
capturedAt: '',
|
|
298
|
-
};
|
|
299
|
-
const current: Baseline = {
|
|
300
|
-
headers: { 'content-type': 'text/plain' },
|
|
301
|
-
hash: 'b',
|
|
302
|
-
capturedAt: '',
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
306
|
-
|
|
307
|
-
expect(result.hasDifferences).toBe(true);
|
|
308
|
-
expect(result.differences[0].path).toBe('headers.content-type');
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test('should detect added headers', () => {
|
|
312
|
-
const differ = new ResponseDiffer({});
|
|
313
|
-
const baseline: Baseline = {
|
|
314
|
-
headers: {},
|
|
315
|
-
hash: 'a',
|
|
316
|
-
capturedAt: '',
|
|
317
|
-
};
|
|
318
|
-
const current: Baseline = {
|
|
319
|
-
headers: { 'x-new-header': 'value' },
|
|
320
|
-
hash: 'b',
|
|
321
|
-
capturedAt: '',
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const result = differ.compare(baseline, current, 'v1', 'v2', 'Get');
|
|
325
|
-
|
|
326
|
-
expect(result.hasDifferences).toBe(true);
|
|
327
|
-
expect(result.differences[0].type).toBe('added');
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
});
|