Files
cloudron-box/src/test/apps-test.js
Girish Ramakrishnan 663e0952fc move wellKnownJson to domains
after some more thought:
* If app moves to another location, user has to remember to move all this config
* It's not really associated with an app. It's to do with the domain info
* We can put some hints in the UI if app is missing.

part of #703
2020-12-23 17:13:22 -08:00

437 lines
16 KiB
JavaScript

/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
var appdb = require('../appdb.js'),
apps = require('../apps.js'),
async = require('async'),
BoxError = require('../boxerror.js'),
database = require('../database.js'),
domains = require('../domains.js'),
expect = require('expect.js'),
groupdb = require('../groupdb.js'),
groups = require('../groups.js'),
hat = require('../hat.js'),
provision = require('../provision.js'),
userdb = require('../userdb.js');
let AUDIT_SOURCE = { ip: '1.2.3.4' };
describe('Apps', function () {
var ADMIN_0 = {
id: 'admin123',
username: 'admin123',
password: 'secret',
email: 'admin@me.com',
fallbackEmail: 'admin@me.com',
salt: 'morton',
createdAt: 'sometime back',
resetToken: hat(256),
displayName: '',
groupIds: [],
role: 'owner',
source: ''
};
var USER_0 = {
id: 'uuid213',
username: 'uuid213',
password: 'secret',
email: 'safe@me.com',
fallbackEmail: 'safe@me.com',
salt: 'morton',
createdAt: 'sometime back',
resetToken: hat(256),
displayName: '',
groupIds: [],
role: 'user',
source: ''
};
var USER_1 = {
id: 'uuid2134',
username: 'uuid2134',
password: 'secret',
email: 'safe1@me.com',
fallbackEmail: 'safe1@me.com',
salt: 'morton',
createdAt: 'sometime back',
resetToken: hat(256),
displayName: '',
groupIds: [ 'somegroup' ],
role: 'user',
source: ''
};
var GROUP_0 = {
id: 'somegroup',
name: 'group0',
source: ''
};
var GROUP_1 = {
id: 'anothergroup',
name: 'group1',
source: 'ldap'
};
const DOMAIN_0 = {
domain: 'example.com',
zoneName: 'example.com',
provider: 'noop',
config: { },
fallbackCertificate: null,
tlsConfig: { provider: 'fallback' },
wellKnown: null
};
const DOMAIN_1 = {
domain: 'example2.com',
zoneName: 'example2.com',
provider: 'noop',
config: { },
fallbackCertificate: null,
tlsConfig: { provider: 'fallback' },
wellKnown: null
};
var APP_0 = {
id: 'appid-0',
appStoreId: 'appStoreId-0',
location: 'some-location-0',
domain: DOMAIN_0.domain,
fqdn: 'some-location-0.' + DOMAIN_0.domain,
manifest: {
version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0',
tcpPorts: {
PORT: {
description: 'this is a port that i expose',
containerPort: '1234'
}
}
},
portBindings: { PORT: 5678 },
accessRestriction: null,
memoryLimit: 0,
cpuShares: 512,
reverseProxyConfig: null,
sso: false,
mailboxDomain: DOMAIN_0.domain,
env: {
'CUSTOM_KEY': 'CUSTOM_VALUE'
},
dataDir: '',
installationState: 'installed',
runState: 'running'
};
var APP_1 = {
id: 'appid-1',
appStoreId: 'appStoreId-1',
location: 'some-location-1',
domain: DOMAIN_0.domain,
fqdn: 'some-location-1.' + DOMAIN_0.domain,
manifest: {
version: '0.1', dockerImage: 'docker/app1', healthCheckPath: '/', httpPort: 80, title: 'app1',
tcpPorts: {}
},
portBindings: {},
accessRestriction: { users: [ 'someuser' ], groups: [ GROUP_0.id ] },
memoryLimit: 0,
cpuShares: 512,
env: {},
dataDir: '',
mailboxDomain: DOMAIN_0.domain,
installationState: 'installed',
runState: 'running'
};
var APP_2 = {
id: 'appid-2',
appStoreId: 'appStoreId-2',
location: 'some-location-2',
domain: DOMAIN_1.domain,
fqdn: 'some-location-2.' + DOMAIN_1.domain,
manifest: {
version: '0.1', dockerImage: 'docker/app2', healthCheckPath: '/', httpPort: 80, title: 'app2',
tcpPorts: {}
},
portBindings: {},
accessRestriction: { users: [ 'someuser', USER_0.id ], groups: [ GROUP_1.id ] },
memoryLimit: 0,
cpuShares: 512,
reverseProxyConfig: null,
sso: false,
env: {},
dataDir: '',
mailboxDomain: DOMAIN_0.domain,
installationState: 'installed',
runState: 'running'
};
before(function (done) {
async.series([
database.initialize,
database._clear,
provision.setup.bind(null, DOMAIN_0, { provider: 'generic' }, AUDIT_SOURCE),
domains.add.bind(null, DOMAIN_1.domain, DOMAIN_1, AUDIT_SOURCE),
userdb.add.bind(null, ADMIN_0.id, ADMIN_0),
userdb.add.bind(null, USER_0.id, USER_0),
userdb.add.bind(null, USER_1.id, USER_1),
groupdb.add.bind(null, GROUP_0.id, GROUP_0.name, GROUP_0.source),
groupdb.add.bind(null, GROUP_1.id, GROUP_1.name, GROUP_1.source),
groups.addMember.bind(null, GROUP_0.id, USER_1.id),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, apps._translatePortBindings(APP_1.portBindings, APP_1.manifest), APP_1),
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, apps._translatePortBindings(APP_2.portBindings, APP_2.manifest), APP_2),
], done);
});
after(function (done) {
async.series([
database._clear,
database.uninitialize
], done);
});
describe('validatePortBindings', function () {
it('does not allow invalid host port', function () {
expect(apps._validatePortBindings({ port: -1 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 0 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 'text' }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 65536 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 470 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
});
it('does not allow ports not as part of manifest', function () {
expect(apps._validatePortBindings({ port: 1567 }, { tcpPorts: { } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 1567 }, { tcpPorts: { port3: null } })).to.be.an(Error);
});
it('does not allow reserved ports', function () {
expect(apps._validatePortBindings({ port: 443 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 50000 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 51000 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 50100 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
});
it('allows valid bindings', function () {
expect(apps._validatePortBindings({ port: 1024 }, { tcpPorts: { port: 5000 } })).to.be(null);
expect(apps._validatePortBindings({
port1: 4033,
port2: 3242,
port3: 1234
}, { tcpPorts: { port1: null, port2: null, port3: null } })).to.be(null);
});
});
describe('getters', function () {
it('cannot get invalid app', function (done) {
apps.get('nope', function (error) {
expect(error).to.be.ok();
expect(error.reason).to.be(BoxError.NOT_FOUND);
done();
});
});
it('can get valid app', function (done) {
apps.get(APP_0.id, function (error, app) {
expect(error).to.be(null);
expect(app).to.be.ok();
expect(app.iconUrl).to.be(null);
expect(app.fqdn).to.eql(APP_0.location + '.' + DOMAIN_0.domain);
expect(app.memoryLimit).to.eql(0);
expect(app.cpuShares).to.eql(512);
done();
});
});
it('can getAll', function (done) {
apps.getAll(function (error, apps) {
expect(error).to.be(null);
expect(apps).to.be.an(Array);
expect(apps[0].id).to.be(APP_0.id);
expect(apps[0].iconUrl).to.be(null);
expect(apps[0].fqdn).to.eql(APP_0.location + '.' + DOMAIN_0.domain);
done();
});
});
});
describe('validateAccessRestriction', function () {
it('allows null input', function () {
expect(apps._validateAccessRestriction(null)).to.eql(null);
});
it('does not allow wrong user type', function () {
expect(apps._validateAccessRestriction({ users: {} })).to.be.an(Error);
});
it('allows user input', function () {
expect(apps._validateAccessRestriction({ users: [] })).to.eql(null);
});
it('allows single user input', function () {
expect(apps._validateAccessRestriction({ users: [ 'someuserid' ] })).to.eql(null);
});
it('allows multi user input', function () {
expect(apps._validateAccessRestriction({ users: [ 'someuserid', 'someuserid1', 'someuserid2', 'someuserid3' ] })).to.eql(null);
});
});
describe('hasAccessTo', function () {
const someuser = { id: 'someuser', groupIds: [], role: 'user' };
const adminuser = { id: 'adminuser', groupIds: [ 'groupie' ], role: 'admin' };
it('returns true for unrestricted access', function (done) {
apps.hasAccessTo({ accessRestriction: null }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(true);
done();
});
});
it('returns true for allowed user', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ 'someuser' ] } }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(true);
done();
});
});
it('returns true for allowed user with multiple allowed', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ 'foo', 'someuser', 'anotheruser' ] } }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(true);
done();
});
});
it('returns false for not allowed user', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ 'foo' ] } }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(false);
done();
});
});
it('returns false for not allowed user with multiple allowed', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ 'foo', 'anotheruser' ] } }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(false);
done();
});
});
it('returns false for no group or user', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ ], groups: [ ] } }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(false);
done();
});
});
it('returns false for invalid group or user', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ ], groups: [ 'nop' ] } }, someuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(false);
done();
});
});
it('returns true for admin user', function (done) {
apps.hasAccessTo({ accessRestriction: { users: [ ], groups: [ 'nop' ] } }, adminuser, function (error, access) {
expect(error).to.be(null);
expect(access).to.be(true);
done();
});
});
});
describe('getAllByUser', function () {
it('succeeds for USER_0', function (done) {
apps.getAllByUser(USER_0, function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result[0].id).to.equal(APP_0.id);
expect(result[1].id).to.equal(APP_2.id);
done();
});
});
it('succeeds for USER_1', function (done) {
apps.getAllByUser(USER_1, function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result[0].id).to.equal(APP_0.id);
expect(result[1].id).to.equal(APP_1.id);
done();
});
});
it('returns all apps for admin', function (done) {
apps.getAllByUser(ADMIN_0, function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(3);
expect(result[0].id).to.equal(APP_0.id);
expect(result[1].id).to.equal(APP_1.id);
expect(result[2].id).to.equal(APP_2.id);
done();
});
});
});
describe('configureInstalledApps', function () {
before(function (done) {
async.series([
appdb.update.bind(null, APP_0.id, { installationState: apps.ISTATE_INSTALLED }),
appdb.update.bind(null, APP_1.id, { installationState: apps.ISTATE_ERROR }),
appdb.update.bind(null, APP_2.id, { installationState: apps.ISTATE_INSTALLED })
], done);
});
it('can mark apps for reconfigure', function (done) {
apps.configureInstalledApps(function (error) {
expect(error).to.be(null);
apps.getAll(function (error, result) {
expect(result[0].installationState).to.be(apps.ISTATE_PENDING_CONFIGURE);
expect(result[1].installationState).to.be(apps.ISTATE_ERROR);
expect(result[2].installationState).to.be(apps.ISTATE_PENDING_CONFIGURE);
done();
});
});
});
});
describe('restoreInstalledApps', function () {
before(function (done) {
async.series([
appdb.update.bind(null, APP_0.id, { installationState: apps.ISTATE_INSTALLED }),
appdb.update.bind(null, APP_1.id, { installationState: apps.ISTATE_ERROR }),
appdb.update.bind(null, APP_2.id, { installationState: apps.ISTATE_INSTALLED })
], done);
});
it('can mark apps for restore', function (done) {
apps.restoreInstalledApps(function (error) {
expect(error).to.be(null);
apps.getAll(function (error, result) {
expect(result[0].installationState).to.be(apps.ISTATE_PENDING_INSTALL);
expect(result[1].installationState).to.be(apps.ISTATE_ERROR);
expect(result[2].installationState).to.be(apps.ISTATE_PENDING_INSTALL);
done();
});
});
});
});
});