unexpected-mitm
Advanced tools
Comparing version 10.2.2 to 11.0.0
/* global setImmediate, process, after, console */ | ||
var messy = require('messy'), | ||
consumeReadableStream = require('./consumeReadableStream'), | ||
createMitm = require('mitm-papandreou'), | ||
createSerializedRequestHandler = require('./createSerializedRequestHandler'), | ||
_ = require('underscore'), | ||
http = require('http'), | ||
https = require('https'), | ||
formatHeaderObj = require('./formatHeaderObj'), | ||
fs = require('fs'), | ||
path = require('path'), | ||
stream = require('stream'), | ||
urlModule = require('url'), | ||
memoizeSync = require('memoizesync'), | ||
@@ -16,26 +17,4 @@ callsite = require('callsite'), | ||
var isNodeZeroTen = !!process.version.match(/v0.10/); | ||
// fallback to an inlined version of 0.12+ path.isAbsolute() for 0.10 compat | ||
var pathIsAbsolute = path.isAbsolute || function (path) { | ||
var len = path.length; | ||
if (len === 0) { | ||
return false; | ||
} | ||
var code = path.charCodeAt(0); | ||
if (code === 47/*/*/ || code === 92/*\*/) { | ||
return true; | ||
} else if ((code >= 65/*A*/ && code <= 90/*Z*/) || | ||
(code >= 97/*a*/ && code <= 122/*z*/)) { | ||
// Possible device root | ||
var UnexpectedMitmMocker = require('./UnexpectedMitmMocker'); | ||
if (len > 2 && path.charCodeAt(1) === 58/*:*/) { | ||
code = path.charCodeAt(2); | ||
if (code === 47/*/*/ || code === 92/*\*/) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
}; | ||
function checkEnvFlag(varName) { | ||
@@ -45,6 +24,2 @@ return process.env[varName] === 'true'; | ||
function isRegExp(obj) { | ||
return Object.prototype.toString.call(obj) === '[object RegExp]'; | ||
} | ||
function isTextualBody(message, content) { | ||
@@ -54,10 +29,2 @@ return message.hasTextualContentType && bufferCanBeInterpretedAsUtf8(content); | ||
function formatHeaderObj(headerObj) { | ||
var result = {}; | ||
Object.keys(headerObj).forEach(function (headerName) { | ||
result[messy.formatHeaderName(headerName)] = headerObj[headerName]; | ||
}); | ||
return result; | ||
} | ||
function bufferCanBeInterpretedAsUtf8(buffer) { | ||
@@ -97,9 +64,2 @@ // Hack: Since Buffer.prototype.toString('utf-8') is very forgiving, convert the buffer to a string | ||
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; | ||
} | ||
function trimMessage(message) { | ||
@@ -152,18 +112,2 @@ if (typeof message.body !== 'undefined') { | ||
function trimMockResponse(mockResponse) { | ||
var responseProperties = {}; | ||
responseProperties.statusCode = mockResponse.statusCode; | ||
responseProperties.statusMessage = mockResponse.statusMessage; | ||
responseProperties.protocolName = mockResponse.protocolName; | ||
responseProperties.protocolVersion = mockResponse.protocolVersion; | ||
responseProperties.headers = formatHeaderObj(mockResponse.headers.valuesByName); | ||
if (mockResponse.body) { | ||
// read out the messy decoded body | ||
responseProperties.body = mockResponse.body; | ||
} | ||
return responseProperties; | ||
} | ||
function trimRecordedExchange(recordedExchange) { | ||
@@ -176,194 +120,2 @@ return { | ||
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(); | ||
}; | ||
} | ||
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; | ||
} | ||
function createMockResponse(responseProperties) { | ||
var mockResponse = new messy.HttpResponse(responseProperties); | ||
mockResponse.statusCode = mockResponse.statusCode || 200; | ||
mockResponse.protocolName = mockResponse.protocolName || 'HTTP'; | ||
mockResponse.protocolVersion = mockResponse.protocolVersion || '1.1'; | ||
mockResponse.statusMessage = mockResponse.statusMessage || http.STATUS_CODES[mockResponse.statusCode]; | ||
return mockResponse; | ||
} | ||
function calculateBodyByteCount(chunk) { | ||
var trailerIdx = findHeaderSeparator(chunk); | ||
if (trailerIdx !== -1) { | ||
return chunk.slice(trailerIdx + 4).length; | ||
} | ||
return 0; | ||
} | ||
function findHeaderSeparator(chunk) { | ||
return chunk.toString().indexOf('\r\n\r\n'); | ||
} | ||
function isTrailerChunk(chunk) { | ||
return chunk.slice(-5).toString() === '0\r\n\r\n'; | ||
} | ||
function attachConnectionHook(connection, callback) { | ||
var bodyByteCount = 0; | ||
var contentLengthByteCount = 0; | ||
var hasContentLength = false; | ||
var rawChunks = []; | ||
var sawHeaders = false; | ||
var _write = connection._write; | ||
/* | ||
* wrap low level write to gather response data and return raw data | ||
* to callback when entire response is written | ||
*/ | ||
connection._write = function (chunk, encoding, cb) { | ||
chunk = Buffer.isBuffer(chunk) ? chunk : new Buffer(chunk); | ||
rawChunks.push(chunk); | ||
if (!sawHeaders) { | ||
var chunkString = chunk.toString(); | ||
var match; | ||
match = chunkString.match(/^Transfer-Encoding: /m); | ||
if (match) { | ||
sawHeaders = true; | ||
} | ||
match = chunkString.match(/^Content-Length: (\d+)/m); | ||
if (match) { | ||
// handle the current chunk containing body bytes | ||
bodyByteCount = calculateBodyByteCount(chunk); | ||
contentLengthByteCount = +match[1]; | ||
hasContentLength = true; | ||
sawHeaders = true; | ||
} | ||
} else { | ||
bodyByteCount += chunk.length; | ||
} | ||
if ((isTrailerChunk(chunk) && !hasContentLength) || | ||
(hasContentLength && bodyByteCount === contentLengthByteCount)) { | ||
/* | ||
* explicitly execute the callback returning raw data here | ||
* to ensure it is run before issuing the final write which | ||
* will complete the request on the wire | ||
*/ | ||
callback(null, Buffer.concat(rawChunks)); | ||
} | ||
_write.call(connection, chunk, encoding, cb); | ||
}; | ||
} | ||
function attachResponseHooks(res, callback) { | ||
var hasEnded = false; // keep track of whether res.end() has been called | ||
attachConnectionHook(res.connection, function (err, rawBuffer) { | ||
if (hasEnded) { | ||
callback(null, rawBuffer); | ||
} | ||
}); | ||
['end', 'destroy'].forEach(function (methodName) { | ||
var orig = res[methodName]; | ||
res[methodName] = function (chunk, encoding) { | ||
if (methodName === 'end') { | ||
hasEnded = true; | ||
} | ||
var returnValue = orig.apply(this, arguments); | ||
if (methodName === 'destroy') { | ||
hasEnded = true; | ||
callback(null); | ||
} | ||
// unconditionally return value given we do not wrap write | ||
return returnValue; | ||
}; | ||
}); | ||
} | ||
function determineCallsite(stack) { | ||
@@ -431,3 +183,4 @@ // discard the first frame | ||
expect = expect.child() | ||
.use(require('unexpected-messy')); | ||
.use(require('unexpected-messy')) | ||
.use(require('./mockerAssertions')); | ||
@@ -547,56 +300,2 @@ expectForRendering.addType({ | ||
function consumeReadableStream(readableStream, options) { | ||
options = options || {}; | ||
var skipConcat = !!options.skipConcat; | ||
return expect.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 }); | ||
}); | ||
}); | ||
} | ||
function getMockResponse(responseProperties) { | ||
var mockResponse; | ||
var mockResponseError; | ||
if (Object.prototype.toString.call(responseProperties) === '[object Error]') { | ||
mockResponseError = responseProperties; | ||
} else { | ||
mockResponse = createMockResponse(responseProperties); | ||
} | ||
return expect.promise(function () { | ||
if (!mockResponseError && mockResponse && mockResponse.body && typeof mockResponse.body.pipe === 'function') { | ||
return consumeReadableStream(mockResponse.body).then(function (result) { | ||
if (result.error) { | ||
mockResponseError = result.error; | ||
} | ||
if (result.body) { | ||
mockResponse.unchunkedBody = result.body; | ||
} | ||
}); | ||
} | ||
}).then(function () { | ||
if (mockResponse && !mockResponseError && (Array.isArray(mockResponse.body) || (mockResponse.body && typeof mockResponse.body === 'object' && (typeof Buffer === 'undefined' || !Buffer.isBuffer(mockResponse.body))))) { | ||
if (!mockResponse.headers.has('Content-Type')) { | ||
mockResponse.headers.set('Content-Type', 'application/json'); | ||
} | ||
} | ||
return [ mockResponse, mockResponseError ]; | ||
}); | ||
} | ||
function observeResponse(res) { | ||
return expect.promise(function (resolve, reject) { | ||
attachResponseHooks(res, function (err, rawBuffer) { | ||
resolve(rawBuffer); | ||
}); | ||
}); | ||
} | ||
function performRequest(requestResult) { | ||
@@ -611,3 +310,3 @@ return expect.promise(function (resolve, reject) { | ||
}, requestResult.metadata)).on('response', function (response) { | ||
consumeReadableStream(response).caught(reject).then(function (result) { | ||
consumeReadableStream(response).catch(reject).then(function (result) { | ||
if (result.error) { | ||
@@ -725,3 +424,3 @@ // TODO: Consider adding support for recording this (the upstream response erroring out while we're recording it) | ||
recordedExchanges.push(recordedExchange); | ||
consumeReadableStream(req).caught(reject).then(function (result) { | ||
consumeReadableStream(req).catch(reject).then(function (result) { | ||
if (result.error) { | ||
@@ -772,3 +471,3 @@ // TODO: Consider adding support for recording this (the request erroring out while we're recording it) | ||
}); | ||
}).caught(function (err) { | ||
}).catch(function (err) { | ||
recordedExchange.response = err; | ||
@@ -782,3 +481,3 @@ clientSocket.emit('error', err); | ||
return expect.shift(); | ||
}).caught(reject).then(function (value) { | ||
}).catch(reject).then(function (value) { | ||
recordedExchanges = recordedExchanges.map(trimRecordedExchange); | ||
@@ -828,3 +527,3 @@ if (recordedExchanges.length === 1) { | ||
if (!pathIsAbsolute(testFile)) { | ||
if (!path.isAbsolute(testFile)) { | ||
testFile = path.join(path.dirname(writeCallsite.fileName), testFile); | ||
@@ -874,7 +573,7 @@ } | ||
}) | ||
.exportAssertion('<any> with http mocked out [and verified] [with extra info] [allowing modification] <array|object> <assertion>', function (expect, subject, requestDescriptions) { // ... | ||
expect.errorMode = 'nested'; | ||
var mitm = createMitm(); | ||
.exportAssertion('<any> with http mocked out [and verified] [with extra info] <array|object> <assertion>', function (expect, subject, requestDescriptions) { // ... | ||
expect.errorMode = 'default'; | ||
var shouldBeVerified = checkEnvFlag('UNEXPECTED_MITM_VERIFY') || expect.flags['and verified']; | ||
var shouldReturnExtraInfo = expect.flags['with extra info']; | ||
var shouldReturnExtraInfo = expect.flags['with extra info'] || shouldBeVerified; | ||
expect.flags['with extra info'] = shouldReturnExtraInfo; | ||
@@ -887,8 +586,6 @@ if (!Array.isArray(requestDescriptions)) { | ||
} | ||
} else if (!expect.flags['allowing modification']) { | ||
// duplicate descriptions to allow array consumption | ||
} else { | ||
requestDescriptions = requestDescriptions.slice(0); | ||
} | ||
var nextRequestDescriptionIndex = 0; | ||
@@ -901,315 +598,15 @@ var verifyBlocks = requestDescriptions.map(function (description) { | ||
var __lastError; | ||
var mocker = new UnexpectedMitmMocker({ | ||
requestDescriptions: requestDescriptions | ||
}); | ||
// Keep track of the http/https agents that we have seen | ||
// during the test so we can clean up afterwards: | ||
var seenAgents = []; | ||
var assertionPromise = expect.promise(function (resolve, reject) { | ||
var httpConversation = new messy.HttpConversation(), | ||
httpConversationSatisfySpec = {exchanges: []}; | ||
__lastError = null; | ||
mitm.on('request', createSerializedRequestHandler(function (req, res) { | ||
if (!res.connection) { | ||
// I've observed some cases where keep-alive is | ||
// being used and we end up with an extra "phantom | ||
// request event" even though only requeest is being | ||
// issued. Seems like a bug in mitm. | ||
// It goes without saying that we should try to get | ||
// rid of this. Hopefully something will happen | ||
// on https://github.com/moll/node-mitm/pull/36 | ||
return; | ||
} | ||
var clientSocket = req.connection._mitm.client; | ||
var clientSocketOptions = req.connection._mitm.opts; | ||
if (typeof clientSocketOptions.port === 'string') { | ||
// The port could have been defined as a string in a 3rdparty library doing the http(s) call, and that seems to be valid use of the http(s) module | ||
clientSocketOptions = _.defaults({ | ||
port: parseInt(clientSocketOptions.port, 10) | ||
}, clientSocketOptions); | ||
} | ||
var agent = clientSocketOptions.agent || (res.connection.encrypted ? https : http).globalAgent; | ||
if (seenAgents.indexOf(agent) === -1) { | ||
seenAgents.push(agent); | ||
} | ||
var currentDescription = requestDescriptions[nextRequestDescriptionIndex]; | ||
nextRequestDescriptionIndex += 1; | ||
var hasRequestDescription = !!currentDescription, | ||
metadata = | ||
_.defaults( | ||
{ encrypted: Boolean(res.connection.encrypted) }, | ||
_.pick(clientSocketOptions, messy.HttpRequest.metadataPropertyNames), | ||
_.pick(clientSocketOptions && clientSocketOptions.agent && clientSocketOptions.agent.options, messy.HttpRequest.metadataPropertyNames) | ||
), | ||
requestDescription = currentDescription, | ||
requestProperties, | ||
responseProperties = requestDescription && requestDescription.response, | ||
expectedRequestProperties; | ||
expect.promise(function () { | ||
expectedRequestProperties = resolveExpectedRequestProperties(requestDescription && requestDescription.request); | ||
}).then(function () { | ||
return consumeReadableStream(req, {skipConcat: true}); | ||
}).then(function (result) { | ||
if (result.error) { | ||
// TODO: Consider adding support for recording this (the request erroring out while we're consuming it) | ||
throw result.error; | ||
} | ||
requestProperties = _.extend({ | ||
method: req.method, | ||
path: req.url, | ||
protocolName: 'HTTP', | ||
protocolVersion: req.httpVersion, | ||
headers: req.headers, | ||
unchunkedBody: Buffer.concat(result.body) | ||
}, metadata); | ||
if (!hasRequestDescription) { | ||
// there was no mock so arrange "<no response>" | ||
assertMockResponse(null); | ||
/* | ||
* We wish to cause the generation of a diff | ||
* so we arrange to enter our 'success' path | ||
* but ensure the delegated assertion fully | ||
* completes by signalling an error condition. | ||
* Given we later fail on "to satisfy" we pass | ||
* a null fulfilment value. | ||
* | ||
* Note the use of the single reject/resolve | ||
* behaviour of promises: while the delegated | ||
* assertion is reject()ed, we have already | ||
* resolve()d thus the rejection of the former | ||
* is effectively ignored and we proceed with | ||
* our output. | ||
*/ | ||
// cancel the delegated assertion | ||
clientSocket.emit('error', new Error('unexpected-mitm: Saw unexpected requests.')); | ||
// continue with current assertion | ||
resolve([null, httpConversation, httpConversationSatisfySpec]); | ||
return [null, null]; | ||
} | ||
if (typeof responseProperties === 'function') { | ||
// reset the readable req stream state | ||
stream.Readable.call(req); | ||
// read stream data from the buffered chunks | ||
req._read = function () { | ||
this.push(result.body.shift() || null); | ||
}; | ||
if (isNodeZeroTen) { | ||
/* | ||
* As is mentioned in the streams documentation this | ||
* call can be issued to kick some of the machinery | ||
* and is apparently done within the standard lib. | ||
*/ | ||
req.read(0); | ||
} | ||
// call response function inside a promise to catch exceptions | ||
return expect.promise(function () { | ||
responseProperties(req, res); | ||
}).then(function () { | ||
return [null, null]; | ||
}).caught(function (err) { | ||
// ensure delivery of the caught error to the underlying socket | ||
return [null, err]; | ||
}); | ||
} else { | ||
return getMockResponse(responseProperties); | ||
} | ||
}).spread(function (mockResponse, mockResponseError) { | ||
if (!(mockResponse || mockResponseError)) { | ||
return; | ||
} | ||
var originalErrorMode = expect.errorMode; | ||
expect.errorMode = 'default'; | ||
return expect.promise(function () { | ||
var assertionMockRequest = new messy.HttpRequest(requestProperties); | ||
trimHeadersLower(assertionMockRequest); | ||
var assertionExchange = new messy.HttpExchange({request: assertionMockRequest}); | ||
if (mockResponse) { | ||
assertionExchange.response = mockResponse; | ||
} else if (mockResponseError) { | ||
assertionExchange.response = mockResponseError; | ||
} | ||
return expect(assertionExchange, 'to satisfy', {request: expectedRequestProperties}); | ||
}).then(function () { | ||
expect.errorMode = originalErrorMode; | ||
// continue thus respond | ||
deliverMockResponse(mockResponse, mockResponseError); | ||
}); | ||
}).caught(function (e) { | ||
/* | ||
* Given an error occurs, the deferred assertion | ||
* will still be pending and must be completed. We | ||
* do this by signalling the error on the socket. | ||
*/ | ||
// record the error | ||
__lastError = e; | ||
// cancel the delegated assertion | ||
try { | ||
clientSocket.emit('error', e); | ||
} finally { | ||
/* | ||
* If an something was thrown trying to signal | ||
* an error we have little choice but to try to | ||
* reject the assertion. This is only safe with | ||
* Unexpected 10.15.x and above. | ||
*/ | ||
reject(e); | ||
} | ||
}); | ||
function assertMockResponse(mockResponse, mockResponseError) { | ||
var mockRequest = new messy.HttpRequest(requestProperties); | ||
var httpExchange = new messy.HttpExchange({request: mockRequest}); | ||
if (mockResponse) { | ||
httpExchange.response = mockResponse; | ||
} else if (mockResponseError) { | ||
httpExchange.response = mockResponseError; | ||
} | ||
httpConversation.exchanges.push(httpExchange); | ||
// only attempt request validation with a mock | ||
if (hasRequestDescription) { | ||
httpConversationSatisfySpec.exchanges.push({request: expectedRequestProperties}); | ||
} | ||
/* | ||
* We explicitly do not complete the promise | ||
* when the last request for which we have a | ||
* mock is seen. | ||
* | ||
* Instead simply record any exchanges that | ||
* passed through and defer the resolution or | ||
* rejection to the handler functions attached | ||
* to the delegated assertion execution below. | ||
*/ | ||
} | ||
function deliverMockResponse(mockResponse, mockResponseError) { | ||
setImmediate(function () { | ||
var nonEmptyMockResponse = false; | ||
if (mockResponse) { | ||
res.statusCode = mockResponse.statusCode; | ||
mockResponse.headers.getNames().forEach(function (headerName) { | ||
mockResponse.headers.getAll(headerName).forEach(function (value) { | ||
nonEmptyMockResponse = true; | ||
res.setHeader(headerName, value); | ||
}); | ||
}); | ||
var unchunkedBody = mockResponse.unchunkedBody; | ||
if (typeof unchunkedBody !== 'undefined' && unchunkedBody.length > 0) { | ||
nonEmptyMockResponse = true; | ||
res.write(unchunkedBody); | ||
} else if (nonEmptyMockResponse) { | ||
res.writeHead(res.statusCode || 200); | ||
} | ||
} | ||
if (mockResponseError) { | ||
setImmediate(function () { | ||
clientSocket.emit('error', mockResponseError); | ||
assertMockResponse(mockResponse, mockResponseError); | ||
}); | ||
} else { | ||
res.end(); | ||
} | ||
}); | ||
} | ||
/* | ||
* hook final write callback to record raw data and immediately | ||
* assert request so it occurs prior to request completion | ||
*/ | ||
observeResponse(res).then(function (rawBuffer) { | ||
var mockResponse = createMockResponse(rawBuffer); | ||
assertMockResponse(mockResponse); | ||
}); | ||
})); | ||
expect.promise(function () { | ||
var assertionPromise = expect.promise(function () { | ||
return mocker.mock(function () { | ||
return expect.shift(); | ||
}).then(function (fulfilmentValue) { | ||
/* | ||
* The deletgated assertion issuing requests may have | ||
* completed successfully (i.e. silent error handling) | ||
* but a request failed our checks. Thus go to reject. | ||
*/ | ||
if (__lastError) { | ||
throw __lastError; | ||
} | ||
/* | ||
* Handle the case of specified but unprocessed mocks. | ||
* If there were none remaining immediately complete. | ||
* | ||
* Where the driving assertion resolves we must check | ||
* if any mocks still exist. If so, we add them to the | ||
* set of expected exchanges and resolve the promise. | ||
*/ | ||
var hasRemainingRequestDescriptions = nextRequestDescriptionIndex < requestDescriptions.length; | ||
if (hasRemainingRequestDescriptions) { | ||
// exhaust remaining mocks using a promises chain | ||
(function nextItem() { | ||
var remainingDescription = requestDescriptions[nextRequestDescriptionIndex]; | ||
nextRequestDescriptionIndex += 1; | ||
if (remainingDescription) { | ||
var expectedRequestProperties; | ||
return expect.promise(function () { | ||
expectedRequestProperties = resolveExpectedRequestProperties(remainingDescription.request); | ||
}).then(function () { | ||
return getMockResponse(remainingDescription.response); | ||
}).spread(function (mockResponse, mockResponseError) { | ||
httpConversationSatisfySpec.exchanges.push({ | ||
request: expectedRequestProperties, | ||
response: mockResponseError || trimMockResponse(mockResponse) | ||
}); | ||
return nextItem(); | ||
}).caught(reject); | ||
} else { | ||
resolve([fulfilmentValue, httpConversation, httpConversationSatisfySpec]); | ||
} | ||
})(); | ||
} else { | ||
resolve([fulfilmentValue, httpConversation, httpConversationSatisfySpec]); | ||
} | ||
}).caught(function (e) { | ||
if (__lastError) { | ||
reject(__lastError); | ||
} else { | ||
reject(e); | ||
} | ||
}); | ||
}).spread(function (fulfilmentValue, httpConversation, httpConversationSatisfySpec) { | ||
expect.errorMode = 'default'; | ||
return expect(httpConversation, 'to satisfy', httpConversationSatisfySpec).then(function () { | ||
if (shouldBeVerified || shouldReturnExtraInfo) { | ||
return [fulfilmentValue, httpConversation, httpConversationSatisfySpec]; | ||
} else { | ||
return fulfilmentValue; | ||
} | ||
}); | ||
}).finally(function () { | ||
seenAgents.forEach(function (agent) { | ||
if (agent.freeSockets) { | ||
Object.keys(agent.freeSockets).forEach(function (key) { | ||
agent.freeSockets[key] = []; | ||
}); | ||
} | ||
}); | ||
mitm.disable(); | ||
}).then(function () { | ||
return expect(mocker, 'to be complete [with extra info]'); | ||
}); | ||
if (shouldBeVerified) { | ||
expect.errorMode = 'default'; | ||
var verifier = createVerifier(expect, verifyBlocks); | ||
@@ -1230,3 +627,4 @@ | ||
}); | ||
} | ||
}; |
{ | ||
"name": "unexpected-mitm", | ||
"version": "10.2.2", | ||
"version": "11.0.0", | ||
"description": "Unexpected plugin for the mitm library", | ||
@@ -27,28 +27,29 @@ "author": "Andreas Lind <andreaslindpetersen@gmail.com>", | ||
"devDependencies": { | ||
"body-parser": "1.15.0", | ||
"coveralls": "2.11.2", | ||
"eslint": "2.13.1", | ||
"eslint-config-onelint": "1.2.0", | ||
"express": "4.13.4", | ||
"mocha": "2.2.5", | ||
"nyc": "6.4.4", | ||
"pem": "1.7.2", | ||
"request": "2.53.0", | ||
"semver": "5.0.1", | ||
"sinon": "1.17.2", | ||
"socketerrors-papandreou": "0.2.0-patch2", | ||
"unexpected": "10.27.0", | ||
"body-parser": "^1.18.2", | ||
"coveralls": "^3.0.0", | ||
"eslint": "^4.19.1", | ||
"eslint-config-onelint": "^3.0.0", | ||
"express": "^4.16.3", | ||
"mocha": "^2.2.5", | ||
"nyc": "^11.6.0", | ||
"pem": "^1.12.5", | ||
"request": "^2.53.0", | ||
"semver": "^5.0.1", | ||
"sinon": "^4.5.0", | ||
"socketerrors-papandreou": "^0.2.0-patch2", | ||
"unexpected": "^10.27.0", | ||
"unexpected-documentation-site-generator": "^4.0.0", | ||
"unexpected-express": "9.0.0", | ||
"unexpected-http": "6.0.0", | ||
"unexpected-express": "^10.0.0", | ||
"unexpected-http": "^6.0.0", | ||
"unexpected-markdown": "^1.4.0", | ||
"unexpected-sinon": "10.1.1" | ||
"unexpected-sinon": "^10.1.1" | ||
}, | ||
"dependencies": { | ||
"callsite": "1.0.0", | ||
"detect-indent": "3.0.0", | ||
"memoizesync": "0.5.0", | ||
"callsite": "^1.0.0", | ||
"createerror": "1.1.0", | ||
"detect-indent": "^5.0.0", | ||
"memoizesync": "^1.1.1", | ||
"messy": "^6.16.0", | ||
"mitm-papandreou": "1.3.3-patch4", | ||
"underscore": "1.7.0", | ||
"mitm-papandreou": "^1.3.3-patch4", | ||
"underscore": "^1.8.3", | ||
"unexpected-messy": "^7.2.0" | ||
@@ -55,0 +56,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
60990
12
1201
9
5
+ Addedcreateerror@1.1.0
+ Addedcreateerror@1.1.0(transitive)
+ Addeddetect-indent@5.0.0(transitive)
+ Addedmemoizesync@1.1.1(transitive)
+ Addedmitm-papandreou@1.3.3-patch5(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedunderscore@1.13.7(transitive)
- Removeddetect-indent@3.0.0(transitive)
- Removedget-stdin@3.0.2(transitive)
- Removedmemoizesync@0.5.0(transitive)
- Removedmitm-papandreou@1.3.3-patch4(transitive)
Updatedcallsite@^1.0.0
Updateddetect-indent@^5.0.0
Updatedmemoizesync@^1.1.1
Updatedunderscore@^1.8.3