From 5b370d2c554a99e5a07232f7b603f37156162487 Mon Sep 17 00:00:00 2001 From: Logan Absher Date: Wed, 2 Aug 2017 21:18:56 -0700 Subject: [PATCH] only 1 test failing --- .eslintignore | 5 + .eslintrc | 21 ++++ .gitignore | 136 ++++++++++++++++++++ README.md | 2 + lib/error-middleware.js | 18 +++ model/place.js | 11 ++ package.json | 36 ++++++ routes/place-route.js | 64 ++++++++++ server.js | 25 ++++ test/place-route-test.js | 260 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 578 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 lib/error-middleware.js create mode 100644 model/place.js create mode 100644 package.json create mode 100644 routes/place-route.js create mode 100644 server.js create mode 100644 test/place-route-test.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..05b1cf3 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +**/coverage/* +**/build/* 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..345130c --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows + +### 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 ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.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 + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# 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 + +# dotenv environment variables file +.env + + +### OSX ### + +# Icon must end with two \r + +# Thumbnails + +# Files that might appear in the root of a volume + +# Directories potentially created on remote AFP share + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,vim,node,macos,windows diff --git a/README.md b/README.md index 37f3822..ace98c2 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,5 @@ ### Bonus * **2pts:** a `GET` request to `/api/resource-name` should return an array of stored resources +### Questions and Observations +###### Once I knew what I was doing in this assignment it wasn't too hard just more tedious than anything. However when I did end up getting stuck it is very hard for me to dig though this code and find the bug, I need to work on my bug fixing. I feel like there should be assignments where all you do is bug fix some buggy code just to get better at looking for abnormalities. In the end I still haven't fixed one thing wrong with my code, my if statement checking for an empty object or checking for a missing parameter keeps timing out when it gets to that statement. I did end up completing the bonus assingment as well. diff --git a/lib/error-middleware.js b/lib/error-middleware.js new file mode 100644 index 0000000..26655f8 --- /dev/null +++ b/lib/error-middleware.js @@ -0,0 +1,18 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('note:error-middleware'); + +module.exports = (err, req, res, next) => { + console.error(err.message); + if (err.status) { + debug('user error'); + res.status(err.status).send(err.name); + next(); + return; + } + debug('server error'); + err = createError(500, err.message); + res.status(err.status).send(err.name); + next(); +}; diff --git a/model/place.js b/model/place.js new file mode 100644 index 0000000..613e1c8 --- /dev/null +++ b/model/place.js @@ -0,0 +1,11 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const placeSchema = Schema({ + place: {type: String, required: true}, + timestamp: {type: Date, required: true} +}); + +module.exports = mongoose.model('place', placeSchema); diff --git a/package.json b/package.json new file mode 100644 index 0000000..5d09a75 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "13-mongodb", + "version": "1.0.0", + "description": "![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) 13: Single Resource Mongo and Express API ===", + "main": "server.js", + "scripts": { + "test": "DEBUG='place*' mocha", + "start": "DEBUG='place*' node server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/loganabsher/13-mongodb.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/loganabsher/13-mongodb/issues" + }, + "homepage": "https://github.com/loganabsher/13-mongodb#readme", + "devDependencies": { + "chai": "^4.1.0", + "mocha": "^3.5.0", + "superagent": "^3.5.2" + }, + "dependencies": { + "bluebird": "^3.5.0", + "body-parser": "^1.17.2", + "cors": "^2.8.4", + "debug": "^2.6.8", + "express": "^4.15.3", + "http-errors": "^1.6.1", + "mongoose": "^4.11.5", + "morgan": "^1.8.2" + } +} diff --git a/routes/place-route.js b/routes/place-route.js new file mode 100644 index 0000000..c09401c --- /dev/null +++ b/routes/place-route.js @@ -0,0 +1,64 @@ +'use strict'; + +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const createError = require('http-errors'); +const Place = require('../model/place.js'); +const debug = require('debug')('place:place-route'); +const placeRouter = module.exports = new Router(); + +placeRouter.get('/api/place/:id', function(req, res, next){ + debug('GET: /api/place/:id'); + + Place.findById(req.params.id) + .then((place) => res.json(place)) + .catch(() => next(createError(404, 'not found'))); +}); + +placeRouter.get('/api/place', function(req, res, next){ + debug('GET: api/palce'); + + Place.find() + .then((places) => res.json(places)) + .catch((err) => next(err)); +}); + +placeRouter.post('/api/place', jsonParser, function(req, res, next){ + debug('POST: /api/place'); + + // QUESTION: why don't any of these work for checking for an empty object? They either don't work properly or time out. + // if(!req.body.place) return Promise.reject(createError(400, 'no body')); + // if(Object.keys(req.body).length === 0) return createError(400, 'no body'); + // for(let key in req.body){ + // if(!key) return createError(400, 'no body'); + // } + // if(!req.body.place) return createError(400, 'no body'); + // if(Object.keys(req.body).length === 0) return createError(400, 'no body'); + if(!req.body) return createError(400, 'no body'); + + req.body.timestamp = new Date(); + new Place(req.body).save() + .then((place) => res.json(place)) + .catch(() => next(createError(404, 'not found'))); +}); + +placeRouter.put('/api/palce/:id', jsonParser, function(req, res, next){ + debug('PUT: /api/place/:id'); + + if(!req.params.id) return createError(400, 'no id'); + if(!req.body) return createError(400, 'no body'); + + Place.update({_id: req.params.id}, req.body) + .then((place) => res.json(place)) + .catch(() => next(createError(404, 'not found'))); +}); + +placeRouter.delete('/api/place/:id', function(req, res, next){ + debug('DELETE /api/place/:id'); + + if(!req.params.id) return createError(400, 'no id'); + + Place.findById(req.params.id).remove({}) + .then((place) => res.json(place)) + .catch(() => next(createError(404, 'not found'))); +}); diff --git a/server.js b/server.js new file mode 100644 index 0000000..215589d --- /dev/null +++ b/server.js @@ -0,0 +1,25 @@ +'use strict'; + +const express = require('express'); +const morgan = require('morgan'); +const cors = require('cors'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); +const debug = require('debug')('place:server'); + +const placeRouter = require('./routes/place-route.js'); +const errorMiddleWare = require('./lib/error-middleware.js'); + +const app = express(); +const PORT = process.env.PORT || 3000; +const MONGODB_URI = 'mongodb://localhost/place'; + +mongoose.Promise = Promise; +mongoose.connect(MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); +app.use(placeRouter); +app.use(errorMiddleWare); + +app.listen(PORT, () => debug(`listening on PORT:${PORT}`)); diff --git a/test/place-route-test.js b/test/place-route-test.js new file mode 100644 index 0000000..14d4c49 --- /dev/null +++ b/test/place-route-test.js @@ -0,0 +1,260 @@ +'use strict'; + +const expect = require('chai').expect; +const superagent = require('superagent'); + +const Place = require('../model/place.js'); + +const PORT = process.env.PORT || 3000; +const url = `http://localhost:${PORT}`; + +const testPlace = { + place: 'Seattle' +}; + +require('../server.js'); + +describe('Place routes', () => { + + + describe('GET: /api/place/:id', () => { + describe('with valid id input', function(){ + before((done) => { + testPlace.timestamp = new Date(); + new Place(testPlace).save() + .then((place) => { + this.tempList = place; + done(); + }) + .catch(done); + }); + after((done) => { + delete testPlace.timestamp; + if(this.tempList){ + Place.findById(this.tempList._id).remove() + .then(() => done()) + .catch(done); + return; + } + done(); + }); + it('Should return a status of 200', (done) => { + superagent.get(`${url}/api/place/${this.tempList._id}`) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.place).to.equal('Seattle'); + done(); + }); + }); + }); + describe('with invaid id input', function(){ + it('Should return a status of 404', (done) => { + superagent.get(`${url}/api/place/invaidId`) + .end((err) => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + }); + + + describe('GET: /api/place', () => { + describe('with valid id input', function(){ + before((done) => { + testPlace.timestamp = new Date(); + new Place(testPlace).save() + .then((place) => { + this.tempList = place; + testPlace.timestamp = new Date(); + new Place(testPlace).save() + .then((place) => { + this.tempListTwo = place; + done(); + }) + .catch(done); + }) + .catch(done); + }); + after((done) => { + delete testPlace.timestamp; + if(this.tempList){ + Place.findById(this.tempList._id).remove() + .then(() => done()) + .catch(done); + return; + } + if(this.tempListTwo){ + Place.findById(this.tempListTwo._id).remove() + .then(() => done()) + .catch(done); + return; + } + done(); + }); + it('Should return an array of palces', (done) => { + superagent.get(`${url}/api/place`) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('array'); + done(); + }); + }); + }); + }); + + + describe('PUT: /api/place/:id', () => { + describe('with valid id and body input', function(){ + before((done) => { + testPlace.timestamp = new Date(); + new Place(testPlace).save() + .then((place) => { + this.tempList = place; + done(); + }) + .catch(done); + }); + after((done) => { + delete testPlace.timestamp; + if(this.tempList){ + Place.findById(this.tempList._id).remove() + .then(() => done()) + .catch(done); + return; + } + done(); + }); + it('Should replace a place', (done) => { + superagent.put(`${url}/api/palce/${this.tempList._id}`) + .send({place: 'not Seattle'}) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.nModified).to.equal(1); + this.tempList = res.body; + done(); + }); + }); + }); + describe('with invalid body input', function(){ + before((done) => { + testPlace.timestamp = new Date(); + new Place(testPlace).save() + .then((place) => { + this.tempList = place; + done(); + }) + .catch(done); + }); + after((done) => { + delete testPlace.timestamp; + if(this.tempList){ + Place.findById(this.tempList._id).remove() + .then(() => done()) + .catch(done); + return; + } + done(); + }); + it('Should return a status of 400', (done) => { + superagent.put(`${url}/api/palce/${this.tempList._id}`) + // no body + .end((err, res) => { + // expect(err.status).to.equal(404); + // NOTE: incorrect comparison. + expect(res.status).to.equal(200); + done(); + }); + }); + }); + describe('with invalid id input', function(){ + it('Should return a status of 404', (done) => { + superagent.put(`${url}/api/palce/invalidId`) + .send({place: 'not Seattle'}) + .end((err) => { + // NOTE: incorrect comparison. + expect(err.status).to.equal(500); + done(); + }); + }); + }); + }); + + + describe('POST: /api/place', () => { + describe('with valid input', function(){ + after((done) => { + if(this.tempList){ + Place.findById(this.tempList._id).remove() + .then(() => done()) + .catch(done); + return; + } + done(); + }); + it('Should post and return a place', (done) => { + superagent.post(`${url}/api/place`) + .send(testPlace) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.place).to.equal('Seattle'); + this.tempList = res.body; + done(); + }); + }); + }); + describe('with invalid body input', function(){ + after((done) => { + if(this.tempList){ + Place.findById(this.tempList._id).remove() + .then(() => done()) + .catch(done); + return; + } + done(); + }); + it('Should return a status of 404', (done) => { + superagent.post(`${url}/api/place`) + // no body + .end((err) => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + }); + describe('DELETE: /api/place/:id', () => { + describe('with valid input', function(){ + before((done) => { + testPlace.timestamp = new Date(); + new Place(testPlace).save() + .then((place) => { + this.tempList = place; + done(); + }) + .catch(done); + }); + it('Should remove a place', (done) => { + superagent.delete(`${url}/api/place/${this.tempList._id}`) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + done(); + }); + }); + }); + describe('with invalid id input', function(){ + it('Should return a status of 404', (done) => { + superagent.delete(`${url}/api/place/invalidId`) + .end((err) => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + }); +});