Skip to content

Commit c3d88d1

Browse files
committed
Add support for trailers in thenReply simple handlers
1 parent 0b7ec3e commit c3d88d1

File tree

4 files changed

+75
-10
lines changed

4 files changed

+75
-10
lines changed

src/rules/requests/request-handler-definitions.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,29 @@ export class SimpleHandlerDefinition extends Serializable implements RequestHand
271271
public status: number,
272272
public statusMessage?: string,
273273
public data?: string | Uint8Array | Buffer | SerializedBuffer,
274-
public headers?: Headers
274+
public headers?: Headers,
275+
public trailers?: Trailers
275276
) {
276277
super();
277278

278279
validateCustomHeaders({}, headers);
280+
validateCustomHeaders({}, trailers);
281+
282+
if (!_.isEmpty(trailers) && !_.isEmpty(headers)) {
283+
if (!Object.entries(headers!).some(([key, value]) =>
284+
key.toLowerCase() === 'transfer-encoding' && value === 'chunked'
285+
)) {
286+
throw new Error("Trailers can only be set when using chunked transfer encoding");
287+
}
288+
}
279289
}
280290

281291
explain() {
282292
return `respond with status ${this.status}` +
283293
(this.statusMessage ? ` (${this.statusMessage})`: "") +
284294
(this.headers ? `, headers ${JSON.stringify(this.headers)}` : "") +
285-
(this.data ? ` and body "${this.data}"` : "");
295+
(this.data ? ` and body "${this.data}"` : "") +
296+
(this.trailers ? `then trailers ${JSON.stringify(this.trailers)}` : "");
286297
}
287298
}
288299

src/rules/requests/request-handlers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ export class SimpleHandler extends SimpleHandlerDefinition {
162162
this.data = Buffer.from(<any> this.data);
163163
}
164164

165+
if (this.trailers) {
166+
response.addTrailers(this.trailers);
167+
}
168+
165169
response.end(this.data || "");
166170
}
167171
}

src/rules/requests/request-rule-builder.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { merge, isString, isBuffer } from "lodash";
22
import { Readable } from "stream";
33

4-
import { Headers, CompletedRequest, Method, MockedEndpoint } from "../../types";
4+
import { Headers, CompletedRequest, Method, MockedEndpoint, Trailers } from "../../types";
55
import type { RequestRuleData } from "./request-rule";
66

77
import {
@@ -87,10 +87,10 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
8787

8888
/**
8989
* Reply to matched requests with a given status code and (optionally) status message,
90-
* body and headers.
90+
* body, headers & trailers.
9191
*
9292
* If one string argument is provided, it's used as the body. If two are
93-
* provided (even if one is empty), then 1st is the status message, and
93+
* provided (even if one is empty) then the 1st is the status message, and
9494
* the 2nd the body. If no headers are provided, only the standard required
9595
* headers are set, e.g. Date and Transfer-Encoding.
9696
*
@@ -104,32 +104,49 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
104104
*
105105
* @category Responses
106106
*/
107-
thenReply(status: number, data?: string | Buffer, headers?: Headers): Promise<MockedEndpoint>;
107+
thenReply(
108+
status: number,
109+
data?: string | Buffer,
110+
headers?: Headers,
111+
trailers?: Trailers
112+
): Promise<MockedEndpoint>;
108113
thenReply(
109114
status: number,
110115
statusMessage: string,
111116
data: string | Buffer,
112-
headers?: Headers
117+
headers?: Headers,
118+
trailers?: Trailers
113119
): Promise<MockedEndpoint>
114120
thenReply(
115121
status: number,
116122
dataOrMessage?: string | Buffer,
117123
dataOrHeaders?: string | Buffer | Headers,
118-
headers?: Headers
124+
headersOrTrailers?: Headers | Trailers,
125+
trailers?: Trailers
119126
): Promise<MockedEndpoint> {
120127
let data: string | Buffer | undefined;
121128
let statusMessage: string | undefined;
129+
let headers: Headers | undefined;
130+
122131
if (isBuffer(dataOrHeaders) || isString(dataOrHeaders)) {
123132
data = dataOrHeaders as (Buffer | string);
124133
statusMessage = dataOrMessage as string;
134+
headers = headersOrTrailers as Headers;
125135
} else {
126136
data = dataOrMessage as string | Buffer | undefined;
127137
headers = dataOrHeaders as Headers | undefined;
138+
trailers = headersOrTrailers as Trailers | undefined;
128139
}
129140

130141
const rule: RequestRuleData = {
131142
...this.buildBaseRuleData(),
132-
handler: new SimpleHandlerDefinition(status, statusMessage, data, headers)
143+
handler: new SimpleHandlerDefinition(
144+
status,
145+
statusMessage,
146+
data,
147+
headers,
148+
trailers
149+
)
133150
};
134151

135152
return this.addRule(rule);

test/integration/handlers/fixed-response.spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import * as http from 'http';
2+
13
import { getLocal } from "../../..";
2-
import { expect, fetch, isNode } from "../../test-utils";
4+
import { expect, fetch, isNode, nodeOnly } from "../../test-utils";
35

46
describe("Simple fixed response handler", function () {
57

@@ -74,6 +76,37 @@ describe("Simple fixed response handler", function () {
7476
expect(response.headers.get('Transfer-Encoding')).to.equal(null);
7577
});
7678

79+
nodeOnly(() => { // Browsers can't read trailers
80+
it("should allow mocking everything with trailers too", async () => {
81+
await server.forGet("/mocked-endpoint").thenReply(200, "status message", "body", {
82+
"header": "hello",
83+
"Transfer-Encoding": "chunked" // Required to send trailers
84+
}, {
85+
"trailer": "goodbye"
86+
});
87+
88+
const response = await new Promise<http.IncomingMessage>((resolve, reject) => {
89+
const req = http.request(server.urlFor("/mocked-endpoint")).end();
90+
req.on('response', (res) => {
91+
// Wait until everything is 100% done, so we definitely have trailers
92+
res.resume();
93+
res.on('end', () => resolve(res));
94+
});
95+
req.on('error', reject);
96+
});
97+
98+
expect(response.statusCode).to.equal(200);
99+
expect(response.statusMessage).to.equal('status message');
100+
expect(response.headers).to.deep.equal({
101+
'header': 'hello',
102+
'transfer-encoding': 'chunked'
103+
});
104+
expect(response.trailers).to.deep.equal({
105+
'trailer': 'goodbye'
106+
});
107+
});
108+
});
109+
77110
it("should not allow mocking HTTP/2 pseudoheaders", async function () {
78111
await expect(() =>
79112
server.forGet("/mocked-endpoint")

0 commit comments

Comments
 (0)