565 lines
14 KiB
JavaScript
565 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
var Driver = lib("driver"),
|
|
Adaptor = lib("adaptor"),
|
|
Robot = lib("robot"),
|
|
Logger = lib("logger");
|
|
|
|
describe("Robot", function() {
|
|
var work, extraFunction, robot;
|
|
|
|
beforeEach(function() {
|
|
work = spy();
|
|
extraFunction = spy();
|
|
|
|
robot = new Robot({
|
|
name: "Robby",
|
|
work: work,
|
|
|
|
extraFunction: extraFunction,
|
|
extraValue: "Hello World",
|
|
|
|
master: { master: true }
|
|
});
|
|
});
|
|
|
|
describe("constructor", function() {
|
|
describe("name", function() {
|
|
context("if provided", function() {
|
|
it("is set to the passed value", function() {
|
|
expect(robot.name).to.be.eql("Robby");
|
|
});
|
|
});
|
|
|
|
context("if not provided", function() {
|
|
it("is set to an incrementing name", function() {
|
|
expect(new Robot({}).name).to.match(/Robot \d/);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("sets @master to the passed Master object", function() {
|
|
expect(robot.master).to.be.eql({ master: true });
|
|
});
|
|
|
|
it("sets @connections to an empty object by default", function() {
|
|
expect(robot.connections).to.be.eql({});
|
|
});
|
|
|
|
it("sets @devices to an empty object by default", function() {
|
|
expect(robot.devices).to.be.eql({});
|
|
});
|
|
|
|
it("sets @work to the passed work function", function() {
|
|
expect(robot.work).to.be.eql(work);
|
|
});
|
|
|
|
it("sets other obj params as values on the robot", function() {
|
|
expect(robot.extraFunction).to.be.a("function");
|
|
expect(robot.extraValue).to.be.eql("Hello World");
|
|
});
|
|
|
|
context("if there are devices but no connections", function() {
|
|
it("throws an error", function() {
|
|
var fn = function() {
|
|
return new Robot({
|
|
name: "BrokenBot",
|
|
devices: {
|
|
ping: { driver: "ping" }
|
|
}
|
|
});
|
|
};
|
|
|
|
expect(fn).to.throw(Error);
|
|
});
|
|
});
|
|
|
|
context("if no commands are provided", function() {
|
|
beforeEach(function() {
|
|
robot = new Robot({
|
|
name: "NewBot",
|
|
otherThings: { more: "details" },
|
|
sayHello: function() { return "Hello!"; }
|
|
});
|
|
});
|
|
|
|
it("sets #commands to the additionally provided functions", function() {
|
|
expect(robot.commands.sayHello).to.be.a("function");
|
|
});
|
|
});
|
|
|
|
context("if a commands function is provided", function() {
|
|
beforeEach(function() {
|
|
robot = new Robot({
|
|
name: "NewBot",
|
|
|
|
sayHello: function() { return this.name + " says hello"; },
|
|
|
|
commands: function() {
|
|
return {
|
|
say_hello: this.sayHello
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
it("sets #commands to the returned object", function() {
|
|
expect(robot.commands.say_hello).to.be.eql(robot.sayHello);
|
|
});
|
|
|
|
context("if the function doesn't return an object", function() {
|
|
var fn;
|
|
beforeEach(function() {
|
|
fn = function() {
|
|
var bot = new Robot({
|
|
name: "NewBot",
|
|
|
|
commands: function() {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
return bot;
|
|
};
|
|
});
|
|
|
|
it("throws an error", function() {
|
|
expect(fn).to.throw(
|
|
Error,
|
|
"#commands must be an object or a function that returns an object"
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
context("if a commands object is provided", function() {
|
|
beforeEach(function() {
|
|
robot = new Robot({
|
|
name: "NewBot",
|
|
|
|
sayHello: function() { return this.name + " says hello"; },
|
|
|
|
commands: {
|
|
say_hello: function() {}
|
|
}
|
|
});
|
|
});
|
|
|
|
it("sets #commands to the provided object", function() {
|
|
expect(robot.commands.say_hello).to.be.a("function");
|
|
});
|
|
});
|
|
|
|
context("arbitrary arguments", function() {
|
|
beforeEach(function() {
|
|
robot = new Robot({
|
|
name: "NewBot",
|
|
|
|
hiThere: "hi there",
|
|
|
|
sayHi: function() {
|
|
return "hi";
|
|
},
|
|
|
|
start: "start"
|
|
});
|
|
});
|
|
|
|
context("if they don't conflict with built-ins", function() {
|
|
it("passes them through", function() {
|
|
expect(robot.hiThere).to.be.eql("hi there");
|
|
expect(robot.sayHi()).to.be.eql("hi");
|
|
});
|
|
});
|
|
|
|
context("if they do conflict with built-ins", function() {
|
|
it("doesn't pass them through", function() {
|
|
expect(robot.start).to.be.a("function");
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("all work and no play", function() {
|
|
var play = spy();
|
|
|
|
var playBot = new Robot({
|
|
play: play
|
|
});
|
|
|
|
it("makes Jack a dull boy", function() {
|
|
expect(playBot.work).to.be.eql(play);
|
|
});
|
|
});
|
|
|
|
describe("#toJSON", function() {
|
|
var bot = new Robot({
|
|
connections: {
|
|
loopback: { adaptor: "loopback" }
|
|
},
|
|
|
|
devices: {
|
|
ping: { driver: "ping" }
|
|
},
|
|
|
|
events: ["hello", "world"]
|
|
});
|
|
|
|
var json = bot.toJSON();
|
|
|
|
it("returns an object", function() {
|
|
expect(json).to.be.a("object");
|
|
});
|
|
|
|
it("contains the robot's name", function() {
|
|
expect(json.name).to.eql(bot.name);
|
|
});
|
|
|
|
it("contains the robot's commands", function() {
|
|
expect(json.commands).to.eql(Object.keys(bot.commands));
|
|
});
|
|
|
|
it("contains the robot's devices", function() {
|
|
expect(json.devices).to.eql([bot.devices.ping.toJSON()]);
|
|
});
|
|
|
|
it("contains the robot's connections", function() {
|
|
expect(json.connections).to.eql([bot.connections.loopback.toJSON()]);
|
|
});
|
|
|
|
it("contains the robot's events, or an empty array", function() {
|
|
expect(json.events).to.eql(["hello", "world"]);
|
|
|
|
bot = new Robot();
|
|
expect(bot.toJSON().events).to.be.eql([]);
|
|
});
|
|
});
|
|
|
|
describe("#connection", function() {
|
|
var opts, bot;
|
|
|
|
beforeEach(function() {
|
|
bot = new Robot();
|
|
opts = { adaptor: "loopback" };
|
|
});
|
|
|
|
it("creates and adds a new Connection", function() {
|
|
expect(bot.connections.loopback).to.be.eql(undefined);
|
|
bot.connection("loopback", opts);
|
|
expect(bot.connections.loopback).to.be.an.instanceOf(Adaptor);
|
|
});
|
|
|
|
it("sets connection.robot on to the Robot initializing it", function() {
|
|
bot.connection("loopback", opts);
|
|
expect(bot.connections.loopback.robot).to.be.eql(bot);
|
|
});
|
|
|
|
it("avoids name collisions", function() {
|
|
bot.connection("loopback", opts);
|
|
bot.connection("loopback", opts);
|
|
|
|
var conns = Object.keys(bot.connections);
|
|
|
|
expect(conns).to.be.eql(["loopback", "loopback-1"]);
|
|
});
|
|
});
|
|
|
|
describe("initRobot", function() {
|
|
var bot;
|
|
|
|
beforeEach(function() {
|
|
bot = new Robot();
|
|
});
|
|
|
|
context("when connection details are provided", function() {
|
|
it("creates new connections with each of the ones provided", function() {
|
|
var connections = {
|
|
loopback: { adaptor: "loopback" }
|
|
};
|
|
|
|
bot.initRobot({ connections: connections });
|
|
expect(bot.connections.loopback).to.be.instanceOf(Adaptor);
|
|
});
|
|
|
|
context("when the object contains device details", function() {
|
|
var opts;
|
|
|
|
beforeEach(function() {
|
|
opts = {
|
|
connections: {
|
|
loopback: {
|
|
adaptor: "loopback",
|
|
devices: {
|
|
ping: { driver: "ping", pin: 1 }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
bot.initRobot(opts);
|
|
});
|
|
|
|
it("adds the devices to opts.devices", function() {
|
|
expect(opts.devices).to.be.eql({
|
|
ping: { driver: "ping", pin: 1, connection: "loopback" }
|
|
});
|
|
});
|
|
|
|
it("removes the device details from optsconnections", function() {
|
|
expect(opts.connections.devices).to.be.eql(undefined);
|
|
});
|
|
});
|
|
});
|
|
|
|
context("when device details are provided", function() {
|
|
it("creates new devices with each of the ones provided", function() {
|
|
var opts = {
|
|
connections: {
|
|
loopback: { adaptor: "loopback" }
|
|
},
|
|
|
|
devices: {
|
|
ping: { driver: "ping" }
|
|
}
|
|
};
|
|
|
|
bot.initRobot(opts);
|
|
expect(bot.devices.ping).to.be.instanceOf(Driver);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("#device", function() {
|
|
var opts, bot;
|
|
|
|
beforeEach(function() {
|
|
bot = new Robot();
|
|
opts = { driver: "ping" };
|
|
});
|
|
|
|
it("creates and adds a new Device", function() {
|
|
expect(bot.devices.ping).to.be.eql(undefined);
|
|
bot.device("ping", opts);
|
|
expect(bot.devices.ping).to.be.an.instanceOf(Driver);
|
|
});
|
|
|
|
it("sets @robot on the Device to be the Robot initializing it", function() {
|
|
bot.device("ping", opts);
|
|
expect(bot.devices.ping.robot).to.be.eql(bot);
|
|
});
|
|
|
|
it("avoids name collisions", function() {
|
|
bot.device("ping", opts);
|
|
bot.device("ping", opts);
|
|
expect(Object.keys(bot.devices)).to.be.eql(["ping", "ping-1"]);
|
|
});
|
|
});
|
|
|
|
describe("initRobot", function() {
|
|
});
|
|
|
|
describe("#start", function() {
|
|
beforeEach(function() {
|
|
stub(robot, "startConnections").callsArg(0);
|
|
stub(robot, "startDevices").callsArg(0);
|
|
stub(robot, "emit").returns(null);
|
|
|
|
robot.start();
|
|
});
|
|
|
|
afterEach(function() {
|
|
robot.startConnections.restore();
|
|
robot.startDevices.restore();
|
|
robot.emit.restore();
|
|
});
|
|
|
|
it("starts the robot's connections", function() {
|
|
expect(robot.startConnections).to.be.called;
|
|
});
|
|
|
|
it("starts the robot's devices", function() {
|
|
expect(robot.startDevices).to.be.called;
|
|
});
|
|
|
|
it("starts the robot's work", function() {
|
|
expect(robot.work).to.be.called;
|
|
});
|
|
|
|
it("emits the 'ready' event", function() {
|
|
expect(robot.emit).to.be.calledWith("ready", robot);
|
|
});
|
|
|
|
it("returns the robot", function() {
|
|
expect(robot.start()).to.be.eql(robot);
|
|
});
|
|
});
|
|
|
|
describe("#startConnections", function() {
|
|
var bot;
|
|
|
|
beforeEach(function() {
|
|
bot = new Robot({
|
|
connections: {
|
|
alpha: { adaptor: "loopback" },
|
|
bravo: { adaptor: "loopback" }
|
|
}
|
|
});
|
|
|
|
stub(bot.connections.alpha, "connect").returns(true);
|
|
stub(bot.connections.bravo, "connect").returns(true);
|
|
});
|
|
|
|
it("runs #connect on each connection", function() {
|
|
bot.startConnections();
|
|
|
|
expect(bot.connections.alpha.connect).to.be.called;
|
|
expect(bot.connections.bravo.connect).to.be.called;
|
|
});
|
|
|
|
it("defines a named connection on robot for each connection", function() {
|
|
bot.startConnections();
|
|
|
|
expect(bot.alpha).to.be.an.instanceOf(Adaptor);
|
|
expect(bot.bravo).to.be.an.instanceOf(Adaptor);
|
|
});
|
|
});
|
|
|
|
describe("#startDevices", function() {
|
|
var bot;
|
|
|
|
beforeEach(function() {
|
|
bot = new Robot({
|
|
connections: {
|
|
loopback: { adaptor: "loopback" }
|
|
},
|
|
|
|
devices: {
|
|
alpha: { driver: "ping" },
|
|
bravo: { driver: "ping" }
|
|
}
|
|
});
|
|
|
|
stub(bot.devices.alpha, "start").returns(true);
|
|
stub(bot.devices.bravo, "start").returns(true);
|
|
});
|
|
|
|
it("runs #start on each device", function() {
|
|
bot.startDevices();
|
|
|
|
expect(bot.devices.alpha.start).to.be.called;
|
|
expect(bot.devices.bravo.start).to.be.called;
|
|
});
|
|
|
|
it("runs #start on each device only once", function() {
|
|
bot.startDevices();
|
|
bot.startDevices();
|
|
|
|
expect(bot.devices.alpha.start).to.be.called.once;
|
|
expect(bot.devices.bravo.start).to.be.called.once;
|
|
});
|
|
|
|
it("runs #start on a newly added device", function() {
|
|
bot.startDevices();
|
|
bot.device("charlie", { driver: "ping" });
|
|
stub(bot.devices.charlie, "start").returns(true);
|
|
bot.startDevices();
|
|
|
|
expect(bot.devices.alpha.start).to.be.called.once;
|
|
expect(bot.devices.bravo.start).to.be.called.once;
|
|
expect(bot.devices.charlie.start).to.be.called.once;
|
|
});
|
|
|
|
it("defines a named device on robot for each device", function() {
|
|
bot.startDevices();
|
|
|
|
expect(bot.alpha).to.be.an.instanceOf(Driver);
|
|
expect(bot.bravo).to.be.an.instanceOf(Driver);
|
|
});
|
|
});
|
|
|
|
describe("#halt", function() {
|
|
var bot, device, connection;
|
|
|
|
beforeEach(function() {
|
|
bot = new Robot({
|
|
devices: {
|
|
ping: { driver: "ping" }
|
|
},
|
|
|
|
connections: {
|
|
loopback: { adaptor: "loopback" }
|
|
}
|
|
});
|
|
|
|
bot.running = true;
|
|
|
|
device = bot.devices.ping;
|
|
connection = bot.connections.loopback;
|
|
|
|
stub(device, "halt").yields();
|
|
stub(connection, "disconnect").yields();
|
|
});
|
|
|
|
it("calls #halt on all devices and connections", function() {
|
|
bot.halt();
|
|
|
|
expect(device.halt).to.be.called;
|
|
expect(connection.disconnect).to.be.called;
|
|
});
|
|
|
|
context("if a subcall triggers it's callback with an error", function() {
|
|
beforeEach(function() {
|
|
bot = new Robot({
|
|
devices: {
|
|
ping: { driver: "ping" },
|
|
ping2: { driver: "ping" }
|
|
},
|
|
|
|
connections: {
|
|
loopback: { adaptor: "loopback" },
|
|
loopback2: { adaptor: "loopback" }
|
|
}
|
|
});
|
|
|
|
bot.running = true;
|
|
|
|
stub(bot.devices.ping, "halt").yields("error!");
|
|
stub(bot.devices.ping2, "halt").yields();
|
|
stub(bot.connections.loopback, "disconnect").yields("another err!");
|
|
stub(bot.connections.loopback2, "disconnect").yields();
|
|
});
|
|
|
|
it("doesn't effect the rest of the shutdown", function() {
|
|
bot.halt();
|
|
|
|
expect(bot.devices.ping.halt).to.be.called;
|
|
expect(bot.devices.ping2.halt).to.be.called;
|
|
expect(bot.connections.loopback.disconnect).to.be.called;
|
|
expect(bot.connections.loopback2.disconnect).to.be.called;
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("#toString", function() {
|
|
it("returns basic information about the robot", function() {
|
|
expect(robot.toString()).to.be.eql("[Robot name='Robby']");
|
|
});
|
|
});
|
|
|
|
describe("#log", function() {
|
|
beforeEach(function() {
|
|
stub(Logger, "log");
|
|
robot.log("an informative message");
|
|
});
|
|
|
|
afterEach(function() {
|
|
Logger.log.restore();
|
|
});
|
|
|
|
it("it passes messages onto Logger, with the Robot's name", function() {
|
|
var nameStr = "[" + robot.name + "] - ";
|
|
expect(Logger.log).to.be.calledWith(nameStr + "an informative message");
|
|
});
|
|
});
|
|
});
|