1601 lines
64 KiB
JavaScript
1601 lines
64 KiB
JavaScript
'use strict';
|
|
|
|
/* global it:false */
|
|
/* global describe:false */
|
|
/* global before:false */
|
|
|
|
const apps = require('../../apps.js'),
|
|
async = require('async'),
|
|
child_process = require('child_process'),
|
|
constants = require('../../constants.js'),
|
|
crypto = require('crypto'),
|
|
database = require('../../database.js'),
|
|
Docker = require('dockerode'),
|
|
expect = require('expect.js'),
|
|
fs = require('fs'),
|
|
hat = require('../../hat.js'),
|
|
http = require('http'),
|
|
ldapServer = require('../../ldapserver.js'),
|
|
net = require('net'),
|
|
nock = require('nock'),
|
|
path = require('path'),
|
|
paths = require('../../paths.js'),
|
|
platform = require('../../platform.js'),
|
|
safe = require('safetydance'),
|
|
server = require('../../server.js'),
|
|
settings = require('../../settings.js'),
|
|
superagent = require('superagent'),
|
|
tokens = require('../../tokens.js'),
|
|
url = require('url');
|
|
|
|
const SERVER_URL = 'http://localhost:' + constants.PORT;
|
|
|
|
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
|
|
|
// Test image information
|
|
const TEST_IMAGE_REPO = 'docker.io/cloudron/io.cloudron.testapp';
|
|
const TEST_IMAGE_TAG = '20201121-223249-985e86ebb';
|
|
const TEST_IMAGE = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG;
|
|
|
|
const DOMAIN_0 = {
|
|
domain: 'example-apps-test.com',
|
|
adminFqdn: 'my.example-apps-test.com',
|
|
zoneName: 'example-apps-test.com',
|
|
config: {},
|
|
provider: 'noop',
|
|
fallbackCertificate: null,
|
|
tlsConfig: { provider: 'fallback' }
|
|
};
|
|
|
|
const APP_STORE_ID = 'test';
|
|
let APP_ID;
|
|
const APP_SUBDOMAIN = 'appssubdomain';
|
|
const APP_SUBDOMAIN_NEW = 'appssubdomainnew';
|
|
|
|
const APP_MANIFEST = {}; // JSON.parse(fs.readFileSync(__dirname + '/../../../../test-app/CloudronManifest.json', 'utf8'));
|
|
APP_MANIFEST.dockerImage = TEST_IMAGE;
|
|
|
|
const USERNAME = 'superadmin';
|
|
const PASSWORD = 'Foobar?1337';
|
|
const EMAIL ='admin@me.com';
|
|
|
|
const USER_1_APPSTORE_TOKEN = 'appstoretoken';
|
|
const USERNAME_1 = 'user';
|
|
const EMAIL_1 ='user@me.com';
|
|
const user_1_id = null;
|
|
|
|
// authentication token
|
|
const token = null;
|
|
const token_1 = null;
|
|
|
|
let KEY, CERT;
|
|
// let appstoreIconServer = hock.createHock({ throwOnUnmatched: false });
|
|
|
|
function checkRedis(containerId, done) {
|
|
let redisIp, exportedRedisPort;
|
|
|
|
docker.getContainer(containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data).to.be.ok();
|
|
|
|
redisIp = safe.query(data, 'NetworkSettings.Networks.cloudron.IPAddress');
|
|
expect(redisIp).to.be.ok();
|
|
|
|
exportedRedisPort = safe.query(data, 'NetworkSettings.Ports.6379/tcp');
|
|
expect(exportedRedisPort).to.be(null);
|
|
|
|
done();
|
|
});
|
|
}
|
|
|
|
function waitForTask(taskId, callback) {
|
|
process.stdout.write('Waiting for task ' + taskId + ' .');
|
|
|
|
async.retry({ times: 50, interval: 4000 }, function (retryCallback) {
|
|
superagent.get(SERVER_URL + '/api/v1/tasks/' + taskId)
|
|
.query({ access_token: token })
|
|
.end(function (error, result) {
|
|
process.stdout.write('.');
|
|
|
|
if (!result || result.statusCode !== 200) return retryCallback(null, new Error('Bad result'));
|
|
|
|
if (result.body.active) return retryCallback(new Error('Still active'));
|
|
|
|
retryCallback();
|
|
});
|
|
}, function (error, result) {
|
|
console.log();
|
|
callback(error || result);
|
|
});
|
|
}
|
|
|
|
function waitForSetup(done) {
|
|
async.retry({ times: 5, interval: 4000 }, function (retryCallback) {
|
|
superagent.get(SERVER_URL + '/api/v1/cloudron/status')
|
|
.end(function (error, result) {
|
|
if (!result || result.statusCode !== 200) return retryCallback(new Error('Bad result'));
|
|
|
|
if (!result.body.setup.active && result.body.setup.errorMessage === '' && result.body.adminFqdn) return retryCallback();
|
|
|
|
retryCallback(new Error('Not done yet: ' + JSON.stringify(result.body)));
|
|
});
|
|
}, done);
|
|
}
|
|
|
|
function startBox(done) {
|
|
console.log('Starting box code...');
|
|
|
|
child_process.execSync('openssl req -subj "/CN=*.' + DOMAIN_0.domain + '/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/server.key -out /tmp/server.crt');
|
|
KEY = fs.readFileSync('/tmp/server.key', 'utf8');
|
|
CERT = fs.readFileSync('/tmp/server.crt', 'utf8');
|
|
|
|
process.env.TEST_CREATE_INFRA = 1;
|
|
|
|
safe.fs.unlinkSync(paths.INFRA_VERSION_FILE);
|
|
|
|
async.series([
|
|
// first clear, then start server. otherwise, taskmanager spins up tasks for obsolete appIds
|
|
database.initialize,
|
|
database._clear,
|
|
server.start,
|
|
ldapServer.start,
|
|
settings._setApiServerOrigin.bind(null, 'http://localhost:6060'),
|
|
|
|
function (callback) {
|
|
superagent.post(SERVER_URL + '/api/v1/provision/setup')
|
|
.send({ domainConfig: DOMAIN_0 })
|
|
.end(function (error, result) {
|
|
expect(result).to.be.ok();
|
|
expect(result.statusCode).to.eql(200);
|
|
|
|
waitForSetup(callback);
|
|
});
|
|
},
|
|
|
|
function (callback) {
|
|
superagent.post(SERVER_URL + '/api/v1/provision/activate')
|
|
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
|
|
.end(function (error, result) {
|
|
expect(result).to.be.ok();
|
|
expect(result.statusCode).to.eql(201);
|
|
|
|
// stash for further use
|
|
token = result.body.token;
|
|
|
|
callback();
|
|
});
|
|
},
|
|
|
|
function (callback) {
|
|
superagent.post(SERVER_URL + '/api/v1/users')
|
|
.query({ access_token: token })
|
|
.send({ username: USERNAME_1, email: EMAIL_1, invite: false })
|
|
.end(async function (err, res) {
|
|
expect(res.statusCode).to.equal(201);
|
|
|
|
user_1_id = res.body.id;
|
|
token_1 = hat(8 * 32);
|
|
|
|
// HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...)
|
|
const token = { clientId: tokens.ID_WEBADMIN, identifier: user_1_id, expires: Date.now() + constants.DEFAULT_TOKEN_EXPIRATION_MSECS };
|
|
const result = await tokens.add(token);
|
|
|
|
token_1 = result.accessToken;
|
|
|
|
callback();
|
|
});
|
|
},
|
|
|
|
function (callback) {
|
|
appstoreIconServer
|
|
.get('/api/v1/apps/' + APP_STORE_ID + '/versions/' + APP_MANIFEST.version + '/icon')
|
|
.replyWithFile(200, path.resolve(__dirname, '../../../assets/avatar.png'));
|
|
|
|
const port = parseInt(url.parse(settings.apiServerOrigin()).port, 10);
|
|
http.createServer(appstoreIconServer.handler).listen(port, callback);
|
|
},
|
|
|
|
function (callback) {
|
|
process.stdout.write('Waiting for platform to be ready...');
|
|
async.retry({ times: 500, interval: 1000 }, function (retryCallback) {
|
|
if (platform.getStatus().message === '') return retryCallback();
|
|
process.stdout.write('.');
|
|
retryCallback('Platform not ready yet');
|
|
}, function (error) {
|
|
if (error) return callback(error);
|
|
console.log();
|
|
console.log('Platform is ready');
|
|
callback();
|
|
});
|
|
}
|
|
], done);
|
|
}
|
|
|
|
function stopBox(done) {
|
|
console.log('Stopping box code...');
|
|
|
|
delete process.env.TEST_CREATE_INFRA;
|
|
|
|
child_process.execSync('docker ps -qa --filter \'network=cloudron\' | xargs --no-run-if-empty docker rm -f');
|
|
appstoreIconServer.done();
|
|
|
|
async.series([
|
|
database._clear,
|
|
server.stop,
|
|
ldapServer.stop,
|
|
], done);
|
|
}
|
|
|
|
xdescribe('App API', function () {
|
|
let taskId = '';
|
|
|
|
before(startBox);
|
|
|
|
describe('Install', function () {
|
|
it('app install fails - missing manifest', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('appStoreId or manifest is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - null manifest', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('appStoreId or manifest is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - bad manifest format', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: 'epic' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('manifest must be an object');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - empty appStoreId format', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: null, appStoreId: '' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('appStoreId or manifest is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - invalid json', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send('garbage')
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - missing domain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: 'some', accessRestriction: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('domain is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - non-existing domain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: 'some', accessRestriction: null, domain: 'doesnotexist.com' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('No such domain');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - invalid subdomain type', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: 42, accessRestriction: null, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.eql('subdomain is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - reserved admin subdomain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: 'my', accessRestriction: null, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.contain('my is reserved');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - reserved smtp subdomain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: constants.SMTP_SUBDOMAIN, accessRestriction: null, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.contain(constants.SMTP_SUBDOMAIN + ' is reserved');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - portBindings must be object', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: APP_SUBDOMAIN, portBindings: 23, accessRestriction: null, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.contain('portBindings must be an object');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - accessRestriction is required', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: APP_SUBDOMAIN, portBindings: {}, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.contain('accessRestriction is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails - accessRestriction type is wrong', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: APP_SUBDOMAIN, portBindings: {}, accessRestriction: '', domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.body.message).to.contain('accessRestriction is required');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails for non admin', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token_1 })
|
|
.send({ manifest: APP_MANIFEST, subdomain: APP_SUBDOMAIN, portBindings: null, accessRestriction: null, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(403);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails because manifest download fails', function (done) {
|
|
const fake = nock(settings.apiServerOrigin()).get('/api/v1/apps/test').reply(404, {});
|
|
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ appStoreId: APP_STORE_ID, subdomain: APP_SUBDOMAIN, portBindings: null, domain: DOMAIN_0.domain, accessRestriction: { users: [ 'someuser' ], groups: [] } })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(404);
|
|
expect(fake.isDone()).to.be.ok();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install fails due to purchase failure', function (done) {
|
|
const fake1 = nock(settings.apiServerOrigin()).get('/api/v1/apps/test').reply(200, { manifest: APP_MANIFEST });
|
|
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ appStoreId: APP_STORE_ID, subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, portBindings: null, accessRestriction: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(402);
|
|
expect(fake1.isDone()).to.be.ok();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('app install succeeds with purchase', async function () {
|
|
const fake1 = nock(settings.apiServerOrigin()).get('/api/v1/apps/' + APP_STORE_ID).reply(200, { manifest: APP_MANIFEST });
|
|
const fake2 = nock(settings.apiServerOrigin()).post(function (uri) { return uri.indexOf('/api/v1/cloudronapps') >= 0; }, (body) => body.appstoreId === APP_STORE_ID && body.manifestId === APP_MANIFEST.id && body.appId).reply(201, { });
|
|
|
|
await settings.setAppstoreApiToken(USER_1_APPSTORE_TOKEN);
|
|
|
|
const res = await superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ appStoreId: APP_STORE_ID, subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: { users: [ 'someuser' ], groups: [] } })
|
|
|
|
expect(res.statusCode).to.equal(202);
|
|
expect(res.body.id).to.be.a('string');
|
|
APP_ID = res.body.id;
|
|
expect(fake1.isDone()).to.be.ok();
|
|
expect(fake2.isDone()).to.be.ok();
|
|
taskId = res.body.taskId;
|
|
});
|
|
|
|
it('app install fails because of conflicting subdomain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.send({ manifest: APP_MANIFEST, subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, portBindings: null, accessRestriction: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(409);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('get', function () {
|
|
it('can get app status', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.id).to.eql(APP_ID);
|
|
expect(res.body.installationState).to.be.ok();
|
|
expect(res.body.mailboxName).to.be(APP_SUBDOMAIN + '.app');
|
|
expect(res.body.mailboxDomain).to.be(DOMAIN_0.domain);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('cannot get invalid app status', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/kubachi')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(404);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can get all apps', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.apps).to.be.an('array');
|
|
expect(res.body.apps[0].id).to.eql(APP_ID);
|
|
expect(res.body.apps[0].installationState).to.be.ok();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('non admin cannot see the app due to accessRestriction', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps')
|
|
.query({ access_token: token_1 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.apps).to.be.an('array');
|
|
expect(res.body.apps.length).to.equal(0);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xdescribe('post installation', function () {
|
|
let appResult, appEntry;
|
|
|
|
it('task completed', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
it('app is running', function (callback) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
if (res.statusCode !== 200) return callback(new Error('Response error'));
|
|
if (res.body.installationState === apps.ISTATE_INSTALLED) { appResult = res.body; return callback(); }
|
|
if (res.body.installationState === apps.ISTATE_ERROR) return callback(new Error('Install error'));
|
|
|
|
callback(new Error('Unknown app state:' + res.body.installationState));
|
|
});
|
|
});
|
|
|
|
it('can get app', function (done) {
|
|
apps.get(appResult.id, function (error, app) {
|
|
expect(!error).to.be.ok();
|
|
expect(app).to.be.an('object');
|
|
appEntry = app;
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('container created', function (done) {
|
|
expect(appResult.containerId).to.be(undefined);
|
|
expect(appEntry.containerId).to.be.ok();
|
|
docker.getContainer(appEntry.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data.Config.ExposedPorts['7777/tcp']).to.eql({ });
|
|
expect(data.Config.Env).to.contain('CLOUDRON_WEBADMIN_ORIGIN=' + settings.dashboardOrigin());
|
|
expect(data.Config.Env).to.contain('CLOUDRON_API_ORIGIN=' + settings.dashboardOrigin());
|
|
expect(data.Config.Env).to.contain('CLOUDRON=1');
|
|
expect(data.Config.Env).to.contain('CLOUDRON_APP_ORIGIN=https://' + APP_SUBDOMAIN + '.' + DOMAIN_0.domain);
|
|
expect(data.Config.Env).to.contain('CLOUDRON_APP_DOMAIN=' + APP_SUBDOMAIN + '.' + DOMAIN_0.domain);
|
|
// Hostname must not be set of app fqdn or app subdomain!
|
|
expect(data.Config.Hostname).to.not.contain(APP_SUBDOMAIN);
|
|
expect(data.Config.Env).to.contain('ECHO_SERVER_PORT=7171');
|
|
expect(data.HostConfig.PortBindings['7778/tcp'][0].HostPort).to.eql('7171');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('nginx config', function (done) {
|
|
expect(fs.existsSync(paths.NGINX_APPCONFIG_DIR + '/' + APP_SUBDOMAIN + '.conf'));
|
|
done();
|
|
});
|
|
|
|
it('volume created', function (done) {
|
|
expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + APP_ID));
|
|
let volume = docker.getVolume(APP_ID + '-localstorage');
|
|
volume.inspect(function (error, volume) {
|
|
expect(error).to.be(null);
|
|
expect(volume.Labels.appId).to.eql(APP_ID);
|
|
expect(volume.Options.device).to.eql(paths.APPS_DATA_DIR + '/' + APP_ID + '/data');
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('tcp port mapping works', function (done) {
|
|
const client = net.connect(7171);
|
|
client.on('data', function (data) {
|
|
expect(data.toString()).to.eql('ECHO_SERVER_PORT=7171');
|
|
done();
|
|
});
|
|
client.on('error', done);
|
|
});
|
|
|
|
it('running container has volume mounted', function (done) {
|
|
docker.getContainer(appEntry.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data.Mounts.filter(function (mount) { return mount.Destination === '/app/data'; })[0].Type).to.eql('volume');
|
|
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('app responds to http request', function (done) {
|
|
console.log(`talking to http://${appEntry.containerIp}:7777`);
|
|
superagent.get(`http://${appEntry.containerIp}:7777`).end(function (error, result) {
|
|
console.dir(error);
|
|
expect(result.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('installation - redis addon created', function (done) {
|
|
checkRedis('redis-' + APP_ID, done);
|
|
});
|
|
});
|
|
|
|
xdescribe('logs', function () {
|
|
it('logs - stdout and stderr', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/logs')
|
|
.query({ access_token: token })
|
|
.buffer(false)
|
|
.end(function (err, res) {
|
|
const data = '';
|
|
res.on('data', function (d) { data += d.toString('utf8'); });
|
|
res.on('end', function () {
|
|
expect(data.length).to.not.be(0);
|
|
done();
|
|
});
|
|
res.on('error', done);
|
|
});
|
|
});
|
|
|
|
it('logStream - requires event-stream accept header', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/logstream')
|
|
.query({ access_token: token, fromLine: 0 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.be(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('logStream - stream logs', function (done) {
|
|
const options = {
|
|
port: constants.PORT, host: 'localhost', path: '/api/v1/apps/' + APP_ID + '/logstream?access_token=' + token,
|
|
headers: { 'Accept': 'text/event-stream', 'Connection': 'keep-alive' }
|
|
};
|
|
|
|
// superagent doesn't work. maybe https://github.com/visionmedia/superagent/issues/420
|
|
const req = http.get(options, function (res) {
|
|
const data = '';
|
|
res.on('data', function (d) { data += d.toString('utf8'); });
|
|
setTimeout(function checkData() {
|
|
expect(data.length).to.not.be(0);
|
|
data.split('\n').forEach(function (line) {
|
|
if (line.indexOf('id: ') !== 0) return;
|
|
expect(parseInt(line.substr(4), 10)).to.be.a('number'); // timestamp
|
|
});
|
|
|
|
req.abort();
|
|
done();
|
|
}, 1000);
|
|
res.on('error', done);
|
|
});
|
|
|
|
req.on('error', done);
|
|
});
|
|
});
|
|
|
|
describe('configure (db fields)', function () {
|
|
it('fails for no label', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/label')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails for invalid label', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/label')
|
|
.query({ access_token: token })
|
|
.send({ label: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set the label', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/label')
|
|
.query({ access_token: token })
|
|
.send({ label: 'LABEL'})
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('did set the label', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.label).to.be('LABEL');
|
|
done();
|
|
});
|
|
});
|
|
|
|
///////////// tags
|
|
it('fails for no tags', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/tags')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails for null tags', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/tags')
|
|
.query({ access_token: token })
|
|
.send({ tags: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails for empty tag', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/tags')
|
|
.query({ access_token: token })
|
|
.send({ tags: ['tag1', '', 'tag2'] })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails for non-string tag', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/tags')
|
|
.query({ access_token: token })
|
|
.send({ tags: ['tag1', 123, 'tag2'] })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set the tags', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/tags')
|
|
.query({ access_token: token })
|
|
.send({ tags: [ 'tag1', 'tag2' ] })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('did set the tags', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.tags).to.eql([ 'tag1', 'tag2' ]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
///////////// icon
|
|
it('fails for no icon', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/icon')
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails for invalid icon', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/icon')
|
|
.send({ icon: 'something non base64' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set the icon', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/icon')
|
|
.query({ access_token: token })
|
|
.send({ icon: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('did set the icon', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID + '/icon')
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200); // response is some PNG
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can reset the icon', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/icon')
|
|
.query({ access_token: token })
|
|
.send({ icon: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
////////////// automatic updates
|
|
it('can disable automatic updates', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/automatic_update')
|
|
.query({ access_token: token })
|
|
.send({ enable: false })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('did disable automatic updates', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.enableAutomaticUpdate).to.be(false);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can disable automatic backups', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/automatic_backup')
|
|
.query({ access_token: token })
|
|
.send({ enable: false })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('did disable automatic backups', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
expect(res.body.enableBackup).to.be(false);
|
|
done();
|
|
});
|
|
});
|
|
|
|
////////////// access restriction
|
|
it('cannot set bad accessRestriction', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/access_restriction')
|
|
.query({ access_token: token })
|
|
.send({ accessRestriction: false })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can clear accessRestriction', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/access_restriction')
|
|
.query({ access_token: token })
|
|
.send({ accessRestriction: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set accessRestriction', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/access_restriction')
|
|
.query({ access_token: token })
|
|
.send({ accessRestriction: { users: [ 'someuserid' ], groups: [ 'somegroupid' ] } })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
////////////// upstreamUri
|
|
it('cannot set empty upstreamUri', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/upstream_uri')
|
|
.query({ access_token: token })
|
|
.send({ upstreamUri: '' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('cannot set bad upstreamUri', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/upstream_uri')
|
|
.query({ access_token: token })
|
|
.send({ upstreamUri: 'foobar:com' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set upstreamUri', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/upstream_uri')
|
|
.query({ access_token: token })
|
|
.send({ upstreamUri: 'https://1.2.3.4:443' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
/////////////// cert
|
|
it('cannot set only the cert, no key', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/cert')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, cert: CERT })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('cannot reconfigure app with only the key, no cert', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/cert')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, key: KEY })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('cannot set invalid cert', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/cert')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, cert: 'x' + CERT, key: KEY })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set cert', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/cert')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, cert: CERT, key: KEY })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can reset cert', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/cert')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: APP_SUBDOMAIN, domain: DOMAIN_0.domain, cert: null, key: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xdescribe('memory limit', function () {
|
|
it('fails for no memory limit', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/memory_limit')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails for invalid memory limit', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/memory_limit')
|
|
.query({ access_token: token })
|
|
.send({ memoryLimit: -34 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set the memory limit', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/memory_limit')
|
|
.query({ access_token: token })
|
|
.send({ memoryLimit: 512 * 1024 * 1024 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('wait for memory limit', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
it('did set memory limit', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data.HostConfig.Memory).to.be(512 * 1024 * 1024/2);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('configure reverseProxy - robotsTxt', function () {
|
|
it('fails with bad robotsTxt', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/reverse_proxy')
|
|
.query({ access_token: token })
|
|
.send({ robotsTxt: 34 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set robotsTxt', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/reverse_proxy')
|
|
.query({ access_token: token })
|
|
.send({ robotsTxt: 'any string is good', csp: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can reset robotsTxt', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/reverse_proxy')
|
|
.query({ access_token: token })
|
|
.send({ robotsTxt: null, csp: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails with bad csp', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/reverse_proxy')
|
|
.query({ access_token: token })
|
|
.send({ robotsTxt: null, csp: 34 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can set frame-ancestors', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/reverse_proxy')
|
|
.query({ access_token: token })
|
|
.send({ robotsTxt: null, csp: 'frame-ancestors \'self\'' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('can reset frame-ancestors', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/reverse_proxy')
|
|
.query({ access_token: token })
|
|
.send({ robotsTxt: null, csp: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(200);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('configure subdomain', function () {
|
|
it('cannot reconfigure app with missing domain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/location')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: 'hellothre' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('cannot reconfigure app with bad subdomain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/location')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: 1234, domain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('non admin cannot reconfigure app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/location')
|
|
.query({ access_token: token_1 })
|
|
.send({ subdomain: APP_SUBDOMAIN_NEW, domain: DOMAIN_0.domain, portBindings: { ECHO_SERVER_PORT: 7172 }, accessRestriction: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(403);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can change subdomain', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/location')
|
|
.query({ access_token: token })
|
|
.send({ subdomain: APP_SUBDOMAIN_NEW, domain: DOMAIN_0.domain, portBindings: { ECHO_SERVER_PORT: 7172 } })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did change subdomain', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
expect(app.mailboxName).to.be(APP_SUBDOMAIN_NEW + '.app'); // must follow subdomain change
|
|
expect(app.mailboxDomain).to.be(DOMAIN_0.domain);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data.Config.Env).to.contain('CLOUDRON_APP_ORIGIN=https://' + APP_SUBDOMAIN_NEW + '.' + DOMAIN_0.domain);
|
|
expect(data.Config.Env).to.contain('CLOUDRON_APP_DOMAIN=' + APP_SUBDOMAIN_NEW + '.' + DOMAIN_0.domain);
|
|
expect(data.Config.Hostname).to.not.contain(APP_SUBDOMAIN_NEW);
|
|
expect(data.Config.Env).to.contain('ECHO_SERVER_PORT=7172');
|
|
expect(data.HostConfig.PortBindings['7778/tcp'][0].HostPort).to.eql('7172');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xit('port mapping works after reconfiguration', function (done) {
|
|
setTimeout(function () {
|
|
const client = net.connect(7172);
|
|
client.on('data', function (data) {
|
|
expect(data.toString()).to.eql('ECHO_SERVER_PORT=7172');
|
|
done();
|
|
});
|
|
client.on('error', done);
|
|
}, 4000);
|
|
});
|
|
});
|
|
|
|
describe('configure debug mode', function () {
|
|
it('fails with missing mode', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/debug_mode')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails with bad debug mode', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/debug_mode')
|
|
.query({ access_token: token })
|
|
.send({ debugMode: 'sleep' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can set debug mode', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/debug_mode')
|
|
.query({ access_token: token })
|
|
.send({ debugMode: { readonlyRootfs: false } })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did change readonly rootfs', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
|
|
expect(data.HostConfig.ReadonlyRootfs).to.be(false);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xit('can reset debug mode', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/debug_mode')
|
|
.query({ access_token: token })
|
|
.send({ debugMode: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did change readonly rootfs', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
|
|
expect(data.HostConfig.ReadonlyRootfs).to.be(true);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('configure env', function () {
|
|
it('fails with missing env', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/env')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails with bad env mode', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/env')
|
|
.query({ access_token: token })
|
|
.send({ env: 'ok' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can set env', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/env')
|
|
.query({ access_token: token })
|
|
.send({ env: { 'OPM': 'SAITAMA' } })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did set env', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
|
|
expect(data.Config.Env).to.contain('OPM=SAITAMA');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xit('can reset env', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/env')
|
|
.query({ access_token: token })
|
|
.send({ env: {} })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did reset env', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data.Config.Env).to.not.contain('OPM=SAITAMA');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('configure mailbox', function () {
|
|
it('fails with missing mailbox', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/mailbox')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails with bad mailbox', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/mailbox')
|
|
.query({ access_token: token })
|
|
.send({ mailbox: 'genos@cloudron.io' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can set mailbox', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/mailbox')
|
|
.query({ access_token: token })
|
|
.send({ mailboxName: 'genos', mailboxDomain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did set mailbox', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
|
|
expect(data.Config.Env).to.contain('CLOUDRON_MAIL_FROM=genos@' + app.domain);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xit('can reset mailbox', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/mailbox')
|
|
.query({ access_token: token })
|
|
.send({ mailboxName: null, mailboxDomain: DOMAIN_0.domain })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did reset mailbox', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
docker.getContainer(app.containerId).inspect(function (error, data) {
|
|
expect(error).to.not.be.ok();
|
|
expect(data.Config.Env).to.not.contain('CLOUDRON_MAIL_FROM=' + app.subdomain + '@' + app.domain + '.app');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('configure data dir', function () {
|
|
it('fails with missing datadir', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/data_dir')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('fails with bad data dir', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/data_dir')
|
|
.query({ access_token: token })
|
|
.send({ dataDir: 'what' })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(400);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can set data dir', function (done) {
|
|
let dataDir = path.join(paths.baseDir(), 'apps-test-datadir-' + crypto.randomBytes(4).readUInt32LE(0));
|
|
fs.mkdirSync(dataDir);
|
|
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/data_dir')
|
|
.query({ access_token: token })
|
|
.send({ dataDir: dataDir })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('can reset data dir', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/configure/data_dir')
|
|
.query({ access_token: token })
|
|
.send({ dataDir: null })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
});
|
|
|
|
describe('start/stop', function () {
|
|
it('non admin cannot stop app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop')
|
|
.query({ access_token: token_1 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(403);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can stop app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/stop')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for task', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
xit('did stop the app', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
superagent.get(`http://${app.containerIp}:7777` + APP_MANIFEST.healthCheckPath).end(function (err) {
|
|
if (!err || err.code !== 'ECONNREFUSED') return done(new Error('App has not died'));
|
|
|
|
// wait for app status to be updated
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID).query({ access_token: token }).end(function (error, result) {
|
|
if (error || result.statusCode !== 200 || result.body.runState !== 'stopped') return done(new Error('App is not in stopped state'));
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
xit('nonadmin cannot start app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/start')
|
|
.query({ access_token: token_1 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(403);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can start app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/start')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for app to start', function (done) {
|
|
waitForTask(taskId, function () { setTimeout(done, 5000); }); // give app 5 seconds to start
|
|
});
|
|
|
|
xit('did start the app', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
superagent.get('http://localhost:' + app.httpPort + APP_MANIFEST.healthCheckPath)
|
|
.end(function (err, res) {
|
|
if (res && res.statusCode === 200) return done();
|
|
done(new Error('app is not running'));
|
|
});
|
|
});
|
|
});
|
|
|
|
xit('can restart app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/restart')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('wait for app to restart', function (done) {
|
|
waitForTask(taskId, function () { setTimeout(done, 12000); }); // give app 12 seconds (to die and start)
|
|
});
|
|
|
|
xit('did restart the app', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
if (error) return done(error);
|
|
|
|
superagent.get('http://localhost:' + app.httpPort + APP_MANIFEST.healthCheckPath)
|
|
.end(function (err, res) {
|
|
if (res && res.statusCode === 200) return done();
|
|
done(new Error('app is not running'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('uninstall', function () {
|
|
it('cannot uninstall invalid app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/whatever/uninstall')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(404);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('non admin cannot uninstall app', function (done) {
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
|
.query({ access_token: token_1 })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(403);
|
|
done();
|
|
});
|
|
});
|
|
|
|
xit('can uninstall app', function (done) {
|
|
const fake1 = nock(settings.apiServerOrigin()).get(function (uri) { return uri.indexOf('/api/v1/cloudronapps/') >= 0; }).reply(200, { });
|
|
const fake2 = nock(settings.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/cloudronapps/') >= 0; }).reply(204, { });
|
|
|
|
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/uninstall')
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
expect(res.statusCode).to.equal(202);
|
|
taskId = res.body.taskId;
|
|
expect(fake1.isDone()).to.be.ok();
|
|
expect(fake2.isDone()).to.be.ok();
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
xdescribe('post uninstall', function () {
|
|
let appEntry;
|
|
|
|
it('can get app', function (done) {
|
|
apps.get(APP_ID, function (error, app) {
|
|
expect(!error).to.be.ok();
|
|
expect(app).to.be.an('object');
|
|
appEntry = app;
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('did uninstall the app', function (done) {
|
|
waitForTask(taskId, done);
|
|
});
|
|
|
|
it('app is gone', function (done) {
|
|
superagent.get(SERVER_URL + '/api/v1/apps/' + APP_ID)
|
|
.query({ access_token: token })
|
|
.end(function (err, res) {
|
|
if (res.statusCode === 404) return done(null);
|
|
done(new Error('App is still there'));
|
|
});
|
|
});
|
|
|
|
it('container destroyed', function (done) {
|
|
docker.getContainer(appEntry.containerId).inspect(function (error, data) {
|
|
expect(error).to.be.ok();
|
|
expect(data).to.not.be.ok();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('volume destroyed', function (done) {
|
|
expect(!fs.existsSync(paths.APPS_DATA_DIR + '/' + APP_ID));
|
|
done();
|
|
});
|
|
|
|
it('removed nginx', function (done) {
|
|
expect(!fs.existsSync(paths.NGINX_APPCONFIG_DIR + '/' + APP_SUBDOMAIN + '.conf'));
|
|
done();
|
|
});
|
|
|
|
it('removed redis addon', function (done) {
|
|
docker.getContainer('redis-' + APP_ID).inspect(function (error) {
|
|
expect(error).to.be.ok();
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('the end', function () {
|
|
// this is here so we can debug things if tests fail
|
|
it('can stop box', stopBox);
|
|
});
|
|
});
|