node-tdd 3.3.1 → 3.4.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.
@@ -1,54 +1,43 @@
1
- "use strict";
2
-
3
- const assert = require('assert');
4
-
5
- const http = require('http');
6
-
7
- const https = require('http');
8
-
9
- const path = require('path');
10
-
11
- const fs = require('smart-fs');
12
-
13
- const Joi = require('joi-strict');
14
-
15
- const nock = require('nock');
16
-
17
- const cloneDeep = require('lodash.clonedeep');
18
-
19
- const compareUrls = require('compare-urls');
20
-
21
- const nockCommon = require('nock/lib/common');
22
-
23
- const nockListener = require('./request-recorder/nock-listener');
24
-
25
- const nockMock = require('./request-recorder/nock-mock');
26
-
27
- const healSqsSendMessageBatch = require('./request-recorder/heal-sqs-send-message-batch');
28
-
29
- const applyModifiers = require('./request-recorder/apply-modifiers');
30
-
31
- const {
1
+ import assert from 'assert';
2
+ import http from 'http';
3
+ import path from 'path';
4
+ import fs from 'smart-fs';
5
+ import Joi from 'joi-strict';
6
+ import nock from 'nock';
7
+ import cloneDeep from 'lodash.clonedeep';
8
+ import compareUrls from 'compare-urls';
9
+ import nockCommon from 'nock/lib/common.js';
10
+ import nockListener from './request-recorder/nock-listener.js';
11
+ import nockMock from './request-recorder/nock-mock.js';
12
+ import healSqsSendMessageBatch from './request-recorder/heal-sqs-send-message-batch.js';
13
+ import applyModifiers from './request-recorder/apply-modifiers.js';
14
+ import requestInjector from './request-recorder/request-injector.js';
15
+
16
+ import {
32
17
  buildKey,
33
18
  tryParseJson,
34
19
  nullAsString,
35
20
  convertHeaders,
36
21
  rewriteHeaders
37
- } = require('./request-recorder/util');
38
-
39
- const requestInjector = require('./request-recorder/request-injector');
22
+ } from './request-recorder/util.js';
40
23
 
41
24
  const nockBack = nock.back;
42
25
  const nockRecorder = nock.recorder;
43
26
 
