Files
cloudron-box/src/test/database-test.js

340 lines
12 KiB
JavaScript
Raw Normal View History

/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
2021-05-04 21:40:11 -07:00
const appdb = require('../appdb.js'),
2019-08-30 13:12:49 -07:00
apps = require('../apps.js'),
2016-04-30 10:16:27 -07:00
async = require('async'),
BoxError = require('../boxerror.js'),
database = require('../database'),
2021-08-13 17:22:28 -07:00
domains = require('../domains.js'),
expect = require('expect.js'),
2021-05-04 21:40:11 -07:00
reverseProxy = require('../reverseproxy.js'),
_ = require('underscore');
2018-01-26 18:32:13 +01:00
const DOMAIN_0 = {
domain: 'foobar.com',
zoneName: 'foobar.com',
provider: 'digitalocean',
config: { token: 'abcd' },
tlsConfig: { provider: 'fallback' },
wellKnown: null
2018-01-26 18:32:13 +01:00
};
2021-05-04 21:40:11 -07:00
DOMAIN_0.fallbackCertificate = reverseProxy.generateFallbackCertificateSync(DOMAIN_0.domain);
2018-01-26 18:32:13 +01:00
2021-08-13 17:22:28 -07:00
const auditSource = { ip: '1.2.3.4' };
2018-01-26 18:32:13 +01:00
const DOMAIN_1 = {
domain: 'foo.cloudron.io',
zoneName: 'cloudron.io',
provider: 'manual',
config: null,
tlsConfig: { provider: 'fallback' },
wellKnown: null
2018-01-26 18:32:13 +01:00
};
2021-05-04 21:40:11 -07:00
DOMAIN_1.fallbackCertificate = reverseProxy.generateFallbackCertificateSync(DOMAIN_1.domain);
2018-01-26 18:32:13 +01:00
describe('database', function () {
before(function (done) {
async.series([
database.initialize,
database._clear
], done);
});
after(function (done) {
async.series([
database._clear,
database.uninitialize
], done);
});
describe('apps', function () {
var APP_0 = {
id: 'appid-0',
appStoreId: 'appStoreId-0',
2019-08-30 13:12:49 -07:00
installationState: apps.ISTATE_PENDING_INSTALL,
2019-08-30 09:45:43 -07:00
error: null,
2019-09-22 22:07:14 -07:00
runState: 'running',
location: 'some-location-0',
2018-01-26 18:32:13 +01:00
domain: DOMAIN_0.domain,
manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0' },
containerId: null,
containerIp: null,
portBindings: { port: { hostPort: 5678, type: 'tcp' } },
health: null,
accessRestriction: null,
2016-04-19 00:10:11 -07:00
memoryLimit: 4294967296,
2020-01-28 21:30:35 -08:00
cpuShares: 256,
2017-01-19 12:51:16 -08:00
sso: true,
debugMode: null,
reverseProxyConfig: {},
enableBackup: true,
2021-03-17 12:14:36 -07:00
enableMailbox: true,
alternateDomains: [],
2021-01-19 19:20:26 -08:00
aliasDomains: [],
env: {
'CUSTOM_KEY': 'CUSTOM_VALUE'
rework how app mailboxes are allocated Our current setup had a mailbox allocated for an app during app install (into the mailboxes table). This has many issues: * When set to a custom mailbox location, there was no way to access this mailbox even via IMAP. Even when using app credentials, we cannot use IMAP since the ldap logic was testing on the addon type (most of our apps only use sendmail addon and thus cannot recvmail). * The mailboxes table was being used to add hidden 'app' type entries. This made it very hard for the user to understand why a mailbox conflicts. For example, if you set an app to use custom mailbox 'blog', this is hidden from all views. The solution is to let an app send email as whatever mailbox name is allocated to it (which we now track in the apps table. the default is in the db already so that REST response contains it). When not using Cloudron email, it will just send mail as that mailbox and the auth checks the "app password" in the addons table. Any replies to that mailbox will end up in the domain's mail server (not our problem). When using cloudron email, the app can send mail like above. Any responses will not end anywhere and bounce since there is no 'mailbox'. This is the expected behavior. If user wants to access this mailbox name, he can create a concrete mailbox and set himself as owner OR set this as an alias. For apps using the recvmail addon, the workflow is to actually create a mailbox at some point. Currently, we have no UI for this 'flow'. It's fine because we have only meemo using it. Intuitive much!
2018-12-06 21:08:19 -08:00
},
mailboxName: 'talktome',
2019-11-14 21:43:14 -08:00
mailboxDomain: DOMAIN_0.domain,
enableAutomaticUpdate: true,
2019-09-15 21:51:38 -07:00
dataDir: null,
2019-03-22 14:26:25 -07:00
tags: [],
2019-08-26 15:28:29 -07:00
label: null,
taskId: null,
2020-10-28 19:42:48 -07:00
mounts: [],
2020-11-10 09:59:28 -08:00
proxyAuth: false,
servicesConfig: {},
hasIcon: false,
hasAppStoreIcon: false
};
2018-01-26 18:32:13 +01:00
var APP_1 = {
id: 'appid-1',
appStoreId: 'appStoreId-1',
2019-08-30 13:12:49 -07:00
installationState: apps.ISTATE_PENDING_INSTALL, // app health tests rely on this initial state
2019-08-30 09:45:43 -07:00
error: null,
2019-09-22 22:07:14 -07:00
runState: 'running',
location: 'some-location-1',
2018-01-26 18:32:13 +01:00
domain: DOMAIN_0.domain,
manifest: { version: '0.2', dockerImage: 'docker/app1', healthCheckPath: '/', httpPort: 80, title: 'app1' },
containerId: null,
containerIp: null,
portBindings: { },
health: null,
accessRestriction: { users: [ 'foobar' ] },
2016-04-19 00:10:11 -07:00
memoryLimit: 0,
2020-01-28 21:30:35 -08:00
cpuShares: 512,
2017-01-19 12:51:16 -08:00
sso: true,
debugMode: null,
reverseProxyConfig: {},
enableBackup: true,
alternateDomains: [],
2021-01-19 19:20:26 -08:00
aliasDomains: [],
rework how app mailboxes are allocated Our current setup had a mailbox allocated for an app during app install (into the mailboxes table). This has many issues: * When set to a custom mailbox location, there was no way to access this mailbox even via IMAP. Even when using app credentials, we cannot use IMAP since the ldap logic was testing on the addon type (most of our apps only use sendmail addon and thus cannot recvmail). * The mailboxes table was being used to add hidden 'app' type entries. This made it very hard for the user to understand why a mailbox conflicts. For example, if you set an app to use custom mailbox 'blog', this is hidden from all views. The solution is to let an app send email as whatever mailbox name is allocated to it (which we now track in the apps table. the default is in the db already so that REST response contains it). When not using Cloudron email, it will just send mail as that mailbox and the auth checks the "app password" in the addons table. Any replies to that mailbox will end up in the domain's mail server (not our problem). When using cloudron email, the app can send mail like above. Any responses will not end anywhere and bounce since there is no 'mailbox'. This is the expected behavior. If user wants to access this mailbox name, he can create a concrete mailbox and set himself as owner OR set this as an alias. For apps using the recvmail addon, the workflow is to actually create a mailbox at some point. Currently, we have no UI for this 'flow'. It's fine because we have only meemo using it. Intuitive much!
2018-12-06 21:08:19 -08:00
env: {},
2021-03-17 12:14:36 -07:00
enableMailbox: true,
mailboxName: 'callme',
2019-11-14 21:43:14 -08:00
mailboxDomain: DOMAIN_0.domain,
enableAutomaticUpdate: true,
2019-09-15 21:51:38 -07:00
dataDir: null,
2019-03-22 14:26:25 -07:00
tags: [],
2019-08-26 15:28:29 -07:00
label: null,
taskId: null,
2020-10-28 19:42:48 -07:00
mounts: [],
2020-11-10 09:59:28 -08:00
proxyAuth: false,
servicesConfig: {},
hasIcon: false,
hasAppStoreIcon: false
};
2018-01-26 18:32:13 +01:00
before(function (done) {
async.series([
2021-06-28 15:15:28 -07:00
database._clear,
2021-08-13 17:22:28 -07:00
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, auditSource)
], done);
2018-01-26 18:32:13 +01:00
});
after(function (done) {
database._clear(done);
});
it('add fails due to missing arguments', function () {
expect(function () { appdb.add(APP_0.id, APP_0.manifest, APP_0.installationState, function () {}); }).to.throwError();
expect(function () { appdb.add(APP_0.id, function () {}); }).to.throwError();
});
it('exists returns false', function (done) {
appdb.exists(APP_0.id, function (error, exists) {
expect(error).to.be(null);
expect(exists).to.be(false);
done();
});
});
it('add succeeds', function (done) {
2019-07-02 20:22:17 -07:00
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0, function (error) {
expect(error).to.be(null);
done();
});
});
it('exists succeeds', function (done) {
appdb.exists(APP_0.id, function (error, exists) {
expect(error).to.be(null);
expect(exists).to.be(true);
done();
});
});
it('getPortBindings succeeds', function (done) {
appdb.getPortBindings(APP_0.id, function (error, bindings) {
expect(error).to.be(null);
expect(bindings).to.be.an(Object);
expect(bindings).to.be.eql({ port: { hostPort: '5678', type: 'tcp' } });
done();
});
});
it('add of same app fails', function (done) {
2019-07-02 20:22:17 -07:00
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, [], APP_0, function (error) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.be(BoxError.ALREADY_EXISTS);
done();
});
});
it('get succeeds', function (done) {
appdb.get(APP_0.id, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an('object');
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime', 'resetTokenCreationTime'])).to.be.eql(APP_0);
done();
});
});
it('get of nonexisting code fails', function (done) {
appdb.get(APP_1.id, function (error, result) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.be(BoxError.NOT_FOUND);
expect(result).to.not.be.ok();
done();
});
});
it('update succeeds', function (done) {
APP_0.installationState = 'some-other-status';
APP_0.location = 'some-other-location';
APP_0.manifest.version = '0.2';
2015-10-13 09:52:21 +02:00
APP_0.accessRestriction = '';
APP_0.memoryLimit = 1337;
2020-01-28 21:30:35 -08:00
APP_0.cpuShares = 1024;
2015-10-13 09:52:21 +02:00
var data = {
installationState: APP_0.installationState,
location: APP_0.location,
2018-12-11 16:26:19 -08:00
domain: APP_0.domain,
2015-10-13 09:52:21 +02:00
manifest: APP_0.manifest,
accessRestriction: APP_0.accessRestriction,
2020-01-28 21:30:35 -08:00
memoryLimit: APP_0.memoryLimit,
cpuShares: APP_0.cpuShares
2015-10-13 09:52:21 +02:00
};
appdb.update(APP_0.id, data, function (error) {
expect(error).to.be(null);
appdb.get(APP_0.id, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an('object');
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime','resetTokenCreationTime'])).to.be.eql(APP_0);
done();
});
});
});
it('update of nonexisting app fails', function (done) {
appdb.update(APP_1.id, { installationState: APP_1.installationState, location: APP_1.location }, function (error) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.be(BoxError.NOT_FOUND);
done();
});
});
it('add second app succeeds', function (done) {
2019-07-02 20:22:17 -07:00
appdb.add(APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, [], APP_1, function (error) {
expect(error).to.be(null);
done();
});
});
it('getAll succeeds', function (done) {
rework how app mailboxes are allocated Our current setup had a mailbox allocated for an app during app install (into the mailboxes table). This has many issues: * When set to a custom mailbox location, there was no way to access this mailbox even via IMAP. Even when using app credentials, we cannot use IMAP since the ldap logic was testing on the addon type (most of our apps only use sendmail addon and thus cannot recvmail). * The mailboxes table was being used to add hidden 'app' type entries. This made it very hard for the user to understand why a mailbox conflicts. For example, if you set an app to use custom mailbox 'blog', this is hidden from all views. The solution is to let an app send email as whatever mailbox name is allocated to it (which we now track in the apps table. the default is in the db already so that REST response contains it). When not using Cloudron email, it will just send mail as that mailbox and the auth checks the "app password" in the addons table. Any replies to that mailbox will end up in the domain's mail server (not our problem). When using cloudron email, the app can send mail like above. Any responses will not end anywhere and bounce since there is no 'mailbox'. This is the expected behavior. If user wants to access this mailbox name, he can create a concrete mailbox and set himself as owner OR set this as an alias. For apps using the recvmail addon, the workflow is to actually create a mailbox at some point. Currently, we have no UI for this 'flow'. It's fine because we have only meemo using it. Intuitive much!
2018-12-06 21:08:19 -08:00
appdb.getAll(function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(Array);
expect(result.length).to.be(2);
expect(_.omit(result[0], ['creationTime', 'updateTime','ts', 'healthTime', 'resetTokenCreationTime'])).to.be.eql(APP_0);
expect(_.omit(result[1], ['creationTime', 'updateTime','ts', 'healthTime', 'resetTokenCreationTime'])).to.be.eql(APP_1);
done();
});
});
it('delete succeeds', function (done) {
appdb.del(APP_0.id, function (error) {
expect(error).to.be(null);
done();
});
});
it('getPortBindings should be empty', function (done) {
appdb.getPortBindings(APP_0.id, function (error, bindings) {
expect(error).to.be(null);
expect(bindings).to.be.an(Object);
expect(bindings).to.be.eql({ });
done();
});
});
it('cannot delete previously delete record', function (done) {
appdb.del(APP_0.id, function (error) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.be(BoxError.NOT_FOUND);
done();
});
});
2019-08-30 13:12:49 -07:00
it('can set app as healthy', function (done) {
appdb.setHealth(APP_1.id, apps.HEALTH_HEALTHY, new Date(), function (error) {
expect(error).to.be(null);
2019-08-30 13:12:49 -07:00
done();
});
});
it('cannot set health of unknown app', function (done) {
2019-08-30 13:12:49 -07:00
appdb.setHealth('randomId', apps.HEALTH_HEALTHY, new Date(), function (error) {
expect(error).to.be.ok();
done();
});
});
});
describe('importFromFile', function () {
before(function (done) {
async.series([
database.initialize,
database._clear
], done);
});
it('cannot import from non-existent file', function (done) {
database.importFromFile('/does/not/exist', function (error) {
expect(error).to.be.ok();
done();
});
});
2017-11-24 15:31:03 -08:00
it('can export to file', function (done) {
2019-05-07 15:31:32 +02:00
// arch only has maria db which lacks some mysqldump options we need, this is only here to allow running the tests :-/
if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done();
2017-11-24 15:31:03 -08:00
database.exportToFile('/tmp/box.mysqldump', function (error) {
expect(error).to.be(null);
done();
});
});
it('can import from file', function (done) {
2019-05-07 15:31:32 +02:00
// arch only has maria db which lacks some mysqldump options we need, this is only here to allow running the tests :-/
if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done();
2017-11-24 15:31:03 -08:00
database.importFromFile('/tmp/box.mysqldump', function (error) {
expect(error).to.be(null);
done();
});
});
});
});