blob: 1fe7c69756222e0f8154da7099c72611701ec414 [file] [log] [blame]
'use strict';
const assert = require('assert');
const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
const Config = require('../../tools/js/config.js');
const Database = require('../../tools/js/database.js');
const RemoteAPI = require('../../tools/js/remote.js').RemoteAPI;
const BrowserPrivilegedAPI = require('../../public/v3/privileged-api.js').PrivilegedAPI;
const NodePrivilegedAPI = require('../../tools/js/privileged-api').PrivilegedAPI;
class TestServer {
constructor()
{
this._pidFile = null;
this._testConfigPath = Config.path('testServer.config');
this._dataDirectory = Config.path('dataDirectory');
this._backupDataPath = null;
this._pidWaitStart = null;
this._shouldLog = false;
this._pgsqlDirectory = null;
this._server = null;
this._databaseName = Config.value('testDatabaseName');
this._databaseUser = Config.value('database.username');
this._databaseHost = Config.value('database.host');
this._databasePort = Config.value('database.port');
this._database = null;
this._remote = null
}
start()
{
let testConfigContent = this.testConfig();
fs.writeFileSync(this._testConfigPath, JSON.stringify(testConfigContent, null, ' '));
this._ensureTestDatabase();
this._ensureDataDirectory();
return this._startApache();
}
stop()
{
this._restoreDataDirectory();
return this._stopApache();
}
remoteAPI()
{
assert(this._remote);
return this._remote;
}
database()
{
assert(this._databaseName);
if (!this._database)
this._database = new Database(this._databaseName);
return this._database;
}
testConfig()
{
return {
'siteTitle': 'Test Dashboard',
'debug': true,
'jsonCacheMaxAge': 600,
'dataDirectory': Config.value('dataDirectory'),
'database': {
'host': Config.value('database.host'),
'port': Config.value('database.port'),
'username': Config.value('database.username'),
'password': Config.value('database.password'),
'name': Config.value('testDatabaseName'),
},
'uploadFileLimitInMB': 2,
'uploadUserQuotaInMB': 5,
'uploadTotalQuotaInMB': 7,
'uploadDirectory': Config.value('dataDirectory') + '/uploaded',
'universalWorkerPassword': null,
'maintenanceMode': false,
'clusterStart': [2000, 1, 1, 0, 0],
'clusterSize': [0, 2, 0],
'defaultDashboard': [[]],
'dashboards': {},
'summaryPages': []
}
}
_ensureDataDirectory()
{
let backupPath = path.resolve(this._dataDirectory, '../original-data');
if (fs.existsSync(this._dataDirectory)) {
assert.ok(!fs.existsSync(backupPath), `Both ${this._dataDirectory} and ${backupPath} exist. Cannot make a backup of data`);
fs.renameSync(this._dataDirectory, backupPath);
this._backupDataPath = backupPath;
} else if (fs.existsSync(backupPath)) // Assume this is a backup from the last failed run
this._backupDataPath = backupPath;
fs.mkdirSync(this._dataDirectory, 0o755);
fs.mkdirSync(path.resolve(this._dataDirectory, 'uploaded'), 0o755);
}
_restoreDataDirectory()
{
childProcess.execFileSync('rm', ['-rf', this._dataDirectory]);
if (this._backupDataPath)
fs.renameSync(this._backupDataPath, this._dataDirectory);
}
cleanDataDirectory()
{
let fileList = fs.readdirSync(this._dataDirectory);
for (let filename of fileList) {
if (filename != 'uploaded')
fs.unlinkSync(path.resolve(this._dataDirectory, filename));
}
fileList = fs.readdirSync(path.resolve(this._dataDirectory, 'uploaded'));
for (let filename of fileList)
fs.unlinkSync(path.resolve(this._dataDirectory, 'uploaded', filename));
}
_ensureTestDatabase()
{
try {
this._executePgsqlCommand('dropdb');
} catch (error) { }
this._executePgsqlCommand('createdb');
this._executePgsqlCommand('psql', ['--command', `grant all privileges on database "${this._databaseName}" to "${this._databaseUser}";`]);
this.initDatabase();
}
initDatabase()
{
if (this._database)
this._database.disconnect();
this._database = null;
const initFilePath = Config.pathFromRoot('init-database.sql');
const migrateFilePath = Config.pathFromRoot('migrate-database.sql');
for (const filePath of [initFilePath, migrateFilePath]) {
this._executePgsqlCommand('psql', ['--username', this._databaseUser, '--file', filePath],
{stdio: ['ignore', 'ignore', 'ignore']});
}
}
_executePgsqlCommand(command, args, options)
{
if (!this._pgsqlDirectory)
this._pgsqlDirectory = this._determinePgsqlDirectory();
childProcess.execFileSync(path.resolve(this._pgsqlDirectory, command),
[this._databaseName, '--host', this._databaseHost, '--port', this._databasePort].concat(args || []), options);
}
_determinePgsqlDirectory()
{
try {
let initdbLocation = childProcess.execFileSync('which', ['initdb']).toString();
return path.dirname(initdbLocation);
} catch (error) {
let serverPgsqlLocation = '/Applications/Server.app/Contents/ServerRoot/usr/bin/';
childProcess.execFileSync(path.resolve(serverPgsqlLocation, 'initdb'), ['--version']);
return serverPgsqlLocation;
}
}
_startApache()
{
let pidFile = Config.path('testServer.httpdPID');
let httpdConfig = Config.path('testServer.httpdConfig');
let port = Config.value('testServer.port');
let errorLog = Config.path('testServer.httpdErrorLog');
let mutexFile = Config.path('testServer.httpdMutexDir');
let phpVersion = childProcess.execFileSync('php', ['-v'], {stdio: ['pipe', 'pipe', 'ignore']}).toString().includes('PHP 5') ? 'PHP5' : 'PHP7';
if (!fs.existsSync(mutexFile))
fs.mkdirSync(mutexFile, 0o755);
let args = [
'-f', httpdConfig,
'-c', `SetEnv ORG_WEBKIT_PERF_CONFIG_PATH ${this._testConfigPath}`,
'-c', `Listen ${port}`,
'-c', `PidFile ${pidFile}`,
'-c', `ErrorLog ${errorLog}`,
'-c', `Mutex file:${mutexFile}`,
'-c', `DocumentRoot ${Config.serverRoot()}`,
'-D', phpVersion];
if (this._shouldLog)
console.log(args);
childProcess.execFileSync('httpd', args);
this._server = {
scheme: 'http',
host: 'localhost',
port: port,
};
this._pidWaitStart = Date.now();
this._pidFile = pidFile;
this._remote = new RemoteAPI(this._server);
return new Promise(this._waitForPid.bind(this, true));
}
_stopApache()
{
if (!this._pidFile)
return;
let pid = fs.readFileSync(this._pidFile, 'utf-8').trim();
if (this._shouldLog)
console.log('Stopping', pid);
childProcess.execFileSync('kill', ['-TERM', pid]);
this._pidWaitStart = Date.now();
return new Promise(this._waitForPid.bind(this, false));
}
_waitForPid(shouldExist, resolve, reject)
{
if (fs.existsSync(this._pidFile) != shouldExist) {
if (Date.now() - this._pidWaitStart > 8000)
reject();
else
setTimeout(this._waitForPid.bind(this, shouldExist, resolve, reject), 100);
return;
}
resolve();
}
inject(privilegedAPIType='browser')
{
console.assert(privilegedAPIType === 'browser' || privilegedAPIType === 'node');
const useNodePrivilegedAPI = privilegedAPIType === 'node';
const self = this;
before(function () {
this.timeout(10000);
return self.start();
});
let originalRemote;
let originalPrivilegedAPI;
beforeEach(function () {
this.timeout(10000);
self.initDatabase();
self.cleanDataDirectory();
originalRemote = global.RemoteAPI;
global.RemoteAPI = self._remote;
self._remote.clearCookies();
originalPrivilegedAPI = global.PrivilegedAPI;
global.PrivilegedAPI = useNodePrivilegedAPI ? NodePrivilegedAPI : BrowserPrivilegedAPI;
if (!useNodePrivilegedAPI) {
global.PrivilegedAPI._token = null;
global.PrivilegedAPI._expiration = null;
}
});
after(function () {
this.timeout(10000);
global.RemoteAPI = originalRemote;
global.PrivilegedAPI = originalPrivilegedAPI;
return self.stop();
});
}
}
if (!global.TestServer)
global.TestServer = new TestServer;
if (typeof module != 'undefined')
module.exports = global.TestServer;