44
- module.exports = opts => {
27
+ export default (opts) => {
45
28
  Joi.assert(opts, Joi.object().keys({
46
29
  cassetteFolder: Joi.string(),
47
30
  stripHeaders: Joi.boolean(),
48
- reqHeaderOverwrite: Joi.object().pattern(Joi.string().case('lower'), Joi.alternatives(Joi.string(), Joi.function())),
31
+ reqHeaderOverwrite: Joi.object().pattern(
32
+ Joi.string().case('lower'),
33
+ Joi.alternatives(Joi.string(), Joi.function())
34
+ ),
49
35
  strict: Joi.boolean(),
50
36
  heal: Joi.alternatives(Joi.boolean(), Joi.string()),
51
- modifiers: Joi.object().pattern(Joi.string(), Joi.alternatives(Joi.function(), Joi.link('#modifiers')))
37
+ modifiers: Joi.object().pattern(
38
+ Joi.string(),
39
+ Joi.alternatives(Joi.function(), Joi.link('#modifiers'))
40
+ )
52
41
  }), 'Invalid Options Provided');
53
42
  let nockDone = null;
54
43
  let cassetteFilePath = null;
@@ -58,47 +47,44 @@ module.exports = opts => {
58
47
  const expectedCassette = [];
59
48
  const pendingMocks = [];
60
49
 
61
- const anyFlagPresent = flags => {
50
+ const anyFlagPresent = (flags) => {
62
51
  assert(Array.isArray(flags) && flags.length !== 0);
63
-
64
52
  if (typeof opts.heal !== 'string') {
65
53
  return false;
66
54
  }
67
-
68
55
  const needleFlags = opts.heal.split(',');
69
- return flags.some(flag => needleFlags.includes(flag));
56
+ return flags.some((flag) => needleFlags.includes(flag));
70
57
  };
71
58
 
72
59
  const overwriteHeaders = (key, value, headers) => {
73
60
  if (key in opts.reqHeaderOverwrite) {
74
- return typeof opts.reqHeaderOverwrite[key] === 'function' ? opts.reqHeaderOverwrite[key]({
75
- key,
76
- value,
77
- headers
78
- }) : opts.reqHeaderOverwrite[key];
61
+ return typeof opts.reqHeaderOverwrite[key] === 'function'
62
+ ? opts.reqHeaderOverwrite[key]({ key, value, headers })
63
+ : opts.reqHeaderOverwrite[key];
79
64
  }
80
-
81
65
  return value;
82
66
  };
83
67
 
84
- return {
85
- inject: async cassetteFile => {
68
+ return ({
69
+ inject: async (cassetteFile) => {
86
70
  assert(nockDone === null);
87
71
  knownCassetteNames.push(cassetteFile);
88
72
  records.length = 0;
89
73
  outOfOrderErrors.length = 0;
90
74
  expectedCassette.length = 0;
91
75
  pendingMocks.length = 0;
76
+
92
77
  cassetteFilePath = path.join(opts.cassetteFolder, cassetteFile);
93
78
  const hasCassette = fs.existsSync(cassetteFilePath);
94
-
95
79
  if (hasCassette) {
96
80
  const cassetteContent = fs.smartRead(cassetteFilePath);
97
- pendingMocks.push(...nock.define(cassetteContent).map((e, idx) => ({
98
- idx,
99
- key: buildKey(e.interceptors[0]),
100
- record: cassetteContent[idx]
101
- })));
81
+ pendingMocks.push(...nock
82
+ .define(cassetteContent)
83
+ .map((e, idx) => ({
84
+ idx,
85
+ key: buildKey(e.interceptors[0]),
86
+ record: cassetteContent[idx]
87
+ })));
102
88
  }
103
89
 
104
90
  nockBack.setMode(hasCassette ? 'lockdown' : 'record');
@@ -106,12 +92,7 @@ module.exports = opts => {
106
92
  nockMock.patch();
107
93
  nockListener.subscribe('no match', () => {
108
94
  assert(hasCassette === true);
109
- const {
110
- protocol,
111
- options,
112
- body
113
- } = requestInjector.getLast();
114
-
95
+ const { protocol, options, body } = requestInjector.getLast();
115
96
  if (anyFlagPresent(['record'])) {
116
97
  expectedCassette.push(async () => {
117
98
  nockRecorder.rec({
@@ -119,25 +100,20 @@ module.exports = opts => {
119
100
  dont_print: true,
120
101
  enable_reqheaders_recording: true
121
102
  });
122
- await new Promise(resolve => {
103
+ await new Promise((resolve) => {
123
104
  options.protocol = `${protocol}:`;
124
- const r = {
125
- http,
126
- https
127
- }[protocol].request(options, response => {
105
+ const r = { http, https: http }[protocol].request(options, (response) => {
128
106
  response.on('data', () => {});
129
107
  response.on('end', resolve);
130
108
  });
131
-
132
109
  if (body !== undefined) {
133
110
  r.write(body);
134
111
  }
135
-
136
112
  r.end();
137
113
  });
138
114
  const recorded = nockRecorder.play();
139
115
  nockRecorder.clear();
140
- return recorded.map(record => Object.assign(record, {
116
+ return recorded.map((record) => Object.assign(record, {
141
117
  headers: opts.stripHeaders === true ? undefined : convertHeaders(record.rawHeaders),
142
118
  rawHeaders: undefined,
143
119
  reqheaders: rewriteHeaders(record.reqheaders, overwriteHeaders)
@@ -156,86 +132,101 @@ module.exports = opts => {
156
132
  });
157
133
  }
158
134
  });
159
- nockDone = await new Promise(resolve => nockBack(cassetteFile, {
160
- before: (scope, scopeIdx) => {
161
- records.push(cloneDeep(scope));
162
- applyModifiers(scope, opts.modifiers); // eslint-disable-next-line no-param-reassign
163
-
164
- scope.reqheaders = rewriteHeaders(scope.reqheaders, (k, valueRecording) => valueRequest => {
165
- const match = nockCommon.matchStringOrRegexp(valueRequest, /^\^.*\$$/.test(valueRecording) ? new RegExp(valueRecording) : valueRecording);
166
-
167
- if (!match && anyFlagPresent(['magic', 'headers'])) {
168
- const idx = pendingMocks.findIndex(m => m.idx === scopeIdx); // overwrite existing headers
169
-
170
- pendingMocks[idx].record.reqheaders[k] = valueRequest;
171
- return true;
172
- }
173
-
174
- return match;
175
- }); // eslint-disable-next-line no-param-reassign
176
-
177
- scope.filteringRequestBody = body => {
178
- if (anyFlagPresent(['magic', 'body'])) {
179
- const idx = pendingMocks.findIndex(m => m.idx === scopeIdx);
180
- const requestBody = tryParseJson(body);
181
- pendingMocks[idx].record.body = nullAsString(requestBody);
182
- return scope.body;
183
- }
184
-
185
- return body;
186
- }; // eslint-disable-next-line no-param-reassign
187
-
188
-
189
- scope.filteringPath = requestPath => {
190
- if (anyFlagPresent(['magic', 'path'])) {
191
- const idx = pendingMocks.findIndex(m => m.idx === scopeIdx);
192
-
193
- if (!compareUrls(pendingMocks[idx].record.path, requestPath)) {
194
- pendingMocks[idx].record.path = requestPath;
135
+ nockDone = await new Promise((resolve) => {
136
+ nockBack(cassetteFile, {
137
+ before: (scope, scopeIdx) => {
138
+ records.push(cloneDeep(scope));
139
+ applyModifiers(scope, opts.modifiers);
140
+ // eslint-disable-next-line no-param-reassign
141
+ scope.reqheaders = rewriteHeaders(
142
+ scope.reqheaders,
143
+ (k, valueRecording) => (valueRequest) => {
144
+ const match = nockCommon.matchStringOrRegexp(
145
+ valueRequest,
146
+ /^\^.*\$$/.test(valueRecording) ? new RegExp(valueRecording) : valueRecording
147
+ );
148
+
149
+ if (!match && anyFlagPresent(['magic', 'headers'])) {
150
+ const idx = pendingMocks.findIndex((m) => m.idx === scopeIdx);
151
+ // overwrite existing headers
152
+ pendingMocks[idx].record.reqheaders[k] = valueRequest;
153
+ return true;
154
+ }
155
+
156
+ return match;
157
+ }
158
+ );
159
+ // eslint-disable-next-line no-param-reassign
160
+ scope.filteringRequestBody = (body) => {
161
+ if (anyFlagPresent(['magic', 'body'])) {
162
+ const idx = pendingMocks.findIndex((m) => m.idx === scopeIdx);
163
+ const requestBody = tryParseJson(body);
164
+ pendingMocks[idx].record.body = nullAsString(requestBody);
165
+ return scope.body;
166
+ }
167
+ return body;
168
+ };
169
+ // eslint-disable-next-line no-param-reassign
170
+ scope.filteringPath = (requestPath) => {
171
+ if (anyFlagPresent(['magic', 'path'])) {
172
+ const idx = pendingMocks.findIndex((m) => m.idx === scopeIdx);
173
+ if (!compareUrls(pendingMocks[idx].record.path, requestPath)) {
174
+ pendingMocks[idx].record.path = requestPath;
175
+ }
176
+ return scope.path;
177
+ }
178
+ return requestPath;
179
+ };
180
+ return scope;
181
+ },
182
+ after: (scope, scopeIdx) => {
183
+ scope.on('request', (req, interceptor, requestBodyString) => {
184
+ const idx = pendingMocks.findIndex((e) => e.idx === scopeIdx);
185
+
186
+ // https://github.com/nock/nock/blob/79ee0429050af929c525ae21a326d22796344bfc/lib/interceptor.js#L616
187
+ if (Number.isInteger(pendingMocks[idx]?.record?.delayConnection)) {
188
+ interceptor.delayConnection(pendingMocks[idx].record.delayConnection);
189
+ }
190
+ if (Number.isInteger(pendingMocks[idx]?.record?.delayBody)) {
191
+ interceptor.delayBody(pendingMocks[idx].record.delayBody);
195
192
  }
196
193
 
197
- return scope.path;
198
- }
199
-
200
- return requestPath;
201
- };
202
-
203
- return scope;
204
- },
205
- after: (scope, scopeIdx) => {
206
- scope.on('request', (req, interceptor, requestBodyString) => {
207
- const idx = pendingMocks.findIndex(e => e.idx === scopeIdx);
208
-
209
- if (anyFlagPresent(['magic', 'headers'])) {
210
- // add new headers
211
- const reqheaders = { ...rewriteHeaders(req.headers),
212
- ...rewriteHeaders(pendingMocks[idx].record.reqheaders)
213
- };
214
- pendingMocks[idx].record.reqheaders = rewriteHeaders(reqheaders, overwriteHeaders);
215
- }
216
-
217
- if (anyFlagPresent(['magic', 'response'])) {
218
- const responseBody = tryParseJson([healSqsSendMessageBatch].reduce((respBody, fn) => fn(requestBodyString, respBody, scope), interceptor.body)); // eslint-disable-next-line no-param-reassign
219
-
220
- interceptor.body = responseBody;
221
- pendingMocks[idx].record.response = responseBody;
222
- }
223
-
224
- expectedCassette.push(pendingMocks[idx].record);
194
+ if (anyFlagPresent(['magic', 'headers'])) {
195
+ // add new headers
196
+ const reqheaders = {
197
+ ...rewriteHeaders(req.headers),
198
+ ...rewriteHeaders(pendingMocks[idx].record.reqheaders)
199
+ };
200
+ pendingMocks[idx].record.reqheaders = rewriteHeaders(reqheaders, overwriteHeaders);
201
+ }
225
202
 
226
- if (idx !== 0) {
227
- outOfOrderErrors.push(pendingMocks[idx].key);
228
- }
203
+ if (anyFlagPresent(['magic', 'response'])) {
204
+ const responseBody = tryParseJson([
205
+ healSqsSendMessageBatch
206
+ ].reduce(
207
+ (respBody, fn) => fn(requestBodyString, respBody, scope),
208
+ interceptor.body
209
+ ));
210
+ // eslint-disable-next-line no-param-reassign
211
+ interceptor.body = responseBody;
212
+ pendingMocks[idx].record.response = responseBody;
213
+ }
229
214
 
230
- pendingMocks.splice(idx, 1);
231
- });
232
- },
233
- afterRecord: recordings => JSON.stringify(recordings.map(r => ({ ...r,
234
- body: tryParseJson(r.body),
235
- rawHeaders: opts.stripHeaders === true ? undefined : r.rawHeaders,
236
- reqheaders: rewriteHeaders(r.reqheaders, overwriteHeaders)
237
- })), null, 2)
238
- }, resolve));
215
+ expectedCassette.push(pendingMocks[idx].record);
216
+ if (idx !== 0) {
217
+ outOfOrderErrors.push(pendingMocks[idx].key);
218
+ }
219
+ pendingMocks.splice(idx, 1);
220
+ });
221
+ },
222
+ afterRecord: (recordings) => JSON.stringify(recordings.map((r) => ({
223
+ ...r,
224
+ body: tryParseJson(r.body),
225
+ rawHeaders: opts.stripHeaders === true ? undefined : r.rawHeaders,
226
+ reqheaders: rewriteHeaders(r.reqheaders, overwriteHeaders)
227
+ })), null, 2)
228
+ }, resolve);
229
+ });
239
230
  requestInjector.inject();
240
231
  },
241
232
  release: async () => {
@@ -245,7 +236,7 @@ module.exports = opts => {
245
236
  for (let idx = 0; idx < expectedCassette.length; idx += 1) {
246
237
  if (typeof expectedCassette[idx] === 'function') {
247
238
  // eslint-disable-next-line no-await-in-loop
248
- expectedCassette.splice(idx, 1, ...(await expectedCassette[idx]()));
239
+ expectedCassette.splice(idx, 1, ...await expectedCassette[idx]());
249
240
  idx -= 1;
250
241
  }
251
242
  }
@@ -256,26 +247,25 @@ module.exports = opts => {
256
247
  nockMock.unpatch();
257
248
 
258
249
  if (opts.heal !== false) {
259
- fs.smartWrite(cassetteFilePath, anyFlagPresent(['prune']) ? expectedCassette : [...expectedCassette, ...pendingMocks.map(({
260
- record
261
- }) => record)], {
262
- keepOrder: outOfOrderErrors.length === 0 && pendingMocks.length === 0
263
- });
250
+ fs.smartWrite(
251
+ cassetteFilePath,
252
+ anyFlagPresent(['prune'])
253
+ ? expectedCassette
254
+ : [...expectedCassette, ...pendingMocks.map(({ record }) => record)],
255
+ { keepOrder: outOfOrderErrors.length === 0 && pendingMocks.length === 0 }
256
+ );
264
257
  }
265
-
266
258
  if (opts.strict !== false) {
267
259
  if (outOfOrderErrors.length !== 0) {
268
260
  throw new Error(`Out of Order Recordings: ${outOfOrderErrors.join(', ')}`);
269
261
  }
270
-
271
262
  if (pendingMocks.length !== 0) {
272
- throw new Error(`Unmatched Recordings: ${pendingMocks.map(e => e.key).join(', ')}`);
263
+ throw new Error(`Unmatched Recordings: ${pendingMocks.map((e) => e.key).join(', ')}`);
273
264
  }
274
265
  }
275
266
  },
276
267
  shutdown: () => {
277
- const unexpectedFiles = fs.walkDir(opts.cassetteFolder).filter(f => !knownCassetteNames.includes(f));
278
-
268
+ const unexpectedFiles = fs.walkDir(opts.cassetteFolder).filter((f) => !knownCassetteNames.includes(f));
279
269
  if (unexpectedFiles.length !== 0) {
280
270
  throw new Error(`Unexpected file(s) in cassette folder: ${unexpectedFiles.join(', ')}`);
281
271
  }
@@ -283,9 +273,9 @@ module.exports = opts => {
283
273
  get: () => ({
284
274
  records: records.slice(),
285
275
  outOfOrderErrors: outOfOrderErrors.slice(),
286
- unmatchedRecordings: pendingMocks.map(e => e.key).slice(),
276
+ unmatchedRecordings: pendingMocks.map((e) => e.key).slice(),
287
277
  expectedCassette: expectedCassette.slice(),
288
278
  cassetteFilePath
289
279
  })
290
- };
291
- };
280
+ });
281
+ };
@@ -1,19 +1,22 @@
1
- "use strict";
1
+ import assert from 'assert';
2
+ import Joi from 'joi-strict';
3
+ import tk from 'timekeeper';
2
4
 
3
- const assert = require('assert');
4
-
5
- const Joi = require('joi-strict');
6
-
7
- const tk = require('timekeeper');
8
-
9
- module.exports = opts => {
5
+ export default (opts) => {
10
6
  Joi.assert(opts, Joi.object().keys({
11
- timestamp: Joi.alternatives(Joi.number().integer().min(0), Joi.date().iso())
7
+ timestamp: Joi.alternatives(
8
+ Joi.number().integer().min(0),
9
+ Joi.date().iso()
10
+ )
12
11
  }), 'Invalid Options Provided');
13
12
  return {
14
13
  inject: () => {
15
14
  assert(tk.isKeepingTime() === false);
16
- tk.freeze(Number.isInteger(opts.timestamp) ? new Date(opts.timestamp * 1000) : new Date(opts.timestamp));
15
+ tk.freeze(
16
+ Number.isInteger(opts.timestamp)
17
+ ? new Date(opts.timestamp * 1000)
18
+ : new Date(opts.timestamp)
19
+ );
17
20
  },
18
21
  release: () => {
19
22
  assert(tk.isKeepingTime());
@@ -21,4 +24,4 @@ module.exports = opts => {
21
24
  },
22
25
  isInjected: () => tk.isKeepingTime()
23
26
  };
24
- };
27
+ };