This repository was archived by the owner on Feb 20, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathmock-response.ts
124 lines (105 loc) · 3.73 KB
/
mock-response.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { IDebugger } from 'debug';
import * as _ from 'lodash';
import * as readable from 'readable-stream';
import Context from './context';
import { YesNoError } from './errors';
import { PartialResponseForRequest } from './filtering/collection';
import * as comparator from './filtering/comparator';
import {
ISerializedHttp,
ISerializedRequest,
ISerializedRequestResponse,
ISerializedResponse,
} from './http-serializer';
import { IInterceptEvent } from './interceptor';
import { RecordMode as Mode } from './recording';
const debug: IDebugger = require('debug')('yesno:mock-response');
/**
* Encapsulates logic for sending a response for an intercepted HTTP request
*/
export default class MockResponse {
private event: IInterceptEvent;
private ctx: Context;
constructor(event: IInterceptEvent, ctx: Context) {
this.ctx = ctx;
this.event = event;
}
/**
* Send a respond for our wrapped intercept event if able.
*
* Give precedence to matching responses in shared context (from `yesno.matching().respond()`).
* Else, if we're in mock mode, lookup the mock response.
*
* @returns The received request & sent response. Returns `undefined` if unable to respond
*/
public async send(
response?: ISerializedResponse,
): Promise<ISerializedRequestResponse | undefined> {
const {
interceptedRequest,
interceptedResponse,
requestSerializer,
requestNumber,
} = this.event;
debug(`[#${requestNumber}] Mock response`);
await (readable as any).pipeline(interceptedRequest, requestSerializer);
const request = requestSerializer.serialize();
if (!response) {
response = this.ctx.getResponseDefinedMatching(request);
}
if (!response && this.ctx.mode === Mode.Mock) {
const mock = this.getMockForIntecept(this.event);
// Assertion must happen before promise -
// mitm does not support promise rejections on "request" event
this.assertMockMatches({ mock, serializedRequest: request, requestNumber });
response = mock.response;
}
if (response) {
this.writeMockResponse(response, interceptedResponse);
return { request, response };
}
}
private getMockForIntecept({ requestNumber }: IInterceptEvent): ISerializedHttp {
const mock = this.ctx.loadedMocks[requestNumber];
if (!mock) {
throw new YesNoError('No mock found for request');
}
return mock;
}
private assertMockMatches({
mock,
serializedRequest,
requestNumber,
}: { mock: ISerializedHttp; serializedRequest: ISerializedRequest } & Pick<
IInterceptEvent,
'requestNumber'
>): void {
try {
// determine how we'll compare the request and the mock
const compareBy: comparator.ComparatorFn = this.ctx.comparatorFn;
// the comparison function must throw an error to signal a mismatch
compareBy(serializedRequest, mock.request, { requestIndex: requestNumber });
} catch (err) {
// ensure any user-thrown error is wrapped in our YesNoError
throw new YesNoError(err.message);
}
}
private writeMockResponse(
response: ISerializedResponse,
interceptedResponse: IInterceptEvent['interceptedResponse'],
): void {
const bodyString = _.isPlainObject(response.body)
? JSON.stringify(response.body)
: (response.body as string);
const responseHeaders = { ...response.headers };
if (
responseHeaders['content-length'] &&
parseInt(responseHeaders['content-length'] as string, 10) !== Buffer.byteLength(bodyString)
) {
responseHeaders['content-length'] = Buffer.byteLength(bodyString);
}
interceptedResponse.writeHead(response.statusCode, responseHeaders);
interceptedResponse.write(bodyString);
interceptedResponse.end();
}
}