Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial import

  • Loading branch information...
commit 0aa6664e1477e8dcfab032ca6f01cf050d10e9a6 0 parents
@raoulmillais authored
5 .gitignore
@@ -0,0 +1,5 @@
+/node_modules/
+.eprj
+.monitor
+*.css
+*.gz
4 .gitmodules
@@ -0,0 +1,4 @@
+[submodule "vendor/node-xml2js"]
+ path = vendor/node-xml2js
+ url = git://github.com/raoulmillais/node-xml2js.git
+
10 LICENSE
@@ -0,0 +1,10 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 Raoul Millais
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
41 README.md
@@ -0,0 +1,41 @@
+WHAT IS THIS?
+=============
+
+It is:
+
+* A library for wrapping 3rd party RESTful APIs based on a JSON schema file in
+nodejs.
+* A serverside javascript wrapper round the 7digital API
+
+USAGE
+=====
+
+You will need to manually install the dependencies. This can be done easily using npm,
+check the packages.json for the most recent dependencies.
+
+ npm link .
+
+or (for npm >=1.0)
+
+ npm install
+
+You will also need to make sure you have pulled in the gitsubmodules.
+
+ git submodule update --init --recursive
+
+The 7digital wrapper currently only supports the portions of the API which
+do not require OAUTH authentication.
+
+To run the tests:
+
+ cd spec && node specs.js
+
+To run the json proxy and API explorer:
+
+ node server.js
+ curl http://localhost:3000/artist/details?artistid=1
+
+Please note, I have only really tested this on recent versions of node (0.3.1 -
+0.4.7 at the time of writing), so please file an issue if you have problems with
+other versions of node.
+
88 lib/api.js
@@ -0,0 +1,88 @@
+"use strict";
+
+/**
+ * Module dependencies.
+ */
+var APIBase = require("apibase").APIBase,
+ sys = require("sys"),
+ fs = require("fs"),
+ StringUtils = require("stringutils").StringUtils,
+ FnUtils = require("./fnutils").FnUtils,
+ api;
+
+/**
+ * API
+ *
+ * Creates a new RESTful API wrapper from a schema definition
+ *
+ * @constructor
+ * @param {String} schema
+ * @param {String} oauthkey
+ * @param {String} oauthsecret
+ */
+ function Api(schema, oauthkey /*optional*/, oauthsecret /*optional*/) {
+ this.schema = schema;
+
+ for(var prop in schema.resources) {
+ if (schema.resources.hasOwnProperty(prop)) {
+ var resourceName = schema.resources[prop].resource;
+
+ var resourceConstructor = (function(resourceName) {
+ return function Resource() {
+ this.resourceUrlSlug = resourceName.toLowerCase();
+ };
+ })(resourceName);
+
+ resourceConstructor.prototype = new APIBase(resourceName, schema.host, schema.version, oauthkey, oauthsecret);
+
+ schema.resources[prop].actions.forEach(FnUtils.bind(resourceConstructor, resourceConstructor.prototype.buildApiCall));
+ this[prop] = resourceConstructor;
+ }
+ }
+}
+
+Api.prototype.getSchema = function() {
+ return this.schema;
+}
+
+Api.prototype.getResourceClassName = function(resource) {
+ for (var resourceName in this.schema.resources) {
+ if (this.schema.resources.hasOwnProperty(resourceName)) {
+ if (this.schema.resources[resourceName].resource.toLowerCase() === resource.toLowerCase()) {
+ return resourceName;
+ }
+ }
+ }
+
+ return '';
+}
+
+Api.prototype.getActionMethodName = function(resourceClassName, action) {
+ var actionMethodName = '';
+
+ this.schema.resources[resourceClassName].actions.forEach(function(act) {
+ if (StringUtils.isAString(act) && act.toLowerCase() === action.toLowerCase()) {
+ actionMethodName = 'get' + StringUtils.capitalize(act);
+ }
+ else if (act.apiCall && act.apiCall.toLowerCase() === action.toLowerCase()) {
+ actionMethodName = act.methodName;
+ }
+ });
+
+ return actionMethodName;
+}
+
+Api.build = function(schema, oauthkey /*optional*/, oauthsecret /*optional*/) {
+ return new Api(schema, oauthkey, oauthsecret);
+}
+
+Api.buildFromFile = function(schemaPath, oauthkey /*optional*/, oauthsecret /*optional*/) {
+ // Blocking here but we should only ever do this once and the library is
+ // unusable until it has read the schema.
+ var schemaText = fs.readFileSync(schemaPath),
+ schema = JSON.parse(schemaText.toString());
+
+ return new Api(schema, oauthkey, oauthsecret);
+}
+
+exports.Api = Api;
68 lib/api.json
@@ -0,0 +1,68 @@
+{
+ "host": "api.7digital.com",
+ "version": "1.2",
+ "resources":
+ {
+ "Releases":
+ {
+ "resource": "release",
+ "actions":
+ [
+ { "apiCall": "byDate", "methodName": "getByDate" },
+ { "apiCall": "details", "methodName": "getDetails" },
+ { "apiCall": "chart", "methodName": "getChart" },
+ { "apiCall": "recommend", "methodName": "getRecommendations" },
+ { "apiCall": "search", "methodName": "search" },
+ { "apiCall": "tracks", "methodName": "getTracks" },
+ { "apiCall": "tags", "methodName": "getTags" },
+ { "apiCall": "bytag/new", "methodName": "getNewByTags" },
+ { "apiCall": "bytag/top", "methodName": "getTopByTags" }
+ ]
+ },
+ "Artists":
+ {
+ "resource": "artist",
+ "actions":
+ [
+ { "apiCall": "browse", "methodName": "browse" },
+ { "apiCall": "chart", "methodName": "getChart" },
+ { "apiCall": "details", "methodName": "getDetails" },
+ { "apiCall": "releases", "methodName": "getReleases" },
+ { "apiCall": "search", "methodName": "search" },
+ { "apiCall": "topTracks", "methodName": "getTopTracks" },
+ { "apiCall": "tags", "methodName": "getTags" },
+ { "apiCall": "bytag/top", "methodName": "getTopByTags"}
+ ]
+ },
+ "Tracks":
+ {
+ "resource": "track",
+ "actions":
+ [
+ { "apiCall": "chart", "methodName": "getChart" },
+ { "apiCall": "details", "methodName": "getDetails" },
+ { "apiCall": "preview", "methodName": "getPreview" },
+ { "apiCall": "search", "methodName": "search" }
+ ]
+ },
+ "Tags":
+ {
+ "resource": "tag",
+ "actions":
+ [
+ { "apiCall": "", "methodName": "all" }
+ ]
+ },
+ "Basket":
+ {
+ "resource": "basket",
+ "actions":
+ [
+ { "apiCall": "", "methodName": "get" },
+ { "apiCall": "addItem", "methodName": "addItem" },
+ { "apiCall": "removeItem", "methodName": "removeItem" },
+ { "apiCall": "create", "methodName": "create" }
+ ]
+ }
+ }
+}
134 lib/apibase.js
@@ -0,0 +1,134 @@
+"use strict";
+
+/**
+ * Module dependencies.
+ */
+var http = require('http'),
+ querystring = require('querystring'),
+ sys = require('sys'),
+ xml2js = require('../vendor/node-xml2js/lib/xml2js'),
+ FnUtils = require('./fnutils').FnUtils,
+ DateUtils = require('./dateutils').DateUtils,
+ StringUtils = require('./stringutils').StringUtils,
+ USER_AGENT = 'Node.js HTTP Client';
+
+/**
+ * APIBase
+ *
+ * Creates a new RESTful API base class for accessing a resource at the given
+ * host with optional predefined oauth credentials.
+ *
+ * @constructor
+ */
+var APIBase = function(name, host, version, oauthkey /* optional */, oauthsecret /* optional */) {
+ // TODO: Looks like we don't need the name parameter here anymore
+ this.host = host;
+ this.version = version;
+
+ if (oauthkey) {
+ this.oauth_consumer_key = oauthkey;
+ }
+
+ if (oauthsecret) {
+ this.oauth_consumer_secret = oauthsecret;
+ }
+
+ this.format = 'text';
+}
+
+/*
+ * Instance methods
+ */
+APIBase.prototype.parseResponse = function(callback, response) {
+ var parser = new xml2js.Parser();
+
+ parser.addListener('end', function(result) {
+ response.json = result;
+ if (result.status == 'error') {
+ callback(result.error);
+ }
+ else {
+ callback(null, response);
+ }
+ });
+
+ parser.parseString(response.xml + '');
+}
+
+APIBase.prototype.doGetRequest = function(action, data, callback) {
+ var requestUrl = '/' + this.version + '/' + this.resourceUrlSlug;
+
+ if (action != '') {
+ requestUrl += '/' + action;
+ }
+
+ data = data || {};
+ for (var prop in data) {
+ if (data.hasOwnProperty(prop)) {
+ if (DateUtils.isDate(data[prop])) {
+ data[prop] = DateUtils.toYYYYMMDD(data[prop]);
+ }
+ }
+ }
+
+ // Default the oauth credentials if not supplied and we have them
+ if (!data.oauth_consumer_key && this.oauth_consumer_key) {
+ data.oauth_consumer_key = this.oauth_consumer_key;
+ }
+
+ if (!data.oauth_consumer_secret && this.oauth_consumer_secret) {
+ data.oauth_consumer_secret = this.oauth_consumer_secret;
+ }
+
+ var qs = querystring.stringify(data);
+ requestUrl += '?' + qs;
+
+ var host = this.host,
+ headers = {
+ 'host': host,
+ 'User-Agent' : USER_AGENT
+ },
+ httpClient = http.createClient(80, host),
+ apiRequest = httpClient.request('GET', requestUrl, headers);
+
+ apiRequest.on('response', function handleResponse(response) {
+ var responseBody = {
+ xml: ""
+ };
+ response.setEncoding("utf8");
+
+ response.on("data", function bufferData(chunk) {
+ responseBody.xml += chunk;
+ });
+
+ response.on("end", function endResponse() {
+ callback(responseBody);
+ });
+ });
+
+ apiRequest.on('error', function logError(data) {
+ sys.puts('Error fetching [' + url + ']. Body:\n' + data);
+ });
+
+ apiRequest.end();
+}
+
+APIBase.prototype.buildApiCall = function(apiCallDefinition) {
+ var fnName;
+
+ if (typeof apiCallDefinition === 'string') {
+ fnName = 'get' + StringUtils.capitalize(apiCallDefinition);
+ } else {
+ fnName = apiCallDefinition.methodName;
+ apiCallDefinition = apiCallDefinition.apiCall;
+ }
+
+ this.prototype[fnName] = function(params, callback) {
+ this.doGetRequest(apiCallDefinition, params, FnUtils.curry(this.parseResponse, callback));
+ };
+}
+
+/*
+ * Exports
+ */
+exports.APIBase = APIBase;
26 lib/dateutils.js
@@ -0,0 +1,26 @@
+"use strict";
+
+exports.DateUtils = {
+
+ padComponent: function(component) {
+ return component <= 9 ? '0' + component : '' + component;
+ },
+
+ toYYYYMMDD: function(theDate) {
+ var month = this.padComponent(theDate.getMonth() + 1);
+
+ return theDate.getUTCFullYear()
+ + month
+ + this.padComponent(theDate.getDate())
+ + '';
+ },
+
+ isDate: function(obj) {
+ return (typeof(obj) === 'date')
+ ? true
+ : (typeof(obj) === 'object')
+ ? obj.constructor.toString().match(/date/i) !== null
+ : false;
+ }
+
+};
20 lib/fnutils.js
@@ -0,0 +1,20 @@
+"use strict";
+
+exports.FnUtils = {
+
+ curry: function(fn) {
+ var slice = Array.prototype.slice,
+ args = slice.apply(arguments, [1]);
+
+ return function() {
+ return fn.apply(null, args.concat(slice.apply(arguments)));
+ }
+ },
+
+ bind: function(ctx, fn) {
+ return function() {
+ fn.apply(ctx, arguments);
+ };
+ }
+
+};
13 lib/stringutils.js
@@ -0,0 +1,13 @@
+"use strict";
+
+exports.StringUtils = {
+
+ capitalize: function(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+ },
+
+ isAString: function(str) {
+ return typeof str === 'string';
+ }
+
+};
36 package.json
@@ -0,0 +1,36 @@
+{
+ "name": "7digital-api",
+ "version": "0.1.0",
+ "description" : "Simple 7digital API wrapper for nodeJS",
+ "homepage": [
+ "https://github.com/raoulmillais/node-7digital-api"
+ ],
+ "repository": {
+ "type" : "git",
+ "url" : "https://github.com/raoulmillais/node-7digital-api.git"
+ },
+ "keywords": [
+ "api",
+ "nodejs",
+ "7digital"
+ ],
+ "author": "Raoul Millais",
+ "maintainers": "Raoul Millais",
+ "licenses": [
+ "MIT"
+ ],
+ "engines": [ "node >= 0.4.1" ],
+ "modules": {
+ "api": "./lib/api"
+
+ },
+ "dependencies": {
+ "sax": "0.1.2",
+ "xml2js": ">= 0.1.5",
+ "oauth": "0.9.0",
+ "jasmine-node": ">= 1.0.0rc1",
+ "step": ">=0.0.3",
+ "eyes": ">=0.1.6"
+ },
+ "main": "./lib/api"
+}
13 spec/helpers/Matchers.js
@@ -0,0 +1,13 @@
+beforeEach(function() {
+
+ this.addMatchers({
+ toBeAFunction: function() {
+ return typeof this.actual === 'function';
+ },
+
+ toHavePrototypeOf: function(proto) {
+ return this.actual.prototype = proto.prototype;
+ }
+ });
+
+});
39 spec/specs.js
@@ -0,0 +1,39 @@
+var jasmine = require('jasmine-node'),
+ sys = require('sys');
+
+require.paths.unshift('../lib/');
+require.paths.unshift('../support/');
+
+for(var key in jasmine) {
+ global[key] = jasmine[key];
+}
+
+var isVerbose = true,
+ showColors = true,
+ folderName = '/suites/unit';
+
+process.argv.forEach(function(arg){
+ switch(arg) {
+ case '--color':
+ showColors = true;
+ break;
+ case '--noColor':
+ showColors = false;
+ break;
+ case '--quiet':
+ isVerbose = false;
+ break;
+ case '--integration':
+ folderName = '/suites/integration';
+ }
+});
+
+
+require('./helpers/Matchers.js');
+jasmine.executeSpecsInFolder(__dirname + folderName, function(runner, log){
+ if (runner.results().failedCount === 0) {
+ process.exit(0);
+ } else {
+ process.exit(1);
+ }
+}, isVerbose, showColors);
40 spec/suites/Integration/BasketIntegrationSpec.js
@@ -0,0 +1,40 @@
+var api = require('api').Api.buildFromFile(__dirname + '/../../../../lib/api.json');
+const API_TIMEOUT_MS = 5000;
+
+describe('Basket Integration Tests', function() {
+
+ var basket;
+
+ beforeEach(function() {
+ basket = new api.Basket();
+ this.addMatchers({
+ toHaveOkStatus: function() {
+ return this.actual['@'].status === 'ok';
+ }
+ });
+ });
+
+ it("should return new basket from get after creating one", function() {
+ var errorData,
+ successData;
+
+ basket.create({}, function(err, data) {
+ var basketId = data.basket['@'].id;
+ basket.get({ basketId: basketId }, function(err, data) {
+ errorData = err;
+ successData = data;
+ });
+ });
+
+ waitsFor(function() {
+ return errorData || successData;
+ }, "api timed out", API_TIMEOUT_MS);
+
+ runs(function() {
+ expect(errorData).toBeFalsy();
+ expect(successData).not.toBeFalsy();
+ expect(successData).toHaveOkStatus();
+ });
+ });
+
+});
56 spec/suites/Integration/ReleasesIntegrationSpec.js
@@ -0,0 +1,56 @@
+var api = require('api').api;
+const API_TIMEOUT_MS = 5000;
+
+describe('Releases Integration Tests', function() {
+
+ var releases;
+
+ beforeEach(function() {
+ releases = new api.Releases();
+ this.addMatchers({
+ toHaveOkStatus: function() {
+ return this.actual['@'].status === 'ok';
+ }
+ });
+ });
+
+ it("should return release from getDetails", function() {
+ var errorData,
+ successData;
+
+ releases.getDetails({ releaseid: 951866 }, function(err, data) {
+ errorData = err;
+ successData = data;
+ });
+
+ waitsFor(function() {
+ return errorData || successData;
+ }, "getDetails timed out", API_TIMEOUT_MS);
+
+ runs(function() {
+ expect(errorData).toBeFalsy();
+ expect(successData).not.toBeFalsy();
+ expect(successData).toHaveOkStatus();
+ });
+ });
+
+ it("should return return an error from getDetails when given no releaseId", function() {
+ var errorData,
+ successData;
+
+ releases.getDetails(null, function(err, data) {
+ errorData = err;
+ successData = data;
+ });
+
+ waitsFor(function() {
+ return errorData || successData;
+ }, "getDetails timed out", API_TIMEOUT_MS);
+
+ runs(function() {
+ expect(successData).toBeFalsy();
+ expect(errorData).not.toBeFalsy();
+ });
+ });
+
+});
55 spec/suites/unit/API/BuildSpecs.js
@@ -0,0 +1,55 @@
+var Api = require('api').Api,
+ APIBase = require('apibase').APIBase;
+
+describe('API.build', function() {
+ var schema = {
+ "host": "api.example.com",
+ "version": "1.0",
+ "resources":
+ {
+ "Test": {
+ "resource": "testresource",
+ "actions":
+ [
+ "byDate"
+ ]
+ }
+ }
+ }, api, testApi;
+
+ beforeEach(function() {
+ api = Api.build(schema),
+ testApi = new api.Test();
+ });
+
+ it('should return a wrapper', function() {
+ expect(api).not.toBeNull();
+ });
+
+ it('should create an API constructor for each resource', function() {
+ expect(api.Test).toBeDefined();
+ expect(api.Test).toHavePrototypeOf(APIBase);
+ });
+
+ it('should supply oauth key and secret when provided', function() {
+ // This is a bit smelly, we're essentially setting up the API
+ // twice which suggests we need a new fixture really.
+ api = Api.build(schema, 'testkey', 'testsecret');
+ testApi = new api.Test();
+
+ expect(testApi.oauth_consumer_key).toEqual('testkey');
+ expect(testApi.oauth_consumer_secret).toEqual('testsecret');
+ });
+
+ it('should supply the API with host, version and resource name', function() {
+ expect(testApi.host).toEqual('api.example.com');
+ expect(testApi.version).toEqual('1.0');
+ expect(testApi.version).toEqual('1.0');
+ });
+
+ it('should create a method for each action', function() {
+ expect(testApi.getByDate).toBeDefined();
+ expect(testApi.getByDate).toBeAFunction();
+ });
+
+});
50 spec/suites/unit/API/getActionMethodName.js
@@ -0,0 +1,50 @@
+var Api = require('api').Api,
+ APIBase = require('apibase').APIBase;
+
+describe('API.getActionMethodName', function() {
+ var schema = {
+ "host": "api.example.com",
+ "version": "1.0",
+ "resources":
+ {
+ "Test": {
+ "resource": "testresource",
+ "actions":
+ [
+ "byDate",
+ { "apiCall": "test", "methodName": "expectedName" }
+ ]
+ }
+ }
+ }, api, testApi;
+
+ beforeEach(function() {
+ api = Api.build(schema),
+ testApi = new api.Test();
+ });
+
+ it('should return an empty string when no action is found', function() {
+ var methodName = api.getActionMethodName('Test', 'nonexistentactionslug');
+ expect(methodName).toEqual('');
+ });
+
+ it('should default to getXxx when no methodName is specified in the schema', function() {
+ var methodName = api.getActionMethodName('Test', 'byDate');
+ expect(methodName).toEqual('getByDate');
+ });
+
+ it('should default to getXxx when no methodName is specified in the schema even with the wrong case', function() {
+ var methodName = api.getActionMethodName('Test', 'BYDATE');
+ expect(methodName).toEqual('getByDate');
+ });
+
+ it('should return specified methodName when in the schema', function() {
+ var methodName = api.getActionMethodName('Test', 'test');
+ expect(methodName).toEqual('expectedName');
+ });
+
+ it('should specified methodName when in the schema even with the wrong case', function() {
+ var methodName = api.getActionMethodName('Test', 'TEST');
+ expect(methodName).toEqual('expectedName');
+ });
+});
50 spec/suites/unit/APIDefinition/ArtistsActionsSpec.js
@@ -0,0 +1,50 @@
+var api = require('api').Api.buildFromFile(__dirname + '/../../../../lib/api.json');
+
+describe("Artists actions", function() {
+
+ var artists;
+
+ beforeEach(function() {
+ artists = new api.Artists();
+ });
+
+ it("should generate a browse method for the browse action", function() {
+ expect(artists.browse).toBeDefined();
+ expect(artists.browse).toBeAFunction();
+ });
+
+ it("should generate a getChart method for the chart action", function() {
+ expect(artists.getChart).toBeDefined();
+ expect(artists.getChart).toBeAFunction();
+ });
+
+ it("should generate a getDetails method for the details action", function() {
+ expect(artists.getDetails).toBeDefined();
+ expect(artists.getDetails).toBeAFunction();
+ });
+
+ it("should generate a getReleases method for the releases action", function() {
+ expect(artists.getReleases).toBeDefined();
+ expect(artists.getReleases).toBeAFunction();
+ });
+
+ it("should generate a search method for the search action", function() {
+ expect(artists.search).toBeDefined();
+ expect(artists.search).toBeAFunction();
+ });
+
+ it("should generate a getTopTracks method for the toptracks action", function() {
+ expect(artists.getTopTracks).toBeDefined();
+ expect(artists.getTopTracks).toBeAFunction();
+ });
+
+ it("should generate a getTags method for the tags action", function() {
+ expect(artists.getTags).toBeDefined();
+ expect(artists.getTags).toBeAFunction();
+ });
+
+ it("should generate a getTopByTags method for the bytag/top action", function() {
+ expect(artists.getTopByTags).toBeDefined();
+ expect(artists.getTopByTags).toBeAFunction();
+ });
+});
30 spec/suites/unit/APIDefinition/BasketActionsSpec.js
@@ -0,0 +1,30 @@
+var api = require('api').Api.buildFromFile(__dirname + '/../../../../lib/api.json');
+
+describe("Basket actions", function() {
+
+ var basket;
+
+ beforeEach(function() {
+ basket = new api.Basket();
+ });
+
+ it("should generate a create method for the create action", function() {
+ expect(basket.create).toBeDefined();
+ expect(basket.create).toBeAFunction();
+ });
+
+ it("should generate a get method for the default action", function() {
+ expect(basket.get).toBeDefined();
+ expect(basket.get).toBeAFunction();
+ });
+
+ it("should generate an addItem method for the addItem action", function() {
+ expect(basket.addItem).toBeDefined();
+ expect(basket.addItem).toBeAFunction();
+ });
+
+ it("should generate a removeItem method for the removeItem action", function() {
+ expect(basket.removeItem).toBeDefined();
+ expect(basket.removeItem).toBeAFunction();
+ });
+});
55 spec/suites/unit/APIDefinition/ReleasesActionsSpec.js
@@ -0,0 +1,55 @@
+var api = require('api').Api.buildFromFile(__dirname + '/../../../../lib/api.json');
+
+describe("Releases actions", function() {
+
+ var releases;
+
+ beforeEach(function() {
+ releases = new api.Releases();
+ });
+
+ it("should generate a getByDate method for the byDate action", function() {
+ expect(releases.getByDate).toBeDefined();
+ expect(releases.getByDate).toBeAFunction();
+ });
+
+ it("should generate a getDetails method for the details action", function() {
+ expect(releases.getDetails).toBeDefined();
+ expect(releases.getDetails).toBeAFunction();
+ });
+
+ it("should generate a getChart method for the chart action", function() {
+ expect(releases.getChart).toBeDefined();
+ expect(releases.getChart).toBeAFunction();
+ });
+
+ it("should generate a getRecommendations method for the recommend action", function() {
+ expect(releases.getRecommendations).toBeDefined();
+ expect(releases.getRecommendations).toBeAFunction();
+ });
+
+ it("should generate a search method for the search action", function() {
+ expect(releases.search).toBeDefined();
+ expect(releases.search).toBeAFunction();
+ });
+
+ it("should generate a getTracks method for the tracks action", function() {
+ expect(releases.getTracks).toBeDefined();
+ expect(releases.getTracks).toBeAFunction();
+ });
+
+ it("should generate a getTags method for the tags action", function() {
+ expect(releases.getTags).toBeDefined();
+ expect(releases.getTags).toBeAFunction();
+ });
+
+ it("should generate a getNewByTags method for the bytag/new action", function() {
+ expect(releases.getNewByTags).toBeDefined();
+ expect(releases.getNewByTags).toBeAFunction();
+ });
+
+ it("should generate a getTopByTags method for the bytag/top action", function() {
+ expect(releases.getNewByTags).toBeDefined();
+ expect(releases.getNewByTags).toBeAFunction();
+ });
+});
16 spec/suites/unit/APIDefinition/TagsActionsSpec.js
@@ -0,0 +1,16 @@
+var api = require('api').Api.buildFromFile(__dirname + '/../../../../lib/api.json');
+
+describe("Tags actions", function() {
+
+ var releases;
+
+ beforeEach(function() {
+ tags = new api.Tags();
+ });
+
+ it("should generate an all method for the default action", function() {
+ expect(tags.all).toBeDefined();
+ expect(tags.all).toBeAFunction();
+ });
+
+});
30 spec/suites/unit/APIDefinition/TracksActionsSpec.js
@@ -0,0 +1,30 @@
+var api = require('api').Api.buildFromFile(__dirname + '/../../../../lib/api.json');
+
+describe("Tracks actions", function() {
+
+ var releases;
+
+ beforeEach(function() {
+ tracks = new api.Tracks();
+ });
+
+ it("should generate a getChart method for the chart action", function() {
+ expect(tracks.getChart).toBeDefined();
+ expect(tracks.getChart).toBeAFunction();
+ });
+
+ it("should generate a getDetails method for the details action", function() {
+ expect(tracks.getDetails).toBeDefined();
+ expect(tracks.getDetails).toBeAFunction();
+ });
+
+ it("should generate a getPreview method for the preview action", function() {
+ expect(tracks.getPreview).toBeDefined();
+ expect(tracks.getPreview).toBeAFunction();
+ });
+
+ it("should generate a search method for the search action", function() {
+ expect(tracks.search).toBeDefined();
+ expect(tracks.search).toBeAFunction();
+ });
+});
59 spec/suites/unit/CodingRulesSpecs.js
@@ -0,0 +1,59 @@
+var JSHINT = require('jshint').JSHINT,
+ fs = require('fs'),
+ eyes = require('eyes'),
+ Step = require('step');
+
+// This needs some work as it is reporting errors far too aggressively, I
+// there is a text encoding issue at work here.
+xdescribe('Coding Rules', function() {
+
+ function getAllServerSource(callback) {
+ var sourcePath = __dirname + '/../../../lib/';
+
+ Step(
+ function getSourceFilenames() {
+ fs.readdir(sourcePath, this);
+ },
+ function readFileContents(err, filenames) {
+ if (err) throw err;
+
+ var group = this.group();
+
+ filenames.forEach(function (filename) {
+ if (/\.js$/.test(filename)) {
+ fs.readFile(sourcePath + filename, 'utf8', group());
+ }
+ });
+ },
+ callback
+ );
+
+ }
+
+ it('server source should passs JSHint validation', function() {
+ var passedValidation,
+ self = this,
+ jshintOptions = { node: true, onevar: false };
+
+ getAllServerSource(function andRunJSHint(err, filecontents) {
+ if (err) throw err;
+
+ passedValidation = JSHINT(filecontents, jshintOptions);
+
+ JSHINT.errors.forEach(function addResultFor(error) {
+ if (!error) return;
+ self.addMatcherResult(new jasmine.ExpectationResult({
+ passed: false,
+ message: 'line ' + error.line + ': ' + error.reason
+ }));
+ });
+ });
+
+ waitsFor(function theResultsToBeIn() {
+ return passedValidation != null;
+ });
+
+ expect(true).toBe(true); // force spec to show up if there are no errors
+ });
+
+});
41 spec/suites/unit/test.js
@@ -0,0 +1,41 @@
+var fs = require('fs'),
+ Step = require('step');
+
+ function getAllServerSource(callback) {
+ var sourcePath = __dirname + '/../../../lib/';
+
+ Step(
+ function getSourceFilenames() {
+ fs.readdir(sourcePath, this);
+ },
+ function readFileContents(err, filenames) {
+ if (err) throw err;
+
+ var group = this.group();
+
+ filenames.forEach(function (filename) {
+ if (/\.js$/.test(filename)) {
+ fs.readFile(sourcePath + filename, 'utf8', group());
+ }
+ });
+ },
+ callback
+ );
+
+ }
+
+ getAllServerSource(function andRunJSHint(err, filecontents) {
+ if (err) throw err;
+
+ console.log(filecontents);
+ // passedValidation = JSHINT(filecontents, jshintOptions);
+
+ // JSHINT.errors.forEach(function addResultFor(error) {
+ // if (!error) return;
+ // self.addMatcherResult(new jasmine.ExpectationResult({
+ // passed: false,
+ // message: "line " + error.line + ': ' + error.reason
+ // }));
+ // });
+ });
+
Please sign in to comment.
Something went wrong with that request. Please try again.