Merge pull request #247 from lorenwest/async-support
Support asynchronous robot commands
This commit is contained in:
commit
90bf775d1d
|
@ -60,9 +60,8 @@ router.get("/commands", function(req, res) {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/commands/:command", function(req, res) {
|
router.post("/commands/:command", function(req, res) {
|
||||||
var command = req.params.command;
|
var command = Cylon.commands[req.params.command];
|
||||||
var result = Cylon.commands[command].apply(Cylon, req.commandParams);
|
router.runCommand(req, res, Cylon, command);
|
||||||
res.json({ result: result });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/robots", function(req, res) {
|
router.get("/robots", function(req, res) {
|
||||||
|
@ -79,8 +78,7 @@ router.get("/robots/:robot/commands", load, function(req, res) {
|
||||||
|
|
||||||
router.all("/robots/:robot/commands/:command", load, function(req, res) {
|
router.all("/robots/:robot/commands/:command", load, function(req, res) {
|
||||||
var command = req.robot.commands[req.params.command];
|
var command = req.robot.commands[req.params.command];
|
||||||
var result = command.apply(req.robot, req.commandParams);
|
router.runCommand(req, res, req.robot, command);
|
||||||
res.json({ result: result });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/robots/:robot/devices", load, function(req, res) {
|
router.get("/robots/:robot/devices", load, function(req, res) {
|
||||||
|
@ -117,8 +115,7 @@ router.get("/robots/:robot/devices/:device/commands", load, function(req, res) {
|
||||||
|
|
||||||
router.all("/robots/:robot/devices/:device/commands/:command", load, function(req, res) {
|
router.all("/robots/:robot/devices/:device/commands/:command", load, function(req, res) {
|
||||||
var command = req.device.commands[req.params.command];
|
var command = req.device.commands[req.params.command];
|
||||||
var result = command.apply(req.device, req.commandParams);
|
router.runCommand(req, res, req.device, command);
|
||||||
res.json({ result: result });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/robots/:robot/connections", load, function(req, res) {
|
router.get("/robots/:robot/connections", load, function(req, res) {
|
||||||
|
@ -128,3 +125,17 @@ router.get("/robots/:robot/connections", load, function(req, res) {
|
||||||
router.get("/robots/:robot/connections/:connection", load, function(req, res) {
|
router.get("/robots/:robot/connections/:connection", load, function(req, res) {
|
||||||
res.json({ connection: req.connection });
|
res.json({ connection: req.connection });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Run an MCP, Robot, or Device command. Process the results immediately,
|
||||||
|
// or asynchronously if the result is a Promise.
|
||||||
|
router.runCommand = function(req, res, my, command) {
|
||||||
|
var result = command.apply(my, req.commandParams);
|
||||||
|
if (typeof result.then === "function") {
|
||||||
|
result
|
||||||
|
.then(function(result) {return res.json({result: result});})
|
||||||
|
.catch(function(err) {return res.status(500).json({error: err});});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.json({ result: result });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -21,6 +21,33 @@ function findFinalHandler(path) {
|
||||||
return handlers[handlers.length - 1];
|
return handlers[handlers.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MockPromise() {
|
||||||
|
var my = this;
|
||||||
|
my.resolve = function() {
|
||||||
|
var args = arguments;
|
||||||
|
process.nextTick(function() {
|
||||||
|
my.thenCallback.apply(null, args);
|
||||||
|
});
|
||||||
|
return my;
|
||||||
|
};
|
||||||
|
my.reject = function() {
|
||||||
|
var args = arguments;
|
||||||
|
process.nextTick(function() {
|
||||||
|
my.errorCallback.apply(null, args);
|
||||||
|
});
|
||||||
|
return my;
|
||||||
|
};
|
||||||
|
my.then = function(thenCallback) {
|
||||||
|
my.thenCallback = thenCallback;
|
||||||
|
return my;
|
||||||
|
};
|
||||||
|
my.catch = function(errorCallback) {
|
||||||
|
my.errorCallback = errorCallback;
|
||||||
|
return my;
|
||||||
|
};
|
||||||
|
my.deferred = my;
|
||||||
|
}
|
||||||
|
|
||||||
describe("API routes", function() {
|
describe("API routes", function() {
|
||||||
var routes = [
|
var routes = [
|
||||||
["GET", "/"],
|
["GET", "/"],
|
||||||
|
@ -55,18 +82,40 @@ describe("API commands", function() {
|
||||||
var req, res;
|
var req, res;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
Cylon.commands.ping = function() { return "pong"; };
|
Cylon.commands.ping = function() { return "pong"; };
|
||||||
|
Cylon.commands.pingAsync = function() {
|
||||||
|
var promise = new MockPromise();
|
||||||
|
return promise.resolve("immediate pong");
|
||||||
|
};
|
||||||
req = new MockRequest();
|
req = new MockRequest();
|
||||||
res = new MockResponse();
|
res = new MockResponse();
|
||||||
|
res.status = function(statusCode) {
|
||||||
|
res.statusCode = statusCode;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
req.device = {
|
req.device = {
|
||||||
name: "testDevice",
|
name: "testDevice",
|
||||||
commands: {
|
commands: {
|
||||||
announce: function(){return "im here";}
|
announce: function(){return "im here";},
|
||||||
|
announceAsync: function() {
|
||||||
|
var promise = new MockPromise();
|
||||||
|
process.nextTick(function(){
|
||||||
|
return promise.reject("sorry, sore throat");
|
||||||
|
});
|
||||||
|
return promise.deferred;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
req.robot = {
|
req.robot = {
|
||||||
name: "fred",
|
name: "fred",
|
||||||
commands: {
|
commands: {
|
||||||
speak: function(){return "ahem";}
|
speak: function(){return "ahem";},
|
||||||
|
speakAsync: function() {
|
||||||
|
var promise = new MockPromise();
|
||||||
|
process.nextTick(function(){
|
||||||
|
return promise.resolve("see ya in another cycle");
|
||||||
|
});
|
||||||
|
return promise.deferred;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
devices: {
|
devices: {
|
||||||
testDevice: req.device
|
testDevice: req.device
|
||||||
|
@ -75,12 +124,13 @@ describe("API commands", function() {
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
delete Cylon.commands.ping;
|
delete Cylon.commands.ping;
|
||||||
|
delete Cylon.commands.pingAsync;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns the list of MCP commands", function() {
|
it("returns the list of MCP commands", function() {
|
||||||
res.json = function(obj){
|
res.json = function(obj){
|
||||||
expect(obj.commands).to.exist();
|
expect(obj.commands).to.exist();
|
||||||
expect(obj.commands.length).to.equal(1);
|
expect(obj.commands.length).to.equal(2);
|
||||||
expect(obj.commands[0]).to.equal("ping");
|
expect(obj.commands[0]).to.equal("ping");
|
||||||
};
|
};
|
||||||
findFinalHandler("/commands")(req, res);
|
findFinalHandler("/commands")(req, res);
|
||||||
|
@ -94,11 +144,19 @@ describe("API commands", function() {
|
||||||
findFinalHandler("/commands/ping")(req, res);
|
findFinalHandler("/commands/ping")(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns an immediate MCP async command", function() {
|
||||||
|
req.params = {command:"pingAsync"};
|
||||||
|
res.json = function(obj){
|
||||||
|
expect(obj.result).to.equal("immediate pong");
|
||||||
|
};
|
||||||
|
findFinalHandler("/commands/pingAsync")(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns the list of robot commands", function() {
|
it("returns the list of robot commands", function() {
|
||||||
req.params = {robot: "fred"};
|
req.params = {robot: "fred"};
|
||||||
res.json = function(obj){
|
res.json = function(obj){
|
||||||
expect(obj.commands).to.exist();
|
expect(obj.commands).to.exist();
|
||||||
expect(obj.commands.length).to.equal(1);
|
expect(obj.commands.length).to.equal(2);
|
||||||
expect(obj.commands[0]).to.equal("speak");
|
expect(obj.commands[0]).to.equal("speak");
|
||||||
};
|
};
|
||||||
findFinalHandler("/robots/fred/commands")(req, res);
|
findFinalHandler("/robots/fred/commands")(req, res);
|
||||||
|
@ -112,11 +170,19 @@ describe("API commands", function() {
|
||||||
findFinalHandler("/robots/fred/commands/speak")(req, res);
|
findFinalHandler("/robots/fred/commands/speak")(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("invokes an asynchronous robot command", function() {
|
||||||
|
req.params = {robot: "fred", command:"speakAsync"};
|
||||||
|
res.json = function(obj){
|
||||||
|
expect(obj.result).to.equal("see ya in another cycle");
|
||||||
|
};
|
||||||
|
findFinalHandler("/robots/fred/commands/speakAsync")(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns the list of device commands", function() {
|
it("returns the list of device commands", function() {
|
||||||
req.params = {robot: "fred", device: "testDevice" };
|
req.params = {robot: "fred", device: "testDevice" };
|
||||||
res.json = function(obj){
|
res.json = function(obj){
|
||||||
expect(obj.commands).to.exist();
|
expect(obj.commands).to.exist();
|
||||||
expect(obj.commands.length).to.equal(1);
|
expect(obj.commands.length).to.equal(2);
|
||||||
expect(obj.commands[0]).to.equal("announce");
|
expect(obj.commands[0]).to.equal("announce");
|
||||||
};
|
};
|
||||||
var path = "/robots/fred/devices/testDevice/commands";
|
var path = "/robots/fred/devices/testDevice/commands";
|
||||||
|
@ -128,7 +194,17 @@ describe("API commands", function() {
|
||||||
res.json = function(obj){
|
res.json = function(obj){
|
||||||
expect(obj.result).to.equal("im here");
|
expect(obj.result).to.equal("im here");
|
||||||
};
|
};
|
||||||
var path = "/robots/fred/devices/testDevice/commands/speak";
|
var path = "/robots/fred/devices/testDevice/commands/announce";
|
||||||
|
findFinalHandler(path)(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns correctly for an async device command that errored", function() {
|
||||||
|
req.params = {robot: "fred", device: "testDevice", command:"announceAsync"};
|
||||||
|
res.json = function(obj){
|
||||||
|
expect(obj.error).to.equal("sorry, sore throat");
|
||||||
|
expect(res.statusCode).to.equal(500);
|
||||||
|
};
|
||||||
|
var path = "/robots/fred/devices/testDevice/commands/announceAsync";
|
||||||
findFinalHandler(path)(req, res);
|
findFinalHandler(path)(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue