From 257abf69ff9c004f2ae47324cdc2512084e0e9bb Mon Sep 17 00:00:00 2001 From: caylazabel Date: Mon, 6 Mar 2017 12:58:54 -0800 Subject: [PATCH 01/11] basic submissions --- .env | 0 .eslintrc | 21 ++++++ .gitignore | 120 +++++++++++++++++++++++++++++++++++ gulpfile.js | 23 +++++++ lib/basic-auth-middleware.js | 0 lib/error-middleware.js | 0 package.json | 24 +++++++ server.js | 0 8 files changed, 188 insertions(+) create mode 100644 .env create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 gulpfile.js create mode 100644 lib/basic-auth-middleware.js create mode 100644 lib/error-middleware.js create mode 100644 package.json create mode 100644 server.js diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 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..989f200 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# Created by https://www.gitignore.io/api/node,vim,osx,macos,linux + +*node_modules + +#ignore data files +data/ + +### Node ### +# 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-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + + +### OSX ### +*.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 + + +### macOS ### +# Icon must end with two \r +# Thumbnails +# Files that might appear in the root of a volume +# Directories potentially created on remote AFP share + + +### 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* + +# End of https://www.gitignore.io/api/node,vim,osx,macos,linux diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..032c482 --- /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() { + return 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/basic-auth-middleware.js b/lib/basic-auth-middleware.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/error-middleware.js b/lib/error-middleware.js new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..54ce830 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "16-basic_auth", + "version": "1.0.0", + "description": "![cf](https://i.imgur.com/7v5ASc8.png) Lab 16 - Basic Auth ======", + "main": "gulpfile.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "DEBUG='cfgram*' mocha", + "start": "DEBUG='cfgram*' node server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/caylazabel/16-basic_auth.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/caylazabel/16-basic_auth/issues" + }, + "homepage": "https://github.com/caylazabel/16-basic_auth#readme" +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..e69de29 From 03a339ea7af5443fb7b59f1556463a88966169aa Mon Sep 17 00:00:00 2001 From: caylazabel Date: Mon, 6 Mar 2017 14:52:55 -0800 Subject: [PATCH 02/11] pushing newest things up --- test/auth-route-test.js | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test/auth-route-test.js diff --git a/test/auth-route-test.js b/test/auth-route-test.js new file mode 100644 index 0000000..9a2a14a --- /dev/null +++ b/test/auth-route-test.js @@ -0,0 +1,73 @@ +'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: 'test user', + password: 'test password', + email: 'testemail@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('token:', res.text); + 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(); + }) + .catch(done); + }); + + after( done => { + User.remove({}) + .then( () => done()) + .catch(done); + }); + + it('should return a token', done => { + request.get(`${url}/api/signin`) + .auth('test user', 'test password') + .end((err, res) => { + if(err) return done(err); + console.log('temporary user:', this.tempUser); + console.log('GET: /api/signin token', res.text); + expect(res.status).to.equal(200); + done(); + }); + }); + }); + }); +}); From ee02c8aae503ffa913d5a868b97b912d2e6a1989 Mon Sep 17 00:00:00 2001 From: caylazabel Date: Mon, 6 Mar 2017 17:15:03 -0800 Subject: [PATCH 03/11] 404 test passing --- .env | 2 + lib/basic-auth-middleware.js | 35 +++++++++++++++++ lib/error-middleware.js | 27 +++++++++++++ model/user.js | 75 ++++++++++++++++++++++++++++++++++++ package.json | 19 ++++++++- route/auth-router.js | 35 +++++++++++++++++ server.js | 30 +++++++++++++++ test/auth-route-test.js | 23 +++++++++++ 8 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 model/user.js create mode 100644 route/auth-router.js diff --git a/.env b/.env index e69de29..9c54e1e 100644 --- a/.env +++ b/.env @@ -0,0 +1,2 @@ +MONGODB_URI='mongodb://localhost/cfgram' +APP_SECRETS='thesecret' diff --git a/lib/basic-auth-middleware.js b/lib/basic-auth-middleware.js index e69de29..cc4bad3 100644 --- a/lib/basic-auth-middleware.js +++ b/lib/basic-auth-middleware.js @@ -0,0 +1,35 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('cfgram: 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(); +}; diff --git a/lib/error-middleware.js b/lib/error-middleware.js index e69de29..6a7d5bf 100644 --- a/lib/error-middleware.js +++ b/lib/error-middleware.js @@ -0,0 +1,27 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('cfgram:error-middleware'); + +module.exports = function(err, req, res, next){ + debug('error middleware'); + console.error('msg:', 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(); +}; diff --git a/model/user.js b/model/user.js new file mode 100644 index 0000000..ba20d18 --- /dev/null +++ b/model/user.js @@ -0,0 +1,75 @@ +'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')('cfgram: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, 'wrong 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_SECRETS))) + .catch( err => reject(err)); + }); +}; + +module.exports = mongoose.model('user', userSchema); diff --git a/package.json b/package.json index 54ce830..e255670 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,22 @@ "bugs": { "url": "https://github.com/caylazabel/16-basic_auth/issues" }, - "homepage": "https://github.com/caylazabel/16-basic_auth#readme" + "homepage": "https://github.com/caylazabel/16-basic_auth#readme", + "devDependencies": { + "chai": "^3.5.0", + "mocha": "^3.2.0", + "superagent": "^3.5.0" + }, + "dependencies": { + "bcrypt": "^1.0.2", + "bluebird": "^3.5.0", + "body-parser": "^1.17.1", + "cors": "^2.8.1", + "debug": "^2.6.1", + "dotenv": "^4.0.0", + "http-errors": "^1.6.1", + "jsonwebtoken": "^7.3.0", + "mongoose": "^4.8.6", + "morgan": "^1.8.1" + } } diff --git a/route/auth-router.js b/route/auth-router.js new file mode 100644 index 0000000..842220d --- /dev/null +++ b/route/auth-router.js @@ -0,0 +1,35 @@ +'use strict'; + +const jsonParser = require('body-parser').json(); +const debug = require('debug')('cfgram: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); +}); diff --git a/server.js b/server.js index e69de29..931b7c1 100644 --- a/server.js +++ b/server.js @@ -0,0 +1,30 @@ +'use strict'; + +const express = require('express'); +const cors = require('cors'); +const dotenv = require('dotenv'); +const morgan = require('morgan'); +const mongoose = require('mongoose'); +const Promise = require('bluebird'); +const debug = require('debug')('cfgram:server'); + +const authRouter = require('./route/auth-router.js'); +const errors = require('./lib/error-middleware.js'); + +dotenv.load(); + +const PORT = process.env.PORT || 3000; +const app = express(); + +mongoose.connect(process.env.MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); + +app.use(authRouter); +app.use(errors); + + +app.listen(PORT, () => { + debug(`server is up: ${PORT}`); +}); diff --git a/test/auth-route-test.js b/test/auth-route-test.js index 9a2a14a..183116f 100644 --- a/test/auth-route-test.js +++ b/test/auth-route-test.js @@ -38,6 +38,18 @@ describe('Auth Routes', function() { }); }); + describe('with invalid body', function(){ + it('should return a 400 for bad request', (done) => { + request.post(`${url}/api/signup`) + .send('hi') + .set('Content-Type', 'application/json') + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + describe('GET: /api/signin', function() { describe('with a valid body', function() { before ( done => { @@ -68,6 +80,17 @@ describe('Auth Routes', function() { done(); }); }); + + describe('without a valid authenication', function () { + it('should return a 401 error', done => { + request.get(`${url}/api/signin`) + .auth('test user', 'wrong password') + .end(err => { + expect(err.status).to.equal(401); + done(); + }); + }); + }); }); }); }); From d470c9f1b8347b16586c8395f38e769279f11aaf Mon Sep 17 00:00:00 2001 From: caylazabel Date: Tue, 7 Mar 2017 17:40:51 -0800 Subject: [PATCH 04/11] get and post tests passing --- .gitignore | 3 + lib/basic-auth-middleware.js | 3 + lib/bearer-auth-middleware.js | 37 ++++++ model/gallery.js | 13 ++ model/user.js | 3 + route/gallery-router.js | 49 +++++++ server.js | 2 + test/gallery-route-test.js | 240 ++++++++++++++++++++++++++++++++++ 8 files changed, 350 insertions(+) create mode 100644 lib/bearer-auth-middleware.js create mode 100644 model/gallery.js create mode 100644 route/gallery-router.js create mode 100644 test/gallery-route-test.js diff --git a/.gitignore b/.gitignore index 989f200..fa0434f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ #ignore data files data/ +#ignore tokens +.env/ + ### Node ### # Logs logs diff --git a/lib/basic-auth-middleware.js b/lib/basic-auth-middleware.js index cc4bad3..c41452a 100644 --- a/lib/basic-auth-middleware.js +++ b/lib/basic-auth-middleware.js @@ -1,5 +1,8 @@ 'use strict'; + +//FOR SIGN UP SIGN IN// + const createError = require('http-errors'); const debug = require('debug')('cfgram: basic-auth-middleware'); diff --git a/lib/bearer-auth-middleware.js b/lib/bearer-auth-middleware.js new file mode 100644 index 0000000..01dbb7b --- /dev/null +++ b/lib/bearer-auth-middleware.js @@ -0,0 +1,37 @@ +'use strict'; + +//FOR ROUTES// + +const jwt = require('jsonwebtoken'); +const createError = require('http-errors') +const debug = require('debug')('cfgram:bearer-auth-middleware'); + +const User = require('../model/user.js'); + +module.exports = function(req, res, next) { + debug('bearer'); + + 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')); + } + + //this token is verified by me with my app secret// + jwt.verify(token, process.env.APP_SECRETS, (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)); + }); + }); +}; diff --git a/model/gallery.js b/model/gallery.js new file mode 100644 index 0000000..0c8d653 --- /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 }, + desc: { type: String, required: true }, + created: { type: Date, required: true, default: Date.now }, + userID: { type: mongoose.Schema.Types.ObjectId, required: true } +}); + +module.exports = mongoose.model('gallery', gallerySchema); diff --git a/model/user.js b/model/user.js index ba20d18..e66d39c 100644 --- a/model/user.js +++ b/model/user.js @@ -1,5 +1,8 @@ 'use strict'; +//PURPOSE OF THIS PAGE: signup and sign in// + + const crypto = require('crypto'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); diff --git a/route/gallery-router.js b/route/gallery-router.js new file mode 100644 index 0000000..691935e --- /dev/null +++ b/route/gallery-router.js @@ -0,0 +1,49 @@ +'use strict'; + +const jsonParser = require('body-parser').json(); +const debug = require('debug')('cfgram:gallery-router'); +const Router = require('express').Router; +const createError = require('http-errors'); + +const Gallery = require('../model/gallery.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +const galleryRouter = module.exports = Router(); + +galleryRouter.post('/api/gallery', bearerAuth, jsonParser, function (req, res, next) { + debug('POST:/api/gallery'); + + //user ID attatched here/ + req.body.userID = req.user._id; + new Gallery(req.body).save() + .then( gallery => res.json(gallery)) + .catch(next); +}); + +galleryRouter.get('/api/gallery/:id', bearerAuth, function(req, res, next){ + debug('GET: /api/gallery/:id'); + + +//this gallery belongs to this user// + Gallery.findById(req.params.id) + .then( gallery => { + if(gallery.userID.toString() !== req.user._id.toString()){ + return next(createError(401, 'invalid user')); + } + res.json(gallery); + }) + .catch(next); +}); + +galleryRouter.put('/api/galley/:id', bearerAuth, jsonParser, function(req, res, next){ + debug('PUT: /api/gallery/:id'); + Gallery.findById(req.params.id) + .then(gallery => { + if(gallery.userID.toString() !== req.user._id.toString()) return(createError(401, 'invalid userId')); + return Gallery.findByIdAndUpdate(req.params.id, req.body, {new: true}); + }) + .then(gallery => { + res.json(gallery); + }) + .catch(err => next(createError(404, err.message))); +}); diff --git a/server.js b/server.js index 931b7c1..9b39cf7 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ const Promise = require('bluebird'); const debug = require('debug')('cfgram:server'); const authRouter = require('./route/auth-router.js'); +const galleryRouter = require('./route/gallery-router.js'); const errors = require('./lib/error-middleware.js'); dotenv.load(); @@ -22,6 +23,7 @@ app.use(cors()); app.use(morgan('dev')); app.use(authRouter); +app.use(galleryRouter); app.use(errors); diff --git a/test/gallery-route-test.js b/test/gallery-route-test.js new file mode 100644 index 0000000..ddb2b1b --- /dev/null +++ b/test/gallery-route-test.js @@ -0,0 +1,240 @@ +'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'); +const Gallery = require('../model/gallery.js'); + +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'test user', + password: 'test password', + email: 'testemail@test.com' +}; + +const exampleGallery = { + name: 'testing gallery', + desc: 'testing gallery description' +}; + +mongoose.Promise = Promise; + +describe('Gallery Routes', function(){ + afterEach( done => { + Promise.all([ + User.remove({}), + Gallery.remove({}) + ]) + .then( () => done()) + .catch(done); + }); + + describe('POST: /api/gallery', () => { + describe('with a valid body',() => { + 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); + }); + + 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); + expect(res.status).to.equal(200); + let date = new Date(res.body.created).toString(); + expect(res.body.name).to.equal(exampleGallery.name); + expect(res.body.desc).to.equal(exampleGallery.desc); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(date).to.not.equal('Invalid date'); + done(); + }); + }); + + describe('with invalid body', function(){ + 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(); + }); + + it('should return a 400 for bad request', done => { + request.post(`${url}/api/gallery`) + .send({}) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .set('Content-Type', 'application/json') + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + + describe('if no token found', () => { + it('should return a 401 status code', done => { + request.post(`${url}/api/gallery`) + .send(exampleGallery) + .set({}) + .end((err, res) => { + expect(res.status).to.equal(401); + 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.status).to.equal(200); + expect(res.body.name).to.equal(exampleGallery.name); + expect(res.body.desc).to.equal(exampleGallery.desc); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(date).to.not.equal('Invalid Date'); + done(); + }); + }); + + describe('with invalid request if id not found', () => { + it('should return a 404 status', done => { + request.get(`${url}/api/gallery/`) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + describe('invalid if no token found', () => { + it('should return a 401 status', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({}) + .end((err, res) => { + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + + describe('PUT: /api/gallery', () => { + describe('valid requests', () => { + 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 200 status code', done => { + let updatedGallery = { + name: 'new name', + desc: 'new description', + }; + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .send(updatedGallery) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.name).to.equal(updatedGallery.name); + expect(res.body.desc).to.equal(updatedGallery.desc); + this.tempGallery = res.body; + done(); + }); + }); + }); + }); +}); From 1510977ed34e8bd3112b46edac801a733efe793f Mon Sep 17 00:00:00 2001 From: caylazabel Date: Tue, 7 Mar 2017 20:24:44 -0800 Subject: [PATCH 05/11] put tests need work --- lib/bearer-auth-middleware.js | 2 +- model/gallery.js | 2 +- route/gallery-router.js | 14 +++++----- server.js | 5 ++-- test/gallery-route-test.js | 51 ++++++++++++++++++++++++++++------- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/lib/bearer-auth-middleware.js b/lib/bearer-auth-middleware.js index 01dbb7b..636c255 100644 --- a/lib/bearer-auth-middleware.js +++ b/lib/bearer-auth-middleware.js @@ -3,7 +3,7 @@ //FOR ROUTES// const jwt = require('jsonwebtoken'); -const createError = require('http-errors') +const createError = require('http-errors'); const debug = require('debug')('cfgram:bearer-auth-middleware'); const User = require('../model/user.js'); diff --git a/model/gallery.js b/model/gallery.js index 0c8d653..f4e6e77 100644 --- a/model/gallery.js +++ b/model/gallery.js @@ -7,7 +7,7 @@ const gallerySchema = Schema({ name: { type: String, required: true }, desc: { type: String, required: true }, created: { type: Date, required: true, default: Date.now }, - userID: { type: mongoose.Schema.Types.ObjectId, required: true } + userID: { type: mongoose.Schema.Types.ObjectId, required: true }, }); module.exports = mongoose.model('gallery', gallerySchema); diff --git a/route/gallery-router.js b/route/gallery-router.js index 691935e..6f7d0ab 100644 --- a/route/gallery-router.js +++ b/route/gallery-router.js @@ -35,15 +35,13 @@ galleryRouter.get('/api/gallery/:id', bearerAuth, function(req, res, next){ .catch(next); }); -galleryRouter.put('/api/galley/:id', bearerAuth, jsonParser, function(req, res, next){ +galleryRouter.put('/api/galley/:id', bearerAuth, jsonParser, function(req, res, next) { debug('PUT: /api/gallery/:id'); - Gallery.findById(req.params.id) - .then(gallery => { - if(gallery.userID.toString() !== req.user._id.toString()) return(createError(401, 'invalid userId')); - return Gallery.findByIdAndUpdate(req.params.id, req.body, {new: true}); - }) - .then(gallery => { + Gallery.findByIdAndUpdate(req.params.id, req.body, {new: true}) + .then( gallery => { + if(gallery.userID.toString() !== req.user._id.toString()) { return next(createError(401, 'invalid user')); + } res.json(gallery); }) - .catch(err => next(createError(404, err.message))); + .catch( err => next(err)); }); diff --git a/server.js b/server.js index 9b39cf7..5d0c9ff 100644 --- a/server.js +++ b/server.js @@ -14,11 +14,12 @@ const errors = require('./lib/error-middleware.js'); dotenv.load(); +mongoose.Promise = Promise; +mongoose.connect(process.env.MONGODB_URI); + const PORT = process.env.PORT || 3000; const app = express(); -mongoose.connect(process.env.MONGODB_URI); - app.use(cors()); app.use(morgan('dev')); diff --git a/test/gallery-route-test.js b/test/gallery-route-test.js index ddb2b1b..b51781b 100644 --- a/test/gallery-route-test.js +++ b/test/gallery-route-test.js @@ -13,12 +13,12 @@ const url = `http://localhost:${process.env.PORT}`; const exampleUser = { username: 'test user', password: 'test password', - email: 'testemail@test.com' + email: 'testemail@test.com', }; const exampleGallery = { name: 'testing gallery', - desc: 'testing gallery description' + desc: 'testing gallery description', }; mongoose.Promise = Promise; @@ -184,8 +184,9 @@ describe('Gallery Routes', function(){ }); }); - describe('PUT: /api/gallery', () => { - describe('valid requests', () => { + describe('PUT: /api/gallery/:id', () => { + describe('with valid requests', () => { + before( done => { new User(exampleUser) .generatePasswordHash(exampleUser.password) @@ -215,7 +216,7 @@ describe('Gallery Routes', function(){ delete exampleGallery.userID; }); - it('should return a 200 status code', done => { + it('should return a gallery', done => { let updatedGallery = { name: 'new name', desc: 'new description', @@ -228,10 +229,42 @@ describe('Gallery Routes', function(){ .end((err, res) => { if(err) return done(err); expect(res.status).to.equal(200); - expect(res.body.userID).to.equal(this.tempUser._id.toString()); - expect(res.body.name).to.equal(updatedGallery.name); - expect(res.body.desc).to.equal(updatedGallery.desc); - this.tempGallery = res.body; + // expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.body.name).to.equal('new name'); + expect(res.body.desc).to.equal('new description'); + // this.tempGallery = res.body; + done(); + }); + }); + }); + describe('with an invalid body', () => { + let updatedGallery = ('string'); + it('should return a 400 status code', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set('Content-Type', 'application/json') + .send(updatedGallery) + .set({ + Authorization: `Bearer ${this.tempToken}`, + }) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); + }); + describe('with no token found', () => { + let updatedGallery = { + name: 'new name', + desc: 'new description', + }; + + it('should return a 401 status', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set('Content-Type', 'application/json') + .send(updatedGallery) + .set({}) + .end((err, res) => { + expect(res.status).to.equal(401); done(); }); }); From bbc70a0129552ccda1c5618a94bb78223489e6b4 Mon Sep 17 00:00:00 2001 From: caylazabel Date: Tue, 7 Mar 2017 22:23:38 -0800 Subject: [PATCH 06/11] passing all tests --- route/gallery-router.js | 13 ++-- server.js | 7 +- test/gallery-route-test.js | 135 +++++++++++++++++++------------------ 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/route/gallery-router.js b/route/gallery-router.js index 6f7d0ab..932bf45 100644 --- a/route/gallery-router.js +++ b/route/gallery-router.js @@ -35,13 +35,16 @@ galleryRouter.get('/api/gallery/:id', bearerAuth, function(req, res, next){ .catch(next); }); -galleryRouter.put('/api/galley/:id', bearerAuth, jsonParser, function(req, res, next) { +galleryRouter.put('/api/gallery/:id', bearerAuth, jsonParser, function(req, res, next) { debug('PUT: /api/gallery/:id'); - Gallery.findByIdAndUpdate(req.params.id, req.body, {new: true}) + + if(!req.body.name) return next(createError(400, 'name required')); + if(!req.body.desc) return next(createError(400, 'description required')); + + Gallery.findByIdAndUpdate(req.params.id, req.body, {new:true}) .then( gallery => { - if(gallery.userID.toString() !== req.user._id.toString()) { return next(createError(401, 'invalid user')); - } + if(!gallery) return next(createError(404, 'gallery not found')); res.json(gallery); }) - .catch( err => next(err)); + .catch(next); }); diff --git a/server.js b/server.js index 5d0c9ff..71b4f7a 100644 --- a/server.js +++ b/server.js @@ -5,20 +5,19 @@ const cors = require('cors'); const dotenv = require('dotenv'); const morgan = require('morgan'); const mongoose = require('mongoose'); -const Promise = require('bluebird'); const debug = require('debug')('cfgram:server'); const authRouter = require('./route/auth-router.js'); const galleryRouter = require('./route/gallery-router.js'); const errors = require('./lib/error-middleware.js'); -dotenv.load(); -mongoose.Promise = Promise; -mongoose.connect(process.env.MONGODB_URI); const PORT = process.env.PORT || 3000; const app = express(); +dotenv.load(); + +mongoose.connect(process.env.MONGODB_URI); app.use(cors()); app.use(morgan('dev')); diff --git a/test/gallery-route-test.js b/test/gallery-route-test.js index b51781b..ec8d87d 100644 --- a/test/gallery-route-test.js +++ b/test/gallery-route-test.js @@ -185,84 +185,85 @@ describe('Gallery Routes', function(){ }); describe('PUT: /api/gallery/:id', () => { - describe('with valid requests', () => { - - 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 => { + new User(exampleUser) + .generatePasswordHash(exampleUser.password) + .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); - }); + before( done => { + this.tempGallery = new Gallery(exampleGallery); + this.tempGallery.userID = this.tempUser._id; + this.tempGallery.save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); - after(() => { - delete exampleGallery.userID; + describe('with a valid token', () => { + describe('with a valid body', () => { + it('should return an updated gallery', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .send({name: 'updatedName', desc: 'updatedDesc'}) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('updatedName'); + expect(res.body.desc).to.equal('updatedDesc'); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); }); - it('should return a gallery', done => { - let updatedGallery = { - name: 'new name', - desc: 'new description', - }; - request.put(`${url}/api/gallery/${this.tempGallery._id}`) - .send(updatedGallery) - .set({ - Authorization: `Bearer ${this.tempToken}`, - }) - .end((err, res) => { - if(err) return done(err); - expect(res.status).to.equal(200); - // expect(res.body.userID).to.equal(this.tempUser._id.toString()); - expect(res.body.name).to.equal('new name'); - expect(res.body.desc).to.equal('new description'); - // this.tempGallery = res.body; - done(); + describe('with an invalid body', () => { + it('should return a 400 error', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .send({wrongName: 'this is wrong', wrongDesc: 'this is also wrong'}) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); }); }); - }); - describe('with an invalid body', () => { - let updatedGallery = ('string'); - it('should return a 400 status code', done => { - request.put(`${url}/api/gallery/${this.tempGallery._id}`) - .set('Content-Type', 'application/json') - .send(updatedGallery) - .set({ - Authorization: `Bearer ${this.tempToken}`, - }) - .end((err, res) => { - expect(res.status).to.equal(400); - done(); + describe('with an invalid gallery id', () => { + it('should return a 404', done => { + request.put(`${url}/api/gallery/`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .send({ name: 'this is wrong', desc: 'this is also wrong'}) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); }); }); }); - describe('with no token found', () => { - let updatedGallery = { - name: 'new name', - desc: 'new description', - }; - it('should return a 401 status', done => { + describe('with an invalid token found', () => { + it('should return a 401 error', done => { request.put(`${url}/api/gallery/${this.tempGallery._id}`) - .set('Content-Type', 'application/json') - .send(updatedGallery) - .set({}) + .set({ + Authorization: 'wrong' + }) + .send({name: 'updatedName', desc: 'updatedDesc'}) .end((err, res) => { expect(res.status).to.equal(401); done(); From 632cb41711d0f3df747e9b6516eeaa28dd1d5a45 Mon Sep 17 00:00:00 2001 From: caylazabel Date: Wed, 8 Mar 2017 09:32:37 -0800 Subject: [PATCH 07/11] newest work --- test/auth-route-test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/auth-route-test.js b/test/auth-route-test.js index 183116f..6938693 100644 --- a/test/auth-route-test.js +++ b/test/auth-route-test.js @@ -2,8 +2,6 @@ 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'); From 1a159960d2847ff38fdd2e59ba6a615638114fcc Mon Sep 17 00:00:00 2001 From: caylazabel Date: Wed, 8 Mar 2017 13:58:18 -0800 Subject: [PATCH 08/11] POST 200 test passing --- .env | 4 ++ .gitignore | 3 -- model/pic.js | 17 +++++++ package.json | 5 +- route/pic-router.js | 68 +++++++++++++++++++++++++ server.js | 6 ++- test/auth-route-test.js | 2 + test/data/tester.png | Bin 0 -> 7690 bytes test/lib/server-toggle.js | 29 +++++++++++ test/pic-route-test.js | 102 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 model/pic.js create mode 100644 route/pic-router.js create mode 100644 test/data/tester.png create mode 100644 test/lib/server-toggle.js create mode 100644 test/pic-route-test.js diff --git a/.env b/.env index 9c54e1e..0b54339 100644 --- a/.env +++ b/.env @@ -1,2 +1,6 @@ +PORT = '3000' MONGODB_URI='mongodb://localhost/cfgram' APP_SECRETS='thesecret' +AWS_BUCKET='cfgrambackend27' +AWS_ACCESS_KEY_ID='AKIAIE4YHFO2B4QGECIA' +AWS_SECRET_ACCESS_KEY='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' diff --git a/.gitignore b/.gitignore index fa0434f..457bb43 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ *node_modules -#ignore data files -data/ - #ignore tokens .env/ diff --git a/model/pic.js b/model/pic.js new file mode 100644 index 0000000..a0313e2 --- /dev/null +++ b/model/pic.js @@ -0,0 +1,17 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const picSchema = Schema({ + name: { type: String, required: true }, + desc: { type: String, required: true }, + userID: { type: Schema.Types.ObjectId, required: true }, + galleryID: { type: Schema.Types.ObjectId, required: true }, + imageURI: { type: String, required: true, unique: true }, + objectKey: { type: String, required: true, unique: true }, + created: { type: Date, default: Date.now } +}); + +//exporting this// +module.exports = mongoose.model('pic', picSchema); diff --git a/package.json b/package.json index e255670..e410f4d 100644 --- a/package.json +++ b/package.json @@ -27,15 +27,18 @@ "superagent": "^3.5.0" }, "dependencies": { + "aws-sdk": "^2.24.0", "bcrypt": "^1.0.2", "bluebird": "^3.5.0", "body-parser": "^1.17.1", "cors": "^2.8.1", "debug": "^2.6.1", + "del": "^2.2.2", "dotenv": "^4.0.0", "http-errors": "^1.6.1", "jsonwebtoken": "^7.3.0", "mongoose": "^4.8.6", - "morgan": "^1.8.1" + "morgan": "^1.8.1", + "multer": "^1.3.0" } } diff --git a/route/pic-router.js b/route/pic-router.js new file mode 100644 index 0000000..90f5f29 --- /dev/null +++ b/route/pic-router.js @@ -0,0 +1,68 @@ +'use strict'; + +const fs = require('fs'); //file system operations +const path = require('path'); //allows to extract info about the file +const del = require('del'); //deleting things in our filesystem// +const AWS = require('aws-sdk'); //constructor allowing uploads +const multer = require('multer'); //hash file name// +const Router = require('express').Router; +const createError = require('http-errors'); +const debug = require('debug')('cfgram:pic-router'); + +const Pic = require('../model/pic.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`; //putting images into a data directory +const upload = multer({ dest: dataDir}); + +const picRouter = module.exports = Router(); + +function s3uploadProm(params){ + debug('s3uploadProm'); + return new Promise((resolve, reject) => { + s3.upload(params, (err, s3data) => { //allows to upload an image, heres info + resolve(s3data); //image uri and access key in s3data + }); + }); +} + +picRouter.post('/api/gallery/:galleryID/pic', bearerAuth, upload.single('image'), function(req, res, next) { + debug('POST: /api/gallery/:galleryID/pic'); + + if(!req.file) { //file is from upload middleware// + return next(createError(400, 'file not found')); + } + if(!req.file.path) { // path is where the file lives + return next(createError(500, 'file not saved')); + } + + let ext = path.extname(req.file.originalname); //will extract extension name(.png) from a file + + let params = { + ACL: 'public-read', //image URI that anyone can see + Bucket: process.env.AWS_BUCKET, //save this image at this bucket + Key: `${req.file.filename}${ext}`, //grabs hashed version of filename & add .png from above + Body: fs.createReadStream(req.file.path) //where we read the image & send + }; + + Gallery.findById(req.params.galleryID) + .then( () => s3uploadProm(params)) + .then( s3data => { + del([`${dataDir}/*`]); + let picData = { + name: req.body.name, + desc: req.body.desc, + objectKey: s3data.Key, + imageURI: s3data.Location, + userID: req.user._id, + galleryID: req.params.galleryID + }; + return new Pic(picData).save(); + }) + .then( pic => res.json(pic)) + .catch( err => next(err)); +}); diff --git a/server.js b/server.js index 71b4f7a..3d1138e 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ const debug = require('debug')('cfgram:server'); const authRouter = require('./route/auth-router.js'); const galleryRouter = require('./route/gallery-router.js'); +const picRouter = require('./route/pic-router.js'); const errors = require('./lib/error-middleware.js'); @@ -24,9 +25,12 @@ app.use(morgan('dev')); app.use(authRouter); app.use(galleryRouter); +app.use(picRouter); app.use(errors); -app.listen(PORT, () => { +const server = module.exports = app.listen(PORT, () => { debug(`server is up: ${PORT}`); }); + + server.isRunning = true; diff --git a/test/auth-route-test.js b/test/auth-route-test.js index 6938693..183116f 100644 --- a/test/auth-route-test.js +++ b/test/auth-route-test.js @@ -2,6 +2,8 @@ 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'); diff --git a/test/data/tester.png b/test/data/tester.png new file mode 100644 index 0000000000000000000000000000000000000000..42969d0bb55e212d8ca51021e50af7a46dd052bd GIT binary patch literal 7690 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 Ef+6 { + server.isRunning = true; + debug('server up!'); + done(); + }); + return; + } + done(); +}; + +exports.serverOff = function(server, done) { + if (server.isRunning) { + server.close( err => { + server.isRunning = false; + debug('server off!'); + done(); + }); + return; + } + done(); +}; diff --git a/test/pic-route-test.js b/test/pic-route-test.js new file mode 100644 index 0000000..632ffc1 --- /dev/null +++ b/test/pic-route-test.js @@ -0,0 +1,102 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const debug = require('debug')('cfgram:pic-router-test'); + +const Pic = require('../model/pic.js'); +const User = require('../model/user.js'); +const Gallery = require('../model/gallery.js'); + +const serverToggle = require('./lib/server-toggle.js'); +const server = require('../server.js'); + +const url = `http://localhost:${process.env.PORT}`; + +const exampleUser = { + username: 'test user', + password: 'test password', + email: 'testemail@test.com', +}; + +const exampleGallery = { + name: 'testing gallery', + desc: 'testing gallery description', +}; + +const examplePic = { + name: 'testing pic', + desc: 'testing pic description', + image: `${__dirname}/data/tester.png` +}; + + +describe('Pic Routes', function() { + before( done => { + serverToggle.serverOn(server, done); + }); + after( done => { + serverToggle.serverOff(server, done); + }); + + afterEach ( done => { + Promise.all([ + Pic.remove({}), + User.remove({}), + Gallery.remove({}) + ]) + .then( () => done()) + .catch(done); + }); + + describe('POST: /api/gallery/:id/pic', function () { + describe('with a valid token and valid data', function() { + 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( done => { + delete exampleGallery.userID; + done(); + }); + + it('should return a pic', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .field('name', examplePic.name) + .field('desc', examplePic.desc) + .attach('image', examplePic.image) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200, 'upload worked'); + expect(res.body.name).to.equal(examplePic.name); + expect(res.body.desc).to.equal(examplePic.desc); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + done(); + }); + }); + }); + }); +}); From 451ebe790fbeea054960562d1fa5c44275dd0f6b Mon Sep 17 00:00:00 2001 From: caylazabel Date: Wed, 8 Mar 2017 16:59:07 -0800 Subject: [PATCH 09/11] tests still passing --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 3d1138e..bd5aa6b 100644 --- a/server.js +++ b/server.js @@ -33,4 +33,4 @@ const server = module.exports = app.listen(PORT, () => { debug(`server is up: ${PORT}`); }); - server.isRunning = true; +server.isRunning = true; From a9da5c8064bef5922bb7801abf710483276b0ba9 Mon Sep 17 00:00:00 2001 From: caylazabel Date: Thu, 9 Mar 2017 09:57:39 -0800 Subject: [PATCH 10/11] added travis --- cf-gram-deploy/.travis.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 cf-gram-deploy/.travis.yml diff --git a/cf-gram-deploy/.travis.yml b/cf-gram-deploy/.travis.yml new file mode 100644 index 0000000..4973e5a --- /dev/null +++ b/cf-gram-deploy/.travis.yml @@ -0,0 +1,19 @@ +language: node_js +node_js: + -'stable' +services: + - mongodb +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.8 + - g++-4.8 +env: + - CXX=g++-4.8 +sudo: required +before_script: npm i +script: + - npm test + - npm lint From 2f68f022337c2ad04aa1fb33d20b63642c8b2fee Mon Sep 17 00:00:00 2001 From: caylazabel Date: Thu, 9 Mar 2017 10:31:38 -0800 Subject: [PATCH 11/11] fixed file errors --- .coveralls.yml | 2 ++ cf-gram-deploy/.travis.yml => .travis.yml | 2 +- package.json | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .coveralls.yml rename cf-gram-deploy/.travis.yml => .travis.yml (93%) diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..f461d81 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,2 @@ +service_name: travis-ci +repo_token: 2uClKoEYjmE6nQADyp3ieRono3IC1uIua diff --git a/cf-gram-deploy/.travis.yml b/.travis.yml similarity index 93% rename from cf-gram-deploy/.travis.yml rename to .travis.yml index 4973e5a..b63954a 100644 --- a/cf-gram-deploy/.travis.yml +++ b/.travis.yml @@ -15,5 +15,5 @@ env: sudo: required before_script: npm i script: - - npm test + - npm run test - npm lint diff --git a/package.json b/package.json index e410f4d..3dd9056 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "test": "DEBUG='cfgram*' mocha", + "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", "start": "DEBUG='cfgram*' node server.js" }, "repository": { @@ -22,7 +23,10 @@ }, "homepage": "https://github.com/caylazabel/16-basic_auth#readme", "devDependencies": { + "aws-sdk-mock": "^1.6.1", "chai": "^3.5.0", + "coveralls": "^2.12.0", + "istanbul": "^0.4.5", "mocha": "^3.2.0", "superagent": "^3.5.0" },