webgme-engine
Version:
WebGME server and Client API without a GUI
825 lines (747 loc) • 25.6 kB
JavaScript
/*globals requireJS*/
/*eslint-env node, mocha*/
/**
* @author kecso / https://github.com/kecso
* @author lattmann / https://github.com/lattmann
* @author pmeijer / https://github.com/pmeijer
*/
;
global.TESTING = true;
global.WebGMEGlobal = {};
process.env.NODE_ENV = (process.env.NODE_ENV && process.env.NODE_ENV.indexOf('test') === 0) ?
process.env.NODE_ENV : 'test';
// If test processes aren't stopping use this to find open handles etc.
//
// $ npm install wtfnode
//
// const wtf = require('wtfnode');
//
// setInterval(() => {
// console.log(wtf.dump());
// }, 5000);
//adding a local storage class to the global Namespace
var WebGME = require('../index'),
mongoUri = require('mongo-uri'),
_gmeConfig,
getGmeConfig = function () {
// makes sure that for each request it returns with a unique object and tests will not interfere
if (!_gmeConfig) {
// if some tests are deleting or unloading the config
_gmeConfig = require(process.cwd() + '/config');
}
return JSON.parse(JSON.stringify(_gmeConfig));
},
_Core,
_NodeStorage,
_storageUtil,
_Logger,
_logger,
getMongoStorage = function (logger, gmeConfig, gmeAuth) {
var SafeStorage = require('../src/server/storage/safestorage'),
Mongo = require('../src/server/storage/mongo'),
mongo = new Mongo(logger, gmeConfig);
return new SafeStorage(mongo, logger, gmeConfig, gmeAuth);
},
getMemoryStorage = function (logger, gmeConfig, gmeAuth) {
var SafeStorage = require('../src/server/storage/safestorage'),
Memory = require('../src/server/storage/memory'),
memory = new Memory(logger, gmeConfig);
return new SafeStorage(memory, logger, gmeConfig, gmeAuth);
},
getRedisStorage = function (logger, gmeConfig, gmeAuth) {
var SafeStorage = require('../src/server/storage/safestorage'),
RedisAdapter = require('../src/server/storage/datastores/redisadapter'),
redisAdapter = new RedisAdapter(logger, gmeConfig);
return new SafeStorage(redisAdapter, logger, gmeConfig, gmeAuth);
},
_generateKey,
_GMEAuth,
_ExecutorClient,
_BlobClient,
_Project,
_STORAGE_CONSTANTS,
_CONSTANTS,
_should,
_expect,
_REGEXP,
_superagent,
_mongodb,
Q = require('q'),
_fs,
_path,
_rimraf,
_childProcess,
_SEED_DIR,
exports = {
WebGME: WebGME,
// test logger instance, used by all tests and only tests
requirejs: requireJS,
Q: Q
};
Object.defineProperties(exports, {
Project: {
get: function () {
if (!_Project) {
_Project = require('../src/server/storage/userproject');
}
return _Project;
}
},
NodeStorage: {
get: function () {
if (!_NodeStorage) {
_NodeStorage = requireJS('../src/common/storage/nodestorage');
}
return _NodeStorage;
}
},
Logger: {
get: function () {
if (!_Logger) {
_Logger = require('../src/server/logger');
}
return _Logger;
}
},
logger: {
get: function () {
if (!_logger) {
var config = getGmeConfig();
_logger = exports.Logger.create('gme:test', config.server.log, false);
}
return _logger;
}
},
BlobClient: {
get: function () {
if (!_BlobClient) {
_BlobClient = requireJS('blob/BlobClient');
}
return _BlobClient;
}
},
ExecutorClient: {
get: function () {
if (!_ExecutorClient) {
_ExecutorClient = requireJS('common/executor/ExecutorClient');
}
return _ExecutorClient;
}
},
Core: {
get: function () {
if (!_Core) {
_Core = requireJS('common/core/coreQ');
}
return _Core;
}
},
GMEAuth: {
get: function () {
if (!_GMEAuth) {
_GMEAuth = require('../src/server/middleware/auth/gmeauth');
}
return _GMEAuth;
}
},
generateKey: {
get: function () {
if (!_generateKey) {
_generateKey = requireJS('common/util/key');
}
return _generateKey;
}
},
superagent: {
get: function () {
if (!_superagent) {
_superagent = require('superagent');
}
return _superagent;
}
},
mongodb: {
get: function () {
if (!_mongodb) {
_mongodb = require('mongodb');
}
return _mongodb;
}
},
rimraf: {
get: function () {
if (!_rimraf) {
_rimraf = require('rimraf').rimraf;
}
return _rimraf;
}
},
childProcess: {
get: function () {
if (!_childProcess) {
_childProcess = require('child_process');
}
return _childProcess;
}
},
path: {
get: function () {
if (!_path) {
_path = require('path');
}
return _path;
}
},
fs: {
get: function () {
if (!_fs) {
_fs = require('fs');
}
return _fs;
}
},
SEED_DIR: {
get: function () {
if (!_SEED_DIR) {
_SEED_DIR = exports.path.join(__dirname, '../seeds/');
}
return _SEED_DIR;
}
},
STORAGE_CONSTANTS: {
get: function () {
if (!_STORAGE_CONSTANTS) {
_STORAGE_CONSTANTS = requireJS('common/storage/constants');
}
return _STORAGE_CONSTANTS;
}
},
CONSTANTS: {
get: function () {
if (!_CONSTANTS) {
_CONSTANTS = requireJS('common/Constants');
}
return _CONSTANTS;
}
},
storageUtil: {
get: function () {
if (!_storageUtil) {
_storageUtil = requireJS('common/storage/util');
}
return _storageUtil;
}
},
should: {
get: function () {
if (!_should) {
_should = require('chai').should();
}
return _should;
}
},
expect: {
get: function () {
if (!_expect) {
_expect = require('chai').expect;
}
return _expect;
}
},
REGEXP: {
get: function () {
if (!_REGEXP) {
_REGEXP = requireJS('common/regexp');
}
return _REGEXP;
}
}
});
/**
* A combination of Q.allSettled and Q.all. It works like Q.allSettled in the sense that
* the promise is not rejected until all promises have finished and like Q.all in that it
* is rejected with the first encountered rejection and resolves with an array of "values".
*
* The rejection is always an Error.
* @param promises
* @returns {*|promise}
*/
Q.allDone = function (promises) {
var deferred = Q.defer();
Q.allSettled(promises)
.then(function (results) {
var i,
err,
values = [];
for (i = 0; i < results.length; i += 1) {
if (results[i].state === 'rejected') {
err = results[i].reason instanceof Error ? results[i].reason : new Error(results[i].reason);
deferred.reject(err);
break;
} else if (results[i].state === 'fulfilled') {
values.push(results[i].value);
} else {
deferred.reject(new Error('Unexpected promise state' + results[i].state));
break;
}
}
deferred.resolve(values);
});
return deferred.promise;
};
function clearDatabase(gmeConfigParameter, callback) {
var deferred = Q.defer(),
client,
db;
exports.mongodb.MongoClient.connect(gmeConfigParameter.mongo.uri, gmeConfigParameter.mongo.options)
.then(function (client_) {
client = client_;
db = client.db(mongoUri.parse(gmeConfigParameter.mongo.uri).database);
return db.listCollections().toArray();
})
.then(function (collectionNames) {
var collectionPromises = [];
collectionNames.map(function (collData) {
if (collData.name.indexOf('system.') === -1) {
collectionPromises.push(db.dropCollection(collData.name));
}
});
return Promise.all(collectionPromises);
})
.then(function () {
return client.close();
})
.then(deferred.resolve)
.catch(deferred.reject);
return deferred.promise.nodeify(callback);
}
function getGMEAuth(gmeConfigParameter, callback) {
var deferred = Q.defer(),
gmeAuth;
gmeAuth = new exports.GMEAuth(null, gmeConfigParameter);
gmeAuth.connect(function (err) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(gmeAuth);
}
});
return deferred.promise.nodeify(callback);
}
function clearDBAndGetGMEAuth(gmeConfigParameter, projectNameOrNames, callback) {
var deferred = Q.defer(),
gmeAuth,
projectAuthParams,
clearDB = clearDatabase(gmeConfigParameter),
guestAccount = gmeConfigParameter.authentication.guestAccount;
clearDB
.then(function () {
return getGMEAuth(gmeConfigParameter);
})
.then(function (gmeAuth_) {
gmeAuth = gmeAuth_;
projectAuthParams = {
entityType: gmeAuth.authorizer.ENTITY_TYPES.PROJECT
};
return Q.allDone([
gmeAuth.addUser(guestAccount, guestAccount + '@example.com', guestAccount, true, { overwrite: true }),
gmeAuth.addUser('admin', 'admin@example.com', 'admin', true, { overwrite: true, siteAdmin: true })
]);
})
.then(function () {
var projectsToAuthorize = [],
projectName,
projectId,
i;
if (typeof projectNameOrNames === 'string') {
projectName = projectNameOrNames;
projectId = guestAccount + exports.STORAGE_CONSTANTS.PROJECT_ID_SEP + projectName;
projectsToAuthorize.push(
gmeAuth.authorizer.setAccessRights(guestAccount, projectId, {
read: true,
write: true,
delete: true
}, projectAuthParams)
);
} else if (projectNameOrNames instanceof Array) {
for (i = 0; i < projectNameOrNames.length; i += 1) {
projectId = guestAccount + exports.STORAGE_CONSTANTS.PROJECT_ID_SEP + projectNameOrNames[i];
projectsToAuthorize.push(
gmeAuth.authorizer.setAccessRights(guestAccount, projectId, {
read: true,
write: true,
delete: true
}, projectAuthParams)
);
}
} else {
exports.logger.warn('No projects to authorize...', projectNameOrNames);
}
return Q.allDone(projectsToAuthorize);
})
.then(function () {
deferred.resolve(gmeAuth);
})
.catch(deferred.reject);
return deferred.promise.nodeify(callback);
}
//TODO globally used functions to implement
function loadJsonFile(path) {
//TODO decide if throwing an exception is fine or we should handle it
return JSON.parse(exports.fs.readFileSync(path, 'utf8'));
}
function importProject(storage, parameters, callback) {
var deferred = Q.defer(),
extractDeferred = Q.defer(),
BC,
blobClient,
storageUtils,
cliImport,
projectJson,
branchName,
data = {};
try {
// Parameters check.
exports.expect(typeof storage).to.equal('object');
exports.expect(typeof parameters).to.equal('object');
exports.expect(typeof parameters.projectName).to.equal('string');
exports.expect(typeof parameters.gmeConfig).to.equal('object');
exports.expect(typeof parameters.logger).to.equal('object');
if (Object.hasOwn(parameters, 'username')) {
exports.expect(typeof parameters.username).to.equal('string');
data.username = parameters.username;
}
if (Object.hasOwn(parameters, 'ownerId')) {
exports.expect(typeof parameters.ownerId).to.equal('string');
data.ownerId = parameters.ownerId;
}
if (Object.hasOwn(parameters, 'kind')) {
exports.expect(typeof parameters.kind).to.equal('string');
data.kind = parameters.kind;
}
} catch (err) {
extractDeferred.reject(err);
}
if (typeof parameters.projectSeed === 'string' && parameters.projectSeed.toLowerCase().indexOf('.webgmex') > -1) {
BC = require('../src/server/middleware/blob/BlobClientWithFSBackend');
blobClient = new BC(parameters.gmeConfig, parameters.logger);
cliImport = require('../src/bin/import');
cliImport._addProjectPackageToBlob(blobClient, parameters.projectSeed)
.then(function (projectJson) {
extractDeferred.resolve(projectJson);
})
.catch(extractDeferred.reject);
} else if (typeof parameters.projectSeed === 'object') {
extractDeferred.resolve(parameters.projectSeed);
} else {
extractDeferred.reject(new Error('parameters.projectSeed must be filePath to a webgmex file ' +
'or a json of the project content.'));
}
branchName = parameters.branchName || 'master';
// Parameters check end.
data.projectName = parameters.projectName;
extractDeferred.promise
.then(function (projectJson_) {
projectJson = projectJson_;
return storage.createProject(data);
})
.then(function (project) {
var core = new exports.Core(project, { globConf: parameters.gmeConfig, logger: parameters.logger }),
result = {
status: null,
branchName: branchName,
commitHash: null,
project: project,
projectId: project.projectId,
core: core,
jsonProject: projectJson,
rootNode: null,
FCO: null,
rootHash: null,
blobClient: blobClient
};
project.setUser(data.username);
storageUtils = requireJS('common/storage/util');
storageUtils.insertProjectJson(project, projectJson, {
commitMessage: 'project imported'
})
.then(function (commitResult) {
result.commitHash = commitResult.hash;
result.rootHash = projectJson.rootHash;
return project.createBranch(branchName, commitResult.hash);
})
.then(function (result_) {
result.status = result_.status;
if (parameters.doNotLoad === true) {
return null;
}
return core.loadRoot(result.rootHash);
})
.then(function (rootNode) {
result.rootNode = rootNode;
result.FCO = result.core.getFCO(rootNode);
deferred.resolve(result);
})
.catch(deferred.reject);
})
.catch(deferred.reject);
return deferred.promise.nodeify(callback);
}
/**
*
* @param core
* @param rootNode
* @param nodePath
* @param [callback]
* @returns {Q.Promise}
*/
function loadNode(core, rootNode, nodePath, callback) {
var deferred = Q.defer();
core.loadByPath(rootNode, nodePath, function (err, node) {
if (err) {
deferred.reject(new Error(err));
} else if (node === null) {
deferred.reject(new Error('Given nodePath does not exist "' + nodePath + '"!'));
} else {
deferred.resolve(node);
}
});
return deferred.promise.nodeify(callback);
}
/**
*
* @param project
* @param core
* @param commitHash
* @param [callback]
* @returns {Q.Promise}
*/
function loadRootNodeFromCommit(project, core, commitHash, callback) {
var deferred = Q.defer();
project.loadObject(commitHash, function (err, commitObj) {
if (err) {
deferred.reject(new Error(err));
} else {
core.loadRoot(commitObj.root, function (err, rootNode) {
if (err) {
deferred.reject(new Error(err));
} else {
deferred.resolve(rootNode);
}
});
}
});
return deferred.promise.nodeify(callback);
}
/**
* This uses the guest account by default
* @param {string} projectName
* @param {string} userId
* @returns {string} projectId
*/
function projectName2Id(projectName, userId) {
if (_gmeConfig) {
userId = userId || _gmeConfig.authentication.guestAccount;
} else {
throw new Error('userId required when webgme is a dependency!');
}
return exports.storageUtil.getProjectIdFromOwnerIdAndProjectName(userId, projectName);
}
function logIn(server, agent, userName, password) {
var serverBaseUrl = server.getUrl(),
deferred = Q.defer();
agent.post(serverBaseUrl + '/login')
.send({
userId: userName,
password: password
})
.end(function (err, res) {
if (err) {
deferred.reject(new Error(err));
} else if (res.status !== 200) {
deferred.reject(new Error('Status code was not 200'));
} else {
deferred.resolve(res);
}
});
return deferred.promise;
}
function openSocketIo(server, agent, userName, password, token) {
var io = require('socket.io-client'),
serverBaseUrl = server.getUrl(),
deferred = Q.defer(),
loginPromise,
socket,
socketReq = { url: serverBaseUrl },
webgmeToken;
if (server.getGmeConfig().authentication.enable === true) {
loginPromise = logIn(server, agent, userName, password);
} else {
loginPromise = Q.resolve();
}
loginPromise
.then(function (/*res*/) {
var split,
options = {
multiplex: false,
reconnection: false
};
agent._attachCookies(socketReq);
split = /access_token=([^;]+)/.exec(socketReq.cookies);
if (token) {
options.extraHeaders = {
Cookie: 'access_token=' + token
};
} else if (split && split.length === 2) {
webgmeToken = split[1];
options.extraHeaders = {
Cookie: 'access_token=' + webgmeToken
};
}
socket = io(serverBaseUrl, options);
socket.on('error', function (err) {
exports.logger.error(err);
deferred.reject(err || 'could not connect');
socket.disconnect();
});
socket.on('connect', function () {
deferred.resolve({ socket: socket, webgmeToken: webgmeToken });
});
})
.catch(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
/**
*
* @param {blobHash|filePath|projectJson} file1
* @param {blobHash|filePath|projectJson} file2
* @param logger
* @param gmeConfigParameter
* @param callback
* @returns {Q.Promise}
*/
function compareWebgmexFiles(file1, file2, logger, gmeConfigParameter, callback) {
var BC = require('../src/server/middleware/blob/BlobClientWithFSBackend'),
blobClient = new BC(gmeConfigParameter, logger),
AdmZip = require('adm-zip');
function getProjectJsonPromise(file) {
var deferred = Q.defer();
if (exports.REGEXP.BLOB_HASH.test(file) === true) {
deferred.promise = blobClient.getObject(file)
.then(function (buffer) {
var zip = new AdmZip(buffer);
return JSON.parse(zip.readAsText('project.json', 'utf8'));
});
} else if (typeof file === 'string') {
deferred.promise = Q.ninvoke(exports.fs, 'readFile', file)
.then(function (buffer) {
var zip = new AdmZip(buffer);
return JSON.parse(zip.readAsText('project.json', 'utf8'));
});
} else {
deferred.resolve(file);
}
return deferred.promise;
}
return Q.allDone([getProjectJsonPromise(file1), getProjectJsonPromise(file2)])
.then(function (projectJsons) {
exports.expect(projectJsons[1].hashes).to.deep.equal(projectJsons[0].hashes);
exports.expect(projectJsons[1].objects).to.deep.equal(projectJsons[0].objects);
})
.nodeify(callback);
}
function getChangedNodesFromPersisted(persisted, printPatches) {
var storageUtil = requireJS('common/storage/util'),
keys = Object.keys(persisted.objects),
i,
coreObjects = {};
for (i = 0; i < keys.length; i += 1) {
if (storageUtil.coreObjectHasOldAndNewData(persisted.objects[keys[i]])) {
coreObjects[keys[i]] = storageUtil.getPatchObject(persisted.objects[keys[i]].oldData,
persisted.objects[keys[i]].newData);
} else {
coreObjects[keys[i]] = persisted.objects[keys[i]].newData;
}
}
if (printPatches) {
keys = Object.keys(coreObjects);
/*eslint-disable no-console*/
for (i = 0; i < keys.length; i += 1) {
console.log('############## ' + keys[i].substring(0, 7) + ' ###################');
if (coreObjects[keys[i]].type === 'patch') {
console.log(JSON.stringify(coreObjects[keys[i]].patch, null, 2));
} else {
coreObjects[keys[i]] = persisted.objects[keys[i]].newData;
console.log('New data');
}
}
/*eslint-enable no-console*/
}
return storageUtil.getChangedNodes(coreObjects, persisted.rootHash);
}
/**
*
* @param gmeConfig
* @param logger
* @param [webgmeToken]
* @param [callback]
* @returns {*}
*/
function getConnectedStorage(gmeConfig, logger, webgmeToken, callback) {
var deferred = Q.defer(),
NodeStorage = requireJS('common/storage/nodestorage'),
STORAGE_CONSTANTS = requireJS('common/storage/constants'),
storage = NodeStorage.createStorage(null, webgmeToken, logger, gmeConfig);
storage.open(function (networkState) {
var err;
if (networkState === STORAGE_CONSTANTS.CONNECTED) {
deferred.resolve(storage);
} else {
err = new Error('Unexpected network state: ' + networkState);
// Log it since promise may already have been resolved..
logger.error(err);
deferred.reject(err);
}
});
return deferred.promise.nodeify(callback);
}
// Only load the config if globals is used for webgme and not for a domain repo.
if (process.cwd() === exports.path.join(__dirname, '..')) {
WebGME.addToRequireJsPaths(getGmeConfig());
}
// requireJS.config({
// paths: {
// js: 'client/js',
// ' /socket.io/socket.io.js': 'socketio-client',
// underscore: 'client/bower_components/underscore/underscore-min'
// }
// });
exports.getGmeConfig = getGmeConfig;
exports.getMongoStorage = getMongoStorage;
exports.getMemoryStorage = getMemoryStorage;
exports.getRedisStorage = getRedisStorage;
exports.getBlobTestClient = function () {
return require('../src/server/middleware/blob/BlobClientWithFSBackend');
};
exports.clearDatabase = clearDatabase;
exports.getGMEAuth = getGMEAuth;
exports.clearDBAndGetGMEAuth = clearDBAndGetGMEAuth;
exports.loadJsonFile = loadJsonFile;
exports.importProject = importProject;
exports.projectName2Id = projectName2Id;
exports.logIn = logIn;
exports.openSocketIo = openSocketIo;
exports.getConnectedStorage = getConnectedStorage;
exports.loadRootNodeFromCommit = loadRootNodeFromCommit;
exports.loadNode = loadNode;
exports.compareWebgmexFiles = compareWebgmexFiles;
exports.getChangedNodesFromPersisted = getChangedNodesFromPersisted;
exports.getConnectedStorage = getConnectedStorage;
exports.noop = function () {
};
exports.copy = function copy(obj) {
return JSON.parse(JSON.stringify(obj));
};
module.exports = exports;