Merge pull request #255 from hybridgroup/feature/extract-out-api
Extract API from core
This commit is contained in:
commit
25f12345b9
123
lib/api.js
123
lib/api.js
|
@ -1,123 +0,0 @@
|
||||||
/*
|
|
||||||
* Cylon API
|
|
||||||
* cylonjs.com
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013-2015 The Hybrid Group
|
|
||||||
* Licensed under the Apache 2.0 license.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var fs = require("fs"),
|
|
||||||
path = require("path");
|
|
||||||
|
|
||||||
var express = require("express"),
|
|
||||||
bodyParser = require("body-parser");
|
|
||||||
|
|
||||||
var Logger = require("./logger"),
|
|
||||||
_ = require("./lodash");
|
|
||||||
|
|
||||||
var API = module.exports = function API(opts) {
|
|
||||||
if (opts == null) {
|
|
||||||
opts = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
_.forEach(this.defaults, function(def, name) {
|
|
||||||
this[name] = _.has(opts, name) ? opts[name] : def;
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.createServer();
|
|
||||||
|
|
||||||
this.express.set("title", "Cylon API Server");
|
|
||||||
|
|
||||||
this.express.use(this.setupAuth());
|
|
||||||
|
|
||||||
this.express.use(bodyParser.json());
|
|
||||||
this.express.use(bodyParser.urlencoded({ extended: true }));
|
|
||||||
|
|
||||||
this.express.use(express["static"](__dirname + "/../node_modules/robeaux/"));
|
|
||||||
|
|
||||||
// set CORS headers for API requests
|
|
||||||
this.express.use(function(req, res, next) {
|
|
||||||
res.set("Access-Control-Allow-Origin", this.CORS || "*");
|
|
||||||
res.set("Access-Control-Allow-Headers", "Content-Type");
|
|
||||||
res.set("Content-Type", "application/json");
|
|
||||||
return next();
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
// extracts command params from request
|
|
||||||
this.express.use(function(req, res, next) {
|
|
||||||
req.commandParams = _.values(req.body);
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// load route definitions
|
|
||||||
this.express.use("/api", require("./api/routes"));
|
|
||||||
|
|
||||||
// error handling
|
|
||||||
this.express.use(function(err, req, res, next) {
|
|
||||||
res.status(500).json({ error: err.message || "An error occured."});
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
API.prototype.defaults = {
|
|
||||||
host: "127.0.0.1",
|
|
||||||
port: "3000",
|
|
||||||
auth: false,
|
|
||||||
CORS: "",
|
|
||||||
ssl: {
|
|
||||||
key: path.normalize(__dirname + "/api/ssl/server.key"),
|
|
||||||
cert: path.normalize(__dirname + "/api/ssl/server.crt")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
API.prototype.createServer = function createServer() {
|
|
||||||
this.express = express();
|
|
||||||
|
|
||||||
//configure ssl if requested
|
|
||||||
if (this.ssl && typeof(this.ssl) === "object") {
|
|
||||||
var https = require("https");
|
|
||||||
|
|
||||||
this.server = https.createServer({
|
|
||||||
key: fs.readFileSync(this.ssl.key),
|
|
||||||
cert: fs.readFileSync(this.ssl.cert)
|
|
||||||
}, this.express);
|
|
||||||
} else {
|
|
||||||
var str = "API using insecure connection. ";
|
|
||||||
str += "We recommend using an SSL certificate with Cylon.";
|
|
||||||
|
|
||||||
Logger.warn(str);
|
|
||||||
this.server = this.express;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
API.prototype.setupAuth = function setupAuth() {
|
|
||||||
var authfn = function auth(req, res, next) { next(); };
|
|
||||||
|
|
||||||
if (!!this.auth && typeof(this.auth) === "object" && this.auth.type) {
|
|
||||||
var type = this.auth.type,
|
|
||||||
module = "./api/auth/" + type,
|
|
||||||
filename = path.normalize(__dirname + "/" + module + ".js"),
|
|
||||||
exists = fs.existsSync(filename);
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
authfn = require(filename)(this.auth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return authfn;
|
|
||||||
};
|
|
||||||
|
|
||||||
API.prototype.listen = function() {
|
|
||||||
this.server.listen(this.port, this.host, null, function() {
|
|
||||||
var title = this.express.get("title");
|
|
||||||
var protocol = this.ssl ? "https" : "http",
|
|
||||||
str;
|
|
||||||
|
|
||||||
str = "Listening at " + protocol + "://" + this.host + ":" + this.port;
|
|
||||||
|
|
||||||
Logger.info(title + " is now online.");
|
|
||||||
Logger.info(str);
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* Cylon API - Basic Auth
|
|
||||||
* cylonjs.com
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013-2015 The Hybrid Group
|
|
||||||
* Licensed under the Apache 2.0 license.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var http = require("http");
|
|
||||||
|
|
||||||
var unauthorized = function unauthorized(res) {
|
|
||||||
res.statusCode = 401;
|
|
||||||
res.setHeader("WWW-Authenticate", "Basic realm=\"Authorization Required\"");
|
|
||||||
res.end("Unauthorized");
|
|
||||||
};
|
|
||||||
|
|
||||||
var error = function error(code, msg){
|
|
||||||
var err = new Error(msg || http.STATUS_CODES[code]);
|
|
||||||
err.status = code;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = function(config) {
|
|
||||||
var user = config.user,
|
|
||||||
pass = config.pass;
|
|
||||||
|
|
||||||
return function auth(req, res, next) {
|
|
||||||
var authorization = req.headers.authorization;
|
|
||||||
|
|
||||||
if (!authorization) {
|
|
||||||
return unauthorized(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
// malformed
|
|
||||||
var parts = authorization.split(" ");
|
|
||||||
|
|
||||||
if ("basic" !== parts[0].toLowerCase() || !parts[1]) {
|
|
||||||
return next(error(400));
|
|
||||||
}
|
|
||||||
|
|
||||||
authorization = parts[1];
|
|
||||||
|
|
||||||
// credentials
|
|
||||||
authorization = new Buffer(authorization, "base64").toString();
|
|
||||||
authorization = authorization.match(/^([^:]+):(.+)$/);
|
|
||||||
|
|
||||||
if (!authorization) {
|
|
||||||
return unauthorized(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authorization[1] === user && authorization[2] === pass) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return unauthorized(res);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,187 +0,0 @@
|
||||||
/* jshint maxlen: false */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Cylon API - Route Definitions
|
|
||||||
* cylonjs.com
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013-2015 The Hybrid Group
|
|
||||||
* Licensed under the Apache 2.0 license.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var Cylon = require("../cylon");
|
|
||||||
|
|
||||||
var router = module.exports = require("express").Router();
|
|
||||||
|
|
||||||
var eventHeaders = {
|
|
||||||
"Content-Type": "text/event-stream",
|
|
||||||
"Connection": "keep-alive",
|
|
||||||
"Cache-Control": "no-cache"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Loads up the appropriate Robot/Device/Connection instances, if they are
|
|
||||||
// present in the route params.
|
|
||||||
var load = function load(req, res, next) {
|
|
||||||
var robot = req.params.robot,
|
|
||||||
device = req.params.device,
|
|
||||||
connection = req.params.connection;
|
|
||||||
|
|
||||||
if (robot) {
|
|
||||||
req.robot = Cylon.robots[robot];
|
|
||||||
if (!req.robot) {
|
|
||||||
return res.status(404).json({
|
|
||||||
error: "No Robot found with the name " + robot
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device) {
|
|
||||||
req.device = req.robot.devices[device];
|
|
||||||
if (!req.device) {
|
|
||||||
return res.status(404).json({
|
|
||||||
error: "No device found with the name " + device
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection) {
|
|
||||||
req.connection = req.robot.connections[connection];
|
|
||||||
if (!req.connection) {
|
|
||||||
return res.status(404).json({
|
|
||||||
error: "No connection found with the name " + connection
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
router.get("/", function(req, res) {
|
|
||||||
res.json({ MCP: Cylon.toJSON() });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/events", function(req, res) {
|
|
||||||
res.json({ events: Cylon.events });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/events/:event", function(req, res) {
|
|
||||||
var event = req.params.event;
|
|
||||||
|
|
||||||
res.writeHead(200, eventHeaders);
|
|
||||||
|
|
||||||
var writeData = function(data) {
|
|
||||||
res.write("data: " + JSON.stringify(data) + "\n\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
Cylon.on(event, writeData);
|
|
||||||
|
|
||||||
res.on("close", function() {
|
|
||||||
Cylon.removeListener(event, writeData);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/commands", function(req, res) {
|
|
||||||
res.json({ commands: Object.keys(Cylon.commands) });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/commands/:command", function(req, res) {
|
|
||||||
var command = Cylon.commands[req.params.command];
|
|
||||||
router.runCommand(req, res, Cylon, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots", function(req, res) {
|
|
||||||
res.json({ robots: Cylon.toJSON().robots });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot", load, function(req, res) {
|
|
||||||
res.json({ robot: req.robot });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/commands", load, function(req, res) {
|
|
||||||
res.json({ commands: Object.keys(req.robot.commands) });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.all("/robots/:robot/commands/:command", load, function(req, res) {
|
|
||||||
var command = req.robot.commands[req.params.command];
|
|
||||||
router.runCommand(req, res, req.robot, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/events", load, function(req, res) {
|
|
||||||
res.json({ events: req.robot.events });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.all("/robots/:robot/events/:event", load, function(req, res) {
|
|
||||||
var event = req.params.event;
|
|
||||||
|
|
||||||
res.writeHead(200, eventHeaders);
|
|
||||||
|
|
||||||
var writeData = function(data) {
|
|
||||||
res.write("data: " + JSON.stringify(data) + "\n\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
req.robot.on(event, writeData);
|
|
||||||
|
|
||||||
res.on("close", function() {
|
|
||||||
req.robot.removeListener(event, writeData);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/devices", load, function(req, res) {
|
|
||||||
res.json({ devices: req.robot.toJSON().devices });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/devices/:device", load, function(req, res) {
|
|
||||||
res.json({ device: req.device });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/devices/:device/events", load, function(req, res) {
|
|
||||||
res.json({ events: req.device.events });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/devices/:device/events/:event", load, function(req, res) {
|
|
||||||
var event = req.params.event;
|
|
||||||
|
|
||||||
res.writeHead(200, eventHeaders);
|
|
||||||
|
|
||||||
var writeData = function(data) {
|
|
||||||
res.write("data: " + JSON.stringify(data) + "\n\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
req.device.on(event, writeData);
|
|
||||||
|
|
||||||
res.on("close", function() {
|
|
||||||
req.device.removeListener(event, writeData);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/devices/:device/commands", load, function(req, res) {
|
|
||||||
res.json({ commands: Object.keys(req.device.commands) });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.all("/robots/:robot/devices/:device/commands/:command", load, function(req, res) {
|
|
||||||
var command = req.device.commands[req.params.command];
|
|
||||||
router.runCommand(req, res, req.device, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/connections", load, function(req, res) {
|
|
||||||
res.json({ connections: req.robot.toJSON().connections });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/robots/:robot/connections/:connection", load, function(req, res) {
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDnDCCAoQCCQDMuSNl5mThYDANBgkqhkiG9w0BAQUFADCBjzELMAkGA1UEBhMC
|
|
||||||
VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMRkw
|
|
||||||
FwYDVQQKExBUaGUgSHlicmlkIEdyb3VwMRIwEAYDVQQDEwlsb2NhbGhvc3QxJjAk
|
|
||||||
BgkqhkiG9w0BCQEWF2N5bG9uanNAaHlicmlkZ3JvdXAuY29tMB4XDTE0MDQwMzIx
|
|
||||||
MjczM1oXDTE1MDQwMzIxMjczM1owgY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpD
|
|
||||||
YWxpZm9ybmlhMRQwEgYDVQQHEwtMb3MgQW5nZWxlczEZMBcGA1UEChMQVGhlIEh5
|
|
||||||
YnJpZCBHcm91cDESMBAGA1UEAxMJbG9jYWxob3N0MSYwJAYJKoZIhvcNAQkBFhdj
|
|
||||||
eWxvbmpzQGh5YnJpZGdyb3VwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
|
||||||
AQoCggEBALhobzDaWlLBwPUFBZRxQt5fsvxFzkGJ8TxD8Uhp4r5P4Idqa2DKaz7q
|
|
||||||
oIob/l96t9Szcxi+nib4Cykxt7rw4mmDJFCS+9aV8+u/aFCXjEicCSPkM95f6NOD
|
|
||||||
a3JvHWeFfYeQOZq0uDS9PTccXpomtvX7ufdmYmPpDNzUcJD+FA8+5tVdtbvoF5aV
|
|
||||||
su4Ufb7CcoE9KuyeWm7UQpjaKuoYVAa/9eCHQfptwf0iPPlW4NcS5JG0CJOqyyrF
|
|
||||||
YC30MIlE6/tol8TCPFrbA4HLdtqBKMkOfhSYor12OBKazVWWqk+AkuDQFfXA/OFk
|
|
||||||
L3VGVFjnUh/RUjCoZQ+CeKAM7grUyUcCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
|
|
||||||
S4knU+SUXmCw+V8ZggGoLW3OelI6vMq+7ThhGeS+ge4ZkQRuqBdJxK7HytC6bysC
|
|
||||||
L9qQuJsKoEPSDoi48ml7XqRH+kXD4lTpRtDSF8WetLvIjh6lJM5I9xLmevBZYeJT
|
|
||||||
9a+gm6eS0oZBm3/cHpUJnQAw8M8wYmHB/d/WdNu7fV9m5+PBzvwhxVlVb3yaOdsH
|
|
||||||
vRh0BZU58NmFcscfV/pTSqTlp59CQGMynHsaaLiKiLje04v+b+wwob/7kuvZvQ9D
|
|
||||||
kYQkg1EnCGMFIk1Slj1GIS/ewD90JTATJa38wshg2VAQiX8sxqsUPdye/MjheWhF
|
|
||||||
hxMSOB6xNhHjBiOQRwtGtw==
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,18 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE REQUEST-----
|
|
||||||
MIIC9jCCAd4CAQAwgY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
|
|
||||||
MRQwEgYDVQQHEwtMb3MgQW5nZWxlczEZMBcGA1UEChMQVGhlIEh5YnJpZCBHcm91
|
|
||||||
cDESMBAGA1UEAxMJbG9jYWxob3N0MSYwJAYJKoZIhvcNAQkBFhdjeWxvbmpzQGh5
|
|
||||||
YnJpZGdyb3VwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALho
|
|
||||||
bzDaWlLBwPUFBZRxQt5fsvxFzkGJ8TxD8Uhp4r5P4Idqa2DKaz7qoIob/l96t9Sz
|
|
||||||
cxi+nib4Cykxt7rw4mmDJFCS+9aV8+u/aFCXjEicCSPkM95f6NODa3JvHWeFfYeQ
|
|
||||||
OZq0uDS9PTccXpomtvX7ufdmYmPpDNzUcJD+FA8+5tVdtbvoF5aVsu4Ufb7CcoE9
|
|
||||||
KuyeWm7UQpjaKuoYVAa/9eCHQfptwf0iPPlW4NcS5JG0CJOqyyrFYC30MIlE6/to
|
|
||||||
l8TCPFrbA4HLdtqBKMkOfhSYor12OBKazVWWqk+AkuDQFfXA/OFkL3VGVFjnUh/R
|
|
||||||
UjCoZQ+CeKAM7grUyUcCAwEAAaAhMB8GCSqGSIb3DQEJAjESExBUaGUgSHlicmlk
|
|
||||||
IEdyb3VwMA0GCSqGSIb3DQEBBQUAA4IBAQC1ap6IIWrAg6ZGmMev4Ef7qGLNSAYV
|
|
||||||
8jg5pG63AHGkZLCMw1ZcT1iZPjOZC1mBzJBy0z9C8fo0ekGvMmmQbQ40i4JgmYH4
|
|
||||||
bmzL4+ulySgiR45DokHkRtyZOa2f2/nOcvVpIDF3EwH7L2yb4AFrCHDwfh8TKl2x
|
|
||||||
oMpy00F23vn7loDxyMkIcR8It+VL1NiJM+TJN9OzGIh0FkJHs2lbRawS30xyxIYy
|
|
||||||
z0yImdzHG5rXGlPS1aeeVue4H9DfinCICSfd0Bx9iJCqzUD98cmK8x4hVezD9r32
|
|
||||||
/6jm88UzF2WgJvuxcQ8EQRiPGCFcLHGB2AFryTWdt6ibCW6gsoRinnvZ
|
|
||||||
-----END CERTIFICATE REQUEST-----
|
|
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEAuGhvMNpaUsHA9QUFlHFC3l+y/EXOQYnxPEPxSGnivk/gh2pr
|
|
||||||
YMprPuqgihv+X3q31LNzGL6eJvgLKTG3uvDiaYMkUJL71pXz679oUJeMSJwJI+Qz
|
|
||||||
3l/o04Nrcm8dZ4V9h5A5mrS4NL09Nxxemia29fu592ZiY+kM3NRwkP4UDz7m1V21
|
|
||||||
u+gXlpWy7hR9vsJygT0q7J5abtRCmNoq6hhUBr/14IdB+m3B/SI8+Vbg1xLkkbQI
|
|
||||||
k6rLKsVgLfQwiUTr+2iXxMI8WtsDgct22oEoyQ5+FJiivXY4EprNVZaqT4CS4NAV
|
|
||||||
9cD84WQvdUZUWOdSH9FSMKhlD4J4oAzuCtTJRwIDAQABAoIBAQCwIqgZrGXbZ88q
|
|
||||||
+On0eB4bkqK9zNsNxHjTTD35IZH+nwLhtObtI0o+ZRKD9+sGPYu6sNA9kUw0AnV+
|
|
||||||
mktYVl6b0zPrdgjvVHkP8tnrKGVIsSkVzBEy1L7o0Dzfp3wZdeqJgltTBkxvq1T9
|
|
||||||
/63oZRQabZ6ZzIQr09yCTLNb+iMkzxuJSbdwVAws+oKqSTVoUKY/Gee66MsJjKkr
|
|
||||||
FfqwS/tVfTFWXp9CamWz/zBDyUXnrtUhzNxE+3LPbeZ/sMq/V/f1F5jiQNjenMGl
|
|
||||||
K/qD3HyNbR0P4xWYCe/4/QLSWjsiZHg51d4Nioegii2775ya9qDvtTfUeNNdvCf6
|
|
||||||
HMcvUIPxAoGBAOF5pSl/9VqRc1FXS+Kr8cs7DORZ6EPXKzUZL59V0NHoMgKbk+sM
|
|
||||||
JZv4TeDx26eBoecT2UYWSYMbKLl7sM9YZDJJhgwMeqa47PHk5Cua/Dpfus/pDl67
|
|
||||||
S5FT/gyA2tpSpULml2G7YegdPnTRRNQdppcrY5v9LQ9CSPhbSAPY/FspAoGBANFf
|
|
||||||
gs6JEq46SOMBw+66svwaT2DQJNchwtCKE+2m7OdNufrtTSAVlKlNpGQQxElwYYg1
|
|
||||||
xkEA+l7MycXIkSWOGC4JfLSRYlBgYOFzCQCME9rTkboAXn8Iru9p9vnRnfsEOUk4
|
|
||||||
Vr8/ehzv4gcf90ZJcnPkQNu5lTkr+s/bNDjAof7vAoGARN3zvU4w8V21nCWOrwgX
|
|
||||||
jRxXHrP7RiVFNC2iJwd+BW7nP3anYkZOgmn/13HnxizI95xPY6HRCDNWZ/jIkzwL
|
|
||||||
NnTQdYOmPqAC9wsTSeJHoci1dWVYl0SbmyLNWKJOthpCEcH+gMJL8CpmdiWo4STB
|
|
||||||
SjDddrqIdb2oLfsrbslqoqkCgYAUJV6OxP25Kf6NaUQTGn/SZi2xIRYKZUM7ka2t
|
|
||||||
NlyhPQdiL6c2KR1u1Pu2bS6V6mxYEOSMqK1upcHceBoPRQbqlxsavMp69WsdBlad
|
|
||||||
aN0YNzdUcGinTIyYmNec3iCXYKaqdvNR36e+VQ6opNjEOJj8sb/T5J2JLMQrb+os
|
|
||||||
c8yinQKBgQCcww6Mr0Wnbw/E9RrwYJqheFWXjdrvbhs5leudzHknL/WXfroOccFr
|
|
||||||
cduywWcHkGq9ig0fay/3iVS5WRAlVqDOGmf60It60ZE0CEDVsHd3w9BEAeJxkq+6
|
|
||||||
vnMZ73dIUKmOozNg0+xK7XsY2YCNdYUaFtKH/ENfsCWfu7r2Kli46A==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
40
lib/cylon.js
40
lib/cylon.js
|
@ -30,7 +30,7 @@ Cylon.IO = {
|
||||||
Utils: require("./io/utils")
|
Utils: require("./io/utils")
|
||||||
};
|
};
|
||||||
|
|
||||||
Cylon.api_instance = null;
|
Cylon.apiInstances = [];
|
||||||
|
|
||||||
Cylon.robots = {};
|
Cylon.robots = {};
|
||||||
Cylon.commands = {};
|
Cylon.commands = {};
|
||||||
|
@ -70,20 +70,42 @@ Cylon.robot = function robot(opts) {
|
||||||
return bot;
|
return bot;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Public: Creates a new API based on passed options
|
// Public: Initializes an API instance based on provided options.
|
||||||
//
|
//
|
||||||
// Returns nothing
|
// Returns nothing
|
||||||
Cylon.api = function api(opts) {
|
Cylon.api = function api(Server, opts) {
|
||||||
if (typeof opts === "object") {
|
// if only passed options (or nothing), assume HTTP server
|
||||||
this.config({ api: opts });
|
if (Server == null || _.isObject(Server) && !_.isFunction(Server)) {
|
||||||
|
opts = Server;
|
||||||
|
Server = "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
var API = require("./api");
|
opts = opts || {};
|
||||||
|
|
||||||
var config = Utils.fetch(Config, "api", {});
|
if (_.isString(Server)) {
|
||||||
|
var req = "cylon-api-" + Server;
|
||||||
|
|
||||||
this.api_instance = new API(config);
|
try {
|
||||||
this.api_instance.listen();
|
Server = require(req);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === "MODULE_NOT_FOUND") {
|
||||||
|
Logger.error("Cannot find the " + req + " API module.");
|
||||||
|
Logger.error(
|
||||||
|
"You may be able to install it: `npm install " + req + "`"
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.mcp = this;
|
||||||
|
|
||||||
|
var instance = new Server(opts);
|
||||||
|
this.apiInstances.push(instance);
|
||||||
|
instance.listen();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Public: Starts up the API and the robots
|
// Public: Starts up the API and the robots
|
||||||
|
|
|
@ -44,9 +44,6 @@
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "0.9.0",
|
"async": "0.9.0",
|
||||||
"express": "4.10.5",
|
|
||||||
"body-parser": "1.10.0",
|
|
||||||
"robeaux": "0.3.0",
|
|
||||||
"lodash": "2.4.1"
|
"lodash": "2.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
/* jshint expr:true */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var https = require("https"),
|
|
||||||
path = require("path");
|
|
||||||
|
|
||||||
var API = source("api"),
|
|
||||||
Logger = source("logger");
|
|
||||||
|
|
||||||
var MockRequest = require("../support/mock_request"),
|
|
||||||
MockResponse = require("../support/mock_response");
|
|
||||||
|
|
||||||
describe("API", function() {
|
|
||||||
var api;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
api = new API();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("constructor", function() {
|
|
||||||
var mod;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
mod = new API({
|
|
||||||
host: "0.0.0.0",
|
|
||||||
port: "1234"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets @express to an Express instance", function() {
|
|
||||||
expect(api.express.listen).to.be.a("function");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets default values", function() {
|
|
||||||
expect(api.host).to.be.eql("127.0.0.1");
|
|
||||||
expect(api.port).to.be.eql("3000");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("overrides default values if passed to constructor", function() {
|
|
||||||
expect(mod.host).to.be.eql("0.0.0.0");
|
|
||||||
expect(mod.port).to.be.eql("1234");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the server title", function() {
|
|
||||||
var title = api.express.get("title");
|
|
||||||
expect(title).to.be.eql("Cylon API Server");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("default", function() {
|
|
||||||
var d = API.prototype.defaults;
|
|
||||||
|
|
||||||
it("host", function() {
|
|
||||||
expect(d.host).to.be.eql("127.0.0.1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("port", function() {
|
|
||||||
expect(d.port).to.be.eql("3000");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auth", function() {
|
|
||||||
expect(d.auth).to.be.eql(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("CORS", function() {
|
|
||||||
expect(d.CORS).to.be.eql("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ssl", function() {
|
|
||||||
var sslDir = path.normalize(__dirname + "/../../lib/api/ssl/");
|
|
||||||
expect(d.ssl.key).to.be.eql(sslDir + "server.key");
|
|
||||||
expect(d.ssl.cert).to.be.eql(sslDir + "server.crt");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#createServer", function() {
|
|
||||||
it("sets @express to an express server", function() {
|
|
||||||
api.express = null;
|
|
||||||
api.createServer();
|
|
||||||
expect(api.express).to.be.a("function");
|
|
||||||
});
|
|
||||||
|
|
||||||
context("if SSL is configured", function() {
|
|
||||||
it("sets @server to a https.Server instance", function() {
|
|
||||||
api.createServer();
|
|
||||||
expect(api.server).to.be.an.instanceOf(https.Server);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("if SSL is not configured", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
api.ssl = false;
|
|
||||||
stub(Logger, "warn");
|
|
||||||
api.createServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
Logger.warn.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logs that the API is insecure", function() {
|
|
||||||
expect(Logger.warn).to.be.calledWithMatch("insecure connection");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets @server to @express", function() {
|
|
||||||
expect(api.server).to.be.eql(api.express);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#setupAuth", function() {
|
|
||||||
context("when auth.type is basic", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
api.auth = { type: "basic", user: "user", pass: "pass" };
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a basic auth middleware function", function() {
|
|
||||||
var fn = api.setupAuth(),
|
|
||||||
req = new MockRequest(),
|
|
||||||
res = new MockResponse(),
|
|
||||||
next = spy();
|
|
||||||
|
|
||||||
var auth = "Basic " + new Buffer("user:pass").toString("base64");
|
|
||||||
|
|
||||||
req.headers.authorization = auth;
|
|
||||||
|
|
||||||
fn(req, res, next);
|
|
||||||
expect(next).to.be.called;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("when auth is null", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
api.auth = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a pass-through middleware function", function() {
|
|
||||||
var fn = api.setupAuth(),
|
|
||||||
next = spy();
|
|
||||||
|
|
||||||
fn(null, null, next);
|
|
||||||
expect(next).to.be.called;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#listen", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
// we create a plain HTTP server to avoid a log message from Node
|
|
||||||
api.ssl = false;
|
|
||||||
api.createServer();
|
|
||||||
api.express.set("title", "Cylon API Server");
|
|
||||||
|
|
||||||
stub(api.server, "listen").yields();
|
|
||||||
stub(Logger, "info");
|
|
||||||
|
|
||||||
api.listen();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
api.server.listen.restore();
|
|
||||||
Logger.info.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens on the configured host and port", function() {
|
|
||||||
expect(api.server.listen).to.be.calledWith("3000", "127.0.0.1");
|
|
||||||
});
|
|
||||||
|
|
||||||
context("when the server is running", function() {
|
|
||||||
it("logs that it's online and listening", function() {
|
|
||||||
var online = "Cylon API Server is now online.",
|
|
||||||
listening = "Listening at http://127.0.0.1:3000";
|
|
||||||
|
|
||||||
expect(Logger.info).to.be.calledWith(online);
|
|
||||||
expect(Logger.info).to.be.calledWith(listening);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,130 +0,0 @@
|
||||||
/* jshint expr:true */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var basic = source("api/auth/basic");
|
|
||||||
|
|
||||||
var MockRequest = require("../../../support/mock_request"),
|
|
||||||
MockResponse = require("../../../support/mock_response");
|
|
||||||
|
|
||||||
describe("Basic Auth", function() {
|
|
||||||
var opts = { user: "user", pass: "pass" },
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
next;
|
|
||||||
|
|
||||||
basic = basic(opts);
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
req = new MockRequest();
|
|
||||||
res = new MockResponse();
|
|
||||||
next = spy();
|
|
||||||
|
|
||||||
var auth = new Buffer("user:pass", "utf8").toString("base64");
|
|
||||||
req.headers = { authorization: "Basic " + auth };
|
|
||||||
});
|
|
||||||
|
|
||||||
var checkUnauthorized = function() {
|
|
||||||
var result;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
result = basic(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns false", function() {
|
|
||||||
expect(result).to.be.falsy;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends a 401 error", function() {
|
|
||||||
expect(res.statusCode).to.be.eql(401);
|
|
||||||
expect(res.end).to.be.calledWith("Unauthorized");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var checkError = function() {
|
|
||||||
var result;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
result = basic(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("triggers next with an error", function() {
|
|
||||||
expect(next).to.be.called;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
context("with a valid request", function() {
|
|
||||||
var result;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
result = basic(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns true", function() {
|
|
||||||
expect(result).to.be.truthy;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't modify the response", function() {
|
|
||||||
expect(res.end).to.not.be.called;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("if the user/pass don't match", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
var auth = new Buffer("bad:wrong", "utf8").toString("base64");
|
|
||||||
req.headers = { authorization: "Basic " + auth };
|
|
||||||
});
|
|
||||||
|
|
||||||
checkUnauthorized();
|
|
||||||
});
|
|
||||||
|
|
||||||
context("if there is already an authorized user", function() {
|
|
||||||
var result;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
req.user = "user";
|
|
||||||
result = basic(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns true", function() {
|
|
||||||
expect(result).to.be.truthy;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't modify the response", function() {
|
|
||||||
expect(res.end).to.not.be.called;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context("if there are no authorization headers", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
delete req.headers.authorization;
|
|
||||||
});
|
|
||||||
|
|
||||||
checkUnauthorized();
|
|
||||||
});
|
|
||||||
|
|
||||||
context("the authorization type isn't Basic", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
var auth = new Buffer("user:pass", "utf8").toString("base64");
|
|
||||||
req.headers = { authorization: "Digest " + auth };
|
|
||||||
});
|
|
||||||
|
|
||||||
checkError();
|
|
||||||
});
|
|
||||||
|
|
||||||
context("the authorization header is missing content", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
req.headers = { authorization: "Basic" };
|
|
||||||
});
|
|
||||||
|
|
||||||
checkError();
|
|
||||||
});
|
|
||||||
|
|
||||||
context("if the authorization header isn't formatted correctly", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
var auth = new Buffer("user-pass", "utf8").toString("base64");
|
|
||||||
req.headers = { authorization: "Basic " + auth };
|
|
||||||
});
|
|
||||||
|
|
||||||
checkUnauthorized();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,211 +0,0 @@
|
||||||
/* jshint expr:true */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var Cylon = source("cylon"),
|
|
||||||
router = source("api/routes");
|
|
||||||
|
|
||||||
var MockRequest = require("../../support/mock_request"),
|
|
||||||
MockResponse = require("../../support/mock_response");
|
|
||||||
|
|
||||||
function findRoute(path) {
|
|
||||||
var routes = router.stack.filter(function(m) {
|
|
||||||
return m.regexp.test(path);
|
|
||||||
});
|
|
||||||
return routes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function findFinalHandler(path) {
|
|
||||||
var handlers = findRoute(path).route.stack.map(function(m) {
|
|
||||||
return m.handle;
|
|
||||||
});
|
|
||||||
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() {
|
|
||||||
var routes = [
|
|
||||||
["GET", "/"],
|
|
||||||
["GET", "/commands"],
|
|
||||||
["POST", "/commands/command"],
|
|
||||||
["GET", "/robots"],
|
|
||||||
["GET", "/robots/TestBot"],
|
|
||||||
["GET", "/robots/TestBot/commands"],
|
|
||||||
["POST", "/robots/TestBot/commands/cmd"],
|
|
||||||
["GET", "/robots/TestBot/devices"],
|
|
||||||
["GET", "/robots/TestBot/devices/ping"],
|
|
||||||
["GET", "/robots/TestBot/devices/ping/commands"],
|
|
||||||
["POST", "/robots/TestBot/devices/ping/commands/ping"],
|
|
||||||
["GET", "/robots/TestBot/connections"],
|
|
||||||
["GET", "/robots/TestBot/connections/loopback"]
|
|
||||||
];
|
|
||||||
|
|
||||||
routes.forEach(function(route) {
|
|
||||||
var method = route[0],
|
|
||||||
path = route[1];
|
|
||||||
|
|
||||||
it("defines a " + method + " route for " + path, function() {
|
|
||||||
expect(findRoute(path)).to.exist();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe("API commands", function() {
|
|
||||||
|
|
||||||
var req, res;
|
|
||||||
beforeEach(function() {
|
|
||||||
Cylon.commands.ping = function() { return "pong"; };
|
|
||||||
Cylon.commands.pingAsync = function() {
|
|
||||||
var promise = new MockPromise();
|
|
||||||
return promise.resolve("immediate pong");
|
|
||||||
};
|
|
||||||
req = new MockRequest();
|
|
||||||
res = new MockResponse();
|
|
||||||
res.status = function(statusCode) {
|
|
||||||
res.statusCode = statusCode;
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
req.device = {
|
|
||||||
name: "testDevice",
|
|
||||||
commands: {
|
|
||||||
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 = {
|
|
||||||
name: "fred",
|
|
||||||
commands: {
|
|
||||||
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: {
|
|
||||||
testDevice: req.device
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
afterEach(function() {
|
|
||||||
delete Cylon.commands.ping;
|
|
||||||
delete Cylon.commands.pingAsync;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns the list of MCP commands", function() {
|
|
||||||
res.json = function(obj){
|
|
||||||
expect(obj.commands).to.exist();
|
|
||||||
expect(obj.commands.length).to.equal(2);
|
|
||||||
expect(obj.commands[0]).to.equal("ping");
|
|
||||||
};
|
|
||||||
findFinalHandler("/commands")(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("invokes an MCP command", function() {
|
|
||||||
req.params = {command:"ping"};
|
|
||||||
res.json = function(obj){
|
|
||||||
expect(obj.result).to.equal("pong");
|
|
||||||
};
|
|
||||||
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() {
|
|
||||||
req.params = {robot: "fred"};
|
|
||||||
res.json = function(obj){
|
|
||||||
expect(obj.commands).to.exist();
|
|
||||||
expect(obj.commands.length).to.equal(2);
|
|
||||||
expect(obj.commands[0]).to.equal("speak");
|
|
||||||
};
|
|
||||||
findFinalHandler("/robots/fred/commands")(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("invokes a robot command", function() {
|
|
||||||
req.params = {robot: "fred", command:"speak"};
|
|
||||||
res.json = function(obj){
|
|
||||||
expect(obj.result).to.equal("ahem");
|
|
||||||
};
|
|
||||||
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() {
|
|
||||||
req.params = {robot: "fred", device: "testDevice" };
|
|
||||||
res.json = function(obj){
|
|
||||||
expect(obj.commands).to.exist();
|
|
||||||
expect(obj.commands.length).to.equal(2);
|
|
||||||
expect(obj.commands[0]).to.equal("announce");
|
|
||||||
};
|
|
||||||
var path = "/robots/fred/devices/testDevice/commands";
|
|
||||||
findFinalHandler(path)(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("invokes a device command", function() {
|
|
||||||
req.params = {robot: "fred", device: "testDevice", command:"announce"};
|
|
||||||
res.json = function(obj){
|
|
||||||
expect(obj.result).to.equal("im here");
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -4,8 +4,7 @@
|
||||||
var Cylon = source("cylon"),
|
var Cylon = source("cylon"),
|
||||||
Robot = source("robot");
|
Robot = source("robot");
|
||||||
|
|
||||||
var API = source("api"),
|
var Logger = source("logger"),
|
||||||
Logger = source("logger"),
|
|
||||||
Adaptor = source("adaptor"),
|
Adaptor = source("adaptor"),
|
||||||
Driver = source("driver"),
|
Driver = source("driver"),
|
||||||
Config = source("config");
|
Config = source("config");
|
||||||
|
@ -24,8 +23,8 @@ describe("Cylon", function() {
|
||||||
expect(Cylon.Driver).to.be.eql(Driver);
|
expect(Cylon.Driver).to.be.eql(Driver);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets @api_instance to null by default", function() {
|
it("sets @apiInstances to an empty array by default", function() {
|
||||||
expect(Cylon.api_instance).to.be.eql(null);
|
expect(Cylon.apiInstances).to.be.eql([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets @robots to an empty object by default", function() {
|
it("sets @robots to an empty object by default", function() {
|
||||||
|
@ -60,23 +59,37 @@ describe("Cylon", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#api", function() {
|
describe("#api", function() {
|
||||||
beforeEach(function() {
|
|
||||||
stub(API.prototype, "listen");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
API.prototype.listen.restore();
|
Cylon.apiInstances = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates a new API instance", function() {
|
context("with a provided API server and opts", function() {
|
||||||
Cylon.api();
|
var API, opts, instance;
|
||||||
expect(Cylon.api_instance).to.be.an.instanceOf(API);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("passes configuration to the API constructor", function() {
|
beforeEach(function() {
|
||||||
Cylon.config({ api: { port: "1234" }});
|
instance = { listen: spy() };
|
||||||
Cylon.api();
|
opts = { https: false };
|
||||||
expect(Cylon.api_instance.port).to.be.eql("1234");
|
API = stub().returns(instance);
|
||||||
|
|
||||||
|
Cylon.api(API, opts);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates an API instance", function() {
|
||||||
|
expect(API).to.be.calledWithNew;
|
||||||
|
expect(API).to.be.calledWith(opts);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes Cylon through to the instance as opts.mcp", function() {
|
||||||
|
expect(opts.mcp).to.be.eql(Cylon);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores the API instance in @apiInstances", function() {
|
||||||
|
expect(Cylon.apiInstances).to.be.eql([instance]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tells the API instance to start listening", function() {
|
||||||
|
expect(instance.listen).to.be.called;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var sinon = require('sinon'),
|
|
||||||
spy = sinon.spy,
|
|
||||||
stub = sinon.stub;
|
|
||||||
|
|
||||||
// A mock version of the http.ClientRequest class
|
|
||||||
var MockRequest = module.exports = function MockRequest(opts) {
|
|
||||||
if (opts == null) {
|
|
||||||
opts = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.url = "/";
|
|
||||||
|
|
||||||
this.headers = {};
|
|
||||||
|
|
||||||
for (var opt in opts) {
|
|
||||||
this[opt] = opts[opt];
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var sinon = require('sinon'),
|
|
||||||
spy = sinon.spy,
|
|
||||||
stub = sinon.stub;
|
|
||||||
|
|
||||||
// A mock version of http.ServerResponse to be used in tests
|
|
||||||
var MockResponse = module.exports = function MockResponse() {
|
|
||||||
this.end = spy();
|
|
||||||
|
|
||||||
this.headers = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
MockResponse.prototype.setHeader = function setHeader(name, value) {
|
|
||||||
this.headers[name] = value;
|
|
||||||
};
|
|
Loading…
Reference in New Issue