http-snapshotter 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/index.js +84 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ In this mode, http-snapshotter will prevent any real HTTP calls from happening b
|
|
|
41
41
|
|
|
42
42
|
There is also a `SNAPSHOT=ignore` option to neither read nor write from snapshot files and do real network requests instead. This could be useful while writing a new test.
|
|
43
43
|
|
|
44
|
-
Tip: When you do `SNAPSHOT=update` to create snapshots, run it against a single test, so you know what exact snapshots that one test created.
|
|
44
|
+
Tip: When you do `SNAPSHOT=update` to create snapshots, run it against a single test, so you know what exact snapshots that one test created/updated.
|
|
45
45
|
|
|
46
46
|
Finally after getting all your tests to use snapshots, run your test runner against all your tests and then take a look at `<snapshots directory>/unused-snapshots.log` file to see which snapshot files haven't been used by your final test suite. You can delete unused snapshot files.
|
|
47
47
|
|
|
@@ -49,8 +49,8 @@ The tests of this library uses this library itself, check the `tests/` directory
|
|
|
49
49
|
|
|
50
50
|
## About snapshot files and its names
|
|
51
51
|
|
|
52
|
-
A snapshot file name
|
|
53
|
-
|
|
52
|
+
A snapshot file name uniquely identifies a request. By default it is a combination of HTTP method + URL + body that makes a request unique.
|
|
53
|
+
A SHA256 hash of concatenated HTTP method + URL + body makes the file name suffix.
|
|
54
54
|
|
|
55
55
|
However you may want to specially handle some requests. e.g. DynamoDB calls also need the `x-amz-target` header to uniquely identify the request,
|
|
56
56
|
because every call is a POST call with DynamoDB. You can add logic to create better snapshot files for this case:
|
|
@@ -134,7 +134,7 @@ test('Test behavior on a free account', async (t) => {
|
|
|
134
134
|
}
|
|
135
135
|
)
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
return response;
|
|
139
139
|
};
|
|
140
140
|
attachResponseTransformer(interceptResponse);
|
package/index.js
CHANGED
|
@@ -140,7 +140,15 @@ async function getSnapshotFileName(request) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// NOTE: This isn't going to work on a test runner that uses multiple processes / workers
|
|
143
|
-
|
|
143
|
+
/**
|
|
144
|
+
* @typedef {Promise<{
|
|
145
|
+
* snapshot: Snapshot,
|
|
146
|
+
* absoluteFilePath: string,
|
|
147
|
+
* fileName: string
|
|
148
|
+
* }>} ReadSnapshotReturnType
|
|
149
|
+
*/
|
|
150
|
+
/** @type {Map<string, ReadSnapshotReturnType>} */
|
|
151
|
+
const alreadyWrittenFiles = new Map();
|
|
144
152
|
const readFiles = new Set();
|
|
145
153
|
|
|
146
154
|
/**
|
|
@@ -152,40 +160,48 @@ async function saveSnapshot(request, response) {
|
|
|
152
160
|
// console.log(fileName);
|
|
153
161
|
|
|
154
162
|
// Prevent multiple tests from having same snapshot
|
|
155
|
-
if (alreadyWrittenFiles.has(absoluteFilePath))
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
let body;
|
|
159
|
-
/** @type {'text' | 'json'} */
|
|
160
|
-
let responseType;
|
|
161
|
-
const contentType = response.headers.get('content-type') || '';
|
|
162
|
-
if (contentType.includes('application/json') || contentType.includes('application/x-amz-json-1.0')) {
|
|
163
|
-
responseType = 'json';
|
|
164
|
-
body = await response.clone().json();
|
|
165
|
-
} else {
|
|
166
|
-
responseType = 'text';
|
|
167
|
-
body = await response.clone().text();
|
|
163
|
+
if (alreadyWrittenFiles.has(absoluteFilePath)) {
|
|
164
|
+
return /** @type {ReadSnapshotReturnType} */ (alreadyWrittenFiles.get(absoluteFilePath));
|
|
168
165
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
166
|
+
|
|
167
|
+
/** @returns {ReadSnapshotReturnType} */
|
|
168
|
+
const saveFreshSnapshot = async () => {
|
|
169
|
+
let body;
|
|
170
|
+
/** @type {'text' | 'json'} */
|
|
171
|
+
let responseType;
|
|
172
|
+
const contentType = response.headers.get('content-type') || '';
|
|
173
|
+
if (contentType.includes('application/json') || contentType.includes('application/x-amz-json-1.0')) {
|
|
174
|
+
responseType = 'json';
|
|
175
|
+
body = await response.clone().json();
|
|
176
|
+
} else {
|
|
177
|
+
responseType = 'text';
|
|
178
|
+
body = await response.clone().text();
|
|
179
|
+
}
|
|
180
|
+
/** @type {Snapshot} */
|
|
181
|
+
const snapshot = {
|
|
182
|
+
request: {
|
|
183
|
+
method: request.method,
|
|
184
|
+
url: request.url,
|
|
185
|
+
headers: [...request.headers.entries()],
|
|
186
|
+
body: await request.clone().text(),
|
|
187
|
+
},
|
|
188
|
+
responseType,
|
|
189
|
+
response: {
|
|
190
|
+
status: response.status,
|
|
191
|
+
statusText: response.statusText,
|
|
192
|
+
headers: [...response.headers.entries()],
|
|
193
|
+
body,
|
|
194
|
+
},
|
|
195
|
+
fileSuffixKey,
|
|
196
|
+
};
|
|
197
|
+
const json = JSON.stringify(snapshot, null, 2);
|
|
198
|
+
await fs.writeFile(absoluteFilePath, json, 'utf-8');
|
|
199
|
+
return { snapshot, absoluteFilePath, fileName };
|
|
185
200
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
|
|
202
|
+
const savePromise = saveFreshSnapshot();
|
|
203
|
+
alreadyWrittenFiles.set(absoluteFilePath, savePromise);
|
|
204
|
+
return savePromise;
|
|
189
205
|
}
|
|
190
206
|
|
|
191
207
|
/** @type {Record<string, Snapshot>} */
|
|
@@ -194,7 +210,7 @@ const snapshotCache = {};
|
|
|
194
210
|
/**
|
|
195
211
|
* @param {Request} request
|
|
196
212
|
*/
|
|
197
|
-
async function
|
|
213
|
+
async function readSnapshot(request) {
|
|
198
214
|
const { absoluteFilePath, fileName, fileSuffixKey } = await getSnapshotFileName(request);
|
|
199
215
|
// console.log(fileName);
|
|
200
216
|
|
|
@@ -221,14 +237,22 @@ async function enforceSnapshotResponse(request) {
|
|
|
221
237
|
} else {
|
|
222
238
|
// @ts-ignore
|
|
223
239
|
console.error('Error reading network snapshot file:', err.message);
|
|
240
|
+
throw err;
|
|
224
241
|
}
|
|
225
|
-
return null;
|
|
226
242
|
}
|
|
227
243
|
snapshotCache[absoluteFilePath] = JSON.parse(json);
|
|
228
244
|
readFiles.add(fileName);
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
const snapshot = snapshotCache[absoluteFilePath];
|
|
248
|
+
return { snapshot, absoluteFilePath, fileName };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {Request} request
|
|
253
|
+
* @param {Snapshot} snapshot
|
|
254
|
+
*/
|
|
255
|
+
async function sendResponse(request, snapshot) {
|
|
232
256
|
const {
|
|
233
257
|
responseType,
|
|
234
258
|
response: {
|
|
@@ -258,6 +282,23 @@ async function enforceSnapshotResponse(request) {
|
|
|
258
282
|
return newResponse;
|
|
259
283
|
}
|
|
260
284
|
|
|
285
|
+
/**
|
|
286
|
+
* @param {Request} request
|
|
287
|
+
*/
|
|
288
|
+
async function readSnapshotAndSendResponse(request) {
|
|
289
|
+
const { snapshot } = await readSnapshot(request);
|
|
290
|
+
return sendResponse(request, snapshot);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @param {Request} request
|
|
295
|
+
* @param {Response} response
|
|
296
|
+
*/
|
|
297
|
+
async function saveSnapshotAndSendResponse(request, response) {
|
|
298
|
+
const { snapshot } = await saveSnapshot(request, response);
|
|
299
|
+
return sendResponse(request, snapshot);
|
|
300
|
+
}
|
|
301
|
+
|
|
261
302
|
/** @typedef {import('@mswjs/interceptors/ClientRequest').ClientRequestInterceptor} ClientRequestInterceptorType */
|
|
262
303
|
/** @typedef {import('@mswjs/interceptors/fetch').FetchInterceptor} FetchInterceptorType */
|
|
263
304
|
/**
|
|
@@ -376,7 +417,7 @@ function start({
|
|
|
376
417
|
// @ts-ignore
|
|
377
418
|
interceptor.on('request', async ({ request }) => {
|
|
378
419
|
if (SNAPSHOT === 'read') {
|
|
379
|
-
await
|
|
420
|
+
await readSnapshotAndSendResponse(request);
|
|
380
421
|
}
|
|
381
422
|
});
|
|
382
423
|
interceptor.on(
|
|
@@ -384,13 +425,6 @@ function start({
|
|
|
384
425
|
'response',
|
|
385
426
|
/** @type {(params: { request: Request, response: Response }) => Promise<void>} */
|
|
386
427
|
async ({ request, response }) => {
|
|
387
|
-
if (SNAPSHOT === 'update') {
|
|
388
|
-
if (!dirCreatePromise) {
|
|
389
|
-
dirCreatePromise = fs.mkdir( /** @type {string} */(snapshotDirectory), { recursive: true });
|
|
390
|
-
}
|
|
391
|
-
await dirCreatePromise;
|
|
392
|
-
saveSnapshot(request, response);
|
|
393
|
-
}
|
|
394
428
|
if (LOG_REQ) {
|
|
395
429
|
const { fileName, fileSuffixKey } = await getSnapshotFileName(request);
|
|
396
430
|
console.debug('Request', {
|
|
@@ -404,6 +438,13 @@ function start({
|
|
|
404
438
|
wouldBeFileSuffixKey: fileSuffixKey,
|
|
405
439
|
});
|
|
406
440
|
}
|
|
441
|
+
if (SNAPSHOT === 'update') {
|
|
442
|
+
if (!dirCreatePromise) {
|
|
443
|
+
dirCreatePromise = fs.mkdir( /** @type {string} */(snapshotDirectory), { recursive: true });
|
|
444
|
+
}
|
|
445
|
+
await dirCreatePromise;
|
|
446
|
+
await saveSnapshotAndSendResponse(request, response);
|
|
447
|
+
}
|
|
407
448
|
},
|
|
408
449
|
);
|
|
409
450
|
interceptor.apply();
|