Skip to content

Commit 5cd508c

Browse files
[Recorder] Workaround for the "url-path with single quotes" bug in nock (Azure#13474)
## Problem As part of moving `data-tables` from `core-http` to `core-https` at Azure#12548, @xirzec has observed that the generated recordings for the node tests was invalid if the url-path has single quotes. Upon investigating, I could see that the request url at core-http and core-https levels is the same. The problem is seen because of the switch from `node-fetch`(core-http) to native `https`(core-https). `node-fetch` encodes the request before sending which encodes the single quotes to `%27` and nock was able to save the fixtures with the encoded request nicely. When migrated to `https` in core-https, there is a difference in behavior with the URL path, there is no default encoding, leading to the presence of single quotes. ## Bug(in `nock`) Given the above problem, "nock" does capture the request with single quotes in the URL path, but fails to save the fixture properly leading to invalid recordings on our end. Reference - https://github.com/nock/nock/blob/dc04694dcd3186a396e48567bdfabbdad2761f6a/lib/recorder.js#L134 This line > lines.push(\` .${methodName}('${path}', ${JSON.stringify(requestBody)})\`) Single quotes can be present in the url path though unusual. When the url path has single quotes, the fixture generated by "nock" is incorrect since it doesn't consider the case. #### Examples below - `.delete('/Tables('node')')` - `.get('/Tables('node')')` - `.post('/Tables('node')', {"TableName":"testTablenode"})` ## Fix To avoid this problem, we replace the single quotes surrounding the url-path in the recording with backticks(`) if there are any single quotes present in the url-path. This would fix the invalid recordings. This is backward compatible as well. This would work for both core-http and core-https. ## To Do - [x] Add workaround - [x] Tests for the workaround - [x] Create an issue in the "nock" repo - [x] nock/nock#2136 - [x] Changelog
1 parent 05bad89 commit 5cd508c

File tree

4 files changed

+152
-3
lines changed

4 files changed

+152
-3
lines changed

sdk/test-utils/recorder/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 1.0.0 (Unreleased)
44

5+
## 2020-01-28
6+
7+
- Single quotes can be present in the url path though unusual. When the url path has single quotes, the fixture generated by "nock" is incorrect leading to invalid recordings. [#13474](https://github.com/Azure/azure-sdk-for-js/pull/13474) introduces a workaround to be applied as part of the default customizations done on the generated recordings to fix the issue.
8+
59
## 2020-12-02
610

711
- Refactored the code to enable `"browser"` module replacement when used with bundlers. Previously, even browser bundled copies of the recorder would carry dependencies on several Node packages, which would lead to unresolved dependency warnings in Rollup regarding node builtins. With this change, the recorder's browser mappings will avoid this issue.

sdk/test-utils/recorder/src/baseRecorder.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
filterSecretsFromStrings,
99
filterSecretsRecursivelyFromJSON,
1010
generateTestRecordingFilePath,
11-
decodeHexEncodingIfExistsInNockFixture
11+
decodeHexEncodingIfExistsInNockFixture,
12+
handleSingleQuotesInUrlPath
1213
} from "./utils";
1314

1415
/**
@@ -41,7 +42,10 @@ export abstract class BaseRecorder {
4142
private defaultCustomizationsOnRecordings = !isBrowser()
4243
? [
4344
// Decodes "hex" strings in the response from the recorded fixture if any exists.
44-
decodeHexEncodingIfExistsInNockFixture
45+
decodeHexEncodingIfExistsInNockFixture,
46+
// Nock bug: Single quotes in the path of the url are not handled by nock.
47+
// The following is the workaround we use in the recorder until nock fixes it.
48+
handleSingleQuotesInUrlPath
4549
]
4650
: [];
4751

sdk/test-utils/recorder/src/utils/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,48 @@ export function decodeHexEncodingIfExistsInNockFixture(fixture: string): string
389389
return fixture;
390390
}
391391

392+
/**
393+
* Meant for node recordings only!
394+
*
395+
* Single quotes can be present in the url path though unusual.
396+
* When the url path has single quotes, the fixture generated by "nock" is incorrect
397+
* since it doesn't consider the case.
398+
* Examples below:
399+
* .delete('/Tables('node')')
400+
* .get('/Tables('node')')
401+
* .post('/Tables('node')', {"TableName":"testTablenode"})
402+
*
403+
* The above problem results in invalid recordings.
404+
*
405+
* To avoid this problem, we replace the single quotes surrounding the url-path in the recording
406+
* with backticks(`). This would fix the invalid recordings.
407+
*
408+
* @private
409+
* @param {string} fixture
410+
*/
411+
export function handleSingleQuotesInUrlPath(fixture: string): string {
412+
let updatedFixture = fixture;
413+
if (!isBrowser()) {
414+
// Fixtures would contain url-path as shown below
415+
// 1) .{method}('{url-path}')
416+
// 2) .{method}('{url-path}', {json-object})
417+
// Examples:
418+
// .get('/Tables('node')')
419+
// .post('/Tables('node')', {"TableName":"node"})
420+
const matches = fixture.match(/\.(get|put|post|delete)\(\'(.*)\'(\,|\))/);
421+
422+
if (matches && matches[2]) {
423+
const match = matches[2]; // Extracted url-path
424+
// If the url-path contains a single quote
425+
if (match.search("'") !== -1) {
426+
// Replace the occurrence of surrounding single quotes with backticks
427+
updatedFixture = fixture.replace("'" + match + "'", "`" + match + "`");
428+
}
429+
}
430+
}
431+
return updatedFixture;
432+
}
433+
392434
/**
393435
* List of binary content types.
394436
* Currently, "avro/binary" is the only one present.

sdk/test-utils/recorder/test/node/utils.spec.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
isBrowser,
44
testHasChanged,
55
isContentTypeInNockFixture,
6-
decodeHexEncodingIfExistsInNockFixture
6+
decodeHexEncodingIfExistsInNockFixture,
7+
handleSingleQuotesInUrlPath
78
} from "../../src/utils";
89

910
import { nodeRequireRecordingIfExists, findRecordingsFolderPath } from "../../src/utils/recordings";
@@ -394,4 +395,102 @@ describe("NodeJS utils", () => {
394395
});
395396
});
396397
});
398+
399+
describe("handleSingleQuotesInUrlPath", () => {
400+
[
401+
{
402+
name: `single quotes in get request in the fixture with request body`,
403+
input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
404+
.get('/'path'', "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><QueryRequest><Expression>select * from BlobStorage</Expression></QueryRequest>")
405+
.query(true)
406+
.reply(200, "4f626a0131c2", [
407+
'Transfer-Encoding',
408+
'chunked'
409+
]);`,
410+
output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
411+
.get(\`/'path'\`, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><QueryRequest><Expression>select * from BlobStorage</Expression></QueryRequest>")
412+
.query(true)
413+
.reply(200, "4f626a0131c2", [
414+
'Transfer-Encoding',
415+
'chunked'
416+
]);`
417+
},
418+
{
419+
name: `single quotes in get request in the fixture with no request body`,
420+
input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
421+
.get('/'path'')
422+
.query(true)
423+
.reply(200, "4f626a0131c2", [
424+
'Transfer-Encoding',
425+
'chunked'
426+
]);`,
427+
output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
428+
.get(\`/'path'\`)
429+
.query(true)
430+
.reply(200, "4f626a0131c2", [
431+
'Transfer-Encoding',
432+
'chunked'
433+
]);`
434+
},
435+
{
436+
name: `single quotes in delete request in the fixture with no request body`,
437+
input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
438+
.delete('/'path'pathx')
439+
.query(true)
440+
.reply(200, "4f626a0131c2", [
441+
'Transfer-Encoding',
442+
'chunked'
443+
]);`,
444+
output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
445+
.delete(\`/'path'pathx\`)
446+
.query(true)
447+
.reply(200, "4f626a0131c2", [
448+
'Transfer-Encoding',
449+
'chunked'
450+
]);`
451+
},
452+
{
453+
name: `no single quotes in get request in the fixture`,
454+
input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
455+
.delete('/path')
456+
.query(true)
457+
.reply(200, "4f626a0131c2", [
458+
'Transfer-Encoding',
459+
'chunked'
460+
]);`,
461+
output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
462+
.delete('/path')
463+
.query(true)
464+
.reply(200, "4f626a0131c2", [
465+
'Transfer-Encoding',
466+
'chunked'
467+
]);`
468+
},
469+
{
470+
name: `more than two single quotes in delete request in the fixture`,
471+
input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
472+
.delete('/p'''a't'h')
473+
.query(true)
474+
.reply(200, "4f626a0131c2", [
475+
'Transfer-Encoding',
476+
'chunked'
477+
]);`,
478+
output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true})
479+
.delete(\`/p'''a't'h\`)
480+
.query(true)
481+
.reply(200, "4f626a0131c2", [
482+
'Transfer-Encoding',
483+
'chunked'
484+
]);`
485+
}
486+
].forEach((test) => {
487+
it(test.name, () => {
488+
chai.assert.equal(
489+
handleSingleQuotesInUrlPath(test.input),
490+
test.output,
491+
`Output from "handleSingleQuotesInUrlPath" did not match the expected output`
492+
);
493+
});
494+
});
495+
});
397496
});

0 commit comments

Comments
 (0)