diff --git a/.gitignore b/.gitignore index 0ac3200..e452874 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ *.sublime* sketch test.html +.c9/ +*.log diff --git a/README.md b/README.md index 4ed830d..f4e0621 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,11 @@ A [lightblue](https://github.com/lightblue-platform) client written in Javascript. -Conceivably one day useful for: +Write... - Node.JS apps talking to a Lightblue REST service - Client side apps communicating with a server that forwards requests to a Lightblue REST service -At the moment this is really just a rough sketch of an idea and will change drastically. - -# Install +## Install `bower install lightblue.js --save` @@ -16,30 +14,61 @@ At the moment this is really just a rough sketch of an idea and will change dras `git clone https://github.com/alechenninger/lightblue.js.git` -# Usage - -It does still sort of work! It won't actually make any requests, but gives you an API for building the key components of the request: the HTTP method, the URL, and the request body. The idea is this information could then be used easily with either XMLHttpRequest, jQuery.ajax, Angular's $http service, or a Node.JS HTTP client. All it takes is a little glue code to tie the necessary components with one of the aforementioned common AJAX mechanisms. -Use browserify `require` or commonjs `define`, or just include dist/lightblue.min.js and use the namespace `lightblue`. +## Imports -## Imports: +### Vanilla.js ```javascript -// Plain old HTML - +// No module framework (use window.lightblue) + +``` -// NodeJS or Browserify -var lightblue = require("./lightblue.min.js"); +### Browserify (CommonJS) or RequireJS (AMD) -// CommonJS and RequireJS work too but I don't have an example +```js +// commonjs +var lightblue = require("lightblue"); + +// asynchronous module definition (amd) +require(["lightblue"], function(lightblue) { + ... +}); ``` -## Construct a find request: +Once you have a `lightblue` object, you can get a client: -```javascript +```js // Assumes /data and /metadata for data and metadata services respectively, // but you can override. var client = lightblue.getClient("http://my.lightblue.host.com/rest"); +``` + +### AngularJS +If angular is detected, a "lightblue" module will be registered with a +"lightblue" service as the client. + +```js +var app = angular.module("app", ["lightblue"]); + +app.config(["lightblueProvider", function(lightblueProvider) { + lightblueProvider.setHost("http://my.lightblue.com"); +}]); + +app.controller("ctrl", ["lightblue", function(lightblueClient) { + lightblueClient.data.find(...) + .then(...); +}]); +``` + +**At the moment you will also need to use the global "lightblue" namespace if +you want query builder API. So don't name your client variable `lightblue` +just yet. See +[issue #9](https://github.com/alechenninger/lightblue.js/issues/9).** + +## Construct a find request + +```javascript var field = lightblue.field; var find = client.data.find({ @@ -51,39 +80,6 @@ var find = client.data.find({ .and(field("age").greaterThan(4))), // No projection builder yet but it would be something like this: projection: include("*").recursively() -}); - -assertEquals("http://my.lightblue.host.com/rest/data/find/User/1.0.0", find.url); -assertEquals("post", find.method); -assertEquals({ - objectType: "User", - version: "1.0.0", - query: { - $or: [ - { - field: "username", - op: "$eq", - rvalue: "bob" - }, - { - $and: [ - { - field: "firstName", - op: "$eq", - rfield: "username" - }, - { - field: "age", - op: "$gt", - rvalue: 4 - } - ] - } - ] - }, - projection: { - field: "*", - recursive: true - } - }, find.body); +}) +.then(console.log); ``` diff --git a/lib/client.js b/lib/client.js new file mode 100644 index 0000000..98d269f --- /dev/null +++ b/lib/client.js @@ -0,0 +1,6 @@ +module.exports = LightblueClient; + +function LightblueClient(dataClient, metadataClient) { + this.data = dataClient; + this.metadata = metadataClient; +} \ No newline at end of file diff --git a/lib/clientutil.js b/lib/clientutil.js index 7c16577..c7ca054 100644 --- a/lib/clientutil.js +++ b/lib/clientutil.js @@ -22,8 +22,55 @@ exports.resolve = function() { .filter(notEmpty) .map(trimSlashes) .join("/"); -} +}; exports.isEmpty = function(s) { return typeof s === "undefined" || s === ""; -} \ No newline at end of file +}; + +function ifDefined(it, otherwise) { + return isDefined(it) ? it : otherwise; +} + +function isDefined(it) { + return typeof it !== "undefined"; +} + +function isObject(it) { + return typeof it === "object"; +} + +var assertArg = { + isInstance: function(arg, ctor, name) { + if (!(arg instanceof ctor)) { + throw new Error("Expected instanceof " + ctor + " but " + name + " was " + + isObject(arg) ? arg.constructor : "undefined"); + } + + return arg; + }, + isNotBlankString: function(arg, name) { + if (typeof arg !== "string") { + throw new Error("Expected a string but " + name + " was " + typeof arg); + } + + if (arg.trim().length === 0) { + throw new Error("Expected non-blank string but " + name + " was empty."); + } + + return arg; + }, + isTypeOf: function(arg, type, name) { + if (typeof arg !== type) { + throw new Error("Expected a typeof " + type + " but " + name + " was " + + typeof arg); + } + + return arg; + } +}; + +exports.assertArg = assertArg; +exports.ifDefined = ifDefined; +exports.isDefined = isDefined; +exports.isObject = isObject; \ No newline at end of file diff --git a/lib/data.js b/lib/data.js index 0057f4e..cea313c 100644 --- a/lib/data.js +++ b/lib/data.js @@ -1,38 +1,47 @@ -var resolve = require("./clientutil").resolve; -var RestRequest = require("./rest").RestRequest; +// TODO: Consider using url.resolve? +var resolve = require("./clientutil").resolve; +var http = require("./http"); + +var HttpRequest = http.HttpRequest; module.exports = LightblueDataClient; /** * Client for making requests against a Lightblue data endpoint. * @constructor - * @param {String} host - The URL where the rest data server is deployed. This + * @param {HttpClient} httpClient + * @param {String} host The URL where the rest data server is deployed. This * path will be the base for requests, like ${host}/find, so if the app is * deployed under a particular context (like /rest/data), this must be * included in the host. */ -function LightblueDataClient(host) { - this.host = host; +function LightblueDataClient(httpClient, host) { + this._httpClient = httpClient; + this._host = host; } +LightblueDataClient.prototype._execute = function(request) { + return this._httpClient.execute(request); +}; + LightblueDataClient.prototype.find = function(config) { - return new FindRequest(this.host, config); + return this._execute(new FindRequest(this._host, config)); }; LightblueDataClient.prototype.insert = function(config) { - return new InsertRequest(this.host, config); + return this._execute(new InsertRequest(this._host, config)); }; LightblueDataClient.prototype.update = function(config) { - return new UpdateRequest(this.host, config); + return this._execute(new UpdateRequest(this._host, config)); }; LightblueDataClient.prototype.save = function(config) { - return new SaveRequest(this.host, config); + return this._execute(new SaveRequest(this._host, config)); }; LightblueDataClient.prototype.delete = function(config) { - return new DeleteRequest(this.host, config); + return this._execute(new DeleteRequest(this._host, config)); }; /** @@ -40,6 +49,7 @@ LightblueDataClient.prototype.delete = function(config) { */ function FindRequest(host, config) { var url = resolve(host, "find", config.entity, config.version); + var body = { objectType: config.entity, version: config.version, @@ -57,10 +67,10 @@ function FindRequest(host, config) { body.range[1] = config.range.to || config.range[1]; } - RestRequest.call(this, "post", url, body); + HttpRequest.call(this, "post", url, body); } -FindRequest.prototype = Object.create(RestRequest.prototype); +FindRequest.prototype = Object.create(HttpRequest.prototype); FindRequest.prototype.constructor = FindRequest; /** @@ -68,6 +78,7 @@ FindRequest.prototype.constructor = FindRequest; */ function InsertRequest(host, config) { var url = resolve(host, "insert", config.entity, config.version); + var body = { objectType: config.entity, version: config.version, @@ -75,10 +86,10 @@ function InsertRequest(host, config) { projection: config.projection }; - RestRequest.call(this, "put", url, body); + HttpRequest.call(this, "put", url, body); } -InsertRequest.prototype = Object.create(RestRequest.prototype); +InsertRequest.prototype = Object.create(HttpRequest.prototype); InsertRequest.prototype.constructor = InsertRequest; /** @@ -86,18 +97,19 @@ InsertRequest.prototype.constructor = InsertRequest; */ function SaveRequest(host, config) { var url = resolve(host, "save", config.entity, config.version); + var body = { objectType: config.entity, version: config.version, data: config.data, upsert: config.upsert, projection: config.projection - } + }; - RestRequest.call(this, "post", url, body); + HttpRequest.call(this, "post", url, body); } -SaveRequest.prototype = Object.create(RestRequest.prototype); +SaveRequest.prototype = Object.create(HttpRequest.prototype); SaveRequest.prototype.constructor = SaveRequest; /** @@ -105,6 +117,7 @@ SaveRequest.prototype.constructor = SaveRequest; */ function UpdateRequest(host, config) { var url = resolve(host, "update", config.entity, config.version); + var body = { objectType: config.entity, version: config.version, @@ -113,10 +126,10 @@ function UpdateRequest(host, config) { projection: config.projection }; - RestRequest.call(this, "post", url, body); + HttpRequest.call(this, "post", url, body); } -UpdateRequest.prototype = Object.create(RestRequest.prototype); +UpdateRequest.prototype = Object.create(HttpRequest.prototype); UpdateRequest.prototype.constructor = UpdateRequest; /** @@ -124,14 +137,15 @@ UpdateRequest.prototype.constructor = UpdateRequest; */ function DeleteRequest(host, config) { var url = resolve(host, "delete", config.entity, config.version); + var body = { objectType: config.entity, version: config.version, query: config.query }; - RestRequest.call(this, "post", url, body); + HttpRequest.call(this, "post", url, body); } -DeleteRequest.prototype = Object.create(RestRequest.prototype); +DeleteRequest.prototype = Object.create(HttpRequest.prototype); DeleteRequest.prototype.constructor = DeleteRequest; \ No newline at end of file diff --git a/lib/http.js b/lib/http.js new file mode 100644 index 0000000..1e71b75 --- /dev/null +++ b/lib/http.js @@ -0,0 +1,25 @@ +var assertArg = require("./clientutil.js").assertArg; + +/** + * @constructor + * @param {String} method Http method, case insensitive. + * @param {Url} url + * @param {String=} body The request body. + */ +exports.HttpRequest = function(method, url, body) { + this.method = assertArg.isNotBlankString(method, "method"); + this.url = assertArg.isTypeOf(url, "string", "url"); + this.body = body; + + this.METHOD = this.method.toUpperCase(); +}; + +/** + * @interface HttpClient + */ + +/** + * @function + * @name HttpClient#execute + * @return {Promise} + */ \ No newline at end of file diff --git a/lib/lightblue.js b/lib/lightblue.js index 3dc6be5..b46bf83 100644 --- a/lib/lightblue.js +++ b/lib/lightblue.js @@ -1,6 +1,12 @@ -var DataClient = require("./data.js"); -var MetadataClient = require("./metadata.js"); -var query = require("./query.js"); +var DataClient = require("./data.js"); +var MetadataClient = require("./metadata.js"); +var LightblueClient = require("./client.js"); +var query = require("./query.js"); +var NodeHttpClient = require("./nodehttp.js"); + +// TODO: reorganize modules +// Install angular module if angular is present +require("./nglightblue.js"); var resolve = require("./clientutil.js").resolve; @@ -9,33 +15,45 @@ exports.getMetadataClient = getMetadataClient; exports.getClient = getClient; exports.field = query.field; -function LightblueClient(dataClient, metadataClient) { - this.data = dataClient; - this.metadata = metadataClient; -} - /** * Returns a LightblueDataClient. * @param {String} dataHost The full path for the base Lightblue data REST * context. * @return {LightblueDataClient} */ -function getDataClient(dataHost) { - return new DataClient(dataHost); +function getDataClient(dataHost, options) { + return new DataClient(getHttpClient(options), dataHost); } -function getMetadataClient(metadataHost) { - return new MetadataClient(metadataHost); +function getMetadataClient(metadataHost, options) { + return new MetadataClient(getHttpClient(options), metadataHost); } -function getClient(dataHost, metadataHost) { - if (typeof metadataHost === "undefined") { +/** + * Returns a LightblueClient. + * @param {String} dataHost The full path for the base Lightblue data service. + * @param {String=} metadataHost The full path for the base Lightblue metadata + * service. + * @param {Object=} options Configuration for http client. + * @param {HttpClient} http An http client. + */ +function getClient(dataHost, metadataHost, options) { + // metadataHost and options are optional + if (typeof metadataHost != "string") { + if (typeof metadataHost == "object") { + options = metadataHost; + } + dataHost = resolve(dataHost, "data"); metadataHost = resolve(dataHost, "metadata"); } - var dataClient = getDataClient(dataHost); - var metadataClient = getMetadataClient(metadataHost); + var dataClient = getDataClient(dataHost, options); + var metadataClient = getMetadataClient(metadataHost, options); return new LightblueClient(dataClient, metadataClient); +} + +function getHttpClient(options) { + return new NodeHttpClient(options); } \ No newline at end of file diff --git a/lib/metadata.js b/lib/metadata.js index d9be7cd..61c5f04 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -1,5 +1,10 @@ -var util = require("./clientutil"); -var RestRequest = require("./rest").RestRequest; +var util = require("./clientutil"); +var http = require("./http"); + +var isEmpty = util.isEmpty; +var resolve = util.resolve; + +var HttpRequest = http.HttpRequest; module.exports = LightblueMetadataClient; @@ -11,17 +16,22 @@ module.exports = LightblueMetadataClient; * deployed under a particular context (like /rest/data), this must be * included in the host. */ -function LightblueMetadataClient(host) { - this.host = host; +function LightblueMetadataClient(httpClient, host) { + this._httpClient = httpClient; + this._host = host; } +LightblueMetadataClient.prototype._execute = function(request) { + return this._httpClient.execute(request); +}; + /** * Request entity names of certain statuses. * @param {String[]} [statuses] An optional array of statuses to reduce the * search to. */ LightblueMetadataClient.prototype.getNames = function(statuses) { - return new NamesRequest(this.host, statuses); + return this._execute(new NamesRequest(this._host, statuses)); }; /** @@ -29,7 +39,7 @@ LightblueMetadataClient.prototype.getNames = function(statuses) { * @param {String} entityName */ LightblueMetadataClient.prototype.getVersions = function(entityName) { - return new VersionsRequest(this.host, entityName); + return this._execute(new VersionsRequest(this._host, entityName)); }; /** @@ -38,7 +48,7 @@ LightblueMetadataClient.prototype.getVersions = function(entityName) { * @param {String} version Version (required). */ LightblueMetadataClient.prototype.getMetadata = function(entityName, version) { - return new MetadataRequest(this.host, entityName, version); + return this._execute(new MetadataRequest(this._host, entityName, version)); }; /** @@ -48,7 +58,7 @@ LightblueMetadataClient.prototype.getMetadata = function(entityName, version) { * @param {String} [version] */ LightblueMetadataClient.prototype.getRoles = function(entityName, version) { - return new RolesRequest(this.host, entityName, version); + return this._execute(new RolesRequest(this._host, entityName, version)); }; function NamesRequest(host, statuses) { @@ -56,34 +66,34 @@ function NamesRequest(host, statuses) { ? "s=" + statuses.join(",") : ""; - RestRequest.call(this, "get", util.resolve(host, query)); + HttpRequest.call(this, "get", resolve(host, query)); } -NamesRequest.prototype = Object.create(RestRequest.prototype); +NamesRequest.prototype = Object.create(HttpRequest.prototype); NamesRequest.prototype.constructor = NamesRequest; function VersionsRequest(host, entityName) { - if (util.isEmpty(entityName)) { + if (isEmpty(entityName)) { throw new Error("entityName required for versions request.") } - RestRequest.call(this, "get", util.resolve(host, entityName)); + HttpRequest.call(this, "get", resolve(host, entityName)); } -VersionsRequest.prototype = Object.create(RestRequest.prototype); +VersionsRequest.prototype = Object.create(HttpRequest.prototype); VersionsRequest.prototype.constructor = VersionsRequest; function RolesRequest(host, entityName, version) { - RestRequest.call(this, "get", util.resolve(host, entityName, version, "roles")); + HttpRequest.call(this, "get", resolve(host, entityName, version, "roles")); } -RolesRequest.prototype = Object.create(RestRequest.prototype); +RolesRequest.prototype = Object.create(HttpRequest.prototype); RolesRequest.prototype.constructor = RolesRequest; function MetadataRequest(host, entityName, version) { - if (util.isEmpty(entityName) || util.isEmpty(version)) { + if (isEmpty(entityName) || isEmpty(version)) { throw new Error("entityName and version required for metadata request.") } - RestRequest.call(this, "get", util.resolve(host, entityName, version)); + HttpRequest.call(this, "get", resolve(host, entityName, version)); } \ No newline at end of file diff --git a/lib/nghttp.js b/lib/nghttp.js new file mode 100644 index 0000000..389a5ae --- /dev/null +++ b/lib/nghttp.js @@ -0,0 +1,13 @@ +module.exports = NgHttpClient; + +// TODO: Use $httpBackend instead? +function NgHttpClient($http) { + this._$http = $http; +} + +/** + * @param {RestRequest} req + */ +NgHttpClient.prototype.execute = function(req) { + return this._$http[req.method](req.url, req.body); +} \ No newline at end of file diff --git a/lib/nglightblue.js b/lib/nglightblue.js new file mode 100644 index 0000000..5400a92 --- /dev/null +++ b/lib/nglightblue.js @@ -0,0 +1,64 @@ +/* globals angular */ + +/** + * @file Entry-point for browserified module which sets up an angular module if + * angular global is present. + * + *
Usage: + *
+ * myModule.config("lightblueProvider", function(lightblueProvider) {
+ * lightblueProvider.setDataHost("http://my.lightblue.com/data");
+ * lightblueProvider.setMetadataHost("http://my.lightblue.com/metadata");
+ *
+ * // Above is equivalent to:
+ * // lightblueProvider.setHost("http://my.lightblue.com");
+ * }
+ *
+ * myModule.controller("foo", ["lightblue", "$scope", function(lightblue, $scope) {
+ * lightblue.data.find(...)
+ * .then(function(response) {
+ * $scope.myEntity = response.processed[0];
+ * });
+ * }
+ *
+ */
+
+var LightblueClient = require("./client.js");
+var DataClient = require("./data.js");
+var MetadataClient = require("./metadata.js");
+var NgHttpClient = require("./nghttp.js");
+var url = require("url");
+
+if (typeof angular == "object" && angular != null && "module" in angular) {
+ angular.module("lightblue", [])
+ .provider("lightblue", LightblueProvider);
+} else {
+ console.warn("nglightblue loaded but angular is not. Make sure angular is " +
+ "loaded first.");
+}
+
+function LightblueProvider() {
+ this._dataHost = "";
+ this._metadataHost = "";
+}
+
+LightblueProvider.prototype.setHost = function(host) {
+ this.setDataHost(url.resolve(host, "/data"));
+ this.setMetadataHost(url.resolve(host, "/metadata"));
+};
+
+LightblueProvider.prototype.setDataHost = function(host) {
+ this._dataHost = host;
+};
+
+LightblueProvider.prototype.setMetadataHost = function(host) {
+ this._metadataHost = host;
+};
+
+LightblueProvider.prototype.$get = ["$http", function($http) {
+ var httpClient = new NgHttpClient($http);
+ var dataClient = new DataClient(httpClient, this._dataHost);
+ var metadataClient = new MetadataClient(httpClient, this._metadataHost);
+
+ return new LightblueClient(dataClient, metadataClient);
+}];
\ No newline at end of file
diff --git a/lib/nodehttp.js b/lib/nodehttp.js
new file mode 100644
index 0000000..651c423
--- /dev/null
+++ b/lib/nodehttp.js
@@ -0,0 +1,91 @@
+var q = require("q");
+var http = require("http");
+var https = require("https");
+var url = require("url");
+var util = require("./clientutil");
+
+var isDefined = util.isDefined;
+var ifDefined = util.ifDefined;
+
+// TODO: What is convention / best practice with exports?
+module.exports = NodeHttpClient;
+
+/**
+ * @param {String} options.auth.user Username to use for basic auth
+ * @param {String} options.auth.pass Password to use for basic auth
+ */
+function NodeHttpClient(options) {
+ this._exec = http.request;
+ this._execSsl = https.request;
+
+ if (options instanceof Object) {
+ this._auth = ifDefined(options.auth, null);
+ // TODO: Support ssl certs
+ }
+}
+
+/**
+ * @param {HttpRequest} requestOpts
+ */
+NodeHttpClient.prototype.execute = function(requestOpts) {
+ var deferred = q.defer();
+ var promise = deferred.promise;
+
+ var exec = requestOpts.url.indexOf("https") === 0
+ ? this._execSsl
+ : this._exec;
+
+ var body = isDefined(requestOpts.body)
+ ? JSON.stringify(requestOpts.body)
+ : "";
+
+ var options = url.parse(requestOpts.url);
+ options.method = requestOpts.METHOD;
+ options.headers = {
+ "Content-Type": "application/json",
+ "Content-Length": body.length
+ };
+
+ if (this._auth != null) {
+ options.auth = this._auth;
+ }
+
+ var request = exec(options, function(res) {
+ var responseBody = "";
+
+ res.setEncoding("utf8");
+
+ res.on("data", function(chunk) {
+ responseBody += chunk;
+ });
+
+ res.on("end", function() {
+ // TODO: resolve with more than just body?
+ // TODO: error on lightblue error?
+ deferred.resolve(responseBody);
+ });
+
+ res.on("error", function(error) {
+ // TODO: what is this resolving with?
+ deferred.reject(error);
+ });
+ });
+
+ request.on("error", function(error) {
+ // TODO: reject with something consistent
+ deferred.reject(error);
+ });
+
+ request.write(body, "utf8");
+ request.end();
+
+ promise.success = function(callback) {
+ return promise.then(callback);
+ };
+
+ promise.error = function(callback) {
+ return promise.then(null, callback);
+ };
+
+ return promise;
+};
\ No newline at end of file
diff --git a/lib/rest.js b/lib/rest.js
deleted file mode 100644
index 4586f2b..0000000
--- a/lib/rest.js
+++ /dev/null
@@ -1,6 +0,0 @@
-exports.RestRequest = function(method, url, body) {
- this.method = (typeof method === "string") ? method.toLowerCase() : "";
- this.METHOD = this.method.toUpperCase();
- this.body = body;
- this.url = url;
-}
\ No newline at end of file
diff --git a/package.json b/package.json
index 0337def..0d60abd 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,28 @@
{
"name": "lightblue.js",
- "version": "0.4.1",
+ "version": "0.5.0-alpha",
"description": "A lightblue client for javascript.",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/alechenninger/lightblue.js.git"
+ },
+ "main": "lib/lightblue.js",
"scripts": {
- "package": "browserify lib/lightblue.js --standalone lightblue -d -p [minifyify --map lightblue.map.json --output dist/lightblue.map.json] > dist/lightblue.min.js",
- "watch": "watchify lib/lightblue.js --standalone lightblue -d -p [minifyify --map lightblue.map.json --output dist/lightblue.map.json] -o dist/lightblue.min.js",
+ "bundle": "browserify lib/lightblue.js --standalone lightblue -d -p [minifyify --map lightblue.map.json --output dist/lightblue.map.json] > dist/lightblue.min.js",
"test": "mocha test/*",
"tdd": "mocha -w test/*"
},
+ "dependencies": {
+ "q": "^1.0.0"
+ },
"devDependencies": {
+ "angular": "^1.3.15",
+ "benv": "^1.1.0",
"browserify": "^6.2.0",
"chai": "^1.9.2",
"minifyify": "^4.4.0",
"mocha": "^2.0.1",
+ "nock": "^1.7.0",
"watchify": "^2.1.0"
}
}
diff --git a/test/data.js b/test/data_test.js
similarity index 63%
rename from test/data.js
rename to test/data_test.js
index fb704f0..0df07d0 100644
--- a/test/data.js
+++ b/test/data_test.js
@@ -1,25 +1,36 @@
+/* globals describe it */
var expect = require("chai").expect;
-var client = require("../lib/lightblue").getDataClient;
+var DataClient = require("../lib/data.js");
describe("LightblueDataClient", function() {
+
+ // Captures request sent to execute(req)
+ var mockHttpClient = {
+ execute: function(request) {
+ this.request = request;
+ return "response";
+ }
+ };
+
+ var dataClient = new DataClient(mockHttpClient, "myhost.com");
+
describe("find", function() {
it("should construct urls like ${host}/find/${entity}/${version}", function() {
- var findRequest = client("myhost.com").find(validFindConfig({
+ dataClient.find(validFindConfig({
entity: "myEntity",
version: "myVersion"
}));
- expect(findRequest.url).to.match(new RegExp("^myhost.com/find/myEntity/myVersion/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/find/myEntity/myVersion/?$"));
});
it("should construct urls like ${host}/find/${entity} when version is undefined", function () {
- var config = validFindConfig({
- entity: "myEntity"});
+ var config = validFindConfig({entity: "myEntity"});
delete config.version;
- var findRequest = client("myhost.com").find(config);
+ dataClient.find(config);
- expect(findRequest.url).to.match(new RegExp("^myhost.com/find/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/find/myEntity/?$"));
});
it("should construct urls like ${host}/find/${entity} when version is empty string", function () {
@@ -28,15 +39,15 @@ describe("LightblueDataClient", function() {
version: ""
});
- var findRequest = client("myhost.com").find(config);
+ dataClient.find(config);
- expect(findRequest.url).to.match(new RegExp("^myhost.com/find/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/find/myEntity/?$"));
});
it("should use POST", function() {
- var findRequest = client("myhost.com").find(validFindConfig());
+ dataClient.find(validFindConfig());
- expect(findRequest.method).to.equal("post");
+ expect(mockHttpClient.request.method).to.equal("post");
});
it("should construct request body with objectType, version, query and projection", function() {
@@ -54,8 +65,9 @@ describe("LightblueDataClient", function() {
projection: expectedBody.projection
};
- var findRequest = client("myhost.com").find(config);
- expect(findRequest.body).to.deep.equal(expectedBody);
+ dataClient.find(config);
+
+ expect(mockHttpClient.request.body).to.deep.equal(expectedBody);
});
it("should construct request body with objectType, version, query, projection, sort, and range", function() {
@@ -77,37 +89,44 @@ describe("LightblueDataClient", function() {
range: expectedBody.range
};
- var findRequest = client("myhost.com").find(config);
- expect(findRequest.body).to.deep.equal(expectedBody);
+ dataClient.find(config);
+
+ expect(mockHttpClient.request.body).to.deep.equal(expectedBody);
});
it("should allow expressing range as an object with from and to properties", function() {
- var findRequest = client("myhost.com").find(validFindConfig({
+ dataClient.find(validFindConfig({
range: {from: 1, to: 10}
}));
- expect(findRequest.body.range[0]).to.equal(1);
- expect(findRequest.body.range[1]).to.equal(10);
+ expect(mockHttpClient.request.body.range[0]).to.equal(1);
+ expect(mockHttpClient.request.body.range[1]).to.equal(10);
+ });
+
+ it("should return result of http client execute", function() {
+ var response = dataClient.find(validFindConfig());
+
+ expect(response).to.equal("response");
});
});
describe("insert", function() {
it("should construct urls like ${host}/insert/${entity}/${version}", function() {
- var insertRequest = client("myhost.com").insert(validInsertConfig({
+ dataClient.insert(validInsertConfig({
entity: "myEntity",
version: "1.2.0"
}));
- expect(insertRequest.url).to.match(new RegExp("^myhost.com/insert/myEntity/1.2.0/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/insert/myEntity/1.2.0/?$"));
});
it("should construct urls like ${host}/insert/${entity} when version is undefined", function () {
var config = validInsertConfig({entity: "myEntity"});
delete config.version;
- var insertRequest = client("myhost.com").insert(config);
+ dataClient.insert(config);
- expect(insertRequest.url).to.match(new RegExp("^myhost.com/insert/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/insert/myEntity/?$"));
});
it("should construct urls like ${host}/insert/${entity} when version is empty string", function () {
@@ -116,15 +135,15 @@ describe("LightblueDataClient", function() {
version: ""
});
- var insertRequest = client("myhost.com").insert(config);
+ dataClient.insert(config);
- expect(insertRequest.url).to.match(new RegExp("^myhost.com/insert/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/insert/myEntity/?$"));
});
it("should use PUT", function() {
- var insertRequest = client("myhost.com").insert(validInsertConfig());
+ dataClient.insert(validInsertConfig());
- expect(insertRequest.method).to.equal("put");
+ expect(mockHttpClient.request.method).to.equal("put");
});
it("should include request data", function() {
@@ -142,29 +161,35 @@ describe("LightblueDataClient", function() {
projection: expectedBody.projection
};
- var insertRequest = client("myhost.com").insert(config);
+ dataClient.insert(config);
- expect(insertRequest.body).to.deep.equal(expectedBody);
+ expect(mockHttpClient.request.body).to.deep.equal(expectedBody);
+ });
+
+ it("should return result of http client execute", function() {
+ var response = dataClient.insert(validInsertConfig());
+
+ expect(response).to.equal("response");
});
});
describe("update", function() {
it("should construct urls like ${host}/update/${entity}/${version}", function() {
- var updateRequest = client("myhost.com").update(validUpdateConfig({
+ dataClient.update(validUpdateConfig({
entity: "myEntity",
version: "1.2.0"
}));
- expect(updateRequest.url).to.match(new RegExp("^myhost.com/update/myEntity/1.2.0/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/update/myEntity/1.2.0/?$"));
});
it("should construct urls like ${host}/update/${entity} when version is undefined", function () {
var config = validUpdateConfig({entity: "myEntity"});
delete config.version;
- var updateRequest = client("myhost.com").update(config);
+ dataClient.update(config);
- expect(updateRequest.url).to.match(new RegExp("^myhost.com/update/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/update/myEntity/?$"));
});
it("should construct urls like ${host}/update/${entity} when version is empty string", function () {
@@ -173,15 +198,15 @@ describe("LightblueDataClient", function() {
version: ""
});
- var updateRequest = client("myhost.com").update(config);
+ dataClient.update(config);
- expect(updateRequest.url).to.match(new RegExp("^myhost.com/update/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/update/myEntity/?$"));
});
it("should use POST", function() {
- var updateRequest = client("myhost.com").update(validUpdateConfig());
+ dataClient.update(validUpdateConfig());
- expect(updateRequest.method).to.equal("post");
+ expect(mockHttpClient.request.method).to.equal("post");
});
it("should include request data", function() {
@@ -201,29 +226,35 @@ describe("LightblueDataClient", function() {
projection: expectedBody.projection
};
- var updateRequest = client("myhost.com").update(config);
+ dataClient.update(config);
- expect(updateRequest.body).to.deep.equal(expectedBody);
+ expect(mockHttpClient.request.body).to.deep.equal(expectedBody);
+ });
+
+ it("should return result of http client execute", function() {
+ var response = dataClient.update(validUpdateConfig());
+
+ expect(response).to.equal("response");
});
});
describe("save", function() {
it("should construct urls like ${host}/save/${entity}/${version}", function() {
- var saveRequest = client("myhost.com").save(validSaveConfig({
+ dataClient.save(validSaveConfig({
entity: "myEntity",
version: "1.2.0"
}));
- expect(saveRequest.url).to.match(new RegExp("^myhost.com/save/myEntity/1.2.0/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/save/myEntity/1.2.0/?$"));
});
it("should construct urls like ${host}/save/${entity} when version is undefined", function () {
var config = validSaveConfig({entity: "myEntity"});
delete config.version;
- var saveRequest = client("myhost.com").save(config);
+ dataClient.save(config);
- expect(saveRequest.url).to.match(new RegExp("^myhost.com/save/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/save/myEntity/?$"));
});
it("should construct urls like ${host}/save/${entity} when version is empty string", function () {
@@ -232,15 +263,15 @@ describe("LightblueDataClient", function() {
version: ""
});
- var saveRequest = client("myhost.com").save(config);
+ dataClient.save(config);
- expect(saveRequest.url).to.match(new RegExp("^myhost.com/save/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/save/myEntity/?$"));
});
it("should use POST", function() {
- var saveRequest = client("myhost.com").save(validSaveConfig());
+ dataClient.save(validSaveConfig());
- expect(saveRequest.method).to.equal("post");
+ expect(mockHttpClient.request.method).to.equal("post");
});
it("should include request data", function() {
@@ -260,29 +291,35 @@ describe("LightblueDataClient", function() {
upsert: expectedBody.upsert
};
- var saveRequest = client("myhost.com").save(config);
+ dataClient.save(config);
- expect(saveRequest.body).to.deep.equal(expectedBody);
+ expect(mockHttpClient.request.body).to.deep.equal(expectedBody);
+ });
+
+ it("should return result of http client execute", function() {
+ var response = dataClient.save(validSaveConfig());
+
+ expect(response).to.equal("response");
});
});
describe("delete", function() {
it("should construct urls like ${host/data/delete/${entity}/${version}", function() {
- var deleteRequest = client("myhost.com").delete(validDeleteConfig({
+ dataClient.delete(validDeleteConfig({
entity: "myEntity",
version: "1.2.0"
}));
- expect(deleteRequest.url).to.match(new RegExp("^myhost.com/delete/myEntity/1.2.0/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/delete/myEntity/1.2.0/?$"));
});
it("should construct urls like ${host}/delete/${entity} when version is undefined", function () {
var config = validDeleteConfig({entity: "myEntity"});
delete config.version;
- var deleteRequest = client("myhost.com").delete(config);
+ dataClient.delete(config);
- expect(deleteRequest.url).to.match(new RegExp("^myhost.com/delete/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/delete/myEntity/?$"));
});
it("should construct urls like ${host}/delete/${entity} when version is empty string", function () {
@@ -291,15 +328,15 @@ describe("LightblueDataClient", function() {
version: ""
});
- var deleteRequest = client("myhost.com").delete(config);
+ dataClient.delete(config);
- expect(deleteRequest.url).to.match(new RegExp("^myhost.com/delete/myEntity/?$"));
+ expect(mockHttpClient.request.url).to.match(new RegExp("^myhost.com/delete/myEntity/?$"));
});
it("should use POST", function() {
- var deleteRequest = client("myhost.com").delete(validDeleteConfig());
+ dataClient.delete(validDeleteConfig());
- expect(deleteRequest.method).to.equal("post");
+ expect(mockHttpClient.request.method).to.equal("post");
});
it("should include request data", function() {
@@ -307,7 +344,7 @@ describe("LightblueDataClient", function() {
objectType: "wizard",
version: "1.0.0",
query: {field: "name", op: "=", rvalue: "Voldemort"}
- }
+ };
var config = {
entity: expectedBody.objectType,
@@ -315,12 +352,17 @@ describe("LightblueDataClient", function() {
query: expectedBody.query
};
- var deleteRequest = client("myhost.com").delete(config);
+ dataClient.delete(config);
- expect(deleteRequest.body).to.deep.equal(expectedBody);
+ expect(mockHttpClient.request.body).to.deep.equal(expectedBody);
+ });
+
+ it("should return result of http client execute", function() {
+ var response = dataClient.delete(validDeleteConfig());
+
+ expect(response).to.equal("response");
});
});
-
});
function validFindConfig(edit) {
diff --git a/test/nghttp_test.js b/test/nghttp_test.js
new file mode 100644
index 0000000..a9326d6
--- /dev/null
+++ b/test/nghttp_test.js
@@ -0,0 +1,22 @@
+/* globals describe it */
+var expect = require("chai").expect;
+var NgHttpClient = require("../lib/nghttp.js");
+var HttpRequest = require("../lib/http.js").HttpRequest;
+
+describe("NgHttpClient", function() {
+ it("performs request with correct method, url, and body", function() {
+ var mock$Http = {
+ post: function(url, body) {
+ this.url = url;
+ this.body = body;
+ }
+ };
+
+ var ngHttp = new NgHttpClient(mock$Http);
+
+ ngHttp.execute(new HttpRequest("post", "http://foo", {foo: "bar"}));
+
+ expect(mock$Http.url).to.equal("http://foo");
+ expect(mock$Http.body).to.deep.equal({foo: "bar"});
+ });
+});
\ No newline at end of file
diff --git a/test/nglightblue_test.js b/test/nglightblue_test.js
new file mode 100644
index 0000000..ee9b294
--- /dev/null
+++ b/test/nglightblue_test.js
@@ -0,0 +1,60 @@
+/* globals describe it beforeEach afterEach angular */
+var benv = require("benv");
+var expect = require("chai").expect;
+
+describe("nglightblue", function() {
+ var testModule;
+
+ describe("with angular frontend", function() {
+ beforeEach("setup browser environment", function(done) {
+ benv.setup(function() {
+ benv.expose({
+ angular: benv.require("../node_modules/angular/angular.js", "angular")
+ });
+ benv.require("../lib/nglightblue.js");
+ done();
+ });
+ });
+
+ beforeEach("create angular module", function() {
+ testModule = angular.module("test", ["lightblue"]);
+ });
+
+ afterEach("remove browser environment", function() {
+ benv.teardown();
+ });
+
+ it("registers a lightblueProvider", function(done) {
+ testModule.config(["lightblueProvider", function(lightblueProvider) {
+ expect(lightblueProvider).not.to.be.null;
+ done();
+ }]);
+
+ angular.bootstrap(document, ["test"]);
+ });
+
+ it("uses $http", function(done) {
+ var mock$Http = "I'm not really $http ssshhhh";
+
+ testModule.factory("$http", function() {
+ return mock$Http;
+ });
+
+ testModule.config(["lightblueProvider", function(lightblueProvider) {
+ lightblueProvider.$get = ["$http", function($http) {
+ expect($http).to.equal(mock$Http);
+ done();
+ }];
+ }]);
+
+ testModule.run(["lightblue", function(lightblue) {}]);
+
+ angular.bootstrap(document, ["test"]);
+ });
+ });
+
+ it("doesn't get mad if angular is not loaded", function() {
+ require("../lib/nglightblue.js");
+ // If require does not fail, we're good
+ });
+});
\ No newline at end of file
diff --git a/test/nodehttp_test.js b/test/nodehttp_test.js
new file mode 100644
index 0000000..b43634f
--- /dev/null
+++ b/test/nodehttp_test.js
@@ -0,0 +1,58 @@
+/* globals describe it before */
+var NodeHttpClient = require("../lib/nodehttp.js");
+var HttpRequest = require("../lib/http.js").HttpRequest;
+var nock = require("nock");
+var expect = require("chai").expect;
+
+describe("NodeHttpClient", function() {
+ var client;
+
+ before(function() {
+ client = new NodeHttpClient();
+ });
+
+ it("makes a get request against url with path", function(done) {
+ nock("http://foo.com")
+ .get("/api")
+ .reply(200);
+
+ client.execute(new HttpRequest("get", "http://foo.com/api"))
+ .then(function(response) {
+ done();
+ });
+ });
+
+ it("makes a get requst against url without path", function(done) {
+ nock("http://foo.com")
+ .get("/")
+ .reply(200);
+
+ client.execute(new HttpRequest("get", "http://foo.com"))
+ .then(function(response) {
+ done();
+ });
+ });
+
+ it("makes a post request with json body", function(done) {
+ nock("http://foo.com")
+ .post("/", {foo: "bar"})
+ .reply(200);
+
+ client.execute(new HttpRequest("post", "http://foo.com", {foo: "bar"}))
+ .then(function(response) {
+ done();
+ });
+ });
+
+ it("makes request and completes promise with success response body", function(done) {
+ nock("http://foo.com")
+ .get("/")
+ .reply(200, "it works");
+
+ client.execute(new HttpRequest("get", "http://foo.com"))
+ .then(function(response) {
+ expect(response).to.equal("it works");
+ done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/query.js b/test/query_test.js
similarity index 100%
rename from test/query.js
rename to test/query_test.js