actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
649 lines (550 loc) • 20.7 kB
JavaScript
var should = require('should');
var request = require('request');
var EventEmitter = require('events').EventEmitter;
var actionheroPrototype = require(__dirname + '/../../actionhero.js').actionheroPrototype;
var actionhero = new actionheroPrototype();
var api;
var clientA;
var clientB;
var clientC;
var url;
var connectClients = function(callback){
// get actionheroClient in scope
// TODO: Perhaps we read this from disk after server boot.
eval(api.servers.servers.websocket.compileActionheroClientJS());
var S = api.servers.servers.websocket.server.Socket;
var url = 'http://localhost:' + api.config.servers.web.port;
var clientAsocket = new S(url);
var clientBsocket = new S(url);
var clientCsocket = new S(url);
clientA = new ActionheroClient({}, clientAsocket);
clientB = new ActionheroClient({}, clientBsocket);
clientC = new ActionheroClient({}, clientCsocket);
setTimeout(function(){
callback();
}, 100);
};
describe('Server: Web Socket', function(){
before(function(done){
actionhero.start(function(error, a){
api = a;
url = 'http://localhost:' + api.config.servers.web.port;
api.config.servers.websocket.clientUrl = 'http://localhost:' + api.config.servers.web.port;
connectClients(function(){
done();
});
});
});
after(function(done){
actionhero.stop(function(){
done();
});
});
it('socket client connections should work: client 1', function(done){
clientA.connect(function(error, data){
data.context.should.equal('response');
data.data.totalActions.should.equal(0);
clientA.welcomeMessage.should.equal('Hello! Welcome to the actionhero api');
done();
});
});
it('socket client connections should work: client 2', function(done){
clientB.connect(function(error, data){
data.context.should.equal('response');
data.data.totalActions.should.equal(0);
clientA.welcomeMessage.should.equal('Hello! Welcome to the actionhero api');
done();
});
});
it('socket client connections should work: client 3', function(done){
clientC.connect(function(error, data){
data.context.should.equal('response');
data.data.totalActions.should.equal(0);
clientA.welcomeMessage.should.equal('Hello! Welcome to the actionhero api');
done();
});
});
it('I can get my connection details', function(done){
clientA.detailsView(function(response){
response.data.connectedAt.should.be.within(0, new Date().getTime());
response.data.remoteIP.should.equal('127.0.0.1');
done();
});
});
it('can run actions with errors', function(done){
clientA.action('cacheTest', function(response){
response.error.should.equal('key is a required parameter for this action');
done();
});
});
it('can run actions properly', function(done){
clientA.action('cacheTest', {key: 'test key', value: 'test value'}, function(response){
should.not.exist(response.error);
done();
});
});
it('does not have sticky params', function(done){
clientA.action('cacheTest', {key: 'test key', value: 'test value'}, function(response){
should.not.exist(response.error);
response.cacheTestResults.loadResp.key.should.equal('cacheTest_test key');
response.cacheTestResults.loadResp.value.should.equal('test value');
clientA.action('cacheTest', function(response){
response.error.should.equal('key is a required parameter for this action');
done();
});
});
});
it('will limit how many simultaneous connections I can have', function(done){
var responses = [];
clientA.action('sleepTest', {sleepDuration: 100}, function(response){ responses.push(response); });
clientA.action('sleepTest', {sleepDuration: 200}, function(response){ responses.push(response); });
clientA.action('sleepTest', {sleepDuration: 300}, function(response){ responses.push(response); });
clientA.action('sleepTest', {sleepDuration: 400}, function(response){ responses.push(response); });
clientA.action('sleepTest', {sleepDuration: 500}, function(response){ responses.push(response); });
clientA.action('sleepTest', {sleepDuration: 600}, function(response){ responses.push(response); });
setTimeout(function(){
responses.length.should.equal(6);
for(var i in responses){
var response = responses[i];
if(i === 0 || i === '0'){
response.error.should.eql('you have too many pending requests');
}else{
should.not.exist(response.error);
}
}
done();
}, 1000);
});
describe('files', function(){
it('can request file data', function(done){
clientA.file('simple.html', function(data){
should.not.exist(data.error);
data.content.should.equal('<h1>ActionHero</h1>\\nI am a flat file being served to you via the API from ./public/simple.html<br />');
data.mime.should.equal('text/html');
data.length.should.equal(101);
done();
});
});
it('missing files', function(done){
clientA.file('missing.html', function(data){
data.error.should.equal('That file is not found (missing.html)');
data.mime.should.equal('text/html');
should.not.exist(data.content);
done();
});
});
});
describe('chat', function(){
before(function(done){
api.chatRoom.addMiddleware({
name: 'join chat middleware',
join: function(connection, room, callback){
api.chatRoom.broadcast({}, room, 'I have entered the room: ' + connection.id, function(e){
callback();
});
}
});
api.chatRoom.addMiddleware({
name: 'leave chat middleware',
leave: function(connection, room, callback){
api.chatRoom.broadcast({}, room, 'I have left the room: ' + connection.id, function(e){
callback();
});
}
});
done();
});
after(function(done){
api.chatRoom.middleware = {};
api.chatRoom.globalMiddleware = [];
done();
});
beforeEach(function(done){
clientA.roomAdd('defaultRoom', function(){
clientB.roomAdd('defaultRoom', function(){
clientC.roomAdd('defaultRoom', function(){
setTimeout(function(){ // timeout to skip welcome messages as clients join rooms
done();
}, 100);
});
});
});
});
afterEach(function(done){
clientA.roomLeave('defaultRoom', function(){
clientB.roomLeave('defaultRoom', function(){
clientC.roomLeave('defaultRoom', function(){
clientA.roomLeave('otherRoom', function(){
clientB.roomLeave('otherRoom', function(){
clientC.roomLeave('otherRoom', function(){
done();
});
});
});
});
});
});
});
it('can change rooms and get room details', function(done){
clientA.roomAdd('otherRoom', function(){
clientA.detailsView(function(response){
should.not.exist(response.error);
response.data.rooms[0].should.equal('defaultRoom');
response.data.rooms[1].should.equal('otherRoom');
clientA.roomView('otherRoom', function(response){
response.data.membersCount.should.equal(1);
done();
});
});
});
});
it('will update client room info when they change rooms', function(done){
clientA.rooms[0].should.equal('defaultRoom');
should.not.exist(clientA.rooms[1]);
clientA.roomAdd('otherRoom', function(response){
should.not.exist(response.error);
clientA.rooms[0].should.equal('defaultRoom');
clientA.rooms[1].should.equal('otherRoom');
clientA.roomLeave('defaultRoom', function(response){
should.not.exist(response.error);
clientA.rooms[0].should.equal('otherRoom');
should.not.exist(clientA.rooms[1]);
done();
});
});
});
it('Clients can talk to each other', function(done){
var listener = function(response){
clientA.removeListener('say', listener);
response.context.should.equal('user');
response.message.should.equal('hello from client 2');
done();
};
clientA.on('say', listener);
clientB.say('defaultRoom', 'hello from client 2');
});
it('The client say method does not rely on order', function(done){
var listener = function(response){
clientA.removeListener('say', listener);
response.context.should.equal('user');
response.message.should.equal('hello from client 2');
done();
};
clientB.say = function(room, message, callback){
this.send({message: message, room: room, event: 'say'}, callback);
};
clientA.on('say', listener);
clientB.say('defaultRoom', 'hello from client 2');
});
it('connections are notified when I join a room', function(done){
var listener = function(response){
clientA.removeListener('say', listener);
response.context.should.equal('user');
response.message.should.equal('I have entered the room: ' + clientB.id);
done();
};
clientA.roomAdd('otherRoom', function(){
clientA.on('say', listener);
clientB.roomAdd('otherRoom');
});
});
it('connections are notified when I leave a room', function(done){
var listener = function(response){
clientA.removeListener('say', listener);
response.context.should.equal('user');
response.message.should.equal('I have left the room: ' + clientB.id);
done();
};
clientA.on('say', listener);
clientB.roomLeave('defaultRoom');
});
it('will not get messages for rooms I am not in', function(done){
clientB.roomAdd('otherRoom', function(response){
should.not.exist(response.error);
clientB.rooms.length.should.equal(2);
var listener = function(response){
clientC.removeListener('say', listener);
should.not.exist(response);
};
clientC.rooms.length.should.equal(1);
clientC.on('say', listener);
setTimeout(function(){
clientC.removeListener('say', listener);
done();
}, 1000);
clientB.say('otherRoom', 'you should not hear this');
});
});
it('connections can see member counts changing within rooms as folks join and leave', function(done){
clientA.roomView('defaultRoom', function(response){
response.data.membersCount.should.equal(3);
clientB.roomLeave('defaultRoom', function(){
clientA.roomView('defaultRoom', function(response){
response.data.membersCount.should.equal(2);
done();
});
});
});
});
describe('middleware - say and onSayReceive', function(){
before(function(done){
clientA.roomAdd('defaultRoom', function(){
clientB.roomAdd('defaultRoom', function(){
clientC.roomAdd('defaultRoom', function(){
setTimeout(function(){ // timeout to skip welcome messages as clients join rooms
done();
}, 100);
});
});
});
});
after(function(done){
clientA.roomLeave('defaultRoom', function(){
clientB.roomLeave('defaultRoom', function(){
clientC.roomLeave('defaultRoom', function(){
done();
});
});
});
});
afterEach(function(done){
api.chatRoom.middleware = {};
api.chatRoom.globalMiddleware = [];
done();
});
it('each listener receive custom message', function(done){
api.chatRoom.addMiddleware({
name: 'say for each',
say: function(connection, room, messagePayload, callback){
messagePayload.message += ' - To: ' + connection.id;
callback(null, messagePayload);
}
});
var listenerA = function(response){
clientA.removeListener('say', listenerA);
response.message.should.equal('Test Message - To: ' + clientA.id); // clientA.id (Receiever)
};
var listenerB = function(response){
clientB.removeListener('say', listenerB);
response.message.should.equal('Test Message - To: ' + clientB.id); // clientB.id (Receiever)
};
var listenerC = function(response){
clientC.removeListener('say', listenerC);
response.message.should.equal('Test Message - To: ' + clientC.id); // clientC.id (Receiever)
};
clientA.on('say', listenerA);
clientB.on('say', listenerB);
clientC.on('say', listenerC);
clientB.say('defaultRoom', 'Test Message');
setTimeout(function(){
clientA.removeListener('say', listenerA);
clientB.removeListener('say', listenerB);
clientC.removeListener('say', listenerC);
done();
}, 1000);
});
it('only one message should be received per connection', function(done){
var firstSayCall = true;
api.chatRoom.addMiddleware({
name: 'first say middleware',
say: function(connection, room, messagePayload, callback){
if(firstSayCall){
firstSayCall = false;
setTimeout(function(){
callback();
}, 200);
}else{
callback();
}
}
});
var messagesReceived = 0;
var listenerA = function(response){
messagesReceived += 1;
};
var listenerB = function(response){
messagesReceived += 2;
};
var listenerC = function(response){
messagesReceived += 4;
};
clientA.on('say', listenerA);
clientB.on('say', listenerB);
clientC.on('say', listenerC);
clientB.say('defaultRoom', 'Test Message');
setTimeout(function(){
clientA.removeListener('say', listenerA);
clientB.removeListener('say', listenerB);
clientC.removeListener('say', listenerC);
messagesReceived.should.equal(7);
done();
}, 1000);
});
it('each listener receive same custom message', function(done){
api.chatRoom.addMiddleware({
name: 'say for each',
onSayReceive: function(connection, room, messagePayload, callback){
messagePayload.message += ' - To: ' + connection.id;
callback(null, messagePayload);
}
});
var listenerA = function(response){
clientA.removeListener('say', listenerA);
response.message.should.equal('Test Message - To: ' + clientB.id); // clientB.id (Sender)
};
var listenerB = function(response){
clientB.removeListener('say', listenerB);
response.message.should.equal('Test Message - To: ' + clientB.id); // clientB.id (Sender)
};
var listenerC = function(response){
clientC.removeListener('say', listenerC);
response.message.should.equal('Test Message - To: ' + clientB.id); // clientB.id (Sender)
};
clientA.on('say', listenerA);
clientB.on('say', listenerB);
clientC.on('say', listenerC);
clientB.say('defaultRoom', 'Test Message');
setTimeout(function(){
clientA.removeListener('say', listenerA);
clientB.removeListener('say', listenerB);
clientC.removeListener('say', listenerC);
done();
}, 1000);
});
});
describe('custom room member data', function(){
var currentSanitize;
var currentGenerate;
before(function(done){
//Ensure that default behavior works
clientA.roomAdd('defaultRoom', function(){
clientA.roomView('defaultRoom', function(response){
response.data.room.should.equal('defaultRoom');
for(var key in response.data.members){
(response.data.members[key].type === undefined).should.eql(true);
}
//save off current functions
currentSanitize = api.chatRoom.sanitizeMemberDetails;
currentGenerate = api.chatRoom.generateMemberDetails;
//override functions
api.chatRoom.sanitizeMemberDetails = function(data){
return {
id: data.id,
joinedAt: data.joinedAt,
type: data.type
};
};
api.chatRoom.generateMemberDetails = function(connection){
return {
id: connection.id,
joinedAt: new Date().getTime(),
type: connection.type
};
};
clientA.roomLeave('defaultRoom', function(){
done();
});
});
});
});
after(function(done){
api.chatRoom.joinCallbacks = {};
api.chatRoom.leaveCallbacks = {};
api.chatRoom.sanitizeMemberDetails = currentSanitize;
api.chatRoom.generateMemberDetails = currentGenerate;
//Check that everything is back to normal
clientA.roomAdd('defaultRoom', function(){
clientA.roomView('defaultRoom', function(response){
response.data.room.should.equal('defaultRoom');
for(var key in response.data.members){
(response.data.members[key].type === undefined).should.eql(true);
}
setTimeout(function(){
clientA.roomLeave('defaultRoom', function(){
done();
});
}, 100);
});
});
});
it('should view non-default member data', function(done){
clientA.roomAdd('defaultRoom', function(){
clientA.roomView('defaultRoom', function(response){
response.data.room.should.equal('defaultRoom');
for(var key in response.data.members){
response.data.members[key].type.should.eql('websocket');
}
clientA.roomLeave('defaultRoom');
done();
});
});
});
});
});
describe('param collisions', function(){
var originalSimultaneousActions;
before(function(){
originalSimultaneousActions = api.config.general.simultaneousActions;
api.config.general.simultaneousActions = 99999999;
});
after(function(){
api.config.general.simultaneousActions = originalSimultaneousActions;
});
it('will not have param colisions', function(done){
var completed = 0;
var started = 0;
var sleeps = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110];
var toComplete = function(sleep, response){
sleep.should.equal(response.sleepDuration);
completed++;
if(completed === started){
done();
}
};
sleeps.forEach(function(sleep){
started++;
clientA.action('sleepTest', {sleepDuration: sleep}, function(response){ toComplete(sleep, response); });
});
});
});
describe('disconnect', function(){
beforeEach(function(done){
try{
clientA.disconnect();
clientB.disconnect();
clientC.disconnect();
}catch(e){}
connectClients(function(){
clientA.connect();
clientB.connect();
clientC.connect();
setTimeout(done, 500);
});
});
it('client can disconnect', function(done){
api.servers.servers.websocket.connections().length.should.equal(3);
clientA.disconnect();
clientB.disconnect();
clientC.disconnect();
setTimeout(function(){
api.servers.servers.websocket.connections().length.should.equal(0);
done();
}, 500);
});
it('can be sent disconnect events from the server', function(done){
clientA.detailsView(function(response){
response.data.remoteIP.should.equal('127.0.0.1');
var count = 0;
for(var id in api.connections.connections){
count++;
api.connections.connections[id].destroy();
}
count.should.equal(3);
clientA.detailsView(function(){
throw new Error('should not get response');
});
setTimeout(done, 500);
});
});
});
});