Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/RequestExecutor/HttpRequestExecutor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,67 @@ describe('HttpRequestExecutor', () => {
expect(firstMismatch(body, expected)).toBe(-1);
});

it('should honour a per-request timeout after a script transform', async () => {
// arrange
// The executor is built without a timeout, so the only one in play is the
// per-request value. If the script transform drops it, nothing bounds the
// request and the slow response arrives successfully.
const { baseUrl } = await startServer((_req, res) => {
setTimeout(() => res.end('too late'), 1000);
});
const request = new Request({
protocol: Protocol.HTTP,
url: `${baseUrl}/`,
method: 'GET',
timeout: 50
});
withVirtualScript(request.url, (options) => options);
const sut = buildSut();

// act
const response = await sut.execute(request);

// assert
expect(response.statusCode).toBeUndefined();
expect(response.errorCode).toBeDefined();
});

it('should hand the script every request option except the body encoding', async () => {
// arrange
// The body the script receives is already decoded, so reporting the
// original `encoding` alongside it would describe it incorrectly.
const { baseUrl, received } = await startBodyCapturingServer();
const request = new Request({
protocol: Protocol.HTTP,
url: `${baseUrl}/`,
method: 'POST',
body: binaryPattern(64).toString('base64'),
encoding: 'base64',
timeout: 5000,
maxContentSize: 7,
decompress: false
});
let seen: RequestOptions | undefined;
withVirtualScript(request.url, (options) => {
seen = options;

return options;
});
const sut = buildSut();

// act
await sut.execute(request);
await received;

// assert
expect(seen).toMatchObject({
timeout: 5000,
maxContentSize: 7,
decompress: false
});
expect(seen.encoding).toBeUndefined();
});

it('should keep the body byte-exact when a script re-declares the original encoding', async () => {
// arrange
// The script echoes back the encoding the request already had, so it has
Expand Down
16 changes: 10 additions & 6 deletions src/RequestExecutor/HttpRequestExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,14 +569,18 @@ export class HttpRequestExecutor implements RequestExecutor {
this.DEFAULT_SCRIPT_ENTRYPOINT,
{
...script.toJSON(),
body: decodedBody
body: decodedBody,
// The body handed to the script is already decoded, so reporting the
// original `encoding` would describe it incorrectly. Every other option
// round-trips verbatim.
encoding: undefined
}
);
// `toJSON()` does not carry `encoding`, so a script that leaves the body
// alone hands it back either undefined or the encoding it was told to use,
// and the lossy decoded view is all that is left of the body — restore
// both. Only a script that asks for an encoding the request did not already
// have is asking for its own body to be decoded, so pass that through.
// The script is handed the body already decoded and no `encoding`, so one it
// hands back is either absent or its own addition, and the lossy decoded
// view is all that is left of the body — restore both. Only a script that
// asks for an encoding the request did not already have is asking for its
// own body to be decoded, so pass that through.
const bodyUntouched = !!result && result.body === decodedBody;
const encodingUnchanged =
!result?.encoding || result.encoding === script.encoding;
Expand Down
59 changes: 59 additions & 0 deletions src/RequestExecutor/Request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,65 @@ describe('Request', () => {
);
});

describe('toJSON', () => {
it('should round-trip every option the constructor accepts', () => {
// arrange
const options = {
protocol: Protocol.HTTP,
url: 'http://foo.bar/',
method: 'POST',
headers: { 'x-key': 'value' },
body: 'AAAA',
passphrase: 'pass',
correlationIdRegex: 'x-correlation-id',
encoding: 'base64' as const,
maxContentSize: 99,
timeout: 1234,
decompress: false,
keepAlive: true
};
const request = new Request(options);

// act
const copy = new Request(request.toJSON());

// assert
expect(copy).toEqual(
expect.objectContaining({
protocol: options.protocol,
url: options.url,
method: options.method,
body: options.body,
passphrase: options.passphrase,
encoding: options.encoding,
maxContentSize: options.maxContentSize,
timeout: options.timeout,
decompress: options.decompress,
keepAlive: options.keepAlive
})
);
expect(copy.headers).toEqual(options.headers);
expect(copy.correlationIdRegex).toEqual(request.correlationIdRegex);
});

it('should preserve an explicit decompress: false rather than defaulting it back to true', () => {
// arrange
// `decompress` defaults to `true` in the constructor, so omitting it from
// `toJSON()` silently flips it instead of merely dropping it.
const request = new Request({
protocol: Protocol.HTTP,
url: 'http://foo.bar/',
decompress: false
});

// act
const copy = new Request(request.toJSON());

// assert
expect(copy.decompress).toBe(false);
});
});

describe('setHeaders', () => {
it('should append headers', () => {
const request = new Request({
Expand Down
14 changes: 13 additions & 1 deletion src/RequestExecutor/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ export class Request {
}
}

/**
* Serializes every option the constructor accepts, so that
* `new Request(request.toJSON())` is a faithful copy. Any field omitted here
* is silently dropped on such a round-trip, and `decompress` is worse than
* dropped: the constructor defaults it to `true`, so omitting it flips an
* explicit `false`.
*/
public toJSON(): RequestOptions {
return {
protocol: this.protocol,
Expand All @@ -194,7 +201,12 @@ export class Request {
passphrase: this._passphrase,
ca: this._ca?.toString('utf8'),
pfx: this._pfx?.toString('utf8'),
correlationIdRegex: this.correlationIdRegex
correlationIdRegex: this.correlationIdRegex,
encoding: this.encoding,
maxContentSize: this.maxContentSize,
timeout: this.timeout,
decompress: this.decompress,
keepAlive: this.keepAlive
};
}

Expand Down
Loading