balena-cli/tests/balena-api-mock.ts
Scott Lowe 33552724a1 Fix issues with devices associated with inaccessible applications.
Change-type: patch
Resolves: #1530
Signed-off-by: Scott Lowe <scott@balena.io>
2019-12-13 09:33:59 +01:00

283 lines
6.6 KiB
TypeScript

/**
* @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 _ from 'lodash';
import * as nock from 'nock';
export 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 expectTestApp() {
this.scope
.get(/^\/v\d+\/application($|\?)/)
.reply(200, { d: [{ id: 1234567 }] });
}
public expectTestDevice(
fullUUID = 'f63fd7d7812c34c4c14ae023fdff05f5',
inaccessibleApp = false,
) {
const id = 7654321;
this.scope.get(/^\/v\d+\/device($|\?)/).reply(200, {
d: [
{
id,
uuid: fullUUID,
belongs_to__application: inaccessibleApp
? []
: [{ app_name: 'test' }],
},
],
});
}
public expectAppEnvVars() {
this.scope
.get(/^\/v\d+\/application_environment_variable($|\?)/)
.reply(200, {
d: [
{
id: 120101,
name: 'var1',
value: 'var1-val',
},
{
id: 120102,
name: 'var2',
value: '22',
},
],
});
}
public expectAppConfigVars() {
this.scope.get(/^\/v\d+\/application_config_variable($|\?)/).reply(200, {
d: [
{
id: 120300,
name: 'RESIN_SUPERVISOR_NATIVE_LOGGER',
value: 'false',
},
],
});
}
public expectAppServiceVars() {
this.scope
.get(/^\/v\d+\/service_environment_variable($|\?)/)
.reply(function(uri, _requestBody) {
const match = uri.match(/service_name%20eq%20%27(.+?)%27/);
const serviceName = (match && match[1]) || undefined;
let varArray: any[];
if (serviceName) {
const varObj = appServiceVarsByService[serviceName];
varArray = varObj ? [varObj] : [];
} else {
varArray = _.map(appServiceVarsByService, value => value);
}
return [200, { d: varArray }];
});
}
public expectDeviceEnvVars() {
this.scope.get(/^\/v\d+\/device_environment_variable($|\?)/).reply(200, {
d: [
{
id: 120203,
name: 'var3',
value: 'var3-val',
},
{
id: 120204,
name: 'var4',
value: '44',
},
],
});
}
public expectDeviceConfigVars() {
this.scope.get(/^\/v\d+\/device_config_variable($|\?)/).reply(200, {
d: [
{
id: 120400,
name: 'RESIN_SUPERVISOR_POLL_INTERVAL',
value: '900900',
},
],
});
}
public expectDeviceServiceVars() {
this.scope
.get(/^\/v\d+\/device_service_environment_variable($|\?)/)
.reply(function(uri, _requestBody) {
const match = uri.match(/service_name%20eq%20%27(.+?)%27/);
const serviceName = (match && match[1]) || undefined;
let varArray: any[];
if (serviceName) {
const varObj = deviceServiceVarsByService[serviceName];
varArray = varObj ? [varObj] : [];
} else {
varArray = _.map(deviceServiceVarsByService, value => value);
}
return [200, { d: varArray }];
});
}
public expectConfigVars() {
this.scope.get('/config/vars').reply(200, {
reservedNames: [],
reservedNamespaces: [],
invalidRegex: '/^d|W/',
whiteListedNames: [],
whiteListedNamespaces: [],
blackListedNames: [],
configVarSchema: [],
});
}
public expectService(serviceName: string, serviceId = 243768) {
this.scope.get(/^\/v\d+\/service($|\?)/).reply(200, {
d: [{ id: serviceId, service_name: serviceName }],
});
}
// User details are cached in the SDK
// so often we don't know if we can expect the whoami request
public expectWhoAmI(persist = false, optional = true) {
const get = (persist ? this.scope.persist() : this.scope).get(
'/user/v1/whoami',
);
(optional ? get.optionally() : get).reply(200, {
id: 99999,
username: 'testuser',
email: 'testuser@test.com',
});
}
public expectMixpanel(optional = true) {
const get = this.scope.get(/^\/mixpanel\/track/);
(optional ? get.optionally() : get).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)
}
public debug() {
const scope = this.scope;
let mocks = scope.pendingMocks();
console.error(`pending mocks ${mocks.length}: ${mocks}`);
this.scope.on('request', function(_req, _interceptor, _body) {
console.log(`>> REQUEST:` + _req.path);
mocks = scope.pendingMocks();
console.error(`pending mocks ${mocks.length}: ${mocks}`);
});
this.scope.on('replied', function(_req) {
console.log(`<< REPLIED:` + _req.path);
});
}
}
const appServiceVarsByService: { [key: string]: any } = {
service1: {
id: 120110,
name: 'svar1',
value: 'svar1-value',
service: [
{
id: 210110,
service_name: 'service1',
},
],
},
service2: {
id: 120111,
name: 'svar2',
value: 'svar2-value',
service: [
{
id: 210111,
service_name: 'service2',
},
],
},
};
const deviceServiceVarsByService: { [key: string]: any } = {
service1: {
id: 120120,
name: 'svar3',
value: 'svar3-value',
service: [
{
id: 210110,
service_name: 'service1',
},
],
},
service2: {
id: 120121,
name: 'svar4',
value: 'svar4-value',
service: [
{
id: 210111,
service_name: 'service2',
},
],
},
};