From 9aded3ddfb959e747166163e500a1fd47f763acf Mon Sep 17 00:00:00 2001 From: Loaye Date: Wed, 9 Aug 2017 18:32:07 -0700 Subject: [PATCH 1/2] finishes test --- .gitignore | 2 + README.md | 55 ------- data/cubone.jpg | Bin 0 -> 7888 bytes lib/basic-auth-middleware.js | 36 +++++ lib/bearer-auth-middleware.js | 34 +++++ lib/error-middleware.js | 28 ++++ model/gallery.js | 13 ++ model/pokemon.js | 16 ++ model/user.js | 74 +++++++++ package.json | 42 +++++ route/auth-router.js | 34 +++++ route/gallery-router.js | 280 ++++++++++++++++++++++++++++++++++ route/pokemon-router.js | 68 +++++++++ server.js | 29 ++++ test/auth-route-test.js | 72 +++++++++ test/gallery-route-test.js | 113 ++++++++++++++ test/pokemon-route-test.js | 280 ++++++++++++++++++++++++++++++++++ 17 files changed, 1121 insertions(+), 55 deletions(-) create mode 100644 .gitignore create mode 100644 data/cubone.jpg create mode 100644 lib/basic-auth-middleware.js create mode 100644 lib/bearer-auth-middleware.js create mode 100644 lib/error-middleware.js create mode 100644 model/gallery.js create mode 100644 model/pokemon.js create mode 100644 model/user.js create mode 100644 package.json create mode 100644 route/auth-router.js create mode 100644 route/gallery-router.js create mode 100644 route/pokemon-router.js create mode 100644 server.js create mode 100644 test/auth-route-test.js create mode 100644 test/gallery-route-test.js create mode 100644 test/pokemon-route-test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37d7e73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env diff --git a/README.md b/README.md index 01534c8..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,55 +0,0 @@ -![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) 18: Image Uploads w/ AWS S3 -=== - -## Submission Instructions - * fork this repository & create a new branch for your work - * write all of your code in a directory named `lab-` + `` **e.g.** `lab-susan` - * push to your repository - * submit a pull request to this repository - * submit a link to your PR in canvas - * write a question and observation on canvas - -## Learning Objectives -* students will be able to upload static assets to AWS S3 -* students will be able to retrieve a cdn url that contains the previously uploaded static asset -* students will be able to work with secret and public access keys - -## Requirements -#### Configuration -* `package.json` -* `.eslintrc` -* `.gitignore` -* `README.md` - -#### Description -* create an AWS account -* create an AWS Access Key and Secret - * add the Access Key and Secret to your `.env` file -* create a new model that represents a file type that you want to store on AWS S3 - * ex: `.mp3`, `.mp4`, `.png`, etc -* create a test that uploads one of these files to your route -* use the `aws-sdk` to assist with uploading -* use `multer` to parse the file upload request - -#### Server Endpoint -* `POST` - `/api/resource/:resourceID/new-resource` - -#### Tests -* `POST` - **200** - test that the upload worked and a resource object is returned - -#### Bonus -* `DELETE` route - `/api/resource/:resourceID/new-resource/:new-resourceID` -* Test: `DELETE` - **204** - test to ensure the object was deleted from s3 - -#### Bonus: 3pts -* try using the `deleteObject` method provided by the `aws-sdk` to delete an object *(file)* from S3 - * you will need to pass in a `params` object that contains the associated Bucket and AWS object key in order to delete the object from s3 - * ex: - ``` javascript - var params = { - Bucket: 's3-bucket-name', - Key: 'object-filename' - } - s3.deleteObject(params) - ``` -* don't forget to remove the resource from the DB diff --git a/data/cubone.jpg b/data/cubone.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e4a2d97c354d3a4d2205c6a484d2c6b82e402c3 GIT binary patch literal 7888 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 GSN{jc`BOpw literal 0 HcmV?d00001 diff --git a/lib/basic-auth-middleware.js b/lib/basic-auth-middleware.js new file mode 100644 index 0000000..694b953 --- /dev/null +++ b/lib/basic-auth-middleware.js @@ -0,0 +1,36 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('pokegram:basic-auth-middleware'); + +module.exports = function(req, res, next) { + debug('basic auth'); + + var authHeader = req.headers.authorization; + if (!authHeader) { + return next(createError(401, 'authorization header required')); + } + + var base64str = authHeader.split('Basic ')[1]; + if (!base64str) { + return next(createError(401, 'username and password required')); + } + + var utf8str = new Buffer(base64str, 'base64').toString(); + var authArr = utf8str.split(':'); + + req.auth = { + username: authArr[0], + password: authArr[1] + } + + if (!req.auth.username) { + return next(createError(401, 'username required')); + } + + if (!req.auth.password) { + return next(createError(401, 'password required')); + } + + next(); +} \ No newline at end of file diff --git a/lib/bearer-auth-middleware.js b/lib/bearer-auth-middleware.js new file mode 100644 index 0000000..4775d0d --- /dev/null +++ b/lib/bearer-auth-middleware.js @@ -0,0 +1,34 @@ +'use strict'; + +const jwt = require('jsonwebtoken'); +const createError = require('http-errors'); +const debug = require('debug')('pokegram:bearer-auth-middleware'); + +const User = require('../model/user.js'); + +module.exports = function(req, res, next) { + debug('bearer auth'); + + var authHeader = req.headers.authorization; + if(!authHeader) { + return next(createError(401, 'authorization header required')); + } + + var token = authHeader.split('Bearer ')[1]; + if(!token) { + return next(createError(401, 'token required')); + } + + jwt.verify(token, process.env.APP_SECRET, (err, decoded) => { + if(err) return next(err); + + User.findOne({findHash: decoded.token}) + .then(user => { + req.user = user; + next(); + }) + .catch(err => { + next(createError(401, err.message)); + }); + }); +}; \ No newline at end of file diff --git a/lib/error-middleware.js b/lib/error-middleware.js new file mode 100644 index 0000000..73b8277 --- /dev/null +++ b/lib/error-middleware.js @@ -0,0 +1,28 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('pokegram:error-middleware'); + +module.exports = function(err, req, res, next) { + debug('error middleware'); + + console.error('message:', err.message); + console.error('name:', err.name); + + if (err.status) { + res.status(err.status).send(err.name); + next(); + return; + } + + if (err.name === 'ValidationError') { + err = createError(400, err.message); + res.status(err.status).send(err.name); + next(); + return; + } + + err = createError(500, err.message); + res.status(err.status).send(err.name); + next(); +} \ No newline at end of file diff --git a/model/gallery.js b/model/gallery.js new file mode 100644 index 0000000..860cf10 --- /dev/null +++ b/model/gallery.js @@ -0,0 +1,13 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const gallerySchema = Schema({ + name: {type: String, required: true}, + description: {type: String, required: true}, + created: {type: Date, required: true, default: Date.now}, + userID: {type: Schema.Types.ObjectId, required: true} +}); + +module.exports = mongoose.model('gallery', gallerySchema); \ No newline at end of file diff --git a/model/pokemon.js b/model/pokemon.js new file mode 100644 index 0000000..818768b --- /dev/null +++ b/model/pokemon.js @@ -0,0 +1,16 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const pokemonSchema = Schema({ + name: { type: String, required: true }, + year: { type: String, required: true }, + userID: { type: Schema.Types.ObjectId, required: true }, + pokemonID: { type: Schema.Types.ObjectId, required: true }, + audioURI: { type: String, required: true, unique: true }, + objectKey: { type: String, required: true, unique: true }, + created: { type: Date, default: Date.now } +}); + +module.exports = mongoose.model('pokemon', pokemonSchema); \ No newline at end of file diff --git a/model/user.js b/model/user.js new file mode 100644 index 0000000..ee55e25 --- /dev/null +++ b/model/user.js @@ -0,0 +1,74 @@ +'use strict'; + +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const mongoose = require('mongoose'); +const createError = require('http-errors'); +const Promise = require('bluebird'); +const debug = require('debug')('pokegram:user'); + +const Schema = mongoose.Schema; + +const userSchema = Schema({ + username: { type: String, required: true, unique: true }, + email: { type: String, required: true, unique: true }, + password: { type: String, required: true }, + findHash: { type: String, unique: true } +}); + +userSchema.methods.generatePasswordHash = function(password) { + debug('generatePasswordHash'); + + return new Promise((resolve, reject) => { + bcrypt.hash(password, 10, (err, hash) => { + if(err) return reject(err); + this.password = hash; + resolve(this); + }); + }); +} + +userSchema.methods.comparePasswordHash = function(password) { + debug('comparePasswordHash'); + return new Promise((resolve, reject) => { + bcrypt.compare(password, this.password, (err, valid) => { + if(err) return reject(err); + if(!valid) return reject(createError(401, 'invalid password')); + resolve(this); + }); + }); +} + +userSchema.methods.generateFindHash = function() { + debug('generateFindHash'); + + return new Promise((resolve, reject) => { + let tries = 0; + + _generateFindHash.call(this); + + function _generateFindHash() { + this.findHash = crypto.randomBytes(32).toString('hex'); + this.save() + .then(() => resolve(this.findHash)) + .catch(err => { + if (tries > 3) return reject(err); + tries++; + _generateFindHash.call(this); + }); + } + }); +} + +userSchema.methods.generateToken = function() { + debug('generateToken'); + + return new Promise((resolve, reject) => { + this.generateFindHash() + .then(findHash => resolve(jwt.sign({ token: findHash }, process.env.APP_SECRET))) + .catch(err => reject(err)); + }); +} + +module.exports = mongoose.model('user', userSchema); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..736dd9a --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "17-bearer-auth", + "version": "1.0.0", + "description": "![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) 17: Bearer Auth ===", + "main": "server.js", + "directories": { + "test": "test" + }, + "dependencies": { + "bcrypt": "^1.0.2", + "bluebird": "^3.5.0", + "body-parser": "^1.17.2", + "cors": "^2.8.4", + "debug": "^2.6.8", + "dotenv": "^4.0.0", + "express": "^4.15.4", + "http-errors": "^1.6.2", + "jsonwebtoken": "^7.4.2", + "mongoose": "^4.11.6", + "morgan": "^1.8.2", + }, + "devDependencies": { + "chai": "^4.1.1", + "mocha": "^3.5.0", + "superagent": "^3.5.2" + }, + "scripts": { + "test": "DEBUG='pokemon*' mocha", + "start": "DEBUG='pokemon*' node server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Loaye/17-bearer-auth.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/Loaye/17-bearer-auth/issues" + }, + "homepage": "https://github.com/Loaye/17-bearer-auth#readme" +} diff --git a/route/auth-router.js b/route/auth-router.js new file mode 100644 index 0000000..6937fb7 --- /dev/null +++ b/route/auth-router.js @@ -0,0 +1,34 @@ +'use strict'; + +const jsonParser = require('body-parser').json(); +const debug = require('debug')('pokegram:auth-router'); +const Router = require('express').Router; +const basicAuth = require('../lib/basic-auth-middleware.js'); +const User = require('../model/user.js'); + +const authRouter = module.exports = Router(); + +authRouter.post('/api/signup', jsonParser, function(req, res, next) { + debug('POST: /api/signup'); + + let password = req.body.password; + delete req.body.password; + + let user = new User(req.body); + + user.generatePasswordHash(password) + .then( user => user.save()) + .then( user => user.generateToken()) + .then( token => res.send(token)) + .catch(next); +}); + +authRouter.get('/api/signin', basicAuth, function(req, res, next) { + debug('GET: /api/signin'); + + User.findOne({ username: req.auth.username }) + .then( user => user.comparePasswordHash(req.auth.password)) + .then( user => user.generateToken()) + .then( token => res.send(token)) + .catch(next); +}); \ No newline at end of file diff --git a/route/gallery-router.js b/route/gallery-router.js new file mode 100644 index 0000000..08f69c0 --- /dev/null +++ b/route/gallery-router.js @@ -0,0 +1,280 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); + +const User = require('../model/user.js'); +const Pokemon = require('../model/pokemon.js'); + +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'exampleruser', + password: '12345', + email: 'example@test.com' +}; + +const examplePokemon = { + name: 'example name', + type: 'example type', + gen: 'example gen' +}; + +describe('Pokemon Routes', function() { + afterEach(done => { + Promise.all([ + User.remove({}), + Pokemon.remove({}) + ]) + .then( () => done()) + .catch(done); + }); + + describe('POST: /api/pokemon', () => { + beforeEach(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + it('should return a pokemon', done => { + request.post(`${url}/api/pokemon`) + .send(examplePokemon) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.body.name).to.equal(examplePokemon.name); + expect(res.body.type).to.equal(examplePokemon.genre); + expect(res.body.gen).to.equal(examplePokemon.gen); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + + it('should return 401', done => { + request.post(`${url}/api/pokemon`) + .send(examplePokemon) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + + it('should return 400', done => { + request.post(`${url}/api/pokemon`) + .send({ name: 'fakename', type: 'faketype' }) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('GET: /api/pokemon/:id', () => { + before(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + before(done => { + examplePokemon.userID = this.tempUser._id.toString(); + new Pokemon(examplePokemon).save() + .then(pokemon => { + this.tempPokemon = pokemon; + done(); + }) + .catch(done); + }); + + after(() => { + delete examplePokemon.userID; + }); + + it('should return a pokemon', done => { + request.get(`${url}/api/pokemon/${this.tempPokemon._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.body.name).to.equal(examplePokemon.name); + expect(res.body.gen).to.equal(examplePokemon.gen); + expect(res.body.type).to.equal(examplePokemon.type); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + + it('should return 404', done => { + request.get(`${url}/api/pokemon`) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + + it('should return 401', done => { + request.get(`${url}/api/pokemon/${this.tempPokemon._id}`) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('PUT: /api/pokemon/:id', function() { + beforeEach(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + beforeEach(done => { + examplePokemon.userID = this.tempUser._id.toString(); + new Pokemon(examplePokemon).save() + .then(pokemon => { + this.tempPokemon = pokemon; + done(); + }) + .catch(done); + }); + + after( () => { + delete examplePokemon.userID; + }); + + it('should return a pokemon', done => { + request.put(`${url}/api/pokemon/${this.tempPokemon._id}`) + .send({ name: 'new pokemon name' }) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('new pokemon name'); + done(); + }); + }); + + it('should return 404', done => { + request.put(`${url}/api/pokemon`) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + + it('should return 401', done => { + request.put(`${url}/api/pokemon/${this.tempPokemon._id}`) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + + it('should return 400', done => { + request.post(`${url}/api/pokemon`) + .send({ name: 'fakename', type: 'faketype' }) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('DELETE: /api/pokemon/:id', function() { + beforeEach(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + beforeEach(done => { + examplePokemon.userID = this.tempUser._id.toString(); + new Pokemon(examplePokemon).save() + .then(pokemon => { + this.tempPokemon = pokemon; + done(); + }) + .catch(done); + }); + + it('should return 204', done => { + request.delete(`${url}/api/pokemon/${this.tempPokemon._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + + it('should return 404', done => { + request.delete(`${url}/api/pokemon`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + + it('should return 401', done => { + request.delete(`${url}/api/pokemon/${this.tempPokemon._id}`) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/route/pokemon-router.js b/route/pokemon-router.js new file mode 100644 index 0000000..163b3e6 --- /dev/null +++ b/route/pokemon-router.js @@ -0,0 +1,68 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const del = require('del'); +const AWS = require('aws-sdk'); +const multer = require('multer'); +const Router = require('express').Router; +const createError = require('http-errors'); +const debug = require('debug')('pokegram:pokemon-router'); + +const Pokemon = require('../model/pokemon.js'); +const Gallery = require('../model/gallery.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +AWS.config.setPromisesDependency(require('bluebird')); + +const s3 = new AWS.S3(); +const dataDir = `${__dirname}/../data`; +const upload = multer({dest: dataDir}); + +const pokemonRouter = module.exports = Router(); + +function s3uploadProm(params) { + return new Promise((resolve, reject) => { + s3.upload(params, (err, s3data) => { + resolve(s3data); + }); + }); +} + +pokemonRouter.post('/api/gallery/:galleryId/pokemon', bearerAuth, upload.single('image'), function(req, res, next) { + debug('POST: /api/gallery/:galleryID/pokemon'); + + if(!req.file) { + return next(createError(404, 'file not found')); + } + + if(!req.file.path) { + return next(createError(500, 'file not saved')); + } + + let ext = path.extname(req.file.originalName); + + let params = { + ACL: 'public-read', + Bucket: process.env.AWS_BUCKET, + Key: `${req.file.filename}${ext}`, + Body: fs.createReadStream(req.file.path), + }; + + Gallery.findById(req.params.galleryID) + .then(() => s3uploadProm(params)) + .then(s3data => { + del(['${dataDir}/*']); + let pokemonData = { + name: req.body.name, + description: req.body.description, + objectKey: s3data.Key, + imageURI: s3data.Location, + userID: req.user._id, + galleryID: req.params.galleryID, + } + return new Pokemon(pokemonData).save(); + }) + .then(pokemon => res.json(pokemon)) + .catch(err => next(err)); +}); \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..b3c0cec --- /dev/null +++ b/server.js @@ -0,0 +1,29 @@ +'use strict'; + +const express = require('express'); +const debug = require('debug')('poke:server'); +const dotenv = require('dotenv'); +const mongoose = require('mongoose'); +const morgan = require('morgan'); +const cors = require('cors'); + +const authRouter = require('./route/auth-route.js'); +const pokemonRouter = require('./route/pokemon-route.js'); +const errors = require('./lib/error-middleware.js'); + +dotenv.load(); + +const app = express(); +const PORT = process.env.PORT || 3000; +mongoose.connect(process.env.MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); + +app.use(authRouter); +app.use(pokemonRouter); +app.use(errors); + +app.listen(PORT, () => { + debug(`Listening on: ${PORT}`); +}); \ No newline at end of file diff --git a/test/auth-route-test.js b/test/auth-route-test.js new file mode 100644 index 0000000..9b0e0c8 --- /dev/null +++ b/test/auth-route-test.js @@ -0,0 +1,72 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const mongoose = require('mongoose'); +const Promise = require('bluebird'); +const User = require('../model/user.js'); + +require('../server.js'); + +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'exampleuser', + password: '1234', + email: 'exampleuser@test.com' +} + +describe('Auth Routes', function() { + describe('POST: /api/signup', function() { + describe('with a valid body', function() { + after(done => { + User.remove({}) + .then(() => done()) + .catch(done); + }); + + it('should return a token', done => { + request.post(`${url}/api/signup`) + .send(exampleUser) + .end((err, res) => { + if (err) return done(err); + console.log('POST: /api/signup TOKEN:', res.text, '\n'); + expect(res.status).to.equal(200); + expect(res.text).to.be.a('string'); + done(); + }); + }); + }); + }); + + describe('GET: /api/signin', function() { + describe('with a valid body', function() { + before( done => { + let user = new User(exampleUser); + user.generatePasswordHash(exampleUser.password) + .then( user => user.save()) + .then( user => { + this.tempUser = user; + done(); + }); + }); + + after( done => { + User.remove({}) + .then( () => done()) + .catch(done); + }); + + it('should return a token', done => { + request.get(`${url}/api/signin`) + .auth('exampleuser', '1234') + .end((err, res) => { + console.log('signed in user:', this.tempUser); + console.log('GET: /api/signin TOKEN:', res.text); + expect(res.status).to.equal(200); + done(); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/gallery-route-test.js b/test/gallery-route-test.js new file mode 100644 index 0000000..92567ba --- /dev/null +++ b/test/gallery-route-test.js @@ -0,0 +1,113 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); + +const User = require('../model/user.js'); +const Gallery = require('../model/gallery.js'); + +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'exampleuser', + password: '12345', + email: 'exampleuser@test.com' +}; + +const exampleGallery = { + name: 'test gallery', + description: 'test gallery description', +}; + +describe('Gallery Routes', function() { + afterEach(done => { + promise.all([ + User.remove({}), + Gallery.remove({}) + ]) + .then(() => done()) + .catch(done); + }); + + describe('POST: /api/gallery', () => { + before(done => { + new User(exampleUser) + .generatePasswordHash(exampleGallery.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + }) + .catch(done); + }) + + it('should return a gallery', done => { + request.post(`${url}/api/gallery`) + .send(exampleGallery) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + let date = new Date(res.body.created).toString(); + expect(res.body.name).to.equal(exampleGallery.name); + expect(res.body.description).to.equal(exampleGallery.description); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); + + describe('GET: /api/gallery/:id', () => { + before(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken() + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }) + + before(done => { + exampleGallery.userID = this.tempUser._id.toString(); + new Gallery(exampleGallery).save() + .then(gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + + after(() => { + delete exampleGallery.userID; + }); + + it('should return a gallery', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + let date = new Date(res.body.created).toString(); + expect(res.body.name).to.equal(exampleGallery.name); + expect(res.body.description).to.equal(exampleGallery.description); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/pokemon-route-test.js b/test/pokemon-route-test.js new file mode 100644 index 0000000..2f3df94 --- /dev/null +++ b/test/pokemon-route-test.js @@ -0,0 +1,280 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const Promise = require('bluebird'); + +const User = require('../model/user.js'); +const Pokemon = require('../model/pokemon.js'); + +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'exampleruser', + password: '12345', + email: 'example@test.com' +}; + +const examplePokemon = { + name: 'example name', + type: 'example type', + gen: 'example gen' +}; + +describe('Pokemon Routes', function() { + afterEach(done => { + Promise.all([ + User.remove({}), + Pokemon.remove({}) + ]) + .then(() => done()) + .catch(done); + }); + + describe('POST: /api/pokemon', () => { + beforeEach(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + it('should return a pokemon', done => { + request.post(`${url}/api/pokemon`) + .send(examplePokemon) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.name).to.equal(examplePokemon.name); + expect(res.body.type).to.equal(examplePokemon.type); + expect(res.body.gen).to.equal(examplePokemon.gen); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + + it('should return 401', done => { + request.post(`${url}/api/pokemon`) + .send(examplePokemon) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + + it('should return 400', done => { + request.post(`${url}/api/pokemon`) + .send({ name: 'fakename', type: 'faketype' }) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('GET: /api/pokemon/:id', () => { + before(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + before(done => { + examplePokemon.userID = this.tempUser._id.toString(); + new Pokemon(examplePokemon).save() + .then(pokemon => { + this.tempPokemon = pokemon; + done(); + }) + .catch(done); + }); + + after(() => { + delete examplePokemon.userID; + }); + + it('should return a pokemon', done => { + request.get(`${url}/api/pokemon/${this.tempPokemon._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.body.name).to.equal(examplePokemon.name); + expect(res.body.type).to.equal(examplePokemon.type); + expect(res.body.gen).to.equal(examplePokemon.gen); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + + it('should return 404', done => { + request.get(`${url}/api/pokemon`) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + + it('should return 401', done => { + request.get(`${url}/api/pokemon/${this.tempPokemon._id}`) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + + describe('PUT: /api/pokemon/:id', function() { + beforeEach(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + beforeEach(done => { + examplePokemon.userID = this.tempUser._id.toString(); + new Pokemon(examplePokemon).save() + .then(pokemon => { + this.tempPokemon = pokemon; + done(); + }) + .catch(done); + }); + + after(() => { + delete examplePokemon.userID; + }); + + it('should return a pokemon', done => { + request.put(`${url}/api/pokemon/${this.tempPokemon._id}`) + .send({ name: 'new pokemon name' }) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('new pokemon name'); + done(); + }); + }); + + it('should return 404', done => { + request.put(`${url}/api/pokemon`) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + + it('should return 401', done => { + request.put(`${url}/api/pokemon/${this.tempPokemon._id}`) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + + it('should return 400', done => { + request.post(`${url}/api/pokemon`) + .send({ name: 'fakename', type: 'faketype' }) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + + describe('DELETE: /api/pokemon/:id', function() { + beforeEach(done => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .then(user => user.save()) + .then(user => { + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + beforeEach(done => { + examplePokemon.userID = this.tempUser._id.toString(); + new Pokemon(examplePokemon).save() + .then(pokemon => { + this.tempPokemon = pokemon; + done(); + }) + .catch(done); + }); + + it('should return 204', done => { + request.delete(`${url}/api/pokemon/${this.tempPokemon._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + + it('should return 404', done => { + request.delete(`${url}/api/pokemon`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + + it('should return 401', done => { + request.delete(`${url}/api/pokemon/${this.tempPokemon._id}`) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); +}); \ No newline at end of file From 8dd61024beab7e712bda795bcbd54cb573cac1d6 Mon Sep 17 00:00:00 2001 From: Loaye Date: Wed, 9 Aug 2017 18:34:12 -0700 Subject: [PATCH 2/2] adds gallery router --- server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.js b/server.js index b3c0cec..4df253d 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ const morgan = require('morgan'); const cors = require('cors'); const authRouter = require('./route/auth-route.js'); +const galleryRouter = require('./route/gallery-router.js'); const pokemonRouter = require('./route/pokemon-route.js'); const errors = require('./lib/error-middleware.js'); @@ -21,6 +22,7 @@ app.use(cors()); app.use(morgan('dev')); app.use(authRouter); +app.use(galleryRouter); app.use(pokemonRouter); app.use(errors);