mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-21 06:33:28 +00:00
4266dc6951
Change-type: patch
525 lines
12 KiB
TypeScript
525 lines
12 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2019-2021 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 path from 'path';
|
|
|
|
import { NockMock, ScopeOpts } from './nock-mock';
|
|
|
|
export const apiResponsePath = path.normalize(
|
|
path.join(__dirname, '..', 'test-data', 'api-response'),
|
|
);
|
|
|
|
const jHeader = { 'Content-Type': 'application/json' };
|
|
|
|
export class BalenaAPIMock extends NockMock {
|
|
constructor() {
|
|
super(/api\.balena-cloud\.com/);
|
|
}
|
|
|
|
public expectGetApplication({
|
|
notFound = false,
|
|
optional = false,
|
|
persist = false,
|
|
times = undefined as number | undefined,
|
|
expandArchitecture = false,
|
|
} = {}) {
|
|
const interceptor = this.optGet(/^\/v6\/application($|[(?])/, {
|
|
optional,
|
|
persist,
|
|
times,
|
|
});
|
|
if (notFound) {
|
|
interceptor.reply(200, { d: [] });
|
|
} else {
|
|
interceptor.replyWithFile(
|
|
200,
|
|
path.join(
|
|
apiResponsePath,
|
|
!expandArchitecture
|
|
? 'application-GET-v6-expanded-app-type.json'
|
|
: 'application-GET-v6-expanded-app-type-cpu-arch.json',
|
|
),
|
|
jHeader,
|
|
);
|
|
}
|
|
}
|
|
|
|
public expectDownloadConfig(opts: ScopeOpts = {}) {
|
|
this.optPost('/download-config', opts).reply(
|
|
200,
|
|
JSON.parse(`{
|
|
"applicationId":1301645,
|
|
"deviceType":"raspberrypi3",
|
|
"userId":43699,
|
|
"appUpdatePollInterval":600000,
|
|
"listenPort":48484,
|
|
"vpnPort":443,
|
|
"apiEndpoint":"https://api.balena-cloud.com",
|
|
"vpnEndpoint":"vpn.balena-cloud.com",
|
|
"registryEndpoint":"registry2.balena-cloud.com",
|
|
"deltaEndpoint":"https://delta.balena-cloud.com",
|
|
"mixpanelToken":"",
|
|
"apiKey":"nothingtoseehere"
|
|
}`),
|
|
);
|
|
}
|
|
|
|
public expectApplicationProvisioning(opts: ScopeOpts = {}) {
|
|
// The endpoint changed in balena-sdk v15.45.0:
|
|
// before: '/api-key/application/${applicationId}/provisioning'
|
|
// after: '/api-key/v1/'
|
|
this.optPost(/^\/api-key\/v[0-9]\/?$/, opts).reply(200, 'dummykey');
|
|
}
|
|
|
|
public expectGetMyApplication(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v6\/my_application($|[(?])/, opts).reply(
|
|
200,
|
|
JSON.parse(`{"d": [{
|
|
"organization": [{ "handle": "bob", "__metadata": {} }],
|
|
"id": 1301645,
|
|
"__metadata": { "uri": "/resin/my_application(@id)?@id=1301645" }}]}
|
|
`),
|
|
);
|
|
}
|
|
|
|
public expectGetAuth(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/auth\/v1\//, opts).reply(200, {
|
|
token: 'test',
|
|
});
|
|
}
|
|
|
|
public expectGetRelease({
|
|
notFound = false,
|
|
optional = false,
|
|
persist = false,
|
|
times = undefined as number | undefined,
|
|
} = {}) {
|
|
const interceptor = this.optGet(/^\/v6\/release($|[(?])/, {
|
|
persist,
|
|
optional,
|
|
times,
|
|
});
|
|
if (notFound) {
|
|
interceptor.reply(200, { d: [] });
|
|
} else {
|
|
this.optGet(/^\/v6\/release($|[(?])/, {
|
|
persist,
|
|
optional,
|
|
}).replyWithFile(
|
|
200,
|
|
path.join(apiResponsePath, 'release-GET-v6.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectPatchRelease({
|
|
replyBody = 'OK',
|
|
statusCode = 200,
|
|
inspectRequest = this.inspectNoOp,
|
|
optional = false,
|
|
persist = false,
|
|
times = undefined as number | undefined,
|
|
}) {
|
|
this.optPatch(/^\/v6\/release($|[(?])/, { optional, persist, times }).reply(
|
|
statusCode,
|
|
this.getInspectedReplyBodyFunction(inspectRequest, replyBody),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectPostRelease({
|
|
statusCode = 200,
|
|
inspectRequest = this.inspectNoOp,
|
|
optional = false,
|
|
persist = false,
|
|
times = undefined as number | undefined,
|
|
}) {
|
|
this.optPost(/^\/v6\/release($|[(?])/, { optional, persist, times }).reply(
|
|
statusCode,
|
|
this.getInspectedReplyFileFunction(
|
|
inspectRequest,
|
|
path.join(apiResponsePath, 'release-POST-v6.json'),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectPatchImage({
|
|
replyBody = 'OK',
|
|
statusCode = 200,
|
|
inspectRequest = this.inspectNoOp,
|
|
optional = false,
|
|
persist = false,
|
|
times = undefined as number | undefined,
|
|
}) {
|
|
this.optPatch(/^\/v6\/image($|[(?])/, { optional, persist, times }).reply(
|
|
statusCode,
|
|
this.getInspectedReplyBodyFunction(inspectRequest, replyBody),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectPostImage(opts: ScopeOpts = {}) {
|
|
this.optPost(/^\/v6\/image($|[(?])/, opts).replyWithFile(
|
|
201,
|
|
path.join(apiResponsePath, 'image-POST-v6.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectPostImageLabel(opts: ScopeOpts = {}) {
|
|
this.optPost(/^\/v6\/image_label($|[(?])/, opts).replyWithFile(
|
|
201,
|
|
path.join(apiResponsePath, 'image-label-POST-v6.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectPostImageIsPartOfRelease(opts: ScopeOpts = {}) {
|
|
this.optPost(
|
|
/^\/v6\/image__is_part_of__release($|[(?])/,
|
|
opts,
|
|
).replyWithFile(
|
|
200,
|
|
path.join(apiResponsePath, 'image-is-part-of-release-POST-v6.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
|
|
public expectGetDevice(opts: {
|
|
fullUUID: string;
|
|
shortUUID?: string;
|
|
inaccessibleApp?: boolean;
|
|
isOnline?: boolean;
|
|
optional?: boolean;
|
|
persist?: boolean;
|
|
}) {
|
|
const id = 7654321;
|
|
const providedUuid = opts.shortUUID ?? opts.fullUUID;
|
|
this.optGet(
|
|
providedUuid.length !== 32
|
|
? /^\/v\d+\/device($|\?)/
|
|
: /^\/v\d+\/device\(uuid=%27[0-9a-f]{32}%27\)/,
|
|
opts,
|
|
).reply(200, {
|
|
d: [
|
|
{
|
|
id,
|
|
uuid: opts.fullUUID,
|
|
is_online: opts.isOnline,
|
|
belongs_to__application: opts.inaccessibleApp
|
|
? []
|
|
: [{ app_name: 'test', slug: 'org/test' }],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
public expectGetDeviceStatus(opts: ScopeOpts = {}) {
|
|
this.optGet(
|
|
/^\/v\d+\/device\?.+&\$select=overall_status$/,
|
|
opts,
|
|
).replyWithFile(
|
|
200,
|
|
path.join(apiResponsePath, 'device-status.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
|
|
public expectGetAppEnvVars(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v\d+\/application_environment_variable($|\?)/, opts).reply(
|
|
200,
|
|
{
|
|
d: [
|
|
{
|
|
id: 120101,
|
|
name: 'var1',
|
|
value: 'var1-val',
|
|
},
|
|
{
|
|
id: 120102,
|
|
name: 'var2',
|
|
value: '22',
|
|
},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
|
|
public expectGetAppConfigVars(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v\d+\/application_config_variable($|\?)/, opts).reply(200, {
|
|
d: [
|
|
{
|
|
id: 120300,
|
|
name: 'RESIN_SUPERVISOR_NATIVE_LOGGER',
|
|
value: 'false',
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
public expectGetAppServiceVars(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v\d+\/service_environment_variable($|\?)/, opts).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 expectGetDeviceEnvVars(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v\d+\/device_environment_variable($|\?)/, opts).reply(200, {
|
|
d: [
|
|
{
|
|
id: 120203,
|
|
name: 'var3',
|
|
value: 'var3-val',
|
|
},
|
|
{
|
|
id: 120204,
|
|
name: 'var4',
|
|
value: '44',
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
public expectGetDeviceConfigVars(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v\d+\/device_config_variable($|\?)/, opts).reply(200, {
|
|
d: [
|
|
{
|
|
id: 120400,
|
|
name: 'RESIN_SUPERVISOR_POLL_INTERVAL',
|
|
value: '900900',
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
public expectGetDeviceServiceVars(opts: ScopeOpts = {}) {
|
|
this.optGet(
|
|
/^\/v\d+\/device_service_environment_variable($|\?)/,
|
|
opts,
|
|
).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 expectGetConfigDeviceTypes(opts: ScopeOpts = {}) {
|
|
this.optGet('/device-types/v1', opts).replyWithFile(
|
|
200,
|
|
path.join(apiResponsePath, 'device-types-GET-v1.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
|
|
public expectGetDeviceTypes(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v\d+\/device_type($|\?)/, opts).replyWithFile(
|
|
200,
|
|
path.join(apiResponsePath, 'device-type-GET-v6.json'),
|
|
jHeader,
|
|
);
|
|
}
|
|
|
|
public expectGetConfigVars(opts: ScopeOpts = {}) {
|
|
this.optGet('/config/vars', opts).reply(200, {
|
|
reservedNames: [],
|
|
reservedNamespaces: [],
|
|
invalidRegex: '/^d|W/',
|
|
whiteListedNames: [],
|
|
whiteListedNamespaces: [],
|
|
blackListedNames: [],
|
|
configVarSchema: [],
|
|
});
|
|
}
|
|
|
|
public expectGetService(opts: {
|
|
optional?: boolean;
|
|
persist?: boolean;
|
|
serviceId?: number;
|
|
serviceName: string;
|
|
}) {
|
|
const serviceId = opts.serviceId || 243768;
|
|
this.optGet(/^\/v\d+\/service($|\?)/, opts).reply(200, {
|
|
d: [{ id: serviceId, service_name: opts.serviceName }],
|
|
});
|
|
}
|
|
|
|
public expectGetServiceFromApp(opts: {
|
|
optional?: boolean;
|
|
persist?: boolean;
|
|
serviceId?: number;
|
|
serviceName: string;
|
|
}) {
|
|
const serviceId = opts.serviceId || 243768;
|
|
this.optGet(/^\/v6\/application($|\?).*\$expand=service.*/, opts).reply(
|
|
200,
|
|
{
|
|
d: [{ service: [{ id: serviceId, service_name: opts.serviceName }] }],
|
|
},
|
|
);
|
|
}
|
|
|
|
public expectPostService409(opts: ScopeOpts = {}) {
|
|
this.optPost(/^\/v\d+\/service$/, opts).reply(
|
|
409,
|
|
'Unique key constraint violated',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mocks balena-release call
|
|
*/
|
|
public expectGetUser(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/v6\/user/, opts).reply(200, {
|
|
d: [
|
|
{
|
|
id: 99999,
|
|
actor: 1234567,
|
|
username: 'gh_user',
|
|
created_at: '2018-08-19T13:55:04.485Z',
|
|
__metadata: {
|
|
uri: '/resin/user(@id)?@id=43699',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
// User details are cached in the SDK
|
|
// so often we don't know if we can expect the whoami request
|
|
public expectGetWhoAmI(opts: ScopeOpts = { optional: true }) {
|
|
this.optGet('/actor/v1/whoami', opts).reply(200, {
|
|
id: 1234,
|
|
actorType: 'user',
|
|
actorTypeId: 99999,
|
|
username: 'gh_user',
|
|
email: 'testuser@test.com',
|
|
});
|
|
}
|
|
|
|
public expectDeviceWhoAmI(opts: ScopeOpts = { optional: true }) {
|
|
this.optGet('/actor/v1/whoami', opts).reply(200, {
|
|
id: 1235,
|
|
actorType: 'device',
|
|
actorTypeId: 88888,
|
|
uuid: 'a11dc1acd31b623a0e4e084a6cf13aaa',
|
|
});
|
|
}
|
|
|
|
public expectApplicationWhoAmI(opts: ScopeOpts = { optional: true }) {
|
|
this.optGet('/actor/v1/whoami', opts).reply(200, {
|
|
id: 1236,
|
|
actorType: 'application',
|
|
actorTypeId: 77777,
|
|
slug: 'mytestorf/mytestfleet',
|
|
});
|
|
}
|
|
|
|
public expectWhoAmIFail(opts: ScopeOpts = { optional: true }) {
|
|
this.optGet('/actor/v1/whoami', opts).reply(401);
|
|
}
|
|
|
|
public expectGetMixpanel(opts: ScopeOpts = {}) {
|
|
this.optGet(/^\/mixpanel\/track/, opts).reply(200, {});
|
|
}
|
|
}
|
|
|
|
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',
|
|
},
|
|
],
|
|
},
|
|
};
|