From ed2f32bdcbe2ca200b276ef0320435770b77e6d9 Mon Sep 17 00:00:00 2001 From: Alex Ugol Date: Mon, 5 Nov 2018 22:28:57 +0200 Subject: [PATCH] Add ability to set log level by configuration with statuscode --- README.md | 20 + lib/express-logger.js | 30 +- lib/logger-helper.js | 62 +- lib/utils.js | 39 +- package-lock.json | 1685 ++++++++++++++++++++++++++++++++++++ test/logger-helper-test.js | 277 +++--- test/utils-test.js | 80 +- 7 files changed, 2013 insertions(+), 180 deletions(-) create mode 100644 package-lock.json diff --git a/README.md b/README.md index bf4932a..db9737e 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,26 @@ Array of strings - pass the header names you wish to exclude from the audit (sen Array of strings - pass the fields you wish to mask in the headers of the responses (senstitive data like authorization headers etc..). +##### levels + +Map of statusCodes to log levels. By default the audit is logged with level 'info'. It is possible to override it by configuration according to the statusCode of the response: + + - Key: status code, or status code group: '2xx', '401', etc.. + - Value: log level, valid values: 'trace', 'debug', 'info', 'warn', 'error'. + - Configuration errors are ignored and the log is info by default. + + + Example: +``` +levels: { + "2xx":"info", // All 2xx responses are info + "401":"warn", // 401 are warn + "4xx':info", // All 4xx exceprt 401 are info + "503":"warn", + "5xx":"error" // All 5xx except 503 are errors, 503 is warn, +} +``` + ### Example diff --git a/lib/express-logger.js b/lib/express-logger.js index 3da5f8c..cd0aab5 100644 --- a/lib/express-logger.js +++ b/lib/express-logger.js @@ -1,12 +1,12 @@ 'use strict'; var _ = require('lodash'), - logger = require('bunyan').createLogger({name: 'ExpressLogger'}), + logger = require('bunyan').createLogger({ name: 'ExpressLogger' }), loggerHelper = require('./logger-helper'), setupOptions, flatten = require('flat'); -var audit = function (req, res, next){ +var audit = function (req, res, next) { var oldWrite = res.write; var oldEnd = res.end; var chunks = []; @@ -14,19 +14,19 @@ var audit = function (req, res, next){ // Log start time of request processing req.timestamp = new Date(); - if (setupOptions.doubleAudit){ + if (setupOptions.doubleAudit) { loggerHelper.auditRequest(req, setupOptions); } - res.write = function (chunk){ + res.write = function (chunk) { chunks.push(new Buffer(chunk)); oldWrite.apply(res, arguments); }; // decorate response#end method from express - res.end = function (chunk){ + res.end = function (chunk) { res.timestamp = new Date(); - if (chunk){ + if (chunk) { chunks.push(new Buffer(chunk)); } @@ -41,7 +41,7 @@ var audit = function (req, res, next){ next(); }; -module.exports = function(options){ +module.exports = function (options) { options = options || {}; var defaults = { logger: logger, @@ -61,7 +61,13 @@ module.exports = function(options){ excludeHeaders: [] }, doubleAudit: false, - excludeURLs: [] + excludeURLs: [], + levels: { + '2xx': 'info', + '3xx': 'info', + '4xx': 'info', + '5xx': 'info' + } }; _.defaultsDeep(options, defaults); @@ -70,13 +76,11 @@ module.exports = function(options){ }; //convert all options fields that need to be array by default -function validateArrayFields(options, defaults) -{ +function validateArrayFields(options, defaults) { Object.keys(flatten(options)).forEach((key) => { let optionValue = _.get(options, key); - let defaultValue =_.get(defaults, key) - if(_.isArray(defaultValue) && !_.isArray(optionValue)) - { + let defaultValue = _.get(defaults, key) + if (_.isArray(defaultValue) && !_.isArray(optionValue)) { _.set(options, key, [optionValue]); } }); diff --git a/lib/logger-helper.js b/lib/logger-helper.js index cdfd32d..7470a73 100644 --- a/lib/logger-helper.js +++ b/lib/logger-helper.js @@ -4,18 +4,19 @@ var utils = require('./utils'); var _ = require('lodash'); var ALL_FIELDS = '*'; const NA = 'N/A'; +const DEFAULT_LOG_LEVEL = 'info'; -var auditRequest = function(req, options){ +var auditRequest = function (req, options) { var shouldAudit = utils.shouldAuditURL(options.excludeURLs, req); - if (shouldAudit){ + if (shouldAudit) { var request; - if (options.setupFunc){ + if (options.setupFunc) { options.setupFunc(req, res); } - if (options.request.audit){ + if (options.request.audit) { request = getRequestAudit(req, options); } @@ -24,7 +25,7 @@ var auditRequest = function(req, options){ }; // Add additional audit fields - if (req && req.additionalAudit){ + if (req && req.additionalAudit) { auditObject = Object.assign(auditObject, req.additionalAudit); } @@ -32,21 +33,21 @@ var auditRequest = function(req, options){ } }; -var auditResponse = function(req, res, options){ +var auditResponse = function (req, res, options) { var request; var response; var shouldAudit = utils.shouldAuditURL(options.excludeURLs, req); - if (shouldAudit){ - if (options.setupFunc){ + if (shouldAudit) { + if (options.setupFunc) { options.setupFunc(req, res); } - if (options.request.audit){ + if (options.request.audit) { request = getRequestAudit(req, options); } - if (options.response.audit){ + if (options.response.audit) { response = getResponseAudit(req, res, options); } @@ -56,15 +57,21 @@ var auditResponse = function(req, res, options){ }; // Add additional audit fields - if (req && req.additionalAudit){ + if (req && req.additionalAudit) { auditObject = Object.assign(auditObject, req.additionalAudit); } - options.logger.info(auditObject, 'Inbound Transaction'); + let level = DEFAULT_LOG_LEVEL; // Default + if (res) { + // Make ssure the resolved log level is supported by our logger: + let resolvedLogLevel = utils.getLogLevel(res.statusCode, options.levels); + level = options.logger[resolvedLogLevel] ? resolvedLogLevel : level; + } + options.logger[level](auditObject, 'Inbound Transaction'); } }; -function getRequestAudit(req, options){ +function getRequestAudit(req, options) { var headers = _.get(req, 'headers'); var requestFullURL = utils.getUrl(req); var requestRoute = utils.getRoute(req); @@ -73,7 +80,7 @@ function getRequestAudit(req, options){ var URLParams = req && req.params ? req.params : NA; var timestamp = req && req.timestamp ? req.timestamp.toISOString() : NA; var timestamp_ms = req && req.timestamp ? req.timestamp.valueOf() : NA; - var requestBody = _.get(req, 'body'); //handle body clone the original body + var requestBody = _.get(req, 'body'); //handle body clone the original body requestBody = handleJson(requestBody, options.logger, options.request.excludeBody, options.request.maskBody); @@ -87,10 +94,10 @@ function getRequestAudit(req, options){ url: requestFullURL, url_route: requestRoute, query: queryParams, - headers: _.isEmpty(headers) ? NA: headers, + headers: _.isEmpty(headers) ? NA : headers, timestamp: timestamp, timestamp_ms: timestamp_ms, - body: _.isEmpty(requestBody) ? NA: JSON.stringify(requestBody) + body: _.isEmpty(requestBody) ? NA : JSON.stringify(requestBody) }; return auditObject; @@ -98,26 +105,23 @@ function getRequestAudit(req, options){ function handleJson(obj, logger, excludeFields, maskFields) { - if(_.includes(excludeFields, ALL_FIELDS)) - { + if (_.includes(excludeFields, ALL_FIELDS)) { obj = undefined; } - else if(obj) - { + else if (obj) { try { //in case obj is string and need to convert to json - if(typeof obj == 'string') { + if (typeof obj == 'string') { obj = JSON.parse(obj); } - else if(typeof obj != 'object') - { + else if (typeof obj != 'object') { throw new Error('only json obj can be exclude/masked'); } //order is important because body is clone first obj = utils.maskJson(obj, maskFields); obj = utils.cleanOmitKeys(obj, excludeFields); - } catch (err){ + } catch (err) { logger.warn('Error parsing json: ' + err); obj = undefined; } @@ -129,17 +133,17 @@ function handleJson(obj, logger, excludeFields, maskFields) { -function getResponseAudit(req, res, options){ +function getResponseAudit(req, res, options) { var headers = _.get(res, '_headers'); var elapsed = req && res ? res.timestamp - req.timestamp : 0; var timestamp = res && res.timestamp ? res.timestamp.toISOString() : NA; var timestamp_ms = res && res.timestamp ? res.timestamp.valueOf() : NA; var statusCode = res && res.statusCode ? res.statusCode : NA; - var responseBody = _.get(res, '_body'); //no need to clone because its not the original body + var responseBody = _.get(res, '_body'); //no need to clone because its not the original body if (headers && headers['content-type'] && headers['content-type'].includes('application/json')) { // Handle JSON only for json responses: - responseBody = handleJson(responseBody, options.logger , options.response.excludeBody, options.response.maskBody); + responseBody = handleJson(responseBody, options.logger, options.response.excludeBody, options.response.maskBody); } headers = handleJson(headers, options.logger, options.response.excludeHeaders, options.response.maskHeaders); @@ -149,8 +153,8 @@ function getResponseAudit(req, res, options){ timestamp: timestamp, timestamp_ms: timestamp_ms, elapsed: elapsed, - headers: _.isEmpty(headers) ? NA: headers, - body: _.isEmpty(responseBody) ? NA: JSON.stringify(responseBody) + headers: _.isEmpty(headers) ? NA : headers, + body: _.isEmpty(responseBody) ? NA : JSON.stringify(responseBody) }; return auditObject; diff --git a/lib/utils.js b/lib/utils.js index 294b180..fee0df4 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,19 +3,21 @@ var _ = require('lodash'); const MASK = 'XXXXX'; const NA = 'N/A'; +const VALID_LEVELS = ['trace', 'debug', 'info', 'warn', 'error'] +const DEFAULT_LEVEL = 'info'; -var getUrl = function(req){ +var getUrl = function (req) { var url = req && req.url || NA; return url; }; -var getRoute = function(req) { +var getRoute = function (req) { var url = NA; - if (req){ + if (req) { var route = req.baseUrl; - if (req.route && route){ + if (req.route && route) { url = route + req.route.path; } } @@ -23,7 +25,7 @@ var getRoute = function(req) { return url; }; -function cleanOmitKeys (obj, omitKeys) { +function cleanOmitKeys(obj, omitKeys) { if (obj && !_.isEmpty(omitKeys)) { Object.keys(obj).forEach(function (key) { if (_.some(omitKeys, omitKey => key === omitKey)) { @@ -36,15 +38,15 @@ function cleanOmitKeys (obj, omitKeys) { return obj; }; -var shouldAuditURL = function(excludeURLs, req){ - return _.every(excludeURLs, function(path){ +var shouldAuditURL = function (excludeURLs, req) { + return _.every(excludeURLs, function (path) { var url = getUrl(req); var route = getRoute(req); return !(url.includes(path) || route.includes(path)); }); }; -var maskJson = function(jsonObj, fieldsToMask){ +var maskJson = function (jsonObj, fieldsToMask) { let jsonObjCopy = _.cloneDeepWith(jsonObj, function (value, key) { if (_.includes(fieldsToMask, key)) { return MASK @@ -53,11 +55,28 @@ var maskJson = function(jsonObj, fieldsToMask){ return jsonObjCopy; }; +var getLogLevel = function (statusCode, levelsMap) { + let level = DEFAULT_LEVEL; // Default + + if (levelsMap) { + let status = statusCode.toString(); + + if (levelsMap[status]) { + level = VALID_LEVELS.includes(levelsMap[status]) ? levelsMap[status] : level; + } else { + let statusGroup = `${status.substring(0, 1)}xx`; // 5xx, 4xx, 2xx, etc.. + level = VALID_LEVELS.includes(levelsMap[statusGroup]) ? levelsMap[statusGroup] : level; + } + } + + return level; +} module.exports = { getRoute: getRoute, getUrl: getUrl, shouldAuditURL: shouldAuditURL, - maskJson:maskJson, - cleanOmitKeys:cleanOmitKeys + maskJson: maskJson, + cleanOmitKeys: cleanOmitKeys, + getLogLevel: getLogLevel }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..322b006 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1685 @@ +{ + "name": "express-requests-logger", + "version": "1.0.19", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "http://npm.zooz.co:8083/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "http://npm.zooz.co:8083/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "http://npm.zooz.co:8083/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "http://npm.zooz.co:8083/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "http://npm.zooz.co:8083/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "http://npm.zooz.co:8083/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "http://npm.zooz.co:8083/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "http://npm.zooz.co:8083/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "http://npm.zooz.co:8083/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "http://npm.zooz.co:8083/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "http://npm.zooz.co:8083/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "http://npm.zooz.co:8083/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://npm.zooz.co:8083/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "http://npm.zooz.co:8083/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://npm.zooz.co:8083/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "http://npm.zooz.co:8083/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "http://npm.zooz.co:8083/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "http://npm.zooz.co:8083/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "http://npm.zooz.co:8083/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "http://npm.zooz.co:8083/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "http://npm.zooz.co:8083/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "bunyan": { + "version": "1.8.12", + "resolved": "http://npm.zooz.co:8083/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "requires": { + "dtrace-provider": "~0.8", + "moment": "^2.10.6", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "http://npm.zooz.co:8083/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "http://npm.zooz.co:8083/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "http://npm.zooz.co:8083/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "http://npm.zooz.co:8083/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "http://npm.zooz.co:8083/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "http://npm.zooz.co:8083/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "http://npm.zooz.co:8083/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "coveralls": { + "version": "3.0.2", + "resolved": "http://npm.zooz.co:8083/coveralls/-/coveralls-3.0.2.tgz", + "integrity": "sha512-Tv0LKe/MkBOilH2v7WBiTBdudg2ChfGbdXafc/s330djpF3zKOmuehTeRwjXWc7pzfj9FrDUTA7tEx6Div8NFw==", + "dev": true, + "requires": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.85.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "http://npm.zooz.co:8083/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "http://npm.zooz.co:8083/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "http://npm.zooz.co:8083/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "http://npm.zooz.co:8083/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "http://npm.zooz.co:8083/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "diff": { + "version": "3.2.0", + "resolved": "http://npm.zooz.co:8083/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "dtrace-provider": { + "version": "0.8.7", + "resolved": "http://npm.zooz.co:8083/dtrace-provider/-/dtrace-provider-0.8.7.tgz", + "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", + "optional": true, + "requires": { + "nan": "^2.10.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "http://npm.zooz.co:8083/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "http://npm.zooz.co:8083/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "http://npm.zooz.co:8083/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "http://npm.zooz.co:8083/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "http://npm.zooz.co:8083/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "http://npm.zooz.co:8083/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "http://npm.zooz.co:8083/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "http://npm.zooz.co:8083/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "http://npm.zooz.co:8083/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "http://npm.zooz.co:8083/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "http://npm.zooz.co:8083/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "flat": { + "version": "4.1.0", + "resolved": "http://npm.zooz.co:8083/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "requires": { + "is-buffer": "~2.0.3" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "http://npm.zooz.co:8083/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "http://npm.zooz.co:8083/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formatio": { + "version": "1.1.1", + "resolved": "http://npm.zooz.co:8083/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "dev": true, + "requires": { + "samsam": "~1.1" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "http://npm.zooz.co:8083/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "http://npm.zooz.co:8083/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "6.0.4", + "resolved": "http://npm.zooz.co:8083/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "http://npm.zooz.co:8083/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "http://npm.zooz.co:8083/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.0.12", + "resolved": "http://npm.zooz.co:8083/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "http://npm.zooz.co:8083/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "http://npm.zooz.co:8083/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "resolved": "http://npm.zooz.co:8083/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "http://npm.zooz.co:8083/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "http://npm.zooz.co:8083/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "http://npm.zooz.co:8083/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "http://npm.zooz.co:8083/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "invariant": { + "version": "2.2.4", + "resolved": "http://npm.zooz.co:8083/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "http://npm.zooz.co:8083/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + }, + "is-finite": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "http://npm.zooz.co:8083/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "http://npm.zooz.co:8083/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "http://npm.zooz.co:8083/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "http://npm.zooz.co:8083/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "http://npm.zooz.co:8083/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "http://npm.zooz.co:8083/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "http://npm.zooz.co:8083/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "http://npm.zooz.co:8083/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "http://npm.zooz.co:8083/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "http://npm.zooz.co:8083/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "http://npm.zooz.co:8083/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "http://npm.zooz.co:8083/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "http://npm.zooz.co:8083/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "http://npm.zooz.co:8083/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "http://npm.zooz.co:8083/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "http://npm.zooz.co:8083/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "http://npm.zooz.co:8083/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "http://npm.zooz.co:8083/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "http://npm.zooz.co:8083/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "http://npm.zooz.co:8083/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "http://npm.zooz.co:8083/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "http://npm.zooz.co:8083/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "http://npm.zooz.co:8083/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "http://npm.zooz.co:8083/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "http://npm.zooz.co:8083/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "http://npm.zooz.co:8083/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "log-driver": { + "version": "1.2.7", + "resolved": "http://npm.zooz.co:8083/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "lolex": { + "version": "1.3.2", + "resolved": "http://npm.zooz.co:8083/lolex/-/lolex-1.3.2.tgz", + "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "http://npm.zooz.co:8083/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "http://npm.zooz.co:8083/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "http://npm.zooz.co:8083/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "http://npm.zooz.co:8083/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "http://npm.zooz.co:8083/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "http://npm.zooz.co:8083/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "http://npm.zooz.co:8083/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "http://npm.zooz.co:8083/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "glob": { + "version": "7.1.1", + "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "http://npm.zooz.co:8083/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "moment": { + "version": "2.22.2", + "resolved": "http://npm.zooz.co:8083/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=", + "optional": true + }, + "ms": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "http://npm.zooz.co:8083/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + } + }, + "nan": { + "version": "2.11.1", + "resolved": "http://npm.zooz.co:8083/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", + "optional": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "http://npm.zooz.co:8083/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "http://npm.zooz.co:8083/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "net": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/net/-/net-1.0.2.tgz", + "integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g=", + "dev": true + }, + "node-mocks-http": { + "version": "1.7.3", + "resolved": "http://npm.zooz.co:8083/node-mocks-http/-/node-mocks-http-1.7.3.tgz", + "integrity": "sha512-wayzLNhEroH3lJj113pFKQ1cd1GKG1mXoZR1HcKp/o9a9lTGGgVY/hYeLajiIFr/z4tXFKOdfJickqqihBtn9g==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "depd": "^1.1.0", + "fresh": "^0.5.2", + "merge-descriptors": "^1.0.1", + "methods": "^1.1.2", + "mime": "^1.3.4", + "net": "^1.0.2", + "parseurl": "^1.3.1", + "range-parser": "^1.2.0", + "type-is": "^1.6.16" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "http://npm.zooz.co:8083/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "http://npm.zooz.co:8083/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "http://npm.zooz.co:8083/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "http://npm.zooz.co:8083/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "http://npm.zooz.co:8083/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "http://npm.zooz.co:8083/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "parseurl": { + "version": "1.3.2", + "resolved": "http://npm.zooz.co:8083/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "http://npm.zooz.co:8083/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "http://npm.zooz.co:8083/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "http://npm.zooz.co:8083/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "http://npm.zooz.co:8083/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "http://npm.zooz.co:8083/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "http://npm.zooz.co:8083/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "http://npm.zooz.co:8083/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "http://npm.zooz.co:8083/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "http://npm.zooz.co:8083/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "http://npm.zooz.co:8083/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "http://npm.zooz.co:8083/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "rewire": { + "version": "3.0.2", + "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-3.0.2.tgz", + "integrity": "sha512-ejkkt3qYnsQ38ifc9llAAzuHiGM7kR8N5/mL3aHWgmWwet0OMFcmJB8aTsMV2PBHCWxNVTLCeRfBpEa8X2+1fw==", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-plugin-transform-es2015-block-scoping": "^6.26.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "http://npm.zooz.co:8083/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "http://npm.zooz.co:8083/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "http://npm.zooz.co:8083/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "http://npm.zooz.co:8083/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "samsam": { + "version": "1.1.2", + "resolved": "http://npm.zooz.co:8083/samsam/-/samsam-1.1.2.tgz", + "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "dev": true + }, + "should": { + "version": "11.2.1", + "resolved": "http://npm.zooz.co:8083/should/-/should-11.2.1.tgz", + "integrity": "sha1-kPVRRVUtAc/CAGZuToGKHJZw7aI=", + "dev": true, + "requires": { + "should-equal": "^1.0.0", + "should-format": "^3.0.2", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/should-equal/-/should-equal-1.0.1.tgz", + "integrity": "sha1-C26VFvJgGp+wuy3MNpr6HH4gCvc=", + "dev": true, + "requires": { + "should-type": "^1.0.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "http://npm.zooz.co:8083/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "http://npm.zooz.co:8083/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "http://npm.zooz.co:8083/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, + "sinon": { + "version": "1.17.7", + "resolved": "http://npm.zooz.co:8083/sinon/-/sinon-1.17.7.tgz", + "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "dev": true, + "requires": { + "formatio": "1.1.1", + "lolex": "1.3.2", + "samsam": "1.1.2", + "util": ">=0.10.3 <1" + } + }, + "slash": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "http://npm.zooz.co:8083/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "http://npm.zooz.co:8083/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "http://npm.zooz.co:8083/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "http://npm.zooz.co:8083/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.15.2", + "resolved": "http://npm.zooz.co:8083/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://npm.zooz.co:8083/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "http://npm.zooz.co:8083/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "http://npm.zooz.co:8083/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "http://npm.zooz.co:8083/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "http://npm.zooz.co:8083/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "http://npm.zooz.co:8083/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "http://npm.zooz.co:8083/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "http://npm.zooz.co:8083/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "http://npm.zooz.co:8083/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "http://npm.zooz.co:8083/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "util": { + "version": "0.11.1", + "resolved": "http://npm.zooz.co:8083/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "http://npm.zooz.co:8083/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "http://npm.zooz.co:8083/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "http://npm.zooz.co:8083/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "http://npm.zooz.co:8083/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "http://npm.zooz.co:8083/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/test/logger-helper-test.js b/test/logger-helper-test.js index 28e6e9b..568ebda 100644 --- a/test/logger-helper-test.js +++ b/test/logger-helper-test.js @@ -26,17 +26,18 @@ var query = { q2: 'fishy' }; -describe('logger-helpers tests', function(){ - var sandbox, clock, loggerInfoStub, shouldAuditURLStub, loggerWarnStub; +describe('logger-helpers tests', function () { + var sandbox, clock, loggerInfoStub, shouldAuditURLStub, loggerWarnStub, loggerErrorStub, getLogLevelStub; var request, response, options, expectedAuditRequest, expectedAuditResponse; - before(function(){ + before(function () { sandbox = sinon.sandbox.create(); clock = sinon.useFakeTimers(); shouldAuditURLStub = sandbox.stub(utils, 'shouldAuditURL'); + getLogLevelStub = sandbox.stub(utils, 'getLogLevel'); }); - beforeEach(function(){ + beforeEach(function () { options = { request: { audit: true, @@ -75,12 +76,15 @@ describe('logger-helpers tests', function(){ response.headers = { 'header2': 'some-other-value', 'content-type': 'application/json' }; response._headers = response.headers; - options.logger.info = function(){}; - options.logger.warn = function(){}; + options.logger.info = function () { }; + options.logger.warn = function () { }; + options.logger.error = function () { }; loggerInfoStub = sandbox.stub(options.logger, 'info'); loggerWarnStub = sandbox.stub(options.logger, 'warn'); + loggerErrorStub = sandbox.stub(options.logger, 'error'); + getLogLevelStub.returns('info'); expectedAuditRequest = { method: method, url: url, @@ -107,31 +111,31 @@ describe('logger-helpers tests', function(){ }; }); - after(function(){ + after(function () { sandbox.restore(); clock.restore(); }); - describe('When calling auditRequest', function(){ - afterEach(function(){ + describe('When calling auditRequest', function () { + afterEach(function () { utils.shouldAuditURL.reset(); }); - describe('And shouldAuditURL returns false', function(){ - it('Should not audit request', function(){ + describe('And shouldAuditURL returns false', function () { + it('Should not audit request', function () { shouldAuditURLStub.returns(false); loggerHelper.auditRequest(request, options); sinon.assert.notCalled(loggerInfoStub); }); }); - describe('And shouldAuditURL returns true', function(){ - it('Should audit request if options.request.audit is true', function(){ + describe('And shouldAuditURL returns true', function () { + it('Should audit request if options.request.audit is true', function () { shouldAuditURLStub.returns(true); options.request.audit = true; loggerHelper.auditRequest(request, options); sinon.assert.calledOnce(loggerInfoStub); }); - it('Should not audit request if options.request.audit is false', function(){ + it('Should not audit request if options.request.audit is false', function () { shouldAuditURLStub.returns(true); options.request.audit = false; loggerHelper.auditRequest(request, options); @@ -139,19 +143,19 @@ describe('logger-helpers tests', function(){ sinon.assert.calledWith(loggerInfoStub, { request: undefined }); }); }); - describe('And additionalAudit is not empty', function(){ - beforeEach(function(){ + describe('And additionalAudit is not empty', function () { + beforeEach(function () { request.additionalAudit = { field1: 'field1', field2: 'field2' }; }); - afterEach(function(){ + afterEach(function () { delete request.additionalAudit; delete expectedAuditRequest.field1; delete expectedAuditRequest.field2; }); - it('Should add to audit the additional audit details', function(){ + it('Should add to audit the additional audit details', function () { shouldAuditURLStub.returns(true); loggerHelper.auditRequest(request, options); @@ -159,7 +163,7 @@ describe('logger-helpers tests', function(){ sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, field1: 'field1', field2: 'field2' }); }); - it('Should not add to audit the additional audit details if its an empty object', function(){ + it('Should not add to audit the additional audit details if its an empty object', function () { request.additionalAudit = {}; delete expectedAuditRequest.field1; delete expectedAuditRequest.field2; @@ -171,8 +175,8 @@ describe('logger-helpers tests', function(){ sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); }); }); - describe('And mask query params that are set to be masked', function(){ - it('Should mask the query param', function(){ + describe('And mask query params that are set to be masked', function () { + it('Should mask the query param', function () { var maskedQuery = 'q1'; options.request.maskQuery = [maskedQuery]; shouldAuditURLStub.returns(true); @@ -186,7 +190,7 @@ describe('logger-helpers tests', function(){ // Clear created header for other tests }); - it('Should mask all query params', function(){ + it('Should mask all query params', function () { var maskedQuery1 = 'q1'; var maskedQuery2 = 'q2'; options.request.maskQuery = [maskedQuery1, maskedQuery2]; @@ -201,12 +205,12 @@ describe('logger-helpers tests', function(){ sinon.assert.calledWith(loggerInfoStub, { request: expected }); }); }); - describe('And exclude headers contains an header to exclude', function(){ + describe('And exclude headers contains an header to exclude', function () { var headerToExclude = 'header-to-exclude'; - beforeEach(function(){ + beforeEach(function () { request.headers[headerToExclude] = 'other-value'; }); - it('Should audit log without the specified header', function(){ + it('Should audit log without the specified header', function () { options.request.excludeHeaders = [headerToExclude]; shouldAuditURLStub.returns(true); let prevHeaders = _.cloneDeep(request.headers); @@ -215,7 +219,7 @@ describe('logger-helpers tests', function(){ sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); should.deepEqual(request.headers, prevHeaders, 'headers of request change'); }); - it('Should audit log without the specified headers, if there are more than one', function(){ + it('Should audit log without the specified headers, if there are more than one', function () { var anotherHeaderToExclude = 'another'; options.request.excludeHeaders = [headerToExclude, anotherHeaderToExclude]; request.headers[anotherHeaderToExclude] = 'some value'; @@ -227,7 +231,7 @@ describe('logger-helpers tests', function(){ should.deepEqual(request.headers, prevHeaders, 'headers of request change'); }); - it('Should audit log with all headers, if exclude headers is an empty list', function(){ + it('Should audit log with all headers, if exclude headers is an empty list', function () { options.request.excludeHeaders = ['other-header']; shouldAuditURLStub.returns(true); let prevHeaders = _.cloneDeep(request.headers); @@ -241,20 +245,20 @@ describe('logger-helpers tests', function(){ delete expectedAuditRequest.headers[headerToExclude]; }); }); - describe('And exclude Body contains field to exclude', function(){ - before(function(){ + describe('And exclude Body contains field to exclude', function () { + before(function () { shouldAuditURLStub.returns(true); }); - afterEach(function(){ + afterEach(function () { expectedAuditRequest.body = JSON.stringify(body); }); - it('Should audit log with body, if no excludeBody was written in options', function(){ + it('Should audit log with body, if no excludeBody was written in options', function () { loggerHelper.auditRequest(request, options); sinon.assert.calledOnce(loggerInfoStub); sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); }); - it('Should audit log without body, when excludeBody with \'*\'', function(){ + it('Should audit log without body, when excludeBody with \'*\'', function () { options.request.excludeBody = [ALL_FIELDS]; let prevBody = _.cloneDeep(request.body); loggerHelper.auditRequest(request, options); @@ -264,7 +268,7 @@ describe('logger-helpers tests', function(){ should.deepEqual(request.body, prevBody, 'body of request change'); }); - it('Should audit log without body, when excludeBody with \'*\' and body is plain text', function(){ + it('Should audit log without body, when excludeBody with \'*\' and body is plain text', function () { options.request.excludeBody = [ALL_FIELDS]; request.body = 'test'; @@ -273,25 +277,25 @@ describe('logger-helpers tests', function(){ expectedAuditRequest.body = NA; sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); }); - it('Should audit log without body, when excludeBody by field and all body', function(){ + it('Should audit log without body, when excludeBody by field and all body', function () { options.request.excludeBody = ['field1', ALL_FIELDS]; - request.body = { 'field1' : 1, 'field2' : 'test'}; + request.body = { 'field1': 1, 'field2': 'test' }; loggerHelper.auditRequest(request, options); sinon.assert.calledOnce(loggerInfoStub); expectedAuditRequest.body = NA; sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); }); - it('Should audit log body without specific field, when excludeBody by existing and unexisting field', function(){ + it('Should audit log body without specific field, when excludeBody by existing and unexisting field', function () { options.request.excludeBody = ['field3', 'field1']; - request.body = { 'field1' : 1, 'field2' : 'test'}; + request.body = { 'field1': 1, 'field2': 'test' }; let prevBody = _.cloneDeep(request.body); loggerHelper.auditRequest(request, options); sinon.assert.calledOnce(loggerInfoStub); - expectedAuditRequest.body = JSON.stringify({'field2' : 'test'}); + expectedAuditRequest.body = JSON.stringify({ 'field2': 'test' }); sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); should.deepEqual(request.body, prevBody, 'body of request change'); }); - it('Should audit log without body, when no body in request and excludeBody by field', function(){ + it('Should audit log without body, when no body in request and excludeBody by field', function () { options.request.excludeBody = ['field3', 'field1']; delete request.body; loggerHelper.auditRequest(request, options); @@ -300,7 +304,7 @@ describe('logger-helpers tests', function(){ sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest }); }); - it('Should audit log without body, when body is number (not json)', function(){ + it('Should audit log without body, when body is number (not json)', function () { options.request.excludeBody = ['field3', 'field1']; request.body = 3; let prevBody = _.cloneDeep(request.body); @@ -312,7 +316,7 @@ describe('logger-helpers tests', function(){ should.deepEqual(request.body, prevBody, 'body of request change'); }); - it('Should audit log without body, when body is string (not json)', function(){ + it('Should audit log without body, when body is string (not json)', function () { options.request.excludeBody = ['field3', 'field1']; request.body = 'test'; let prevBody = _.cloneDeep(request.body); @@ -324,9 +328,9 @@ describe('logger-helpers tests', function(){ should.deepEqual(request.body, prevBody, 'body of request change'); }); - it('Should audit log without body, when body is json array', function(){ + it('Should audit log without body, when body is json array', function () { options.request.excludeBody = ['field3', 'field1']; - let newBody = ['a','b','c']; + let newBody = ['a', 'b', 'c']; request.body = _.cloneDeep(newBody); expectedAuditRequest.body = JSON.stringify(newBody); let prevBody = _.cloneDeep(request.body); @@ -339,20 +343,20 @@ describe('logger-helpers tests', function(){ }); }); - describe('When calling auditResponse', function(){ - afterEach(function(){ + describe('When calling auditResponse', function () { + afterEach(function () { utils.shouldAuditURL.reset(); }); - describe('And shouldAuditURL returns false', function(){ - it('Should not audit request/response', function(){ + describe('And shouldAuditURL returns false', function () { + it('Should not audit request/response', function () { shouldAuditURLStub.returns(false); loggerHelper.auditResponse(request, response, options); sinon.assert.notCalled(loggerInfoStub); }); }); - describe('And shouldAuditURL returns true', function(){ - it('Should audit request if options.request.audit is true', function(){ + describe('And shouldAuditURL returns true', function () { + it('Should audit request if options.request.audit is true', function () { shouldAuditURLStub.returns(true); options.request.audit = true; clock.tick(elapsed); @@ -363,7 +367,40 @@ describe('logger-helpers tests', function(){ response: expectedAuditResponse }); }); - it('Should audit request if options.request.audit is true', function(){ + it('Should log as error if getLogLevel returns error', () => { + getLogLevelStub.returns('error'); + shouldAuditURLStub.returns(true); + loggerHelper.auditResponse(request, response, options); + + sinon.assert.calledOnce(loggerErrorStub); + sinon.assert.calledWith(loggerErrorStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); + }) + it('Should log as info if getLogLevel returns garbage', () => { + getLogLevelStub.returns('garbage'); + shouldAuditURLStub.returns(true); + loggerHelper.auditResponse(request, response, options); + + sinon.assert.calledOnce(loggerInfoStub); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); + }) + it('Should log as info if getLogLevel returns undefined', () => { + getLogLevelStub.returns(undefined); + shouldAuditURLStub.returns(true); + loggerHelper.auditResponse(request, response, options); + + sinon.assert.calledOnce(loggerInfoStub); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); + }) + it('Should audit request if options.request.audit is true', function () { shouldAuditURLStub.returns(true); options.request.audit = true; clock.tick(elapsed); @@ -374,7 +411,7 @@ describe('logger-helpers tests', function(){ response: expectedAuditResponse }); }); - it('Should not audit request if options.request.audit is false', function(){ + it('Should not audit request if options.request.audit is false', function () { shouldAuditURLStub.returns(true); options.request.audit = false; clock.tick(elapsed); @@ -385,7 +422,7 @@ describe('logger-helpers tests', function(){ response: expectedAuditResponse }); }); - it('Should audit response if options.response.audit is true', function(){ + it('Should audit response if options.response.audit is true', function () { shouldAuditURLStub.returns(true); options.response.audit = true; clock.tick(elapsed); @@ -396,7 +433,7 @@ describe('logger-helpers tests', function(){ response: expectedAuditResponse }); }); - it('Should not audit response if options.response.audit is false', function(){ + it('Should not audit response if options.response.audit is false', function () { shouldAuditURLStub.returns(true); options.response.audit = false; clock.tick(elapsed); @@ -407,7 +444,7 @@ describe('logger-helpers tests', function(){ response: undefined }); }); - it('Should log empty values as N/A', function(){ + it('Should log empty values as N/A', function () { request = undefined; response = undefined; @@ -432,113 +469,129 @@ describe('logger-helpers tests', function(){ timestamp: NA, timestamp_ms: NA, elapsed: 0, - headers:NA, + headers: NA, body: NA } }); }); }); - describe('And exclude Body contains field to exclude', function(){ - before(function(){ + describe('And exclude Body contains field to exclude', function () { + before(function () { shouldAuditURLStub.returns(true); }); - afterEach(function(){ + afterEach(function () { expectedAuditResponse.body = JSON.stringify(body); }); - it('Should audit log with body, if no excludeBody was written in options', function(){ + it('Should audit log with body, if no excludeBody was written in options', function () { loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); }); - it('Should audit log without body, when excludeBody with \'*\'', function(){ + it('Should audit log without body, when excludeBody with \'*\'', function () { options.response.excludeBody = [ALL_FIELDS]; let prevBody = _.cloneDeep(response.body); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); expectedAuditResponse.body = NA; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); - it('Should audit log without body, when excludeBody with \'*\' and body is plain text', function(){ + it('Should audit log without body, when excludeBody with \'*\' and body is plain text', function () { options.response.excludeBody = [ALL_FIELDS]; response.body = 'test'; let prevBody = _.cloneDeep(response.body); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); expectedAuditResponse.body = NA; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); - it('Should audit log without body, when excludeBody by field and all body', function(){ + it('Should audit log without body, when excludeBody by field and all body', function () { options.response.excludeBody = ['field1', ALL_FIELDS]; - response._body = JSON.stringify({ 'field1' : 1, 'field2' : 'test'}); + response._body = JSON.stringify({ 'field1': 1, 'field2': 'test' }); let prevBody = _.cloneDeep(response.body); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); expectedAuditResponse.body = NA; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); - it('Should audit log body without specific field, when excludeBody by existing and unexisting field', function(){ + it('Should audit log body without specific field, when excludeBody by existing and unexisting field', function () { options.response.excludeBody = ['field3', 'field1']; - response._body = JSON.stringify({ 'field1' : 1, 'field2' : 'test'}); + response._body = JSON.stringify({ 'field1': 1, 'field2': 'test' }); let prevBody = _.cloneDeep(response.body); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); - expectedAuditResponse.body = JSON.stringify({'field2' : 'test'}); - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + expectedAuditResponse.body = JSON.stringify({ 'field2': 'test' }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); - it('Should audit log without body, when no body in response and excludeBody by field', function(){ + it('Should audit log without body, when no body in response and excludeBody by field', function () { options.response.excludeBody = ['field3', 'field1']; delete response._body; let prevBody = _.cloneDeep(response.body); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); expectedAuditResponse.body = NA; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); }); - describe('And exclude headers contains an header to exclude', function(){ + describe('And exclude headers contains an header to exclude', function () { var headerToExclude = 'header-to-exclude'; before(() => { shouldAuditURLStub.returns(true); }); - beforeEach(function(){ + beforeEach(function () { response.headers[headerToExclude] = 'other-value'; }); - it('Should audit log without the specified header', function(){ + it('Should audit log without the specified header', function () { options.response.excludeHeaders = [headerToExclude]; let prevHeaders = _.cloneDeep(response.headers); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.headers, prevHeaders, 'headers of response change'); }); - it('Should audit log without all headers', function(){ + it('Should audit log without all headers', function () { options.response.excludeHeaders = [ALL_FIELDS]; let prevHeaders = _.cloneDeep(response.headers); loggerHelper.auditResponse(request, response, options); expectedAuditResponse.headers = NA; sinon.assert.calledOnce(loggerInfoStub); - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.headers, prevHeaders, 'headers of response change'); }); - it('Should audit log without the specified headers, if there are more than one', function(){ + it('Should audit log without the specified headers, if there are more than one', function () { var anotherHeaderToExclude = 'another'; options.response.excludeHeaders = [headerToExclude, anotherHeaderToExclude]; response.headers[anotherHeaderToExclude] = 'some value'; @@ -546,33 +599,37 @@ describe('logger-helpers tests', function(){ let prevHeaders = _.cloneDeep(response.headers); loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.headers, prevHeaders, 'headers of response change'); }); - it('Should audit log with all headers, if exclude headers is an empty list', function(){ + it('Should audit log with all headers, if exclude headers is an empty list', function () { options.response.excludeHeaders = ['other-header']; loggerHelper.auditResponse(request, response, options); sinon.assert.calledOnce(loggerInfoStub); expectedAuditResponse.headers[headerToExclude] = 'other-value'; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); - // Clear created header for other tests + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); + // Clear created header for other tests delete expectedAuditResponse.headers[headerToExclude]; }); }); - describe('And mask Body', function(){ - before(function(){ + describe('And mask Body', function () { + before(function () { shouldAuditURLStub.returns(true); }); - afterEach(function(){ + afterEach(function () { expectedAuditResponse.body = JSON.stringify(body); }); - it('Should audit log with body, if mask body specific field', function(){ + it('Should audit log with body, if mask body specific field', function () { options.response.maskBody = ['test1']; let newBody = { body: 'body', @@ -584,8 +641,10 @@ describe('logger-helpers tests', function(){ sinon.assert.calledOnce(loggerInfoStub); newBody.test1 = MASK; expectedAuditResponse.body = JSON.stringify(newBody); - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); it('Should not mask body if response content type is not json', () => { @@ -604,8 +663,10 @@ describe('logger-helpers tests', function(){ expectedAuditResponse.body = JSON.stringify(newBody); expectedAuditResponse.headers['content-type'] = testContentType; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); it('Should not mask body if no headers', () => { @@ -625,8 +686,10 @@ describe('logger-helpers tests', function(){ expectedAuditResponse.headers = NA; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); it('Should not mask body if no content type header', () => { @@ -645,8 +708,10 @@ describe('logger-helpers tests', function(){ expectedAuditResponse.body = JSON.stringify(newBody); expectedAuditResponse.headers['content-type'] = undefined; - sinon.assert.calledWith(loggerInfoStub, { request: expectedAuditRequest, - response: expectedAuditResponse }); + sinon.assert.calledWith(loggerInfoStub, { + request: expectedAuditRequest, + response: expectedAuditResponse + }); should.deepEqual(response.body, prevBody, 'body of resopnse change'); }); }); diff --git a/test/utils-test.js b/test/utils-test.js index c2bc218..c295e8c 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -4,21 +4,21 @@ var httpMocks = require('node-mocks-http'), utils = require('../lib/utils'), should = require('should'); -describe('utils tests', function(){ - describe('When calling getUrl', function(){ +describe('utils tests', function () { + describe('When calling getUrl', function () { var request; var expectedPath = 'path/123'; - beforeEach(function(){ + beforeEach(function () { request = httpMocks.createRequest({ method: 'POST', url: expectedPath }); }); - it('Should return url for a path with no route', function(){ + it('Should return url for a path with no route', function () { var url = utils.getUrl(request); should(url).eql(expectedPath); }); - it('Should return url for a path - route doesn\'t change the url', function(){ + it('Should return url for a path - route doesn\'t change the url', function () { request.baseUrl = 'path'; request.route = { path: '/:id' @@ -27,16 +27,16 @@ describe('utils tests', function(){ var url = utils.getUrl(request); should(url).eql(expectedPath); }); - it('Should N/A for not valid req obj', function(){ + it('Should N/A for not valid req obj', function () { var url = utils.getUrl(undefined); should(url).eql('N/A'); }); }); - describe('When calling getRoute', function(){ + describe('When calling getRoute', function () { var request; var expectedRoute = '/path/:id'; - beforeEach(function(){ + beforeEach(function () { request = httpMocks.createRequest({ method: 'POST', route: { @@ -45,24 +45,24 @@ describe('utils tests', function(){ baseUrl: '/path', }); }); - it('Should return url_route for a path', function(){ + it('Should return url_route for a path', function () { var url_route = utils.getRoute(request); should(url_route).eql(expectedRoute); }); - it('Should N/A for not valid req obj', function(){ + it('Should N/A for not valid req obj', function () { var url = utils.getUrl(undefined); should(url).eql('N/A'); }); }); - describe('When calling maskJsonValue', function(){ + describe('When calling maskJsonValue', function () { var originalJsonObj; var expectedJsonObj; var fieldsToMask; var expectedMaskedValue = 'XXXXX'; - beforeEach(function(){ + beforeEach(function () { originalJsonObj = { secret: 'secret', public: 'public' @@ -75,16 +75,16 @@ describe('utils tests', function(){ fieldsToMask = ['secret']; }); - it('Should return original Json Object if specified field not found', function(){ + it('Should return original Json Object if specified field not found', function () { fieldsToMask = ['other_field']; var masked = utils.maskJson(originalJsonObj, fieldsToMask); should(masked).eql(originalJsonObj); }); - it('Should return original Json Object with specified field masked', function(){ + it('Should return original Json Object with specified field masked', function () { var masked = utils.maskJson(originalJsonObj, fieldsToMask); should(masked).eql(expectedJsonObj); }); - it('Should return original Json Object with more than one specified field masked', function(){ + it('Should return original Json Object with more than one specified field masked', function () { fieldsToMask = ['secret', 'public']; expectedJsonObj = { secret: expectedMaskedValue, @@ -94,15 +94,15 @@ describe('utils tests', function(){ var masked = utils.maskJson(originalJsonObj, fieldsToMask); should(masked).eql(expectedJsonObj); }); - it('Should return null for null input', function(){ + it('Should return null for null input', function () { var masked = utils.maskJson(null, fieldsToMask); should(masked).eql(null); }); - it('Should return undefined for undefined input', function(){ + it('Should return undefined for undefined input', function () { var masked = utils.maskJson(undefined, fieldsToMask); should(masked).eql(undefined); }); - it('Should return empty object for empty object input', function(){ + it('Should return empty object for empty object input', function () { var masked = utils.maskJson({}, fieldsToMask); should(masked).eql({}); }); @@ -147,23 +147,23 @@ describe('utils tests', function(){ }); }); - describe('When calling shouldAuditURL', function(){ + describe('When calling shouldAuditURL', function () { var urls = []; var request; var urlToExclude = 'exclude'; var urlNotToExclude = 'audit'; - beforeEach(function(){ + beforeEach(function () { urls = ['exclude']; request = httpMocks.createRequest({ method: 'POST', url: urlNotToExclude + '/123' }); }); - it('Should return true if none of the specified urls match the current path', function(){ + it('Should return true if none of the specified urls match the current path', function () { var res = utils.shouldAuditURL(urls, request); should(res).eql(true); }); - it('Should return false if one of the specified urls match the current path', function(){ + it('Should return false if one of the specified urls match the current path', function () { request.url = urlToExclude + '/123'; var res = utils.shouldAuditURL(urls, request); should(res).eql(false); @@ -179,4 +179,40 @@ describe('utils tests', function(){ should(res).eql(false); }); }); + describe('When calling getLogLevel', () => { + it('Should return info if levels is undefined', () => { + let result = utils.getLogLevel(200, undefined); + should(result).eql('info'); + }); + it('Should return info if levels is empty array', () => { + let result = utils.getLogLevel(200, []); + should(result).eql('info'); + }); + it('Should return info if exact match but levelMap value is not valid level', () => { + let result = utils.getLogLevel(200, { '200': 'not-valid' }); + should(result).eql('info'); + }) + it('Should return correct exact match of status with level map', () => { + let result = utils.getLogLevel(200, { '200': 'error' }); + should(result).eql('error'); + }); + it('Should fallback to status group if exact match not found', () => { + let result = utils.getLogLevel(404, { + '200': 'error', + '4xx': 'debug', + '401': 'error', + '500': 'error' + }); + should(result).eql('debug'); + }) + it('Should fallback to default info level if no match is found', () => { + let result = utils.getLogLevel(302, { + '200': 'error', + '4xx': 'debug', + '401': 'error', + '500': 'error' + }); + should(result).eql('info'); + }) + }); }); \ No newline at end of file