| '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; |