Skip to content

Commit

Permalink
Merge branch 'cleanup/exchangeContainingNamedErrors'
Browse files Browse the repository at this point in the history
  • Loading branch information
alexjeffburke committed Apr 27, 2018
2 parents 1f525ba + c24723c commit e7f7522
Show file tree
Hide file tree
Showing 11 changed files with 811 additions and 607 deletions.
528 changes: 528 additions & 0 deletions lib/UnexpectedMitmMocker.js

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions lib/consumeReadableStream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*global Promise:false*/
module.exports = function consumeReadableStream(readableStream, options) {
options = options || {};
var skipConcat = !!options.skipConcat;

return new Promise(function (resolve, reject) {
var chunks = [];
readableStream.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function (chunk) {
resolve({ body: skipConcat ? chunks : Buffer.concat(chunks) });
}).on('error', function (err) {
resolve({ body: skipConcat ? chunks : Buffer.concat(chunks), error: err });
});
});
};
32 changes: 32 additions & 0 deletions lib/createSerializedRequestHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module.exports = function createSerializedRequestHandler(onRequest) {
var activeRequest = false,
requestQueue = [];

function processNextRequest() {
function cleanUpAndProceed() {
if (activeRequest) {
activeRequest = false;
setImmediate(processNextRequest);
}
}
while (requestQueue.length > 0 && !activeRequest) {
activeRequest = true;
var reqAndRes = requestQueue.shift(),
req = reqAndRes[0],
res = reqAndRes[1],
resEnd = res.end;
res.end = function () {
resEnd.apply(this, arguments);
cleanUpAndProceed();
};
// This happens upon an error, so we need to make sure that we catch that case also:
res.on('close', cleanUpAndProceed);
onRequest(req, res);
}
}

return function (req, res) {
requestQueue.push([req, res]);
processNextRequest();
};
};
14 changes: 14 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var createError = require('createerror');

// base error
var UnexpectedMitmError = createError({ name: 'UnexpectedMitmError' });
// marker errors
var EarlyExitError = createError({ name: 'EarlyExitError' }, UnexpectedMitmError);
var SawUnexpectedRequestsError = createError({ name: 'SawUnexpectedRequestsError' }, UnexpectedMitmError);
var UnexercisedMocksError = createError({ name: 'UnexercisedMocksError' }, UnexpectedMitmError);


exports.UnexpectedMitmError = UnexpectedMitmError;
exports.EarlyExitError = EarlyExitError;
exports.SawUnexpectedRequestsError = SawUnexpectedRequestsError;
exports.UnexercisedMocksError = UnexercisedMocksError;
9 changes: 9 additions & 0 deletions lib/formatHeaderObj.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var messy = require('messy');

module.exports = function formatHeaderObj(headerObj) {
var result = {};
Object.keys(headerObj).forEach(function (headerName) {
result[messy.formatHeaderName(headerName)] = headerObj[headerName];
});
return result;
};
86 changes: 86 additions & 0 deletions lib/mockerAssertions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
var messy = require('messy');
var trimHeadersLower = require('./trimHeadersLower');
var UnexpectedMitmMocker = require('./UnexpectedMitmMocker');

