Commit ed8dc251 authored by Tom JORQUERA's avatar Tom JORQUERA
Browse files

Merge branch 'client_registry' into 'master'

Add server-side controller with client registry

See merge request !39
parents b45d497e e3bd3fac
Pipeline #4470 passed with stage
in 37 seconds
......@@ -21,16 +21,22 @@
const config = require('./config.json');
const runner = require('./lib/runner.js')(config.runner);
const controller = require('./lib/controller.js')('./client');
const loader = require('./lib/loader.js')('./client');
const controllerFactory = require('./lib/controller.js');
console.log('starting hublot...');
controller.loadAll('controller', 'lib', 'robot')
loader.loadAll('controller', 'lib', 'robot')
.then(modules => {
console.log('modules loaded... launching runner');
// Note: result can be stored in a variable to control further the browser
// e.g.: let client = runner.run(...); client.end();
runner.run(modules, config.visio.url, 'test-bot', config.client);
console.log('modules loaded... creating controller');
const controller = controllerFactory.create(runner, modules, config);
console.log('creating client');
// Note: client returned object can be used to control further the browser
// e.g.: let client = controller.client('room'); client.end();
controller.client('test-bot');
})
.catch(err => {
console.error(err);
......
/*
* Copyright (c) 2017 Linagora.
*
......@@ -20,55 +21,26 @@
'use strict';
const fs = require('mz/fs');
// Module to control the server-side behavior and state
module.exports = root => {
const create = (runner, modules, config) => {
const controller = {
load: module => new Promise((resolve, reject) => {
// First we read the file with the same name than the folder
fs.readFile(root + '/' + module + '/' + module + '.js', 'utf8')
.then(content => {
// Then we read all the other files in the folder
fs.readdir(root + '/' + module)
.then(files => {
// Remove the file we already read
files.splice(files.indexOf(module + '.js'), 1);
// Ignore test files
files = files.filter(filename => !filename.endsWith('.test.js'));
Promise.all(files.map(
f => fs.readFile(root + '/' + module + '/' + f, 'utf8')
))
.then(contents => {
// Concatenate all the files and resolve
resolve([content].concat(contents));
})
.catch(err => {
reject(err);
});
})
.catch(err => {
reject(err);
});
})
.catch(err => {
reject(err);
});
}),
registry: {},
client: room => {
if (room in controller.registry) {
return null;
}
loadAll: (...modules) => new Promise((resolve, reject) => {
// Load all the passed modules
Promise.all(
modules.map(f => controller.load(f)))
.then(values => {
// Concatenate all the arrays into a single one
resolve(Array.prototype.concat.apply([], values));
}).catch(err => {
reject(err);
});
})
controller.registry[room] = runner.run(modules,
config.visio.url,
room,
config.client);
return controller.registry[room];
}
};
return controller;
};
module.exports = {
create
};
......@@ -20,100 +20,44 @@
'use strict';
jest.mock('mz/fs');
const {create} = require('./controller.js');
describe('loadModules', () => {
const MOCK_FILES = {
'/test/controller/controller.js': '1',
'/test/controller/controller.test.js': 'sometest',
'/test/controller/file.js': '2',
'/test/controller/file.test.js': 'sometest',
'/test/lib/lib.js': '3',
'/test/robot/robot.js': '4',
'/test/notavalidmodule/file.js': '5'
};
// Here are some needed mocks
beforeEach(() => {
require('mz/fs').__setup(MOCK_FILES);
});
describe('loading a module', () => {
test('should include all (and only) non-test files in its dir.', done => {
const controller = require('./controller.js')('/test');
controller.load('controller')
.then(res => {
expect(res.length).toBe(2);
expect(res.includes('1')).toBeTruthy();
expect(res.includes('2')).toBeTruthy();
done();
});
});
test('should return the files in the correct order', done => {
const controller = require('./controller.js')('/test');
controller.load('controller')
.then(res => {
expect(res[0]).toBe('1');
expect(res[1]).toBe('2');
done();
});
});
test('should fail for a nonexistent module', done => {
const controller = require('./controller.js')('/test');
const runnerMock = {
run: () => {}
};
controller.load('nonexistent')
.catch(() => done());
});
const configMock = {
visio: {url: 'someurl.test'},
client: {}
};
test('should fail for a module missing its base file', done => {
const controller = require('./controller.js')('/test');
let controller;
controller.load('notavalidmodule')
.catch(() => done());
});
describe('controller', () => {
beforeEach(() => {
controller = create(runnerMock, [], configMock);
});
describe('loading several modules', () => {
test('should include all files from all modules', done => {
const controller = require('./controller.js')('/test');
controller.loadAll('controller', 'lib', 'robot')
.then(res => {
expect(res.length).toBe(4);
expect(res.includes('1')).toBeTruthy();
expect(res.includes('2')).toBeTruthy();
expect(res.includes('3')).toBeTruthy();
expect(res.includes('4')).toBeTruthy();
done();
});
});
test('should return files in correct order', done => {
const controller = require('./controller.js')('/test');
test('should allow to create a client to a new room', () => {
expect(controller.client).toBeDefined();
});
controller.loadAll('controller', 'lib', 'robot')
.then(res => {
expect(res[0]).toBe('1');
expect(res[1]).toBe('2');
expect(res[2]).toBe('3');
expect(res[3]).toBe('4');
done();
});
});
test('should register a newly created client to its room', () => {
const client = controller.client('test');
expect(controller.registry).toHaveProperty('test', client);
});
test('should fail when trying to load a non-existent module ', done => {
const controller = require('./controller.js')('/test');
controller.loadAll('controller', 'lib', 'robot', 'nonexistent')
.catch(() => done());
});
test('should return null when trying to create a client for an existing room', () => {
controller.client('test');
const client2 = controller.client('test');
expect(client2).toBeNull();
});
test('should fail when trying to load an invalid module', done => {
const controller = require('./controller.js')('/test');
controller.loadAll('controller', 'lib', 'robot', 'invalid')
.catch(() => done());
});
test('should not replace the client for an existing room', () => {
const client1 = controller.client('test');
controller.client('test');
expect(controller.registry).toHaveProperty('test', client1);
});
});
/*
* Copyright (c) 2017 Linagora.
*
* This file is part of Hublot
* (see https://ci.linagora.com/linagora/lgs/labs/hublot).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
const fs = require('mz/fs');
module.exports = root => {
const controller = {
load: module => new Promise((resolve, reject) => {
// First we read the file with the same name than the folder
fs.readFile(root + '/' + module + '/' + module + '.js', 'utf8')
.then(content => {
// Then we read all the other files in the folder
fs.readdir(root + '/' + module)
.then(files => {
// Remove the file we already read
files.splice(files.indexOf(module + '.js'), 1);
// Ignore test files
files = files.filter(filename => !filename.endsWith('.test.js'));
Promise.all(files.map(
f => fs.readFile(root + '/' + module + '/' + f, 'utf8')
))
.then(contents => {
// Concatenate all the files and resolve
resolve([content].concat(contents));
})
.catch(err => {
reject(err);
});
})
.catch(err => {
reject(err);
});
})
.catch(err => {
reject(err);
});
}),
loadAll: (...modules) => new Promise((resolve, reject) => {
// Load all the passed modules
Promise.all(
modules.map(f => controller.load(f)))
.then(values => {
// Concatenate all the arrays into a single one
resolve(Array.prototype.concat.apply([], values));
}).catch(err => {
reject(err);
});
})
};
return controller;
};
/*
* Copyright (c) 2017 Linagora.
*
* This file is part of Hublot
* (see https://ci.linagora.com/linagora/lgs/labs/hublot).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
jest.mock('mz/fs');
describe('loadModules', () => {
const MOCK_FILES = {
'/test/controller/controller.js': '1',
'/test/controller/controller.test.js': 'sometest',
'/test/controller/file.js': '2',
'/test/controller/file.test.js': 'sometest',
'/test/lib/lib.js': '3',
'/test/robot/robot.js': '4',
'/test/notavalidmodule/file.js': '5'
};
beforeEach(() => {
require('mz/fs').__setup(MOCK_FILES);
});
describe('loading a module', () => {
test('should include all (and only) non-test files in its dir.', done => {
const loader = require('./loader.js')('/test');
loader.load('controller')
.then(res => {
expect(res.length).toBe(2);
expect(res.includes('1')).toBeTruthy();
expect(res.includes('2')).toBeTruthy();
done();
});
});
test('should return the files in the correct order', done => {
const loader = require('./loader.js')('/test');
loader.load('controller')
.then(res => {
expect(res[0]).toBe('1');
expect(res[1]).toBe('2');
done();
});
});
test('should fail for a nonexistent module', done => {
const loader = require('./loader.js')('/test');
loader.load('nonexistent')
.catch(() => done());
});
test('should fail for a module missing its base file', done => {
const loader = require('./loader.js')('/test');
loader.load('notavalidmodule')
.catch(() => done());
});
});
describe('loading several modules', () => {
test('should include all files from all modules', done => {
const loader = require('./loader.js')('/test');
loader.loadAll('controller', 'lib', 'robot')
.then(res => {
expect(res.length).toBe(4);
expect(res.includes('1')).toBeTruthy();
expect(res.includes('2')).toBeTruthy();
expect(res.includes('3')).toBeTruthy();
expect(res.includes('4')).toBeTruthy();
done();
});
});
test('should return files in correct order', done => {
const loader = require('./loader.js')('/test');
loader.loadAll('controller', 'lib', 'robot')
.then(res => {
expect(res[0]).toBe('1');
expect(res[1]).toBe('2');
expect(res[2]).toBe('3');
expect(res[3]).toBe('4');
done();
});
});
test('should fail when trying to load a non-existent module ', done => {
const loader = require('./loader.js')('/test');
loader.loadAll('controller', 'lib', 'robot', 'nonexistent')
.catch(() => done());
});
test('should fail when trying to load an invalid module', done => {
const loader = require('./loader.js')('/test');
loader.loadAll('controller', 'lib', 'robot', 'invalid')
.catch(() => done());
});
});
});
......@@ -18,7 +18,7 @@
},
"devDependencies": {
"jest": "20.0.0",
"xo": "^0.18.2"
"xo": "0.18.2"
},
"author": "Linagora Folks",
"license": "AGPL-3.0",
......
......@@ -3,8 +3,8 @@
abab@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
acorn-globals@^3.1.0:
version "3.1.0"
......@@ -26,9 +26,9 @@ acorn@^4.0.4:
version "4.0.13"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
acorn@^5.0.1:
version "5.0.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
acorn@^5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7"
ajv-keywords@^1.0.0:
version "1.5.1"
......@@ -41,6 +41,15 @@ ajv@^4.7.0, ajv@^4.9.1:
co "^4.6.0"
json-stable-stringify "^1.0.1"
ajv@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
......@@ -63,30 +72,38 @@ ansi-escapes@^1.1.0, ansi-escapes@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
ansi-escapes@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b"
ansi-regex@^2.0.0, ansi-regex@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
ansi-styles@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.0.0.tgz#5404e93a544c4fec7f048262977bebfe3155e0c1"
ansi-styles@^3.0.0, ansi-styles@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
dependencies:
color-convert "^1.0.0"
color-convert "^1.9.0"
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
anymatch@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
version "1.3.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
dependencies:
arrify "^1.0.0"
micromatch "^2.1.5"
normalize-path "^2.0.0"
append-transform@^0.4.0:
version "0.4.0"
......@@ -132,8 +149,8 @@ arr-diff@^2.0.0:
arr-flatten "^1.0.1"
arr-flatten@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.3.tgz#a274ed85ac08849b6bd7847c4580745dc51adfb1"
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
array-differ@^1.0.0:
version "1.0.0"
......@@ -182,8 +199,8 @@ async@^1.4.0:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.0.0, async@^2.1.4:
version "2.4.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7"
version "2.5.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
dependencies:
lodash "^4.14.0"
......@@ -199,53 +216,57 @@ aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
aws4@^1.2.1:
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
babel-code-frame@^6.16.0, babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
dependencies:
chalk "^1.1.0"
chalk "^1.1.3"
esutils "^2.0.2"
js-tokens "^3.0.0"
js-tokens "^3.0.2"
babel-core@^6.0.0, babel-core@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.24.1.tgz#8c428564dce1e1f41fb337ec34f4c3b022b5ad83"
babel-core@^6.0.0, babel-core@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
dependencies:
babel-code-frame "^6.22.0"
babel-generator "^6.24.1"
babel-code-frame "^6.26.0"
babel-generator "^6.26.0"
babel-helpers "^6.24.1"
babel-messages "^6.23.0"
babel-register "^6.24.1"
babel-runtime "^6.22.0"
babel-template "^6.24.1"
babel-traverse "^6.24.1"
babel-types "^6.24.1"
babylon "^6.11.0"
convert-source-map "^1.1.0"
debug "^2.1.1"
json5 "^0.5.0"
lodash "^4.2.0"
minimatch "^3.0.2"
path-is-absolute "^1.0.0"
private "^0.1.6"
babel-register "^6.26.0"
babel-runtime "^6.26.0"
babel-template "^6.26.0"
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
convert-source-map "^1.5.0"
debug "^2.6.8"
json5 "^0.5.1"
lodash "^4.17.4"
minimatch "^3.0.4"
path-is-absolute "^1.0.1"
private "^0.1.7"
slash "^1.0.0"
source-map "^0.5.0"
source-map "^0.5.6"
babel-generator@^6.18.0, babel-generator@^6.24.1:
version "6.24.1