Skip to content

Commit 98b9c7d

Browse files
committed
Fix bug with remote client use of updateHeader transforms
1 parent 9622ea9 commit 98b9c7d

File tree

2 files changed

+74
-22
lines changed

2 files changed

+74
-22
lines changed

src/rules/requests/request-handlers.ts

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -656,12 +656,18 @@ interface SerializedPassThroughData {
656656
lookupOptions?: PassThroughLookupOptions;
657657

658658
transformRequest?: Replace<
659-
Replace<RequestTransform, 'replaceBody', string | undefined>, // Serialized as base64 buffer
660-
'updateJsonBody', string | undefined // Serialized as a string to preserve undefined values
659+
RequestTransform,
660+
| 'replaceBody' // Serialized as base64 buffer
661+
| 'updateHeaders' // // Serialized as a string to preserve undefined values
662+
| 'updateJsonBody', // Serialized as a string to preserve undefined values
663+
string | undefined
661664
>,
662665
transformResponse?: Replace<
663-
Replace<ResponseTransform, 'replaceBody', string | undefined>, // Serialized as base64 buffer
664-
'updateJsonBody', string | undefined // Serialized as a string to preserve undefined values
666+
ResponseTransform,
667+
| 'replaceBody' // Serialized as base64 buffer
668+
| 'updateHeaders' // // Serialized as a string to preserve undefined values
669+
| 'updateJsonBody', // Serialized as a string to preserve undefined values
670+
string | undefined
665671
>,
666672

667673
hasBeforeRequestCallback?: boolean;
@@ -852,6 +858,17 @@ function validateCustomHeaders(
852858
const OMIT_SYMBOL = Symbol('omit-value');
853859
const SERIALIZED_OMIT = "__mockttp__transform__omit__";
854860

861+
// We play some games to preserve undefined values during serialization, because we differentiate them
862+
// in some transforms from null/not-present keys.
863+
const mapOmitToUndefined = <T extends { [key: string]: any }>(
864+
input: T
865+
): { [K in keyof T]: T[K] | undefined } =>
866+
_.mapValues(input, (v) =>
867+
v === SERIALIZED_OMIT || v === OMIT_SYMBOL
868+
? undefined // Replace our omit placeholders with actual undefineds
869+
: v
870+
);
871+
855872
export class PassThroughHandler extends Serializable implements RequestHandler {
856873
readonly type = 'passthrough';
857874

@@ -1509,10 +1526,17 @@ export class PassThroughHandler extends Serializable implements RequestHandler {
15091526
),
15101527
transformRequest: {
15111528
...this.transformRequest,
1529+
// Body is always serialized as a base64 buffer:
15121530
replaceBody: !!this.transformRequest?.replaceBody
1513-
// Always serialize as a base64 buffer:
15141531
? serializeBuffer(asBuffer(this.transformRequest.replaceBody))
15151532
: undefined,
1533+
// Update objects need to capture undefined & null as distict values:
1534+
updateHeaders: !!this.transformRequest?.updateHeaders
1535+
? JSON.stringify(
1536+
this.transformRequest.updateHeaders,
1537+
(k, v) => v === undefined ? SERIALIZED_OMIT : v
1538+
)
1539+
: undefined,
15161540
updateJsonBody: !!this.transformRequest?.updateJsonBody
15171541
? JSON.stringify(
15181542
this.transformRequest.updateJsonBody,
@@ -1522,10 +1546,17 @@ export class PassThroughHandler extends Serializable implements RequestHandler {
15221546
},
15231547
transformResponse: {
15241548
...this.transformResponse,
1549+
// Body is always serialized as a base64 buffer:
15251550
replaceBody: !!this.transformResponse?.replaceBody
1526-
// Always serialize as a base64 buffer:
15271551
? serializeBuffer(asBuffer(this.transformResponse.replaceBody))
15281552
: undefined,
1553+
// Update objects need to capture undefined & null as distict values:
1554+
updateHeaders: !!this.transformResponse?.updateHeaders
1555+
? JSON.stringify(
1556+
this.transformResponse.updateHeaders,
1557+
(k, v) => v === undefined ? SERIALIZED_OMIT : v
1558+
)
1559+
: undefined,
15291560
updateJsonBody: !!this.transformResponse?.updateJsonBody
15301561
? JSON.stringify(
15311562
this.transformResponse.updateJsonBody,
@@ -1581,25 +1612,25 @@ export class PassThroughHandler extends Serializable implements RequestHandler {
15811612
...(data.transformRequest?.replaceBody !== undefined ? {
15821613
replaceBody: deserializeBuffer(data.transformRequest.replaceBody)
15831614
} : {}),
1615+
...(data.transformRequest?.updateHeaders !== undefined ? {
1616+
updateHeaders: mapOmitToUndefined(JSON.parse(data.transformRequest.updateHeaders))
1617+
} : {}),
15841618
...(data.transformRequest?.updateJsonBody !== undefined ? {
1585-
updateJsonBody: JSON.parse(
1586-
data.transformRequest.updateJsonBody,
1587-
(k, v) => v === SERIALIZED_OMIT ? OMIT_SYMBOL : v
1588-
)
1619+
updateJsonBody: mapOmitToUndefined(JSON.parse(data.transformRequest.updateJsonBody))
15891620
} : {}),
1590-
},
1621+
} as RequestTransform,
15911622
transformResponse: {
15921623
...data.transformResponse,
15931624
...(data.transformResponse?.replaceBody !== undefined ? {
15941625
replaceBody: deserializeBuffer(data.transformResponse.replaceBody)
15951626
} : {}),
1627+
...(data.transformResponse?.updateHeaders !== undefined ? {
1628+
updateHeaders: mapOmitToUndefined(JSON.parse(data.transformResponse.updateHeaders))
1629+
} : {}),
15961630
...(data.transformResponse?.updateJsonBody !== undefined ? {
1597-
updateJsonBody: JSON.parse(
1598-
data.transformResponse.updateJsonBody,
1599-
(k, v) => v === SERIALIZED_OMIT ? OMIT_SYMBOL : v
1600-
)
1631+
updateJsonBody: mapOmitToUndefined(JSON.parse(data.transformResponse.updateJsonBody))
16011632
} : {})
1602-
},
1633+
} as ResponseTransform,
16031634
// Backward compat for old clients:
16041635
...data.forwardToLocation ? {
16051636
forwarding: { targetHost: data.forwardToLocation }

test/integration/remote-client.spec.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,23 @@ nodeOnly(() => {
192192

193193
await client.post(targetServer.urlFor('/req')).thenPassThrough({
194194
transformRequest: {
195+
updateHeaders: {
196+
'custom-header': undefined, // Remove
197+
'injected-header': 'injected-value' // Add
198+
},
195199
updateJsonBody: {
196-
initialValue: undefined,
197-
replacementValue: true
200+
initialValue: undefined, // Remove
201+
replacementValue: true // Add
198202
}
199203
}
200204
});
201205

202206
expect(await request.post(targetServer.urlFor('/req'), {
203207
proxy: client.urlFor("/"),
204208
json: true,
209+
headers: {
210+
'custom-header': 'a custom value'
211+
},
205212
body: {
206213
initialValue: true
207214
}
@@ -213,7 +220,8 @@ nodeOnly(() => {
213220
accept: 'application/json',
214221
'content-type': 'application/json',
215222
'content-length': '25',
216-
connection: 'close'
223+
connection: 'close',
224+
'injected-header': 'injected-value' // Only injected header remains
217225
},
218226
body: JSON.stringify({
219227
// Initial value is removed, only replacement remains:
@@ -223,17 +231,30 @@ nodeOnly(() => {
223231

224232
await client.post(targetServer.urlFor('/res')).thenPassThrough({
225233
transformResponse: {
234+
updateHeaders: {
235+
'custom-header': undefined, // Remove
236+
'injected-header': 'injected-value' // Add
237+
},
226238
updateJsonBody: {
227239
method: 'REPLACEMENT METHOD',
228240
headers: undefined
229241
}
230242
}
231243
});
232244

233-
expect(await request.post(targetServer.urlFor('/res'), {
245+
const response = await request.post(targetServer.urlFor('/res'), {
234246
proxy: client.urlFor("/"),
235-
json: true
236-
})).to.deep.equal({
247+
json: true,
248+
resolveWithFullResponse: true
249+
});
250+
251+
expect(response.headers).to.deep.equal({
252+
'access-control-allow-origin': '*',
253+
'content-type': 'application/json',
254+
'injected-header': 'injected-value'
255+
});
256+
257+
expect(response.body).to.deep.equal({
237258
url: `http://localhost:${targetServer.port}/res`,
238259
// Method field replaced, headers field removed:
239260
method: 'REPLACEMENT METHOD',

0 commit comments

Comments
 (0)