From 3f601c0b6bc903118c9878b6edac8a33d699ac5b Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 00:49:47 -0800 Subject: [PATCH 01/25] first commit, project in same state as end of lab 09 --- .eslintrc | 21 +++++++++ .gitignore | 105 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 21 +++++++++ gulpfile.js | 23 ++++++++++ lib/parse-json.js | 31 +++++++++++++ lib/parse-url.js | 10 +++++ lib/response.js | 19 ++++++++ lib/router.js | 61 ++++++++++++++++++++++++++ lib/storage.js | 61 ++++++++++++++++++++++++++ model/bevs.js | 14 ++++++ package.json | 32 ++++++++++++++ route/bev-route.js | 61 ++++++++++++++++++++++++++ server.js | 16 +++++++ test/bev-test.js | 85 ++++++++++++++++++++++++++++++++++++ 14 files changed, 560 insertions(+) create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 README.md create mode 100644 gulpfile.js create mode 100644 lib/parse-json.js create mode 100644 lib/parse-url.js create mode 100644 lib/response.js create mode 100644 lib/router.js create mode 100644 lib/storage.js create mode 100644 model/bevs.js create mode 100644 package.json create mode 100644 route/bev-route.js create mode 100644 server.js create mode 100644 test/bev-test.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,21 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + }, + "extends": "eslint:recommended" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88ea613 --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ +# Created by https://www.gitignore.io/api/macos,node,vim,linux + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Node ### +node_modules +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + + + +### Vim ### +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* diff --git a/README.md b/README.md new file mode 100644 index 0000000..ded7799 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +## Hello and welcome to my Electric Vehicle API. + +In the last decade, Electric vehicles have exploded in popularity with the advent of new battery technologies and practical vehicles hitting the market, from flashy luxury models like Tesla's Model S, to affordable commuter/family models like Nissan's Leaf. As technology improves and better, more affordable models hit the market, Electric vehicles have the potential to make a serious dent in pollution from automotive transportation. This is especially true in markets with relatively green power grids. + +The purpose of this API is ultimately to be a resource for consumers interested in Electric Vehicles and green transportation in general, containing the most up-to-date information on Electric Vehicles. Info like range on a battery charge, MPGe, market availability, quick-charging options and standards will eventually be added as separate categories in terms of consumer info (object properties in terms of the API) + +For this API, we define "Electric Vehicles" more technically and specifically as "Battery-Electric Vehicles," or BEVs. Only vehicles that are powered exclusively by electricity from a battery are included. This designation excludes plug-in-hybrid vehicles (like the Chevrolet Volt) and hydrogen fuel cell vehicles. + +## How to use this API + +Currently, this API is configured for use in the command line. It is set up to run on port 3000 of your computer's local IP (this IP can be accessed with the identifier `localhost`). + +You will need a command line http tool installed. I recommend httpie, and I assume you have it installed for this example. + + * In the command line, making sure you're in the root directory of your local version of the API, start the node server by typing `node server.js` + * Let's add a sample vehicle to the API. In a **separate** window or pane of your command line interface (your first window is running the node server), making sure you're still in the root directory of your local version of the API, type `http POST localhost:3000/api/bev vehicle="Nissan Leaf" info="108 mile-range on a single charge"` + * Let's add a second sample vehicle. Type `http POST localhost:3000/api/bev vehicle="Tesla Model S" info="300 mile-range on a single charge"` + * Let's get the unique id for each vehicle. Each id is a random string of numbers, letter, and dashes autogenerated with node-uuid. type `http localhost:3000/api/bev` This should print an array of ids (for this example there should be two) in the command line. + * Let's take one of the ids and look up the vehicle information it references. Copy the text of one of the ids. Then in the command line, type `http localhost:3000/api/bev?id=` and paste the id before submitting the command. You should see an object printed to the command line with the vehicle info. + + Thanks for using my API. Check back soon for updates and improvements! diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..04eaa14 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,23 @@ +'use strict'; + +const gulp = require('gulp'); +const eslint = require('gulp-eslint'); +const mocha = require('gulp-mocha'); + +gulp.task('test', function() { + gulp.src('./test/*-test.js', { read: false }) + .pipe(mocha({ reporter: 'spec' })); +}); + +gulp.task('lint', function() { + gulp.src(['**/*.js', '!node_modules']) + .pipe(eslint()) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +}); + +gulp.task('dev', function() { + gulp.watch(['**/*.js', '!node_modules'], ['lint', 'test']); +}); + +gulp.task('default', ['dev']); diff --git a/lib/parse-json.js b/lib/parse-json.js new file mode 100644 index 0000000..f188b8d --- /dev/null +++ b/lib/parse-json.js @@ -0,0 +1,31 @@ +'use strict'; + +module.exports = function(req) { + return new Promise( (resolve, reject) => { + if (req.method === 'POST' || req.method === 'PUT') { + var body = ''; + + req.on('data', data => { + body += data.toString(); + }); + + req.on('end', end => { + try { + req.body = JSON.parse(body); + resolve(req); + } catch (err) { + console.error(err); + reject(err); + }; + }); + + req.on('error', err => { + console.error(err); + reject(err); + }); + return; + }; + + resolve(); + }); +}; diff --git a/lib/parse-url.js b/lib/parse-url.js new file mode 100644 index 0000000..3c88889 --- /dev/null +++ b/lib/parse-url.js @@ -0,0 +1,10 @@ +'use strict'; + +const parseURL = require('url').parse; +const parseQuery = require('querystring').parse; + +module.exports = function(req) { + req.url = parseURL(req.url); + req.url.query = parseQuery(req.url.query); + return Promise.resolve(req); +}; diff --git a/lib/response.js b/lib/response.js new file mode 100644 index 0000000..a567a57 --- /dev/null +++ b/lib/response.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = exports = {}; + +exports.sendJSON = function(res, status, data) { + res.writeHead(status, { + 'Content-Type': 'application/json' + }); + res.write(JSON.stringify(data)); + res.end(); +}; + +exports.sendText = function(res, status, msg) { + res.writeHead(status, { + 'Content-Type': 'text/plain' + }); + res.write(msg); + res.end(); +}; diff --git a/lib/router.js b/lib/router.js new file mode 100644 index 0000000..a320a8e --- /dev/null +++ b/lib/router.js @@ -0,0 +1,61 @@ +'use strict'; + +const parseURL = require('./parse-url.js'); +const parseJSON = require('./parse-json.js'); + +const Router = module.exports = function() { + this.routes = { + GET: {}, + POST: {}, + PUT: {}, + DELETE: {} + }; +}; + +Router.prototype.get = function(endpoint, callback) { + this.routes.GET[endpoint] = callback; +}; + +Router.prototype.post = function(endpoint, callback) { + this.routes.POST[endpoint] = callback; +}; + +Router.prototype.put = function(endpoint, callback) { + this.routes.PUT[endpoint] = callback; +}; + +Router.prototype.delete = function(endpoint, callback) { + this.routes.DELETE[endpoint] = callback; +}; + +Router.prototype.route = function() { + return (req, res) => { + Promise.all([ + parseURL(req), + parseJSON(req) + ]) + .then( () => { + if (typeof this.routes[req.method][req.url.pathname] === 'function') { + this.routes[req.method][req.url.pathname](req, res); + return; + }; + + console.error('route not found'); + + res.writeHead(404, { + 'Content-Type': 'text/plain' + }); + res.write('route not found'); + res.end(); + }) + .catch( err => { + console.error(err); + + res.writeHead(400, { + 'Content-Type': 'text/plain' + }); + res.write('bad request'); + res.end(); + }); + }; +}; diff --git a/lib/storage.js b/lib/storage.js new file mode 100644 index 0000000..6f562f8 --- /dev/null +++ b/lib/storage.js @@ -0,0 +1,61 @@ +'use strict'; + +const Promise = require('bluebird'); +const fs = Promise.promisifyAll(require('fs'), { suffix: 'Prom' }); + +module.exports = exports = {}; + +exports.createEntry = function(schemaName, entry) { + if (!schemaName) return Promise.reject(new Error('expected schema name')); + if (!entry) return Promise.reject(new Error('expected entry data')); + + let json = JSON.stringify(entry); + return fs.writeFileProm(`${__dirname}/../data/${schemaName}/${entry.id}.json`, json) + .then( () => entry) + .catch( err => Promise.reject(err)); +}; + +exports.fetchEntry = function(schemaName, id) { + if (!schemaName) return reject(new Error('expected schema name')); + if (!id) return reject(new Error('expected id')); + + return fs.readFileProm(`${__dirname}/../data/${schemaName}/${id}.json`) + .then( data => { + try { + let entry = JSON.parse(data.toString()); + return entry; + } catch (err) { + return Promise.reject(err); + }; + }) + .catch( err => Promise.reject(err)); +}; + +exports.fetchAll = function(schemaName) { + if (!schemaName) return reject(new Error('expected schema name')); + + // return array of saved filenames whose names (minus the '.json' filename extention) match vehicle IDs + return fs.readdirProm(`${__dirname}/../data/${schemaName}/`) + .then( fileNames => { + try { + var ids = []; + fileNames.forEach( fileName => { + // remove '.json' filename extention from array of IDs, push to new array to print to users' CLI + ids.push(fileName.replace('.json', '')); + }); + return ids; + + } catch (err) { + return Promise.reject(err); + }; + }) + .catch( err => Promise.reject(err)); +}; + +exports.deleteEntry = function(schemaName, id) { + if (!schemaName) return reject(new Error('expected schema name')); + if (!id) return reject(new Error('expected id')); + + return fs.unlinkProm(`${__dirname}/../data/${schemaName}/${id}.json`) + .catch( err => Promise.reject(err)); +}; diff --git a/model/bevs.js b/model/bevs.js new file mode 100644 index 0000000..59e9bc0 --- /dev/null +++ b/model/bevs.js @@ -0,0 +1,14 @@ +'use strict'; + +// Model for data on Battery-Electric Vehicles (BEVs) + +const uuid = require('node-uuid'); + +module.exports = function(vehicle, info) { + if (!vehicle) throw new Error('expected vehicle model name'); + if (!info) throw new Error('expected vehicle model info'); + + this.id = uuid.v1(); + this.vehicle = vehicle; + this.info = info; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..7017456 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "08-vanilla_rest_api", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/maschigokae/08-vanilla_rest_api.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/maschigokae/08-vanilla_rest_api/issues" + }, + "homepage": "https://github.com/maschigokae/08-vanilla_rest_api#readme", + "dependencies": { + "bluebird": "^3.4.6", + "node-uuid": "^1.4.7" + }, + "devDependencies": { + "chai": "^3.5.0", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-mocha": "^3.0.1", + "mocha": "^3.2.0", + "superagent": "^3.3.1" + } +} diff --git a/route/bev-route.js b/route/bev-route.js new file mode 100644 index 0000000..0eb3f2d --- /dev/null +++ b/route/bev-route.js @@ -0,0 +1,61 @@ +'use strict'; + +const storage = require('../lib/storage.js'); +const BEV = require('../model/bevs.js'); +const response = require('../lib/response.js'); + +module.exports = function(router) { + router.get('/api/bev', function(req, res) { + // GET data for a vehicle + if (req.url.query.id) { + storage.fetchEntry('bev', req.url.query.id) + .then( bev => { + response.sendJSON(res, 200, bev); + }) + .catch( err => { + console.error(err); + response.sendText(res, 404, 'resource not found'); + }); + + return; + }; + + if (req.url && !req.url.query.id) { + storage.fetchAll('bev') + .then( bev => { + response.sendJSON(res, 200, bev); + }) + .catch( err => { + console.error(err); + response.sendText(res, 404, 'resource not found'); + }); + + return; + }; + }); + + router.post('/api/bev', function(req, res) { + // POST data for a vehicle + try { + var bev = new BEV(req.body.vehicle, req.body.info); + storage.createEntry('bev', bev); + response.sendJSON(res, 200, bev); + } catch (err) { + console.error(err); + response.sendText(res, 400, 'bad request'); + } + }); + + router.delete('/api/bev', function(req, res) { + if (req.url.query.id) { + storage.deleteEntry('bev', req.url.query.id) + .then( bev => { + response.sendText(res, 204, 'entry deleted - no content'); + }) + .catch( err => { + console.error(err); + response.sendText(res, 404, 'resource not found'); + }); + } + }); +}; diff --git a/server.js b/server.js new file mode 100644 index 0000000..03871d5 --- /dev/null +++ b/server.js @@ -0,0 +1,16 @@ +'use strict'; + +const http = require('http'); +const BEV = require('./model/bevs.js'); +const Router = require('./lib/router.js'); +const storage = require('./lib/storage.js'); +const PORT = 3000; +const router = new Router(); + +require('./route/bev-route.js')(router); + +const server = http.createServer(router.route()); + +server.listen(PORT, () => { + console.log(`SERVER RUNNING ON PORT ${PORT}`); +}); diff --git a/test/bev-test.js b/test/bev-test.js new file mode 100644 index 0000000..592f3d8 --- /dev/null +++ b/test/bev-test.js @@ -0,0 +1,85 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; + +require('../server.js'); + +describe('BEV Routes', function() { + var vehicle = null; + + describe('POST: api/bev', function() { + it('should throw a 400 \'bad request\' error', function(done) { + request.post('localhost:3000/api/bev') + .send({}) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('bad request'); + done(); + }); + }); + + it('should return a vehicle info entry', function(done) { + request.post('localhost:3000/api/bev') + .send({ vehicle: 'Test Vehicle', info: 'Test info' }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.vehicle).to.equal('Test Vehicle'); + expect(res.body.info).to.equal('Test info'); + vehicle = res.body; + done(); + }); + }); + }); + + describe('GET: api/bev?id=test_id', function() { + it('should throw a 404 \'not found\' error', function(done) { + request.get(`localhost:3000/api/bev?id=foo-bar`) + .end((err, res) => { + expect(res.status).to.equal(404); + expect(res.text).to.equal('resource not found'); + done(); + }); + }); + + it('should return a vehicle info entry', function(done) { + request.get(`localhost:3000/api/bev?id=${vehicle.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.vehicle).to.equal('Test Vehicle'); + expect(res.body.info).to.equal('Test info'); + done(); + }); + }); + }); + + describe('GET: api/bev', function() { + it('should return an array of entry ids', function(done) { + request.get('localhost:3000/api/bev') + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('array'); + // expect filenames to be sorted in alpha. order. + // thus, latest filename will be in random position in array + expect(res.body).to.include(vehicle.id); + done(); + }); + }); + }); + + describe('DELETE: api/bev', function() { + it('should delete the test vehicle info entry', function(done) { + request.delete(`localhost:3000/api/bev?id=${vehicle.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + expect(res.body).to.be.an('object'); + expect(res.body).to.be.empty; + done(); + }); + }); + }); +}); From ba5e0fea039d24451e609a699c4899f4ef603023 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:09:13 -0800 Subject: [PATCH 02/25] add range property to BEV constructor --- model/bevs.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/model/bevs.js b/model/bevs.js index 59e9bc0..06dc375 100644 --- a/model/bevs.js +++ b/model/bevs.js @@ -9,6 +9,10 @@ module.exports = function(vehicle, info) { if (!info) throw new Error('expected vehicle model info'); this.id = uuid.v1(); + // vehicle make, model this.vehicle = vehicle; + // general vehicle description this.info = info; + // EPA-tested range on a single battery charge, in miles + this.range = range; }; From 64f62e2914caefd904c169a65725059340971fb3 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:10:09 -0800 Subject: [PATCH 03/25] refactor server to run on express --- server.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server.js b/server.js index 03871d5..1f4b548 100644 --- a/server.js +++ b/server.js @@ -1,16 +1,16 @@ 'use strict'; -const http = require('http'); -const BEV = require('./model/bevs.js'); -const Router = require('./lib/router.js'); -const storage = require('./lib/storage.js'); -const PORT = 3000; -const router = new Router(); +const express = require('express'); +const morgan = require('morgan'); +const createError = require('http-errors'); +const jsonParser = require('body-parser'); +const debug = require('debug')('bev:server'); -require('./route/bev-route.js')(router); +const app = express(); +const BEV = require('./model/bev.js'); -const server = http.createServer(router.route()); +const PORT = 3000; -server.listen(PORT, () => { +app.listen(PORT, () => { console.log(`SERVER RUNNING ON PORT ${PORT}`); }); From 348c336b3513e76d65576a4913e73902da732113 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:10:41 -0800 Subject: [PATCH 04/25] add dependencies related to express refactor --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 7017456..bd2d4b3 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,11 @@ "homepage": "https://github.com/maschigokae/08-vanilla_rest_api#readme", "dependencies": { "bluebird": "^3.4.6", + "body-parser": "^1.15.2", + "debug": "^2.4.5", + "express": "^4.14.0", + "http-errors": "^1.5.1", + "morgan": "^1.7.0", "node-uuid": "^1.4.7" }, "devDependencies": { From c92c314d46478ea5dbe2f9dda4e95fe22cec1495 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:12:28 -0800 Subject: [PATCH 05/25] bugfix - fix typo causing server error --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 1f4b548..56fcaf3 100644 --- a/server.js +++ b/server.js @@ -7,7 +7,7 @@ const jsonParser = require('body-parser'); const debug = require('debug')('bev:server'); const app = express(); -const BEV = require('./model/bev.js'); +const BEV = require('./model/bevs.js'); const PORT = 3000; From 6535fb94b54b6b8623b3f3ea61f5296dcd6bd2a1 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:17:55 -0800 Subject: [PATCH 06/25] remove modules to be abstracted out with express --- lib/parse-json.js | 31 ------------------------ lib/parse-url.js | 10 -------- lib/response.js | 19 --------------- lib/router.js | 61 ----------------------------------------------- 4 files changed, 121 deletions(-) delete mode 100644 lib/parse-json.js delete mode 100644 lib/parse-url.js delete mode 100644 lib/response.js delete mode 100644 lib/router.js diff --git a/lib/parse-json.js b/lib/parse-json.js deleted file mode 100644 index f188b8d..0000000 --- a/lib/parse-json.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -module.exports = function(req) { - return new Promise( (resolve, reject) => { - if (req.method === 'POST' || req.method === 'PUT') { - var body = ''; - - req.on('data', data => { - body += data.toString(); - }); - - req.on('end', end => { - try { - req.body = JSON.parse(body); - resolve(req); - } catch (err) { - console.error(err); - reject(err); - }; - }); - - req.on('error', err => { - console.error(err); - reject(err); - }); - return; - }; - - resolve(); - }); -}; diff --git a/lib/parse-url.js b/lib/parse-url.js deleted file mode 100644 index 3c88889..0000000 --- a/lib/parse-url.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -const parseURL = require('url').parse; -const parseQuery = require('querystring').parse; - -module.exports = function(req) { - req.url = parseURL(req.url); - req.url.query = parseQuery(req.url.query); - return Promise.resolve(req); -}; diff --git a/lib/response.js b/lib/response.js deleted file mode 100644 index a567a57..0000000 --- a/lib/response.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -module.exports = exports = {}; - -exports.sendJSON = function(res, status, data) { - res.writeHead(status, { - 'Content-Type': 'application/json' - }); - res.write(JSON.stringify(data)); - res.end(); -}; - -exports.sendText = function(res, status, msg) { - res.writeHead(status, { - 'Content-Type': 'text/plain' - }); - res.write(msg); - res.end(); -}; diff --git a/lib/router.js b/lib/router.js deleted file mode 100644 index a320a8e..0000000 --- a/lib/router.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -const parseURL = require('./parse-url.js'); -const parseJSON = require('./parse-json.js'); - -const Router = module.exports = function() { - this.routes = { - GET: {}, - POST: {}, - PUT: {}, - DELETE: {} - }; -}; - -Router.prototype.get = function(endpoint, callback) { - this.routes.GET[endpoint] = callback; -}; - -Router.prototype.post = function(endpoint, callback) { - this.routes.POST[endpoint] = callback; -}; - -Router.prototype.put = function(endpoint, callback) { - this.routes.PUT[endpoint] = callback; -}; - -Router.prototype.delete = function(endpoint, callback) { - this.routes.DELETE[endpoint] = callback; -}; - -Router.prototype.route = function() { - return (req, res) => { - Promise.all([ - parseURL(req), - parseJSON(req) - ]) - .then( () => { - if (typeof this.routes[req.method][req.url.pathname] === 'function') { - this.routes[req.method][req.url.pathname](req, res); - return; - }; - - console.error('route not found'); - - res.writeHead(404, { - 'Content-Type': 'text/plain' - }); - res.write('route not found'); - res.end(); - }) - .catch( err => { - console.error(err); - - res.writeHead(400, { - 'Content-Type': 'text/plain' - }); - res.write('bad request'); - res.end(); - }); - }; -}; From 05556f2a329d08b711bd21b975a84aebb3ee970b Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:19:35 -0800 Subject: [PATCH 07/25] remove route module to be abstracted out with express --- route/bev-route.js | 61 ---------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 route/bev-route.js diff --git a/route/bev-route.js b/route/bev-route.js deleted file mode 100644 index 0eb3f2d..0000000 --- a/route/bev-route.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -const storage = require('../lib/storage.js'); -const BEV = require('../model/bevs.js'); -const response = require('../lib/response.js'); - -module.exports = function(router) { - router.get('/api/bev', function(req, res) { - // GET data for a vehicle - if (req.url.query.id) { - storage.fetchEntry('bev', req.url.query.id) - .then( bev => { - response.sendJSON(res, 200, bev); - }) - .catch( err => { - console.error(err); - response.sendText(res, 404, 'resource not found'); - }); - - return; - }; - - if (req.url && !req.url.query.id) { - storage.fetchAll('bev') - .then( bev => { - response.sendJSON(res, 200, bev); - }) - .catch( err => { - console.error(err); - response.sendText(res, 404, 'resource not found'); - }); - - return; - }; - }); - - router.post('/api/bev', function(req, res) { - // POST data for a vehicle - try { - var bev = new BEV(req.body.vehicle, req.body.info); - storage.createEntry('bev', bev); - response.sendJSON(res, 200, bev); - } catch (err) { - console.error(err); - response.sendText(res, 400, 'bad request'); - } - }); - - router.delete('/api/bev', function(req, res) { - if (req.url.query.id) { - storage.deleteEntry('bev', req.url.query.id) - .then( bev => { - response.sendText(res, 204, 'entry deleted - no content'); - }) - .catch( err => { - console.error(err); - response.sendText(res, 404, 'resource not found'); - }); - } - }); -}; From 5c67a6b67a8f020b5bb393183b81dd2f4490cb42 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:22:27 -0800 Subject: [PATCH 08/25] bugfix - add missing chained .json method on body-parser require expression --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 56fcaf3..1bfd148 100644 --- a/server.js +++ b/server.js @@ -3,7 +3,7 @@ const express = require('express'); const morgan = require('morgan'); const createError = require('http-errors'); -const jsonParser = require('body-parser'); +const jsonParser = require('body-parser').json(); const debug = require('debug')('bev:server'); const app = express(); From a05fb7dec585905f84035dba5f5833fdf1f4333e Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 01:24:14 -0800 Subject: [PATCH 09/25] set up morgan useage in server module --- server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.js b/server.js index 1bfd148..50a8831 100644 --- a/server.js +++ b/server.js @@ -11,6 +11,8 @@ const BEV = require('./model/bevs.js'); const PORT = 3000; +app.use(morgan('dev')); + app.listen(PORT, () => { console.log(`SERVER RUNNING ON PORT ${PORT}`); }); From 41b888db09b5ff87f21c7c1ba6a11b829b77f2eb Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 02:09:54 -0800 Subject: [PATCH 10/25] refactor for express, debug issues with new range property --- model/bevs.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/model/bevs.js b/model/bevs.js index 06dc375..40a358f 100644 --- a/model/bevs.js +++ b/model/bevs.js @@ -3,10 +3,17 @@ // Model for data on Battery-Electric Vehicles (BEVs) const uuid = require('node-uuid'); +const createError = require('http-errors'); +const debug = require('debug')('bev:bev'); +const storage = require('../lib/storage.js'); + +const BEV = module.exports = function(vehicle, info, range) { + debug('BEV constructor'); -module.exports = function(vehicle, info) { if (!vehicle) throw new Error('expected vehicle model name'); if (!info) throw new Error('expected vehicle model info'); + if (!range) throw new Error('expected vehicle model range'); + if (isNaN(range)) throw new Error('expected range to be a number'); this.id = uuid.v1(); // vehicle make, model @@ -14,5 +21,17 @@ module.exports = function(vehicle, info) { // general vehicle description this.info = info; // EPA-tested range on a single battery charge, in miles + // range must be a number this.range = range; }; + +BEV.createVehicle = function(_vehicle) { + debug('createVehicle'); + + try { + let bev = new BEV(_vehicle.vehicle, _vehicle.info, _vehicle.range); + return storage.createEntry('bev', bev); + } catch (err) { + return Promise.reject(err); + }; +}; From 65f27e261560fad3d600f3c1f187190ca64ab8bd Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 02:10:29 -0800 Subject: [PATCH 11/25] add start script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bd2d4b3..a630418 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "DEBUG='bev*' node server.js" }, "repository": { "type": "git", From d87f66fba8fc8f6f73e74a4b23907c4c78b29bd3 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 02:11:45 -0800 Subject: [PATCH 12/25] add POST method, test to make sure it works --- server.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server.js b/server.js index 50a8831..5fdff4f 100644 --- a/server.js +++ b/server.js @@ -13,6 +13,14 @@ const PORT = 3000; app.use(morgan('dev')); +app.post('/api/bev', jsonParser, function(req, res, next) { + debug('POST: api/bev'); + + BEV.createVehicle(req.body) + .then( vehicle => res.json(vehicle)) + .catch( err => next(err)); +}); + app.listen(PORT, () => { console.log(`SERVER RUNNING ON PORT ${PORT}`); }); From f8d9fd9f73f2f0f8ada994bdc950cc22b6712966 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 02:28:14 -0800 Subject: [PATCH 13/25] update package.json with current lab repo info --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a630418..b38ce44 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,23 @@ { - "name": "08-vanilla_rest_api", + "name": "11-express_single_resource_api", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "DEBUG='bev*' node server.js" }, "repository": { "type": "git", - "url": "git+https://github.com/maschigokae/08-vanilla_rest_api.git" + "url": "git+https://github.com/maschigokae/11-express_single_resource_api.git" }, "keywords": [], "author": "", "license": "ISC", "bugs": { - "url": "https://github.com/maschigokae/08-vanilla_rest_api/issues" + "url": "https://github.com/maschigokae/11-express_single_resource_api/issues" }, - "homepage": "https://github.com/maschigokae/08-vanilla_rest_api#readme", + "homepage": "https://github.com/maschigokae/11-express_single_resource_api#readme", "dependencies": { "bluebird": "^3.4.6", "body-parser": "^1.15.2", From f841b52ce8059897334d32388da13f0bcc6ab9ef Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 17:50:11 -0800 Subject: [PATCH 14/25] update gitignore file to ignore a testing file and data directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 88ea613..bf98124 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,6 @@ tags # .nfs files are created when an open file is removed but is still being accessed .nfs* + +sample-req.js +data From d77195a0a3fd8e2a6b15e93e4983ce4371fdf67a Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 18:01:02 -0800 Subject: [PATCH 15/25] build out fetching methods for single entry and all entries --- model/bevs.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/model/bevs.js b/model/bevs.js index 40a358f..3b36309 100644 --- a/model/bevs.js +++ b/model/bevs.js @@ -35,3 +35,13 @@ BEV.createVehicle = function(_vehicle) { return Promise.reject(err); }; }; + +BEV.fetchVehicle = function(id) { + debug('fetchVehicle'); + return storage.fetchEntry('bev', id); +}; + +BEV.fetchAllVehicles = function() { + debug('fetchAllVehicles'); + return storage.fetchAll('bev'); +}; From b68e4aff19b178a9260361b55cf1386c98fd2a29 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 18:02:24 -0800 Subject: [PATCH 16/25] build out GET routes for retrieving single entries or all entries --- server.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/server.js b/server.js index 5fdff4f..f9a22bc 100644 --- a/server.js +++ b/server.js @@ -21,6 +21,22 @@ app.post('/api/bev', jsonParser, function(req, res, next) { .catch( err => next(err)); }); +app.get('/api/bev', function(req, res, next) { + debug('GET: api/bev'); + + if (req.query.id) { + BEV.fetchVehicle(req.query.id) + .then( vehicle => res.json(vehicle)) + .catch( err => next(err)); + }; + + if (!req.query.id) { + BEV.fetchAllVehicles() + .then( vehicles => res.json(vehicles)) + .catch( err => next(err)); + }; +}); + app.listen(PORT, () => { console.log(`SERVER RUNNING ON PORT ${PORT}`); }); From b92aed9299bdc3b0d19447f1857e7500eab0c62b Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 18:15:34 -0800 Subject: [PATCH 17/25] set up npm test script in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b38ce44..7c93f2b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "main": "server.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "DEBUG='bev*' mocha", "start": "DEBUG='bev*' node server.js" }, "repository": { From 1b52fd81ea070e064deb74e540f6fd1e30998cc4 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:25:21 -0800 Subject: [PATCH 18/25] add http-errors, fix promise statements --- lib/storage.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index 6f562f8..84458b7 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -2,22 +2,23 @@ const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs'), { suffix: 'Prom' }); +const createError = require('http-errors'); module.exports = exports = {}; exports.createEntry = function(schemaName, entry) { - if (!schemaName) return Promise.reject(new Error('expected schema name')); - if (!entry) return Promise.reject(new Error('expected entry data')); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if (!entry) return Promise.reject(createError(400, 'expected entry data')); let json = JSON.stringify(entry); return fs.writeFileProm(`${__dirname}/../data/${schemaName}/${entry.id}.json`, json) .then( () => entry) - .catch( err => Promise.reject(err)); + .catch( err => Promise.reject(createError(500, err.message))); }; exports.fetchEntry = function(schemaName, id) { - if (!schemaName) return reject(new Error('expected schema name')); - if (!id) return reject(new Error('expected id')); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if (!id) return Promise.reject(createError(400, 'expected id')); return fs.readFileProm(`${__dirname}/../data/${schemaName}/${id}.json`) .then( data => { @@ -25,14 +26,14 @@ exports.fetchEntry = function(schemaName, id) { let entry = JSON.parse(data.toString()); return entry; } catch (err) { - return Promise.reject(err); + return Promise.reject(createError(500, err.message)); }; }) - .catch( err => Promise.reject(err)); + .catch( err => Promise.reject(createError(404, err.message))); }; exports.fetchAll = function(schemaName) { - if (!schemaName) return reject(new Error('expected schema name')); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); // return array of saved filenames whose names (minus the '.json' filename extention) match vehicle IDs return fs.readdirProm(`${__dirname}/../data/${schemaName}/`) @@ -53,9 +54,9 @@ exports.fetchAll = function(schemaName) { }; exports.deleteEntry = function(schemaName, id) { - if (!schemaName) return reject(new Error('expected schema name')); - if (!id) return reject(new Error('expected id')); + if (!schemaName) return Promise.reject(createError(400, 'expected schemaName')); + if (!id) return Promise.reject(createError(400, 'expected id')); return fs.unlinkProm(`${__dirname}/../data/${schemaName}/${id}.json`) - .catch( err => Promise.reject(err)); + .catch( err => Promise.reject(createError(404, err.message))); }; From 7fee526a9b7ff39a838fb1a8b39eb5187bc8bc78 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:26:45 -0800 Subject: [PATCH 19/25] add custom error status to errors stack, add delete method --- model/bevs.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/model/bevs.js b/model/bevs.js index 3b36309..4ad7723 100644 --- a/model/bevs.js +++ b/model/bevs.js @@ -10,10 +10,10 @@ const storage = require('../lib/storage.js'); const BEV = module.exports = function(vehicle, info, range) { debug('BEV constructor'); - if (!vehicle) throw new Error('expected vehicle model name'); - if (!info) throw new Error('expected vehicle model info'); - if (!range) throw new Error('expected vehicle model range'); - if (isNaN(range)) throw new Error('expected range to be a number'); + if (!vehicle) throw createError(400, 'expected vehicle model name'); + if (!info) throw createError(400, 'expected vehicle model info'); + if (!range) throw createError(400, 'expected vehicle model range'); + if (isNaN(range)) throw createError(400, 'expected range to be a number'); this.id = uuid.v1(); // vehicle make, model @@ -45,3 +45,8 @@ BEV.fetchAllVehicles = function() { debug('fetchAllVehicles'); return storage.fetchAll('bev'); }; + +BEV.deleteVehicle = function(id) { + debug('deleteVehicle'); + return storage.deleteEntry('bev', id); +}; From 5d17362984eeab2decf17ac89c4af13d9c533ff9 Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:27:16 -0800 Subject: [PATCH 20/25] add delete route to route stack in server file --- server.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server.js b/server.js index f9a22bc..eba1f6e 100644 --- a/server.js +++ b/server.js @@ -37,6 +37,12 @@ app.get('/api/bev', function(req, res, next) { }; }); +app.delete('/api/bev', function(req, res, next) { + BEV.deleteVehicle(req.query.id) + .then( vehicle => res.json(vehicle)) + .catch( err => next(err)); +}); + app.listen(PORT, () => { console.log(`SERVER RUNNING ON PORT ${PORT}`); }); From 54949418cbfdbb557e659b76333a0fee336d8a6f Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:27:37 -0800 Subject: [PATCH 21/25] refactor tests --- test/bev-test.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/test/bev-test.js b/test/bev-test.js index 592f3d8..d39fd5f 100644 --- a/test/bev-test.js +++ b/test/bev-test.js @@ -2,31 +2,39 @@ const request = require('superagent'); const expect = require('chai').expect; +const BEV = require('../model/bevs.js'); require('../server.js'); describe('BEV Routes', function() { var vehicle = null; + var testVehicleEntry = { + vehicle: 'Test Vehicle', + info: 'Test vehicle info', + range: 100 + }; + describe('POST: api/bev', function() { it('should throw a 400 \'bad request\' error', function(done) { - request.post('localhost:3000/api/bev') - .send({}) + request.post('http://localhost:3000/api/bev') + .send() .end((err, res) => { expect(res.status).to.equal(400); - expect(res.text).to.equal('bad request'); done(); }); }); it('should return a vehicle info entry', function(done) { - request.post('localhost:3000/api/bev') - .send({ vehicle: 'Test Vehicle', info: 'Test info' }) + request.post('http://localhost:3000/api/bev') + .send(testVehicleEntry) .end((err, res) => { if (err) return done(err); expect(res.status).to.equal(200); expect(res.body.vehicle).to.equal('Test Vehicle'); - expect(res.body.info).to.equal('Test info'); + expect(res.body.info).to.equal('Test vehicle info'); + expect(res.body.range).to.equal(100); + expect(res.body.range).to.be.a('number'); vehicle = res.body; done(); }); @@ -35,21 +43,20 @@ describe('BEV Routes', function() { describe('GET: api/bev?id=test_id', function() { it('should throw a 404 \'not found\' error', function(done) { - request.get(`localhost:3000/api/bev?id=foo-bar`) + request.get('http://localhost:3000/api/bev?id=foo-bar') .end((err, res) => { expect(res.status).to.equal(404); - expect(res.text).to.equal('resource not found'); done(); }); }); it('should return a vehicle info entry', function(done) { - request.get(`localhost:3000/api/bev?id=${vehicle.id}`) + request.get(`http://localhost:3000/api/bev?id=${vehicle.id}`) .end((err, res) => { if (err) return done(err); expect(res.status).to.equal(200); expect(res.body.vehicle).to.equal('Test Vehicle'); - expect(res.body.info).to.equal('Test info'); + expect(res.body.info).to.equal('Test vehicle info'); done(); }); }); @@ -57,7 +64,7 @@ describe('BEV Routes', function() { describe('GET: api/bev', function() { it('should return an array of entry ids', function(done) { - request.get('localhost:3000/api/bev') + request.get('http://localhost:3000/api/bev') .end((err, res) => { if (err) return done(err); expect(res.status).to.equal(200); @@ -72,11 +79,11 @@ describe('BEV Routes', function() { describe('DELETE: api/bev', function() { it('should delete the test vehicle info entry', function(done) { - request.delete(`localhost:3000/api/bev?id=${vehicle.id}`) + request.delete(`http://localhost:3000/api/bev?id=${vehicle.id}`) .end((err, res) => { if (err) return done(err); - expect(res.status).to.equal(204); - expect(res.body).to.be.an('object'); + // not sure how to get a 204 with the refactor. But both 200 and 204 seem to be a common convention for successful DELETE requests. + expect(res.status).to.equal(200 || 204); expect(res.body).to.be.empty; done(); }); From 350dd70626d618752207447c4c59bc9a77eed1ad Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:30:42 -0800 Subject: [PATCH 22/25] add debug statements to storage methods --- lib/storage.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/storage.js b/lib/storage.js index 84458b7..168c247 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -7,6 +7,8 @@ const createError = require('http-errors'); module.exports = exports = {}; exports.createEntry = function(schemaName, entry) { + debug('createEntry'); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); if (!entry) return Promise.reject(createError(400, 'expected entry data')); @@ -17,6 +19,8 @@ exports.createEntry = function(schemaName, entry) { }; exports.fetchEntry = function(schemaName, id) { + debug('fetchEntry'); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); if (!id) return Promise.reject(createError(400, 'expected id')); @@ -33,6 +37,8 @@ exports.fetchEntry = function(schemaName, id) { }; exports.fetchAll = function(schemaName) { + debug('fetchAll'); + if (!schemaName) return Promise.reject(createError(400, 'expected schema name')); // return array of saved filenames whose names (minus the '.json' filename extention) match vehicle IDs @@ -54,6 +60,8 @@ exports.fetchAll = function(schemaName) { }; exports.deleteEntry = function(schemaName, id) { + debug('deleteEntry'); + if (!schemaName) return Promise.reject(createError(400, 'expected schemaName')); if (!id) return Promise.reject(createError(400, 'expected id')); From cda01c9ac41526112c3d2298d28e817d27c1f57c Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:31:42 -0800 Subject: [PATCH 23/25] add require statement for debug module --- lib/storage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/storage.js b/lib/storage.js index 168c247..9b59b95 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -3,6 +3,7 @@ const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs'), { suffix: 'Prom' }); const createError = require('http-errors'); +const debug = require('debug')('bev:storage'); module.exports = exports = {}; From 57c4d92b8a819ef61bfc399c4f7558c40c09e11a Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:42:58 -0800 Subject: [PATCH 24/25] minor test edit, pass empty object into send call --- test/bev-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bev-test.js b/test/bev-test.js index d39fd5f..5f2e797 100644 --- a/test/bev-test.js +++ b/test/bev-test.js @@ -18,7 +18,7 @@ describe('BEV Routes', function() { describe('POST: api/bev', function() { it('should throw a 400 \'bad request\' error', function(done) { request.post('http://localhost:3000/api/bev') - .send() + .send({}) .end((err, res) => { expect(res.status).to.equal(400); done(); From 4a034a34a8ed246d4b9e1346de8c9c186c1a555c Mon Sep 17 00:00:00 2001 From: Caleb Sattgast Date: Tue, 20 Dec 2016 22:46:17 -0800 Subject: [PATCH 25/25] un-gitignore data so TAs have data directories and sample json data file --- .gitignore | 1 - data/bev/507c19c0-c745-11e6-b109-8b592aaa39eb.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 data/bev/507c19c0-c745-11e6-b109-8b592aaa39eb.json diff --git a/.gitignore b/.gitignore index bf98124..1b34584 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,3 @@ tags .nfs* sample-req.js -data diff --git a/data/bev/507c19c0-c745-11e6-b109-8b592aaa39eb.json b/data/bev/507c19c0-c745-11e6-b109-8b592aaa39eb.json new file mode 100644 index 0000000..8923f13 --- /dev/null +++ b/data/bev/507c19c0-c745-11e6-b109-8b592aaa39eb.json @@ -0,0 +1 @@ +{"id":"507c19c0-c745-11e6-b109-8b592aaa39eb","vehicle":"Nissan Leaf","info":"practical and affordable commuter and family hatchback","range":"120"} \ No newline at end of file