Skip to content

Commit

Permalink
Merge branch 'on-call-number'
Browse files Browse the repository at this point in the history
  • Loading branch information
James-Burba committed Feb 20, 2020
2 parents 2417927 + c9abe30 commit 1d532ff
Show file tree
Hide file tree
Showing 7 changed files with 2,497 additions and 321 deletions.
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
test/reports/
**/node_modules
**/test/reports
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "sparkpost/api"
}
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,22 @@ myInspector.respondToRequest(specialRequest, specialResponse);
```

Accessing the requests made to the mock -
A nock inspector can respond with a specific response on the nth request to it:
```
// responds with the provided response on the second request
myInspector.respondOnCall(2, {
status: 403,
body: { error: 'no snakes allowed.' }
});
```

##### Accessing the requests made to the mock

`myInspector.request` is the most recent request intercepted by the mock. Its properties are `headers`, `body`, and `query`.

`myInspector.requests` is an array of requests, each with `headers`, `body`, and `query`. `myInspector.requests[0]` is the first request intercepted by the mock.

Listing and cleaning mocks -
##### Listing and cleaning mocks
```
nockInspector = require('@sparkpost/nock-inspector');
Expand Down
219 changes: 130 additions & 89 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
'use strict';
/* eslint-disable consistent-this */
/* eslint-disable complexity */
/* eslint-disable no-unexpected-multiline */

const nock = require('nock');
const deepEqual = require('deep-equal');
const _ = require('lodash');
Expand All @@ -11,106 +16,142 @@ const _ = require('lodash');
* @param {Object} [options.response] - The default response.
*/

module.exports = function (options){
return new NockInspector(options)
module.exports = function makeNockInspector(options) {
return new NockInspector(options);
};

module.exports.cleanAll = () => nock.cleanAll();

module.exports.activeMocks = () => nock.activeMocks();

function NockInspector({method, basePath, endpoint, response}){

if(response && !response.status){
response.status = 200;
function NockInspector({ method, basePath, endpoint, response }) {
if (response && !response.status) {
response.status = 200;
}

if (!method) {
throw new Error('method is required');
}

const headersMatch = (request, specRequest) =>
!specRequest.headers
? true
: deepEqual(
specRequest.headers,
_.pick(request.headers, _.keys(specRequest.headers))
);

const bodiesMatch = (request, specRequest) =>
deepEqual(request.body, specRequest.body);

const inspectorProps = this;

//the default response
inspectorProps.response = response;

//an array of all requests made to the thing
inspectorProps.requests = [];

//specific responses and headers that correlate to one another
inspectorProps.specifics = [];

//this is the nock itself
inspectorProps.scope = createScope({ method, basePath, endpoint });

//things to respond with when scope has been called a specific number of times
inspectorProps.numberedResponses = {};

/**
* Tailor a response for a specific request
* @param {Object} request - The request.
* @param {Object} [request.body] - The request body.
* @param {Object} [request.headers] - The request headers.
* @param {Object} response - The response.
* @param {number} [response.status] - The response status.
* @param {Object} [response.body] - The response body.
* @param {Object} [response.headers] - The response headers.
*/
inspectorProps.respondToRequest = function requestResponse(
request,
response
) {
if (!request || !response) {
throw new Error('both a request and a response are required');
}

if(!method){
throw new Error('method is required');
if (!request.body && !request.headers) {
throw new Error('request must have a body or headers');
}

const headersMatch = (request, specRequest) =>
!specRequest.headers
? true
: deepEqual(specRequest.headers, _.pick(request.headers, _.keys(specRequest.headers)));

const bodiesMatch = (request, specRequest) =>
deepEqual(request.body, specRequest.body);

const inspectorProps = this;

//the default response
inspectorProps.response = response;

//an array of all requests made to the thing
inspectorProps.requests = [];

//specific responses and headers that correlate to one another
inspectorProps.specifics = [];

//this is the nock itself
inspectorProps.scope = createScope({method, basePath, endpoint});

/**
* Tailor a response for a specific request
* @param {Object} request - The request.
* @param {Object} [request.body] - The request body.
* @param {Object} [request.headers] - The request headers.
* @param {Object} response - The response.
* @param {number} [response.status] - The response status.
* @param {Object} [response.body] - The response body.
* @param {Object} [response.headers] - The response headers.
*/
inspectorProps.respondToRequest = function requestResponse(request, response){
if(!request || !response){
throw new Error('both a request and a response are required');
}

if(!request.body && !request.headers){
throw new Error('request must have a body or headers');
}

if(!response.status){
response.status = 200;
}

const existingSpec = _.findIndex(inspectorProps.specifics, (specific) => deepEqual(specific.request, request));
if(existingSpec >= 0){
inspectorProps.specifics[existingSpec] = {request, response};
return;
}
if (!response.status) {
response.status = 200;
}

inspectorProps.specifics.push({request, response});
};

function createScope({method, basePath, endpoint}) {
return nock(basePath)[method.toLowerCase()](endpoint).query((query) => {
inspectorProps.request = {query};
return true;
}).reply(function (uri, requestBody) {
const requestInfo = {headers: this.req.headers, body: requestBody};
inspectorProps.request = {...inspectorProps.request, ...requestInfo};
inspectorProps.requests.push(inspectorProps.request);

const specific = _.find(inspectorProps.specifics, specific => headersMatch(requestInfo, specific.request) && bodiesMatch(requestInfo, specific.request));

//todo if body is undefined should it be {}? right now it just comes back with no body.
if(specific){
return [
specific.response.status,
specific.response.body,
specific.response.headers
]
} else if (inspectorProps.response) {
return[
inspectorProps.response.status,
inspectorProps.response.body,
inspectorProps.response.headers
]
} else {
throw new Error('There is no matching or default response');
}
}).persist(true);
const existingSpec = _.findIndex(inspectorProps.specifics, (specific) =>
deepEqual(specific.request, request)
);
if (existingSpec >= 0) {
inspectorProps.specifics[existingSpec] = { request, response };
return;
}

inspectorProps.specifics.push({ request, response });
};

/**
* @param {number} callNumber
* @param {Object} response - The response.
* @param {number} [response.status] - The response status.
* @param {Object} [response.body] - The response body.
* @param {Object} [response.headers] - The response headers.
*/
inspectorProps.respondOnCall = function respondOnCall(callNumber, response) {
inspectorProps.numberedResponses[callNumber] = response;
};

function createScope({ method, basePath, endpoint }) {
return nock(basePath)
[method.toLowerCase()](endpoint)
.query((query) => {
inspectorProps.request = { query };
return true;
})
.reply(function reply(uri, requestBody) {
const requestInfo = { headers: this.req.headers, body: requestBody };
inspectorProps.request = { ...inspectorProps.request, ...requestInfo };
inspectorProps.requests.push(inspectorProps.request);

const specific = _.find(
inspectorProps.specifics,
(specific) =>
headersMatch(requestInfo, specific.request)
&& bodiesMatch(requestInfo, specific.request)
);
const numberedResponse = inspectorProps.numberedResponses[inspectorProps.requests.length];

//todo if body is undefined should it be {}? right now it just comes back with no body.
if (numberedResponse) {
return [
numberedResponse.status,
numberedResponse.body,
numberedResponse.headers
];
} else if (specific) {
return [
specific.response.status,
specific.response.body,
specific.response.headers
];
} else if (inspectorProps.response) {
return [
inspectorProps.response.status,
inspectorProps.response.body,
inspectorProps.response.headers
];
} else {
throw new Error('There is no matching or default response');
}
})
.persist(true);
}
}
Loading

0 comments on commit 1d532ff

Please sign in to comment.