module.exports = {
name: 'unexpected-mitm-mocker',
version: require('../package.json').version,
installInto: function (expect) {
expect = expect.child()
.use(require('unexpected-messy'))
.exportType({
name: 'UnexpectedMitmMocker',
base: 'object',
identify: function (obj) {
return obj instanceof UnexpectedMitmMocker;
}
})
.exportAssertion('<UnexpectedMitmMocker> to be complete [with extra info]', function (expect, subject) {
var shouldReturnExtraInfo = expect.flags['with extra info'];

return expect.promise(function () {
return subject;
}).then(function (result) {
return [result.timeline, result.fulfilmentValue];
}).spread(function (timeline, fulfilmentValue) {
var lastEventOrError = null;

// pull out the last event if it exists
if (timeline.length > 0) {
lastEventOrError = timeline[timeline.length - 1];
}

if (lastEventOrError instanceof Error) {
var name = lastEventOrError.name;
if (name === 'Error' || name === 'UnexpectedError') {
throw lastEventOrError;
} else if (name === 'EarlyExitError') {
// in the case of an early exirt error we need
// to generate a diff from the last recorded
// event & spec
var failedEvent = timeline[timeline.length - 2];
var failedExchange = failedEvent.exchange;
trimHeadersLower(failedExchange.request);

expect.errorMode = 'default';
return expect(failedExchange, 'to satisfy', failedEvent.spec);
} else {
// ignore to cause generation of a diff
}
} else if (lastEventOrError === null && fulfilmentValue) {
return [null, fulfilmentValue];
}

return [timeline, fulfilmentValue];
}).spread(function (timeline, fulfilmentValue) {
// in the absence of a timeline immedistely resolve with fulfilmentValue
if (timeline === null) {
return fulfilmentValue;
}

var httpConversation = new messy.HttpConversation();
var httpConversationSatisfySpec = { exchanges: [] };

function recordEventForAssertion(event) {
if (event.exchange) {
httpConversation.exchanges.push(event.exchange);
}
if (event.spec) {
httpConversationSatisfySpec.exchanges.push(event.spec);
}
}

timeline.forEach(recordEventForAssertion);

expect.errorMode = 'default';
return expect(httpConversation, 'to satisfy', httpConversationSatisfySpec).then(function () {
if (shouldReturnExtraInfo) {
return [fulfilmentValue, httpConversation, httpConversationSatisfySpec];
} else {
return fulfilmentValue;
}
});
});
});
}
};
58 changes: 58 additions & 0 deletions lib/resolveExpectedRequestProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
var _ = require('underscore');
var urlModule = require('url');

function isRegExp(obj) {
return Object.prototype.toString.call(obj) === '[object RegExp]';
}

module.exports = function resolveExpectedRequestProperties(expectedRequestProperties) {
if (typeof expectedRequestProperties === 'string') {
expectedRequestProperties = { url: expectedRequestProperties };
} else if (expectedRequestProperties && typeof expectedRequestProperties === 'object') {
expectedRequestProperties = _.extend({}, expectedRequestProperties);
}
if (expectedRequestProperties) {
if (typeof expectedRequestProperties.url === 'string') {
var matchMethod = expectedRequestProperties.url.match(/^([A-Z]+) ([\s\S]*)$/);
if (matchMethod) {
expectedRequestProperties.method = expectedRequestProperties.method || matchMethod[1];
expectedRequestProperties.url = matchMethod[2];
}
}
} else {
expectedRequestProperties = {};
}
if (/^https?:\/\//.test(expectedRequestProperties.url)) {
var urlObj = urlModule.parse(expectedRequestProperties.url);
expectedRequestProperties.headers = expectedRequestProperties.headers || {};
if (Object.keys(expectedRequestProperties.headers).every(function (key) {
return key.toLowerCase() !== 'host';
})) {
expectedRequestProperties.headers.host = urlObj.host;
}
expectedRequestProperties.host = expectedRequestProperties.host || urlObj.hostname;
if (urlObj.port && typeof expectedRequestProperties.port === 'undefined') {
expectedRequestProperties.port = parseInt(urlObj.port, 10);
}

if (urlObj.protocol === 'https:' && typeof expectedRequestProperties.encrypted === 'undefined') {
expectedRequestProperties.encrypted = true;
}
expectedRequestProperties.url = urlObj.path;
}

var expectedRequestBody = expectedRequestProperties.body;
if (Array.isArray(expectedRequestBody) || (expectedRequestBody && typeof expectedRequestBody === 'object' && !isRegExp(expectedRequestBody) && (typeof Buffer === 'undefined' || !Buffer.isBuffer(expectedRequestBody)))) {
// in the case of a streamed request body and skip asserting the body
if (typeof expectedRequestBody.pipe === 'function') {
throw new Error('unexpected-mitm: a stream cannot be used to verify the request body, please specify the buffer instead.');
}
expectedRequestProperties.headers = expectedRequestProperties.headers || {};
if (Object.keys(expectedRequestProperties.headers).every(function (key) {
return key.toLowerCase() !== 'content-type';
})) {
expectedRequestProperties.headers['Content-Type'] = 'application/json';
}
}
return expectedRequestProperties;
};
8 changes: 8 additions & 0 deletions lib/trimHeadersLower.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = function trimHeadersLower(message) {
delete message.headers.valuesByName['content-length'];
delete message.headers.valuesByName['transfer-encoding'];
delete message.headers.valuesByName.connection;
delete message.headers.valuesByName.date;

return message;
};
Loading

0 comments on commit e7f7522

Please sign in to comment.