Skip to content

Commit 17ec4f6

Browse files
authored
Merge pull request #1425 from blackflux/dev
[Gally]: master <- dev
2 parents 52cddc0 + 0992b23 commit 17ec4f6

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
lines changed

src/modules/request-recorder.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
convertHeaders,
2323
rewriteHeaders
2424
} from './request-recorder/util.js';
25+
import healBody from './request-recorder/heal-body.js';
2526

2627
const nockBack = nock.back;
2728
const nockRecorder = nock.recorder;
@@ -180,7 +181,7 @@ export default (opts) => {
180181
const idx = pendingMocks.findIndex((m) => m.idx === scopeIdx);
181182
const requestBody = nullAsString(tryParseJson(body));
182183
if (!isEqual(scope.body, requestBody)) {
183-
pendingMocks[idx].record.body = requestBody;
184+
pendingMocks[idx].record.body = healBody(pendingMocks[idx].record.body, scope.body, requestBody);
184185
}
185186
return scope.body;
186187
}
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import objectScan from 'object-scan';
2+
import cloneDeep from 'lodash.clonedeep';
3+
4+
const last = (arr) => arr[arr.length - 1];
5+
6+
const healer = objectScan(['**.*|*'], {
7+
breakFn: ({
8+
isMatch, depth, property, context
9+
}) => {
10+
if (property === undefined) {
11+
return false;
12+
}
13+
context.expected[depth] = last(context.expected)?.[property];
14+
context.actual[depth] = last(context.actual)?.[property];
15+
return isMatch;
16+
},
17+
filterFn: ({
18+
context, depth, property, value
19+
}) => {
20+
const k = property.split('|')[0];
21+
const parentExpected = context.expected[depth - 1];
22+
const parentActual = context.actual[depth - 1];
23+
const childExpected = parentExpected?.[k];
24+
const childActual = parentActual?.[k];
25+
if (
26+
parentActual !== undefined
27+
&& !(childExpected instanceof Object)
28+
&& !(childActual instanceof Object)
29+
&& childExpected === childActual
30+
) {
31+
delete parentActual[k];
32+
parentActual[property] = value;
33+
}
34+
},
35+
afterFn: ({ context }) => context.actual[0]
36+
});
37+
38+
export default (original, expected, actual) => healer(
39+
original,
40+
{
41+
expected: [expected],
42+
actual: [cloneDeep(actual)]
43+
}
44+
);

test/modules/request-recorder.spec.js

+51-7
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,29 @@ describe('Testing RequestRecorder', { useTmpDir: true, timestamp: 0 }, () => {
3737
},
3838
stripHeaders = undefined,
3939
strict = undefined,
40-
heal = undefined
40+
heal = undefined,
41+
method = 'GET'
4142
} = {}) => nockRecord(
4243
async () => {
4344
for (let idx = 0; idx < qs.length; idx += 1) {
4445
// eslint-disable-next-line no-await-in-loop
4546
const { data } = await axios({
4647
url: `${server.uri}?q=${qs[idx]}`,
4748
data: body,
48-
responseType: 'json'
49+
responseType: 'json',
50+
method
4951
});
5052
expect(data).to.deep.equal({ data: String(qs[idx]) });
5153
}
5254
},
53-
{ stripHeaders, strict, heal }
55+
{
56+
stripHeaders,
57+
strict,
58+
heal,
59+
modifiers: {
60+
'JSON.stringify': JSON.stringify
61+
}
62+
}
5463
);
5564

5665
it('Testing headers captured', async () => {
@@ -267,7 +276,7 @@ describe('Testing RequestRecorder', { useTmpDir: true, timestamp: 0 }, () => {
267276
cassetteContent === null
268277
? [{
269278
scope: server.uri,
270-
method,
279+
method: 'GET',
271280
path: '/?q=1',
272281
body: method === 'GET' ? '' : {
273282
id: 123,
@@ -281,19 +290,19 @@ describe('Testing RequestRecorder', { useTmpDir: true, timestamp: 0 }, () => {
281290
);
282291
if (raises) {
283292
const e = await capture(() => runTest({
284-
heal, qs, body, stripHeaders
293+
heal, qs, body, stripHeaders, method
285294
}));
286295
expect(e.message).to.match(/^Nock: No match for request/);
287296
} else {
288297
await runTest({
289-
heal, qs, body, stripHeaders
298+
heal, qs, body, stripHeaders, method
290299
});
291300
}
292301
const content = fs.smartRead(cassettePath);
293302
if (heals) {
294303
expect(content[0].body.payload).to.not.equal(null);
295304
expect(content[0].path).to.equal(`/?q=${qs[0]}`);
296-
await runTest({ qs, body });
305+
await runTest({ qs, body, method });
297306
} else {
298307
expect(get(content, [0, 'body', 'payload'], null)).to.equal(null);
299308
}
@@ -313,6 +322,41 @@ describe('Testing RequestRecorder', { useTmpDir: true, timestamp: 0 }, () => {
313322
await runner('body');
314323
});
315324

325+
it('Testing body healing with modifiers', async () => {
326+
const cassette = {
327+
...makeCassetteEntry(1),
328+
method: 'POST',
329+
reqheaders: {
330+
accept: 'application/json, text/plain, */*',
331+
'content-type': 'application/json',
332+
'user-agent': 'axios/1.6.7',
333+
'content-length': '^\\d+$',
334+
'accept-encoding': 'gzip, compress, deflate, br'
335+
},
336+
body: {
337+
'data|JSON.stringify': { a: 1 },
338+
'other|JSON.stringify': { a: 1 }
339+
}
340+
};
341+
await runner('body', {
342+
raises: false,
343+
heals: true,
344+
body: {
345+
data: '{"a":1}',
346+
other: '{"a":2}'
347+
},
348+
method: 'POST',
349+
qs: [1],
350+
cassetteContent: [cassette]
351+
});
352+
const cassettePath = path.join(tmpDir, cassetteFile);
353+
const content = fs.smartRead(cassettePath);
354+
expect(content[0].body).to.deep.equal({
355+
'data|JSON.stringify': { a: 1 },
356+
other: '{"a":2}'
357+
});
358+
});
359+
316360
it('Testing body healing with null body', async () => {
317361
await runner('body', { body: null });
318362
});

0 commit comments

Comments
 (0)