diff --git a/lib/api.js b/lib/api.js index 71c4341..8bb4bb5 100644 --- a/lib/api.js +++ b/lib/api.js @@ -18,19 +18,17 @@ namespace("Cylon", function() { // The Cylon API Server provides an interface to communicate with master class // and retrieve information about the robots being controlled. this.ApiServer = (function() { - var master; - - master = null; - function ApiServer(opts) { var self = this; if (opts == null) { opts = {}; } + this.opts = opts; + this.host = opts.host || "127.0.0.1"; this.port = opts.port || "3000"; - master = opts.master; + this.master = opts.master; var options = { cert: fs.readFileSync(opts.cert || __dirname + "/ssl/server.crt"), @@ -44,7 +42,7 @@ namespace("Cylon", function() { this.server.set('title', 'Cylon API Server'); // configure basic auth, if requested - if (opts.auth.type && opts.auth.type === 'basic') { + if (opts.auth && opts.auth.type && opts.auth.type === 'basic') { var user = opts.auth.user, pass = opts.auth.pass; @@ -56,24 +54,15 @@ namespace("Cylon", function() { this.server.use(express.json()); this.server.use(express.urlencoded()); this.server.use(express["static"](__dirname + "/../node_modules/robeaux/")); + }; - this.server.use(function(req, res, next) { - res.setHeader("Access-Control-Allow-Origin", opts.CORS || "*"); - next(); - }); - - this.server.get("/*", function(req, res, next) { - res.set('Content-Type', 'application/json'); - return next(); - }); - - this.configureRoutes(); - + ApiServer.prototype.listen = function() { + var self = this; this.server.https.listen(this.port, this.host, null, function() { var title = self.server.get('title'); Logger.info(title + " is listening on " + self.host + ":" + self.port); }); - } + }; // Parses req to extract params to be used for commands. // @@ -93,9 +82,18 @@ namespace("Cylon", function() { return params; }; + // Sets all routes for the server ApiServer.prototype.configureRoutes = function() { var self = this; + var master = this.master; + + this.server.all("/*", function(req, res, next) { + res.set('Content-Type', 'application/json'); + res.set("Access-Control-Allow-Origin", self.opts.CORS || "*"); + return next(); + }); + this.server.get("/robots", function(req, res) { var data = []; @@ -107,50 +105,50 @@ namespace("Cylon", function() { res.json(data); }); - this.server.get("/robots/:robotname", function(req, res) { - master.findRobot(req.params.robotname, function(err, robot) { + this.server.get("/robots/:robot", function(req, res) { + master.findRobot(req.params.robot, function(err, robot) { res.json(err ? err : robot.data()); }); }); - this.server.get("/robots/:robotname/commands", function(req, res) { - master.findRobot(req.params.robotname, function(err, robot) { + this.server.get("/robots/:robot/commands", function(req, res) { + master.findRobot(req.params.robot, function(err, robot) { res.json(err ? err : robot.data().commands); }); }); - this.server.all("/robots/:robotname/commands/:commandname", function(req, res) { + this.server.all("/robots/:robot/commands/:command", function(req, res) { var params = self.parseCommandParams(req); - master.findRobot(req.params.robotname, function(err, robot) { + master.findRobot(req.params.robot, function(err, robot) { if (err) { return res.json(err); } - var result = robot[req.params.commandname].apply(robot, params); + var result = robot[req.params.command].apply(robot, params); res.json({ result: result }); }); }); - this.server.get("/robots/:robotname/devices", function(req, res) { - master.findRobot(req.params.robotname, function(err, robot) { + this.server.get("/robots/:robot/devices", function(req, res) { + master.findRobot(req.params.robot, function(err, robot) { res.json(err ? err : robot.data().devices); }); }); - this.server.get("/robots/:robotname/devices/:devicename", function(req, res) { - var robotname = req.params.robotname, - devicename = req.params.devicename; + this.server.get("/robots/:robot/devices/:device", function(req, res) { + var robot = req.params.robot, + device = req.params.device; - master.findRobotDevice(robotname, devicename, function(err, device) { + master.findRobotDevice(robot, device, function(err, device) { res.json(err ? err : device.data()); }); }); - this.server.get("/robots/:robotname/devices/:devicename/events/:eventname", function(req, res) { - var robotname = req.params.robotname, - devicename = req.params.devicename, - eventname = req.params.eventname; + this.server.get("/robots/:robot/devices/:device/events/:event", function(req, res) { + var robot = req.params.robot, + device = req.params.device, + event = req.params.event; - master.findRobotDevice(robotname, devicename, function(err, device) { + master.findRobotDevice(robot, device, function(err, device) { if (err) { res.json(err); } res.writeHead(200, { @@ -163,49 +161,49 @@ namespace("Cylon", function() { res.write("data: " + JSON.stringify(data) + "\n\n") }; - device.on(eventname, writeData); + device.on(event, writeData); res.on('close', function() { - device.removeListener(eventname, writeData); + device.removeListener(event, writeData); }); }); }); - this.server.get("/robots/:robotname/devices/:devicename/commands", function(req, res) { - var robotname = req.params.robotname, - devicename = req.params.devicename; + this.server.get("/robots/:robot/devices/:device/commands", function(req, res) { + var robot = req.params.robot, + device = req.params.device; - master.findRobotDevice(robotname, devicename, function(err, device) { + master.findRobotDevice(robot, device, function(err, device) { res.json(err ? err : device.data().commands); }); }); - this.server.all("/robots/:robot/devices/:device/commands/:commandname", function(req, res) { - var robotname = req.params.robot, - devicename = req.params.device, - commandname = req.params.commandname; + this.server.all("/robots/:robot/devices/:device/commands/:command", function(req, res) { + var robot = req.params.robot, + device = req.params.device, + command = req.params.command; var params = self.parseCommandParams(req); - master.findRobotDevice(robotname, devicename, function(err, device) { + master.findRobotDevice(robot, device, function(err, device) { if (err) { return res.json(err); } - var result = device[commandname].apply(device, params); + var result = device[command].apply(device, params); res.json({ result: result }); }); }); - this.server.get("/robots/:robotname/connections", function(req, res) { - master.findRobot(req.params.robotname, function(err, robot) { + this.server.get("/robots/:robot/connections", function(req, res) { + master.findRobot(req.params.robot, function(err, robot) { res.json(err ? err : robot.data().connections); }); }); this.server.get("/robots/:robot/connections/:connection", function(req, res) { - var robotname = req.params.robot, - connectionname = req.params.connection; + var robot = req.params.robot, + connection = req.params.connection; - master.findRobotConnection(robotname, connectionname, function(err, connection) { + master.findRobotConnection(robot, connection, function(err, connection) { res.json(err ? err : connection.data()); }); }); diff --git a/lib/cylon.js b/lib/cylon.js index 930e9a6..2e3d7fe 100644 --- a/lib/cylon.js +++ b/lib/cylon.js @@ -222,6 +222,8 @@ var Cylon = (function() { if (this.api_instance === null) { this.api_instance = new Server(this.api_config); + this.api_instance.configureRoutes(); + this.api_instance.listen(); } return this.api_instance; diff --git a/test/specs/api.spec.js b/test/specs/api.spec.js new file mode 100644 index 0000000..444b913 --- /dev/null +++ b/test/specs/api.spec.js @@ -0,0 +1,130 @@ +'use strict'; + +var express = require('express'), + https = require('https'), + fs = require('fs'); + +var API = source('api'); + +describe("API", function() { + var api, opts; + + beforeEach(function() { + stub(https, 'createServer').returns({ listen: spy() }); + + opts = { + master: { name: 'master' } + } + + api = new API(opts); + }); + + afterEach(function() { + https.createServer.restore(); + }); + + describe("constructor", function() { + it("sets @opts to the passed opts object", function() { + expect(api.opts).to.be.eql(opts); + }); + + it("sets @host to 127.0.0.1 by default", function() { + expect(api.host).to.be.eql("127.0.0.1") + }); + + it("sets @post to 3000 by default", function() { + expect(api.port).to.be.eql("3000") + }); + + it("sets @master to the passed master", function() { + expect(api.master).to.be.eql(opts.master) + }); + + it("sets @server to an Express server instance", function() { + expect(api.server).to.be.a('function'); + + var methods = ['get', 'post', 'put', 'delete']; + + for (var i = 0; i < methods.length; i++) { + expect(api.server[methods[i]]).to.be.a('function'); + } + }); + + it("sets @server.https to a https.createServer", function() { + expect(https.createServer).to.be.calledWith(); + }); + + it("sets the server's title", function() { + var title = api.server.get('title'); + expect(title).to.be.eql("Cylon API Server"); + }); + }); + + describe("#configureRoutes", function() { + var server; + var methods = ['all', 'get', 'post']; + + beforeEach(function() { + server = api.server; + for (var i = 0; i < methods.length; i++) { + stub(server, methods[i]); + } + + api.configureRoutes(); + }); + + afterEach(function() { + for (var i = 0; i < methods.length; i++) { + server[methods[i]].restore(); + } + }); + + it("ALL /*", function() { + expect(server.all).to.be.calledWith("/*"); + }); + + it("GET /robots", function() { + expect(server.get).to.be.calledWith("/robots"); + }); + + it("GET /robots/:robot", function() { + expect(server.get).to.be.calledWith("/robots/:robot"); + }); + + it("GET /robots/:robot/commands", function() { + expect(server.get).to.be.calledWith("/robots/:robot/commands"); + }); + + it("ALL /robots/:robot/commands/:command", function() { + expect(server.all).to.be.calledWith("/robots/:robot/commands/:command"); + }); + + it("GET /robots/:robot/devices", function() { + expect(server.get).to.be.calledWith("/robots/:robot/devices"); + }); + + it("GET /robots/:robot/devices/:device", function() { + expect(server.get).to.be.calledWith("/robots/:robot/devices/:device"); + }); + + it("GET /robots/:robot/devices/:device/events/:event", function() { + expect(server.get).to.be.calledWith("/robots/:robot/devices/:device/events/:event"); + }); + + it("GET /robots/:robot/devices/:device/commands", function() { + expect(server.get).to.be.calledWith("/robots/:robot/devices/:device/commands"); + }); + + it("ALL /robots/:robot/devices/:device/commands/:command", function() { + expect(server.all).to.be.calledWith("/robots/:robot/devices/:device/commands/:command"); + }); + + it("GET /robots/:robot/connections", function() { + expect(server.get).to.be.calledWith("/robots/:robot/connections"); + }); + + it("GET /robots/:robot/connections/:connection", function() { + expect(server.get).to.be.calledWith("/robots/:robot/connections/:connection"); + }); + }); +});