@curl-runner/cli 1.14.0 → 1.15.0

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.
@@ -0,0 +1,330 @@
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
+ });