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 @@ ...@@ -21,16 +21,22 @@
const config = require('./config.json'); const config = require('./config.json');
const runner = require('./lib/runner.js')(config.runner); 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...'); console.log('starting hublot...');
controller.loadAll('controller', 'lib', 'robot') loader.loadAll('controller', 'lib', 'robot')
.then(modules => { .then(modules => {
console.log('modules loaded... launching runner'); console.log('modules loaded... creating controller');
// Note: result can be stored in a variable to control further the browser
// e.g.: let client = runner.run(...); client.end(); const controller = controllerFactory.create(runner, modules, config);
runner.run(modules, config.visio.url, 'test-bot', config.client);
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 => { .catch(err => {
console.error(err); console.error(err);
......
/* /*
* Copyright (c) 2017 Linagora. * Copyright (c) 2017 Linagora.
* *
...@@ -20,55 +21,26 @@ ...@@ -20,55 +21,26 @@
'use strict'; '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 = { const controller = {
load: module => new Promise((resolve, reject) => { registry: {},
// First we read the file with the same name than the folder client: room => {
fs.readFile(root + '/' + module + '/' + module + '.js', 'utf8') if (room in controller.registry) {
.then(content => { return null;
// 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) => { controller.registry[room] = runner.run(modules,
// Load all the passed modules config.visio.url,
Promise.all( room,
modules.map(f => controller.load(f))) config.client);
.then(values => { return controller.registry[room];
// Concatenate all the arrays into a single one }
resolve(Array.prototype.concat.apply([], values));
}).catch(err => {
reject(err);
});
})
}; };
return controller; return controller;
}; };
module.exports = {
create
};
...@@ -20,100 +20,44 @@ ...@@ -20,100 +20,44 @@
'use strict'; 'use strict';
jest.mock('mz/fs'); const {create} = require('./controller.js');
describe('loadModules', () => { // Here are some needed mocks
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(() => { const runnerMock = {
require('mz/fs').__setup(MOCK_FILES); run: () => {}
}); };
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');
controller.load('nonexistent') const configMock = {
.catch(() => done()); visio: {url: 'someurl.test'},
}); client: {}
};
test('should fail for a module missing its base file', done => { let controller;
const controller = require('./controller.js')('/test');
controller.load('notavalidmodule') describe('controller', () => {
.catch(() => done()); beforeEach(() => {
}); controller = create(runnerMock, [], configMock);
}); });
describe('loading several modules', () => { test('should allow to create a client to a new room', () => {
test('should include all files from all modules', done => { expect(controller.client).toBeDefined();
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');
controller.loadAll('controller', 'lib', 'robot') test('should register a newly created client to its room', () => {
.then(res => { const client = controller.client('test');
expect(res[0]).toBe('1'); expect(controller.registry).toHaveProperty('test', client);
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 => { test('should return null when trying to create a client for an existing room', () => {
const controller = require('./controller.js')('/test'); controller.client('test');
controller.loadAll('controller', 'lib', 'robot', 'nonexistent') const client2 = controller.client('test');
.catch(() => done()); expect(client2).toBeNull();
}); });
test('should fail when trying to load an invalid module', done => { test('should not replace the client for an existing room', () => {
const controller = require('./controller.js')('/test'); const client1 = controller.client('test');
controller.loadAll('controller', 'lib', 'robot', 'invalid') controller.client('test');
.catch(() => done()); 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 @@ ...@@ -18,7 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"jest": "20.0.0", "jest": "20.0.0",
"xo": "^0.18.2" "xo": "0.18.2"
}, },
"author": "Linagora Folks", "author": "Linagora Folks",
"license": "AGPL-3.0", "license": "AGPL-3.0",
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
abab@^1.0.3: abab@^1.0.3:
version "1.0.3" version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
acorn-globals@^3.1.0: acorn-globals@^3.1.0:
version "3.1.0" version "3.1.0"
...@@ -26,9 +26,9 @@ acorn@^4.0.4: ...@@ -26,9 +26,9 @@ acorn@^4.0.4:
version "4.0.13" version "4.0.13"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
acorn@^5.0.1: acorn@^5.1.1:
version "5.0.3" version "5.1.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7"
ajv-keywords@^1.0.0: ajv-keywords@^1.0.0:
version "1.5.1" version "1.5.1"
...@@ -41,6 +41,15 @@ ajv@^4.7.0, ajv@^4.9.1: ...@@ -41,6 +41,15 @@ ajv@^4.7.0, ajv@^4.9.1:
co "^4.6.0" co "^4.6.0"
json-stable-stringify "^1.0.1" 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: align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" 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: ...@@ -63,30 +72,38 @@ ansi-escapes@^1.1.0, ansi-escapes@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" 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: ansi-regex@^2.0.0, ansi-regex@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 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: ansi-styles@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
ansi-styles@^3.0.0: ansi-styles@^3.0.0, ansi-styles@^3.1.0:
version "3.0.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.0.0.tgz#5404e93a544c4fec7f048262977bebfe3155e0c1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
dependencies: dependencies:
color-convert "^1.0.0" color-convert "^1.9.0"
any-promise@^1.0.0: any-promise@^1.0.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
anymatch@^1.3.0: anymatch@^1.3.0:
version "1.3.0" version "1.3.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
dependencies: dependencies:
arrify "^1.0.0"
micromatch "^2.1.5" micromatch "^2.1.5"
normalize-path "^2.0.0"
append-transform@^0.4.0: append-transform@^0.4.0:
version "0.4.0" version "0.4.0"
...@@ -132,8 +149,8 @@ arr-diff@^2.0.0: ...@@ -132,8 +149,8 @@ arr-diff@^2.0.0:
arr-flatten "^1.0.1" arr-flatten "^1.0.1"
arr-flatten@^1.0.1: arr-flatten@^1.0.1:
version "1.0.3" version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.3.tgz#a274ed85ac08849b6bd7847c4580745dc51adfb1" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
array-differ@^1.0.0: array-differ@^1.0.0:
version "1.0.0" version "1.0.0"
...@@ -182,8 +199,8 @@ async@^1.4.0: ...@@ -182,8 +199,8 @@ async@^1.4.0:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.0.0, async@^2.1.4: async@^2.0.0, async@^2.1.4:
version "2.4.1" version "2.5.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
dependencies: dependencies:
lodash "^4.14.0" lodash "^4.14.0"
...@@ -199,53 +216,57 @@ aws-sign2@~0.6.0: ...@@ -199,53 +216,57 @@ aws-sign2@~0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 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"