diff --git a/lib/in-process-server-test-helpers.js b/lib/in-process-server-test-helpers.js index 4679a8c9c821a1ef9ea8784d45c21e0796105882..2187427362885bda191c6c19d35b1b749bb22493 100644 --- a/lib/in-process-server-test-helpers.js +++ b/lib/in-process-server-test-helpers.js @@ -13,6 +13,7 @@ 'use strict' const config = require('./test-config') +const serverConfig = require('./server-config') let startCalled = false @@ -31,13 +32,13 @@ function start() { } startCalled = true - const originalArgv = process.argv - // Modifying argv during import is a bit dirty, but it works, and avoids - // making bigger changes to server.js. - process.argv = ['', '', config.port, 'localhost'] + // Modifying config is a bit dirty, but it works, and avoids making bigger + // changes to server.js. + serverConfig.bind = { + host: 'localhost', + port: config.port, + } const server = require('../server') - - process.argv = originalArgv return server } diff --git a/lib/server-config.js b/lib/server-config.js index e3634df31952f328abbf1f04b83ae5c37af80f4a..838b6004d046055652a9bcdd966e9ed4648b9968 100644 --- a/lib/server-config.js +++ b/lib/server-config.js @@ -60,6 +60,7 @@ const config = { intervalSeconds: process.env.GITHUB_DEBUG_INTERVAL_SECONDS || 300, }, }, + trace: envFlag(process.env.TRACE_SERVICES), }, font: { path: process.env.FONT_PATH || defaults.font.path, diff --git a/package-lock.json b/package-lock.json index 4d022c25ec025a193c31a85b828200697db4b4bd..faf5cf46608560a97fc2d6a6e020c55d7bcd20c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -378,8 +378,7 @@ "acorn": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "optional": true + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" }, "acorn-dynamic-import": { "version": "2.0.2", @@ -756,6 +755,27 @@ "chalk": "^1.1.3", "esutils": "^2.0.2", "js-tokens": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "babel-core": { @@ -2237,6 +2257,15 @@ } } }, + "camelo": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/camelo/-/camelo-1.1.11.tgz", + "integrity": "sha512-kYyJyTKwCcKXtWMNqHogVBgQMRArxK0sLcPaMgDteo4vHr5vRytzBZt6n2T81RrQIU9FPi4MW8X+snzMDQqH0w==", + "requires": { + "regex-escape": "^3.3.0", + "uc-first-array": "^1.0.0" + } + }, "camp": { "version": "17.2.1", "resolved": "https://registry.npmjs.org/camp/-/camp-17.2.1.tgz", @@ -2334,16 +2363,23 @@ } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "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" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + } } }, "chardet": { @@ -2491,8 +2527,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2513,14 +2548,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2535,20 +2568,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2665,8 +2695,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2678,7 +2707,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2693,7 +2721,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2701,14 +2728,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2727,7 +2752,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2808,8 +2832,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2821,7 +2844,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2907,8 +2929,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2944,7 +2965,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2964,7 +2984,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3008,14 +3027,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -3239,7 +3256,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", - "dev": true, "requires": { "color-name": "^1.1.1" } @@ -3247,8 +3263,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.1.2", @@ -3725,8 +3740,7 @@ "cssom": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", - "optional": true + "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=" }, "cssstyle": { "version": "0.2.37", @@ -4300,6 +4314,22 @@ "integrity": "sha1-WUjLKG8uSO3DslGnz8H3iDOW1lw=", "dev": true }, + "emojic": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/emojic/-/emojic-1.1.14.tgz", + "integrity": "sha512-n6yEaHU9GB6AfHTDAvV/263Ds+nIvboyiYYrAOuZox64RP6AlXPW1jbLsT3cBX+8hRVc7IV3P59ODPr2bC0OJA==", + "requires": { + "camelo": "^1.0.0", + "emojilib": "^2.0.2", + "iterate-object": "^1.2.0", + "r-json": "^1.1.0" + } + }, + "emojilib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.3.0.tgz", + "integrity": "sha512-gDrDiITC7W5lvXTtUmk0k9soizsjfY9Wqr2GWsEMX7+cKChoxLt3RnT8zW32BvN1SFDQbi7+D5/+HNu/R+qvKA==" + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -5655,6 +5685,27 @@ "chalk": "^1.1.3", "error-stack-parser": "^2.0.0", "string-length": "^1.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "from2": { @@ -6853,6 +6904,11 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "iterate-object": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.2.tgz", + "integrity": "sha1-JOwVr/pdADnog5aVohwsrh9Ftms=" + }, "jison": { "version": "0.4.13", "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.13.tgz", @@ -8778,7 +8834,6 @@ "version": "0.1.4", "bundled": true, "dev": true, - "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -9809,8 +9864,7 @@ "longest": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "loose-envify": { "version": "1.3.1", @@ -11765,6 +11819,11 @@ } } }, + "r-json": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.2.8.tgz", + "integrity": "sha1-dEBWDMHt8AudjZT6MLytfd6U6uI=" + }, "ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", @@ -12153,6 +12212,11 @@ "private": "^0.1.6" } }, + "regex-escape": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/regex-escape/-/regex-escape-3.4.8.tgz", + "integrity": "sha512-DG0VFPTDwfl+XsuVaM/0RmGJvCpZNB9UG/limzbev50XQ44G4mbOG+0Eh5M7Al9JB68NbP7YeY1KhDzpnX7qSw==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -13618,10 +13682,19 @@ "dev": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "requires": { + "has-flag": "^3.0.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + } + } }, "supports-hyperlinks": { "version": "1.0.1", @@ -14121,6 +14194,19 @@ "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==", "dev": true }, + "uc-first-array": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/uc-first-array/-/uc-first-array-1.1.8.tgz", + "integrity": "sha512-LTAnb8G/BxmXyBqiUxRhlbYt+IPCCpAOJJsC3xp5cwtXZKdtBfBxAMWRJlNzknM+Gpw3DRD2K3sQ64srroRf3w==", + "requires": { + "ucfirst": "^1.0.0" + } + }, + "ucfirst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ucfirst/-/ucfirst-1.0.0.tgz", + "integrity": "sha1-ThBbZEjQXiZOzsQ14LkZNjxfLy8=" + }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", @@ -14496,6 +14582,27 @@ "requires": { "chalk": "^1.1.1", "object-assign": "^4.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "verror": { @@ -14892,6 +14999,19 @@ "moment": "^2.11.2" }, "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/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" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -14906,6 +15026,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true } } }, diff --git a/package.json b/package.json index 50d0186f82a1ea0cb2f43c862564f4c89fe6159a..8c5281ccddc1b5806e4c1c79fa50e193fe01361e 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,10 @@ }, "dependencies": { "camp": "~17.2.1", + "chalk": "^2.4.1", "chrome-web-store-item-property": "~1.1.2", "dot": "~1.1.2", + "emojic": "^1.1.14", "escape-string-regexp": "^1.0.5", "glob": "^7.1.1", "gm": "^1.23.0", @@ -69,6 +71,7 @@ "test:js:server": "HANDLE_INTERNAL_ERRORS=false mocha \"*.spec.js\" \"lib/**/*.spec.js\" \"services/**/*.spec.js\"", "test:integration": "mocha \"services/**/*.integration.js\"", "test:services": "HANDLE_INTERNAL_ERRORS=false mocha --delay lib/service-test-runner/cli.js", + "test:services:trace": "TRACE_SERVICES=true npm run test:services -- $*", "test:services:pr:prepare": "node lib/service-test-runner/pull-request-services-cli.js > pull-request-services.log", "test:services:pr:run": "HANDLE_INTERNAL_ERRORS=false mocha --delay lib/service-test-runner/cli.js --stdin < pull-request-services.log", "test:services:pr": "npm run test:services:pr:prepare && npm run test:services:pr:run", diff --git a/services/base.js b/services/base.js index a5450c6ea6a0da58efbd6592bd1ca8f209c69a88..3c1e5a51f7945a4cbe9680d740c4fdafd68b4d24 100644 --- a/services/base.js +++ b/services/base.js @@ -1,6 +1,9 @@ 'use strict' const Joi = require('joi') +// See available emoji at http://emoji.muan.co/ +const emojic = require('emojic') +const chalk = require('chalk') const { NotFound, InvalidResponse, Inaccessible } = require('./errors') const queryString = require('query-string') const { @@ -10,6 +13,14 @@ const { setBadgeColor, } = require('../lib/badge-data') const { checkErrorResponse, asJson } = require('../lib/error-helper') +// Config is loaded globally but it would be better to inject it. To do that, +// there needs to be one instance of the service created at registration time, +// which gets the config injected into it, instead of one instance per request. +// That way most of the current static methods could become instance methods, +// thereby gaining access to the injected config. +const { + services: { trace: enableTraceLogging }, +} = require('../lib/server-config') class BaseService { constructor({ sendAndCacheRequest }, { handleInternalErrors }) { @@ -141,10 +152,20 @@ class BaseService { } async invokeHandler(namedParams, queryParams) { + const logTrace = (...args) => this.constructor.logTrace(...args) + logTrace( + 'inbound', + emojic.womanCook, + 'Service class', + this.constructor.name + ) + logTrace('inbound', emojic.ticket, 'Named params', namedParams) + logTrace('inbound', emojic.crayon, 'Query params', queryParams) try { return await this.handle(namedParams, queryParams) } catch (error) { if (error instanceof NotFound) { + logTrace('outbound', emojic.noGoodWoman, 'Handled error', error) return { message: error.prettyMessage, color: 'red', @@ -153,18 +174,36 @@ class BaseService { error instanceof InvalidResponse || error instanceof Inaccessible ) { + logTrace('outbound', emojic.noGoodWoman, 'Handled error', error) return { message: error.prettyMessage, color: 'lightgray', } } else if (this._handleInternalErrors) { - console.log(error) + if ( + !logTrace( + 'unhandledError', + emojic.boom, + 'Unhandled internal error', + error + ) + ) { + // This is where we end up if an unhandled exception is thrown in + // production. Send the error to the logs. + console.log(error) + } return { label: 'shields', message: 'internal error', color: 'lightgray', } } else { + logTrace( + 'unhandledError', + emojic.boom, + 'Unhandled internal error', + error + ) throw error } } @@ -232,6 +271,7 @@ class BaseService { namedParams, queryParams ) + this.logTrace('outbound', emojic.shield, 'Service data', serviceData) const badgeData = this._makeBadgeData(queryParams, serviceData) // Assumes the final capture group is the extension @@ -241,6 +281,31 @@ class BaseService { }) ) } + + static _formatLabelForStage(stage, label) { + const colorFn = { + inbound: chalk.black.bgBlue, + fetch: chalk.black.bgYellow, + validate: chalk.black.bgGreen, + unhandledError: chalk.white.bgRed, + outbound: chalk.black.bgBlue, + }[stage] + return colorFn(` ${label} `) + } + + static logTrace(stage, symbol, label, ...content) { + if (enableTraceLogging) { + console.log( + this._formatLabelForStage(stage, label), + symbol, + '\n', + ...content + ) + return true + } else { + return false + } + } } class BaseJsonService extends BaseService { @@ -250,29 +315,47 @@ class BaseJsonService extends BaseService { stripUnknown: true, }) if (error) { + this.logTrace( + 'error', + emojic.womanShrugging, + 'Response did not match schema', + error.message + ) throw new InvalidResponse({ prettyMessage: 'invalid json response', underlyingError: error, }) } else { + this.logTrace('validate', emojic.bathtub, 'JSON after validation', value) return value } } async _requestJson({ schema, url, options = {}, notFoundMessage }) { + const logTrace = (...args) => this.constructor.logTrace('fetch', ...args) if (!schema || !schema.isJoi) { throw Error('A Joi schema is required') } - return this._sendAndCacheRequest(url, { + const mergedOptions = { ...{ headers: { Accept: 'application/json' } }, ...options, - }) + } + logTrace(emojic.bowAndArrow, 'Request', url, '\n', mergedOptions) + return this._sendAndCacheRequest(url, mergedOptions) + .then(({ res, buffer }) => { + logTrace(emojic.dart, 'Response status code', res.statusCode) + return { res, buffer } + }) .then( checkErrorResponse.asPromise( notFoundMessage ? { notFoundMessage: notFoundMessage } : undefined ) ) .then(asJson) + .then(json => { + logTrace(emojic.dart, 'Response JSON (before validation)', json) + return json + }) .then(json => this.constructor._validate(json, schema)) } } diff --git a/services/base.spec.js b/services/base.spec.js index b553f43f6af6a8a548fc21d8d25af7092415058e..5faf615d7eec25e2f163d1e52a3da89ee6b29f64 100644 --- a/services/base.spec.js +++ b/services/base.spec.js @@ -83,6 +83,48 @@ describe('BaseService', () => { expect(serviceData).to.deep.equal({ message: 'Hello bar.bar.bar!' }) }) + describe('Logging', function() { + let sandbox + beforeEach(function() { + sandbox = sinon.createSandbox() + }) + afterEach(function() { + sandbox.restore() + }) + beforeEach(function() { + sinon.stub(DummyService, 'logTrace') + }) + it('Invokes the logger as expected', async function() { + const serviceInstance = new DummyService({}, defaultConfig) + await serviceInstance.invokeHandler( + { + namedParamA: 'bar.bar.bar', + }, + { queryParamA: '!' } + ) + expect(DummyService.logTrace).to.be.calledWithMatch( + 'inbound', + sinon.match.string, + 'Service class', + 'DummyService' + ) + expect(DummyService.logTrace).to.be.calledWith( + 'inbound', + sinon.match.string, + 'Named params', + { + namedParamA: 'bar.bar.bar', + } + ) + expect(DummyService.logTrace).to.be.calledWith( + 'inbound', + sinon.match.string, + 'Query params', + { queryParamA: '!' } + ) + }) + }) + describe('Error handling', function() { it('Handles internal errors', async function() { const serviceInstance = new DummyService(