mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-20 06:07:57 +00:00
a4c13aa2e9
Change-type: patch
387 lines
9.6 KiB
TypeScript
387 lines
9.6 KiB
TypeScript
import { expect } from 'chai';
|
|
import { isRight } from 'fp-ts/lib/Either';
|
|
import * as sinon from 'sinon';
|
|
import App from '~/src/compose/app';
|
|
import Network from '~/src/compose/network';
|
|
import * as config from '~/src/config';
|
|
import * as dbFormat from '~/src/device-state/db-format';
|
|
import log from '~/lib/supervisor-console';
|
|
import { TargetApps } from '~/src/types/state';
|
|
import * as dbHelper from '~/test-lib/db-helper';
|
|
|
|
function getDefaultNetwork(appId: number) {
|
|
return {
|
|
default: Network.fromComposeObject('default', appId, 'deadbeef', {}),
|
|
};
|
|
}
|
|
|
|
describe('db-format', () => {
|
|
let testDb: dbHelper.TestDatabase;
|
|
let apiEndpoint: string;
|
|
before(async () => {
|
|
testDb = await dbHelper.createDB();
|
|
|
|
await config.initialized();
|
|
// Prevent side effects from changes in config
|
|
sinon.stub(config, 'on');
|
|
|
|
// TargetStateCache checks the API endpoint to
|
|
// store and invalidate the cache
|
|
// TODO: this is an implementation detail that
|
|
// should not be part of the test suite. We need to change
|
|
// the target state architecture for this
|
|
apiEndpoint = await config.get('apiEndpoint');
|
|
|
|
// disable log output during testing
|
|
sinon.stub(log, 'debug');
|
|
sinon.stub(log, 'warn');
|
|
sinon.stub(log, 'info');
|
|
sinon.stub(log, 'event');
|
|
sinon.stub(log, 'success');
|
|
});
|
|
|
|
after(async () => {
|
|
try {
|
|
await testDb.destroy();
|
|
} catch {
|
|
/* noop */
|
|
}
|
|
sinon.restore();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await testDb.reset();
|
|
});
|
|
|
|
it('converts target apps into the database format', async () => {
|
|
await dbFormat.setApps(
|
|
{
|
|
deadbeef: {
|
|
id: 1,
|
|
name: 'test-app',
|
|
class: 'fleet',
|
|
releases: {
|
|
one: {
|
|
id: 1,
|
|
services: {
|
|
ubuntu: {
|
|
id: 1,
|
|
image_id: 1,
|
|
image: 'ubuntu:latest',
|
|
environment: {},
|
|
labels: { 'my-label': 'true' },
|
|
composition: {
|
|
command: ['sleep', 'infinity'],
|
|
},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'local',
|
|
);
|
|
|
|
const [app] = await testDb.models('app').where({ uuid: 'deadbeef' });
|
|
expect(app).to.not.be.undefined;
|
|
expect(app.name).to.equal('test-app');
|
|
expect(app.releaseId).to.equal(1);
|
|
expect(app.commit).to.equal('one');
|
|
expect(app.appId).to.equal(1);
|
|
expect(app.source).to.equal('local');
|
|
expect(app.uuid).to.equal('deadbeef');
|
|
expect(app.isHost).to.equal(0);
|
|
expect(app.services).to.equal(
|
|
'[{"image":"ubuntu:latest","environment":{},"labels":{"my-label":"true"},"composition":{"command":["sleep","infinity"]},"appId":1,"appUuid":"deadbeef","releaseId":1,"commit":"one","imageId":1,"serviceId":1,"serviceName":"ubuntu"}]',
|
|
);
|
|
expect(app.volumes).to.equal('{}');
|
|
expect(app.networks).to.equal('{}');
|
|
});
|
|
|
|
it('should retrieve a single app from the database', async () => {
|
|
await dbFormat.setApps(
|
|
{
|
|
deadbeef: {
|
|
id: 1,
|
|
name: 'test-app',
|
|
class: 'fleet',
|
|
releases: {
|
|
one: {
|
|
id: 1,
|
|
services: {
|
|
ubuntu: {
|
|
id: 1,
|
|
image_id: 1,
|
|
image: 'ubuntu:latest',
|
|
environment: {},
|
|
labels: { 'my-label': 'true' },
|
|
composition: {
|
|
command: ['sleep', 'infinity'],
|
|
},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
apiEndpoint,
|
|
);
|
|
|
|
const app = await dbFormat.getApp(1);
|
|
expect(app).to.be.an.instanceOf(App);
|
|
expect(app).to.have.property('appId').that.equals(1);
|
|
expect(app).to.have.property('commit').that.equals('one');
|
|
expect(app).to.have.property('appName').that.equals('test-app');
|
|
expect(app).to.have.property('source').that.equals(apiEndpoint);
|
|
expect(app).to.have.property('services').that.has.lengthOf(1);
|
|
expect(app).to.have.property('volumes').that.deep.equals({});
|
|
expect(app)
|
|
.to.have.property('networks')
|
|
.that.deep.equals(getDefaultNetwork(1));
|
|
|
|
const [service] = app.services;
|
|
expect(service).to.have.property('appId').that.equals(1);
|
|
expect(service).to.have.property('serviceId').that.equals(1);
|
|
expect(service).to.have.property('imageId').that.equals(1);
|
|
expect(service).to.have.property('releaseId').that.equals(1);
|
|
expect(service.config)
|
|
.to.have.property('image')
|
|
.that.equals('ubuntu:latest');
|
|
expect(service.config)
|
|
.to.have.property('labels')
|
|
.that.deep.includes({ 'my-label': 'true' });
|
|
expect(service.config)
|
|
.to.have.property('command')
|
|
.that.deep.equals(['sleep', 'infinity']);
|
|
});
|
|
|
|
it('should retrieve multiple apps from the database', async () => {
|
|
await dbFormat.setApps(
|
|
{
|
|
deadbeef: {
|
|
id: 1,
|
|
name: 'test-app',
|
|
class: 'fleet',
|
|
releases: {
|
|
one: {
|
|
id: 1,
|
|
services: {
|
|
ubuntu: {
|
|
id: 1,
|
|
image_id: 1,
|
|
image: 'ubuntu:latest',
|
|
environment: {},
|
|
labels: {},
|
|
composition: {
|
|
command: ['sleep', 'infinity'],
|
|
},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
deadc0de: {
|
|
id: 2,
|
|
name: 'other-app',
|
|
class: 'app',
|
|
releases: {
|
|
two: {
|
|
id: 2,
|
|
services: {},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
apiEndpoint,
|
|
);
|
|
|
|
const apps = Object.values(await dbFormat.getApps());
|
|
expect(apps).to.have.lengthOf(2);
|
|
|
|
const [app, otherapp] = apps;
|
|
expect(app).to.be.an.instanceOf(App);
|
|
expect(app).to.have.property('appId').that.equals(1);
|
|
expect(app).to.have.property('commit').that.equals('one');
|
|
expect(app).to.have.property('appName').that.equals('test-app');
|
|
expect(app).to.have.property('source').that.equals(apiEndpoint);
|
|
expect(app).to.have.property('services').that.has.lengthOf(1);
|
|
expect(app).to.have.property('volumes').that.deep.equals({});
|
|
expect(app)
|
|
.to.have.property('networks')
|
|
.that.deep.equals(getDefaultNetwork(1));
|
|
|
|
expect(otherapp).to.have.property('appId').that.equals(2);
|
|
expect(otherapp).to.have.property('commit').that.equals('two');
|
|
expect(otherapp).to.have.property('appName').that.equals('other-app');
|
|
});
|
|
|
|
it('should retrieve non-fleet apps from the database if local mode is set', async () => {
|
|
await dbFormat.setApps(
|
|
{
|
|
deadbeef: {
|
|
id: 1,
|
|
name: 'test-app',
|
|
class: 'fleet',
|
|
releases: {
|
|
one: {
|
|
id: 1,
|
|
services: {
|
|
ubuntu: {
|
|
id: 1,
|
|
image_id: 1,
|
|
image: 'ubuntu:latest',
|
|
environment: {},
|
|
labels: {},
|
|
composition: {
|
|
command: ['sleep', 'infinity'],
|
|
},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
deadc0de: {
|
|
id: 2,
|
|
name: 'other-app',
|
|
class: 'app',
|
|
releases: {
|
|
two: {
|
|
id: 2,
|
|
services: {},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
apiEndpoint,
|
|
);
|
|
|
|
// Once local mode is set to true, only 'other-app' should be returned
|
|
// as part of the target
|
|
await config.set({ localMode: true });
|
|
|
|
const apps = Object.values(await dbFormat.getApps());
|
|
expect(apps).to.have.lengthOf(1);
|
|
|
|
const [app] = apps;
|
|
expect(app).to.be.an.instanceOf(App);
|
|
expect(app).to.have.property('appId').that.equals(2);
|
|
expect(app).to.have.property('commit').that.equals('two');
|
|
expect(app).to.have.property('appName').that.equals('other-app');
|
|
|
|
// Set the app as local now
|
|
await dbFormat.setApps(
|
|
{
|
|
deadbeef: {
|
|
id: 1,
|
|
name: 'test-app',
|
|
class: 'fleet',
|
|
releases: {
|
|
one: {
|
|
id: 1,
|
|
services: {
|
|
ubuntu: {
|
|
id: 1,
|
|
image_id: 1,
|
|
image: 'ubuntu:latest',
|
|
environment: {},
|
|
labels: {},
|
|
composition: {
|
|
command: ['sleep', 'infinity'],
|
|
},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'local',
|
|
);
|
|
|
|
// Now both apps should be returned
|
|
const newapps = Object.values(await dbFormat.getApps());
|
|
expect(newapps).to.have.lengthOf(2);
|
|
|
|
const [newapp, otherapp] = newapps;
|
|
expect(newapp).to.be.an.instanceOf(App);
|
|
expect(newapp).to.have.property('appId').that.equals(1);
|
|
expect(newapp).to.have.property('commit').that.equals('one');
|
|
expect(newapp).to.have.property('appName').that.equals('test-app');
|
|
expect(newapp).to.have.property('source').that.equals('local');
|
|
expect(newapp).to.have.property('services').that.has.lengthOf(1);
|
|
expect(newapp).to.have.property('volumes').that.deep.equals({});
|
|
expect(newapp)
|
|
.to.have.property('networks')
|
|
.that.deep.equals(getDefaultNetwork(1));
|
|
|
|
expect(otherapp).to.have.property('appId').that.equals(2);
|
|
expect(otherapp).to.have.property('commit').that.equals('two');
|
|
expect(otherapp).to.have.property('appName').that.equals('other-app');
|
|
});
|
|
|
|
it('should retrieve app target state from database', async () => {
|
|
const srcApps: TargetApps = {
|
|
deadbeef: {
|
|
id: 1,
|
|
name: 'test-app',
|
|
class: 'fleet',
|
|
is_host: false,
|
|
releases: {
|
|
one: {
|
|
id: 1,
|
|
services: {
|
|
ubuntu: {
|
|
id: 1,
|
|
image_id: 1,
|
|
image: 'ubuntu:latest',
|
|
environment: {},
|
|
labels: { 'my-label': 'true' },
|
|
composition: {
|
|
command: ['sleep', 'infinity'],
|
|
},
|
|
},
|
|
},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
deadc0de: {
|
|
id: 2,
|
|
name: 'other-app',
|
|
class: 'app',
|
|
is_host: false,
|
|
releases: {
|
|
two: {
|
|
id: 2,
|
|
services: {},
|
|
volumes: {},
|
|
networks: {},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
await dbFormat.setApps(srcApps, apiEndpoint);
|
|
const result = await dbFormat.getTargetJson();
|
|
expect(
|
|
isRight(TargetApps.decode(result)),
|
|
'resulting target apps is a valid TargetApps object',
|
|
);
|
|
expect(result).to.deep.equal(srcApps);
|
|
});
|
|
});
|