diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index aec182aa..a8c1f878 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -681,12 +681,12 @@ } }, "@types/nock": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz", - "integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", + "integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==", "dev": true, "requires": { - "@types/node": "*" + "nock": "*" } }, "@types/node": { @@ -12711,20 +12711,25 @@ } }, "nock": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", - "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-11.7.0.tgz", + "integrity": "sha512-7c1jhHew74C33OBeRYyQENT+YXQiejpwIrEjinh6dRurBae+Ei4QjeUaPlkptIF0ZacEiVCnw8dWaxqepkiihg==", "dev": true, "requires": { "chai": "^4.1.2", "debug": "^4.1.0", - "deep-equal": "^1.0.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.5", + "lodash": "^4.17.13", "mkdirp": "^0.5.0", - "propagate": "^1.0.0", - "qs": "^6.5.1", - "semver": "^5.5.0" + "propagate": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "node-abi": { @@ -13873,9 +13878,9 @@ "integrity": "sha1-cflVSpic9r+Jh7Q6UhMtXHkxJlc=" }, "propagate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", - "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, "proper-lockfile": { diff --git a/package.json b/package.json index e0e44887..0b16a4f3 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "@types/mocha": "^5.2.7", "@types/mz": "0.0.32", "@types/net-keepalive": "^0.4.0", - "@types/nock": "^10.0.3", + "@types/nock": "^11.0.7", "@types/node": "10.14.5", "@types/prettyjson": "0.0.28", "@types/raven": "2.5.1", @@ -136,7 +136,7 @@ "husky": "^3.0.9", "intercept-stdout": "^0.1.2", "mocha": "^6.2.0", - "nock": "^10.0.6", + "nock": "^11.0.7", "parse-link-header": "~1.0.1", "pkg": "^4.4.0", "prettier": "1.17.0", diff --git a/tests/balena-api-mock.ts b/tests/balena-api-mock.ts new file mode 100644 index 00000000..4a7b4fad --- /dev/null +++ b/tests/balena-api-mock.ts @@ -0,0 +1,92 @@ +/** + * @license + * Copyright 2019 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as nock from 'nock'; + +class BalenaAPIMock { + public static basePathPattern = /api\.balena-cloud\.com/; + public readonly scope: nock.Scope; + // Expose `scope` as `expect` to allow for better semantics in tests + public readonly expect = this.scope; + + // For debugging tests + get unfulfilledCallCount(): number { + return this.scope.pendingMocks().length; + } + + constructor() { + nock.cleanAll(); + + if (!nock.isActive()) { + nock.activate(); + } + + this.scope = nock(BalenaAPIMock.basePathPattern); + + nock.emitter.on('no match', this.handleUnexpectedRequest); + } + + public done() { + // scope.done() will throw an error if there are expected api calls that have not happened. + // So ensures that all expected calls have been made. + this.scope.done(); + // Remove 'no match' handler, for tests using nock without this module + nock.emitter.removeListener('no match', this.handleUnexpectedRequest); + // Restore unmocked behaviour + nock.cleanAll(); + nock.restore(); + } + + public expectConfigVars() { + this.scope.get('/config/vars').reply(200, { + reservedNames: [], + reservedNamespaces: [], + invalidRegex: '/^d|W/', + whiteListedNames: [], + whiteListedNamespaces: [], + blackListedNames: [], + configVarSchema: [], + }); + } + + // User details are cached in the SDK + // so often we don't know if we can expect the whoami request + public expectOptionalWhoAmI() { + this.scope + .get('/user/v1/whoami') + .optionally() + .reply(200, { + id: 99999, + username: 'testuser', + email: 'testuser@test.com', + }); + } + + public expectMixpanel() { + this.scope.get(/^\/mixpanel\/track/).reply(200, {}); + } + + protected handleUnexpectedRequest(req: any) { + console.error(`Unexpected http request!: ${req.path}`); + // Errors thrown here are not causing the tests to fail for some reason. + // Possibly due to CLI global error handlers? (error.js) + // (Also, nock should automatically throw an error, but also not happening) + // For now, the console.error is sufficient (will fail the test) + } +} + +export { BalenaAPIMock }; diff --git a/tests/helpers.ts b/tests/helpers.ts index 9a138572..0ce9346a 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -67,6 +67,10 @@ export const runCommand = async (cmd: string) => { }; export const balenaAPIMock = () => { + if (!nock.isActive()) { + nock.activate(); + } + return nock(/./) .get('/config/vars') .reply(200, {