diff --git a/lab-yana/.env b/lab-yana/.env new file mode 100644 index 0000000..54acbf9 --- /dev/null +++ b/lab-yana/.env @@ -0,0 +1,7 @@ +PORT='3003' +MONGODB_URI='mongodb://localhost/cfgram' +APP_SECRET='mysupersekritthingy' +APP_SECRET='uhwhut' +AWS_BUCKET='cfgrambackend42' +AWS_ACCESS_KEY_ID='AKIAIUKO3XB6OQZSFZQA' +AWS_SECRET_ACCESS_KEY='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' diff --git a/lab-yana/.eslintrc b/lab-yana/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/lab-yana/.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/lab-yana/.gitignore b/lab-yana/.gitignore new file mode 100644 index 0000000..850b46c --- /dev/null +++ b/lab-yana/.gitignore @@ -0,0 +1,119 @@ +# Created by https://www.gitignore.io/api/node,vim,osx,macos,linux +*node_modules + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# file containing SUPER SEKRIT VARIABLES +.env + +# 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/lab-yana/README.md b/lab-yana/README.md new file mode 100644 index 0000000..4d76fc5 --- /dev/null +++ b/lab-yana/README.md @@ -0,0 +1,3 @@ +This is a server that allows you to sign in or sign up, using basic authorization and authentication using a token generated by the middleware jsonwebtoken as well as hashing using the native node module crypto as well as the imported module bcrypt. The server now allows you to create galleries with a name and description. + +_created by_ Yana Radenska diff --git a/lab-yana/data/d36c6fc77352cf478b70c0db17b86142 b/lab-yana/data/d36c6fc77352cf478b70c0db17b86142 new file mode 100644 index 0000000..04c99f2 Binary files /dev/null and b/lab-yana/data/d36c6fc77352cf478b70c0db17b86142 differ diff --git a/lab-yana/gulpfile.js b/lab-yana/gulpfile.js new file mode 100644 index 0000000..9da355a --- /dev/null +++ b/lab-yana/gulpfile.js @@ -0,0 +1,20 @@ +'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/lab-yana/lib/basic-auth-middleware.js b/lab-yana/lib/basic-auth-middleware.js new file mode 100644 index 0000000..f273255 --- /dev/null +++ b/lab-yana/lib/basic-auth-middleware.js @@ -0,0 +1,27 @@ +'use strict'; + +const debug = require('debug')('cfgram:basic-auth-middleware'); +const createError = require('http-errors'); + +module.exports = function (req, res, next) { + debug('basic-auth-middleware'); + + var authHeader = req.headers.authorization; + if (!authHeader) return next(createError(401, 'authorization headers required')); + + var base64string = authHeader.split('Basic ')[1]; //takes the username and password part of the auth header + if (!base64string) return next(createError(401, 'username and password required')); + + var utf8string = new Buffer(base64string, 'base64').toString(); //turns our string into a readable by humans string + var authArray = utf8string.split(':'); //puts the username in index 0 of array and the password in index 1 + + req.auth = { //create new property auth of request object + username: authArray[0], + password: authArray[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/lab-yana/lib/bearer-auth-middleware.js b/lab-yana/lib/bearer-auth-middleware.js new file mode 100644 index 0000000..b4a992f --- /dev/null +++ b/lab-yana/lib/bearer-auth-middleware.js @@ -0,0 +1,26 @@ +'use strict'; + +const debug = require('debug')('cfgram:bearer-auth-middleware'); +const createError = require('http-errors'); +const jwt = require('jsonwebtoken'); +const User = require('../model/user.js'); + +module.exports = function(req, res, next) { + debug('bearer-auth-middleware'); + + 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, function(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/lab-yana/lib/error-middleware.js b/lab-yana/lib/error-middleware.js new file mode 100644 index 0000000..1d2fc4d --- /dev/null +++ b/lab-yana/lib/error-middleware.js @@ -0,0 +1,35 @@ +'use strict'; + +const debug = require('debug')('cfgram:error-middleware'); +const createError = require('http-errors'); + +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; + } + + if (err.name === 'CastError') { + err = createError(404, err.message); + res.status(err.status).send(err.name); + next(); + return; + } + + err = createError(500, err.message); + res.status(err.status).send(err.message); + next(); + +}; diff --git a/lab-yana/model/gallery.js b/lab-yana/model/gallery.js new file mode 100644 index 0000000..98c2f5c --- /dev/null +++ b/lab-yana/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: Schema.Types.ObjectId, required: true } +}); + +module.exports = mongoose.model('gallery', gallerySchema); diff --git a/lab-yana/model/pic.js b/lab-yana/model/pic.js new file mode 100644 index 0000000..0ea0c75 --- /dev/null +++ b/lab-yana/model/pic.js @@ -0,0 +1,16 @@ +'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 }, + objectKey: { type: String, required: true, unique: true }, + created: { type: Date, default: Date.now }, + imageURI: { type: String, required: true, unique: true } +}); + +module.exports = mongoose.model('pic', picSchema); diff --git a/lab-yana/model/user.js b/lab-yana/model/user.js new file mode 100644 index 0000000..d2c0da8 --- /dev/null +++ b/lab-yana/model/user.js @@ -0,0 +1,69 @@ +'use strict'; + +const debug = require('debug')('cfgram:user'); +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 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) => { //take the plain text password the user chose and turn it into a hash + if (err) return reject(err); + this.password = hash; //store the hashed password + resolve(this); + }); + }); +}; + +userSchema.methods.comparePasswordHash = function(password) { + debug('comparePasswordHash'); + return new Promise((resolve, reject) => { + bcrypt.compare(password, this.password, (err, valid) => { //compare a user entered password with their hashed password + 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'); //assign to findHash a randomly generated hash for two step authentication + this.save() //make sure to save it in db + .then( () => resolve(this.findHash)) + .catch(err => { + if (tries > 3) return reject(err); + tries++; + _generateFindHash.call(this); //try to generate the FindHash again until it has been tried 3 times + }); + } + }); +}; + +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); diff --git a/lab-yana/package.json b/lab-yana/package.json new file mode 100644 index 0000000..afad0e9 --- /dev/null +++ b/lab-yana/package.json @@ -0,0 +1,40 @@ +{ + "name": "lab-yana", + "version": "1.0.0", + "description": "", + "main": "server.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "DEBUG='cfgram*' mocha", + "start": "DEBUG='cfgram*' nodemon server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "chai": "^3.5.0", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-mocha": "^4.0.1", + "mocha": "^3.2.0", + "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", + "express": "^4.15.2", + "http-errors": "^1.6.1", + "jsonwebtoken": "^7.3.0", + "mongoose": "^4.8.6", + "morgan": "^1.8.1", + "multer": "^1.3.0" + } +} diff --git a/lab-yana/route/auth-routes.js b/lab-yana/route/auth-routes.js new file mode 100644 index 0000000..f5c0266 --- /dev/null +++ b/lab-yana/route/auth-routes.js @@ -0,0 +1,34 @@ +'use strict'; + +const debug = require('debug')('cfgram:auth-routes'); +const parseJSON = require('body-parser').json(); +const Router = require('express').Router; +const basicAuth = require('../lib/basic-auth-middleware.js'); +const User = require('../model/user.js'); +const createError = require('http-errors'); + +const authRouter = module.exports = Router(); + +authRouter.post('/api/signup', parseJSON, function(req, res, next) { + debug('POST: /api/signup'); + + let password = req.body.password; + delete req.body.password; //remove the password which is in plain text + let user = new User(req.body); + user.generatePasswordHash(password) + .then(user => user.save()) //save new user info to db + .then(user => user.generateToken()) //generate a new auth token + .then(token => res.send(token)) //send auth token to user + .catch(err => { + next(createError(400, err.message)); + }); +}); + +authRouter.get('/api/signin', basicAuth, function(req, res, next) { + debug('GET: /api/signin'); + User.findOne( { username: req.auth.username }) //find the user name entered by the user from the request object where we put it in basic auth middleware + .then(user => user.comparePasswordHash(req.auth.password)) //compare user entered password to hash of actual user password + .then(user => user.generateToken) //make a token after confirming passwords match + .then(token => res.send(token)) //send user the token as part of authorization process + .catch(next); +}); diff --git a/lab-yana/route/gallery-router.js b/lab-yana/route/gallery-router.js new file mode 100644 index 0000000..af0961e --- /dev/null +++ b/lab-yana/route/gallery-router.js @@ -0,0 +1,57 @@ +'use strict'; + +const debug = require('debug')('cfgram:gallery-router'); +const createError = require('http-errors'); +const parseJSON = require('body-parser').json(); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); +const Gallery = require('../model/gallery.js'); +const Router = require('express').Router; + +const galleryRouter = module.exports = Router(); + +galleryRouter.get('/api/gallery/:id', bearerAuth, function(req, res, next) { + debug('GET: /api/gallery/:id'); + Gallery.findById(req.params.id) + .then(gallery => { + if (gallery.userID.toString() !== req.user._id.toString()) return next(createError(401, 'invalid user')); //check that the user is trying to access their own gallery + res.json(gallery); + }) + .catch(next); +}); + +galleryRouter.get('/api/gallery', bearerAuth, function(req, res, next) { + debug('GET (list): /api/gallery'); + + Gallery.find({}, function(err, galleries) { + if (err) return(next(err)); + var ids = []; + galleries.forEach(gallery => ids.push(gallery._id)); //take just the IDs from the returned objects + res.json(ids); + }); +}); + +galleryRouter.post('/api/gallery', bearerAuth, parseJSON, function(req, res, next) { + debug('POST: /api/gallery'); + // if (req._body === false) return next(createError(400, 'bad request')); + req.body.userID = req.user._id; //associate the user with the gallery before creating the new gallery and saving it to the db + new Gallery(req.body).save() + .then(gallery => res.json(gallery)) + .catch(next); +}); + +galleryRouter.put('/api/gallery/:id', bearerAuth, parseJSON, function(req, res, next) { + debug('PUT: /api/gallery/:id'); + + if (req._body !== true) return next(createError(400, 'bad request')); + Gallery.findByIdAndUpdate(req.params.id, req.body, { new: true } ) + .then(gallery => res.json(gallery)) + .catch(next); +}); + +galleryRouter.delete('/api/gallery/:id', bearerAuth, function(req, res, next) { + debug('DELETE: /api/gallery/:id'); + + Gallery.findByIdAndRemove(req.params.id) + .then( () => res.status(204).send()) + .catch(next); +}); diff --git a/lab-yana/route/pic-router.js b/lab-yana/route/pic-router.js new file mode 100644 index 0000000..b74388c --- /dev/null +++ b/lab-yana/route/pic-router.js @@ -0,0 +1,78 @@ +'use strict'; + +const Pic = require('../model/pic.js'); +const Gallery = require('../model/gallery.js'); +const bearerAuth = require('../lib/bearer-auth-middleware.js'); + +const debug = require('debug')('cfgram:pic-router'); +const path = require('path'); +const multer = require('multer'); +const AWS = require('aws-sdk'); +const createError = require('http-errors'); +const fs = require('fs'); +const del = require('del'); +const Router = require('express').Router; + +AWS.config.setPromisesDependency(require('bluebird')); +const s3 = new AWS.S3(); +const picDir = `${__dirname}/../data`; +const upload = multer( { dest: picDir } ); + +const picRouter = module.exports = Router(); + +function s3uploadProm(params) { + debug('s3uploadProm'); + + return new Promise((resolve, reject) => { + s3.upload(params, (err, s3data) => { + resolve(s3data); + }); + }); +} + +picRouter.post('/api/gallery/:galleryID/pic', bearerAuth, upload.single('image'), function(req, res, next) { + debug('POST: /api/gallery/:galleryID/pic'); + if (!req.file) return next(createError(400, 'file not found')); + if (!req.file.path) return next(createError(500, 'file not saved')); + + let fileExt = path.extname(req.file.originalname); + let params = { + ACL: 'public-read', + Bucket: process.env.AWS_BUCKET, + Key: `${req.file.filename}${fileExt}`, + Body: fs.createReadStream(req.file.path) + }; + + Gallery.findById(req.params.galleryID) + .then( () => s3uploadProm(params)) + .then(s3data => { + del([`${picDir}/*`]); + let picData = { + name: req.body.name, + desc: req.body.desc, + objectKey: s3data.Key, + imageURI: s3data.Location, + galleryID: req.params.galleryID, + userID: req.user._id + }; + return new Pic(picData).save(); + }) + .then(pic => res.json(pic)) + .catch(next); +}); + +// picRouter.delete('/api/gallery/:galleryID/pic/:picID', bearerAuth, function(req, res, next) { +// debug('DELETE: /api/gallery/:galleryID/pic/:picID'); +// +// Pic.findByIdAndRemove(req.params.picID) +// .then(res => { +// console.log('RES', res); +// let params = { +// Bucket: process.env.AWS_BUCKET, +// Key: res._id.toString() +// } +// s3.deleteObject(params); +// res.status(204).send(); +// }) +// .catch(next); +// }); diff --git a/lab-yana/server.js b/lab-yana/server.js new file mode 100644 index 0000000..3264333 --- /dev/null +++ b/lab-yana/server.js @@ -0,0 +1,30 @@ +'use strict'; + +const debug = require('debug')('cfgram:server'); +const morgan = require('morgan'); +const cors = require('cors'); +const mongoose = require('mongoose'); +const express = require('express'); +const authRouter = require('./route/auth-routes.js'); +const galleryRouter = require('./route/gallery-router.js'); +const picRouter = require('./route/pic-router.js'); +const errors = require('./lib/error-middleware.js'); +const dotenv = require('dotenv'); +const PORT = 3003; + +dotenv.load(); //use the variables in the .env file; + +const app = express(); + +mongoose.connect(process.env.MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); +app.use(authRouter); +app.use(galleryRouter); +app.use(picRouter); +app.use(errors); + +const server = module.exports = app.listen(PORT, () => debug(`Listening on port ${PORT}.`)); + +server.isRunning = true; diff --git a/lab-yana/test/auth-routes-test.js b/lab-yana/test/auth-routes-test.js new file mode 100644 index 0000000..2302d7f --- /dev/null +++ b/lab-yana/test/auth-routes-test.js @@ -0,0 +1,82 @@ +// 'use strict'; +// +// const expect = require('chai').expect; +// const request = require('superagent'); +// const User = require('../model/user.js'); +// const url = 'http://localhost:3003'; +// +// require('../server.js'); +// +// const testUser = { +// username: 'testUser', +// password: 'word', +// email: 'testUser@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(testUser) +// .end((err, res) => { +// if (err) return done(err); +// expect(res.status).to.equal(200); +// expect(res.text).to.be.a('string'); +// done(); +// }); +// }); +// }); +// describe('without a valid body', function() { +// it('should return a 400 error', done => { +// request.post(`${url}/api/signup`) +// .end(err => { +// expect(err.status).to.equal(400); +// done(); +// }); +// }); +// }); +// }); +// describe('GET: /api/signin', function() { +// describe('with a valid authentication header', function() { +// before(done => { +// let user = new User(testUser); +// user.generatePasswordHash(user.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('testUser', 'word') +// .end((err, res) => { +// if (err) return done(err); +// expect(res.status).to.equal(200); +// expect(res.text).to.be.a('string'); +// done(); +// }); +// }); +// }); +// describe('without a valid authentication header', function() { +// it('should return a 401 error', done => { +// request.get(`${url}/api/signin`) +// .end(err => { +// expect(err.status).to.equal(401); +// done(); +// }); +// }); +// }); +// }); +// }); diff --git a/lab-yana/test/data/tester.png b/lab-yana/test/data/tester.png new file mode 100644 index 0000000..04c99f2 Binary files /dev/null and b/lab-yana/test/data/tester.png differ diff --git a/lab-yana/test/gallery-router-test.js b/lab-yana/test/gallery-router-test.js new file mode 100644 index 0000000..f077852 --- /dev/null +++ b/lab-yana/test/gallery-router-test.js @@ -0,0 +1,253 @@ +// 'use strict'; +// +// const expect = require('chai').expect; +// const request = require('superagent'); +// const User = require('../model/user.js'); +// const Gallery = require('../model/gallery.js'); +// const url = 'http://localhost:3003'; +// const mongoose = require('mongoose'); +// const Promise = require('bluebird'); +// mongoose.Promise = Promise; +// +// +// const testUser = { +// username: 'testUser', +// password: 'word', +// email: 'testUser@user.com' +// }; +// +// const testGallery = { +// name: 'testGallery name', +// desc: 'testGallery description' +// }; +// +// require('../server.js'); +// +// describe('Gallery Routes', function() { +// beforeEach(done => { +// let user = new User(testUser); +// user.generatePasswordHash(testUser.password) +// .then(user => { +// user.save(); +// this.tempUser = user; +// return user.generateToken(); +// }) +// .then(token => { +// this.tempToken = token; +// done(); +// }) +// .catch(done); +// }); +// beforeEach(done => { //make a gallery and save it to db, needed for GET, PUT, and DELETE +// testGallery.userID = this.tempUser._id.toString(); +// new Gallery(testGallery).save() +// .then(gallery => { +// this.tempGallery = gallery; +// done(); +// }) +// .catch(done); +// }); +// afterEach(done => { //remove user and gallery db entries +// Promise.all([ +// User.remove({}), +// Gallery.remove({}) +// ]) +// .then( () => { +// delete testGallery.userID; //remove user ID +// done(); +// }) +// .catch(done); +// }); +// describe('POST: /api/gallery', () => { +// describe('with a valid body and token', () => { +// it('should return a gallery', done => { +// request.post(`${url}/api/gallery`) +// .send(testGallery) +// .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(testGallery.name); +// expect(res.body.desc).to.equal(testGallery.desc); +// expect(date).to.not.equal('invalid date'); +// expect(res.body.userID.toString()).to.equal(this.tempUser._id.toString()); +// done(); +// }); +// }); +// }); +// describe('wthout a valid token', () => { +// it('should return a 401 error', done => { +// request.post(`${url}/api/gallery`) +// .send(testGallery) +// .end(err => { +// expect(err.status).to.equal(401); +// done(); +// }); +// }); +// }); +// describe('without a valid body', () => { +// it('should return a 400 error', done => { +// request.post(`${url}/api/gallery`) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end(err => { +// expect(err.status).to.equal(400); +// done(); +// }); +// }); +// }); +// }); +// describe('GET: /ap1/s0m3rand0mwr0ngr0ut3', () => { +// describe('with an invalid route', () => { +// it('should return a 404 error', done => { +// request.get(`${url}/ap1/s0m3rand0mwr0ngr0ut3`) +// .end(err => { +// expect(err.status).to.equal(404); +// done(); +// }); +// }); +// }); +// }); +// describe('GET: /api/gallery', () => { +// describe('with a valid token', () => { +// it('should return a list of gallery IDs in the db', done => { +// request.get(`${url}/api/gallery`) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end((err, res) => { +// if (err) return done(err); +// expect(res.status).to.equal(200); +// expect(res.body).to.be.an('array'); +// expect(res.body.length).to.equal(1); +// expect(res.body[0]).to.equal(this.tempGallery._id.toString); +// done(); +// }); +// }); +// }); +// describe('without a valid token', () => { +// it('should return a 401 error', done => { +// request.get(`${url}/api/gallery`) +// .end(err => { +// expect(err.status).to.equal(401); +// done(); +// }); +// }); +// }); +// }); +// describe('GET: /api/gallery/:id', () => { +// describe('with a valid id and token', () => { +// 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(date).to.not.equal('invalid date'); +// expect(res.status).to.equal(200); +// expect(res.body.name).to.equal(testGallery.name); +// expect(res.body.desc).to.equal(testGallery.desc); +// expect(res.body.userID).to.equal(this.tempUser._id.toString()); +// done(); +// }); +// }); +// }); +// describe('wthout a valid token', () => { +// it('should return a 401 error', done => { +// request.get(`${url}/api/gallery/${this.tempGallery._id}`) +// .end(err => { +// expect(err.status).to.equal(401); +// done(); +// }); +// }); +// }); +// describe('with an invalid id' , () => { +// it('should return a 404 error', done => { +// request.get(`${url}/api/gallery/1nval1did`) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end(err => { +// expect(err.status).to.equal(404); +// done(); +// }); +// }); +// }); +// }); +// describe('DELETE: /api/gallery/:id', () => { +// describe('with a valid id and token', () => { +// it('should return a 204 code', done => { +// request.delete(`${url}/api/gallery/${this.tempGallery._id}`) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end((err, res) => { +// if (err) return done(err); +// expect(res.status).to.equal(204); +// done(); +// }); +// }); +// }); +// describe('without a valid id', () => { +// it('should return a 404 error code', done => { +// request.delete(`${url}/api/gallery/1nvalid1d`) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end(err => { +// expect(err.status).to.equal(404); +// done(); +// }); +// }); +// }); +// describe('without a valid token', () => { +// it('should return a 401 error code', done => { +// request.delete(`${url}/api/gallery/${this.tempGallery._id}`) +// .end(err => { +// expect(err.status).to.equal(401); +// done(); +// }); +// }); +// }); +// }); +// describe('PUT: /api/gallery/:id', () => { +// var update = { name: 'updated gallery name', desc: 'updated gallery description' }; +// describe('with a valid body, id, and token', () => { +// it('should return an updated gallery', done => { +// request.put(`${url}/api/gallery/${this.tempGallery._id}`) +// .send(update) +// .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(update.name); +// expect(res.body.desc).to.equal(update.desc); +// done(); +// }); +// }); +// }); +// describe('without a valid body', () => { +// it('should return a 400 error', done => { +// request.put(`${url}/api/gallery/${this.tempGallery._id}`) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end(err => { +// expect(err.status).to.equal(400); +// done(); +// }); +// }); +// }); +// describe('without a valid token', () => { +// it('should return a 401 error', done => { +// request.put(`${url}/api/gallery/${this.tempGallery._id}`) +// .send(update) +// .end(err => { +// expect(err.status).to.equal(401); +// done(); +// }); +// }); +// }); +// describe('without a valid gallery id', () => { +// it('should return a 404 error', done => { +// request.put(`${url}/api/gallery/00p5wr0ng1d`) +// .send(update) +// .set( { Authorization: `Bearer ${this.tempToken}` } ) +// .end(err => { +// expect(err.status).to.equal(404); +// done(); +// }); +// }); +// }); +// }); +// }); diff --git a/lab-yana/test/lib/server-toggle.js b/lab-yana/test/lib/server-toggle.js new file mode 100644 index 0000000..be80cee --- /dev/null +++ b/lab-yana/test/lib/server-toggle.js @@ -0,0 +1,30 @@ +'use strict'; + +const debug = require('debug')('cfgram:server-toggle'); + +module.exports = exports = {}; + +exports.serverOn = function(server, done) { + if (!server.isRunning) { + server.listen(3003, () => { + server.isRunning = true; + debug('server is up!'); + done(); + }); + return; + } + done(); +}; + +exports.serverOff = function(server, done) { + if (server.isRunning) { + server.close(err => { + if (err) console.error('error in server.close', err); + server.isRunning = false; + debug('server is off!'); + done(); + }); + return; + } + done(); +}; diff --git a/lab-yana/test/pic-routes-test.js b/lab-yana/test/pic-routes-test.js new file mode 100644 index 0000000..bdf6b2f --- /dev/null +++ b/lab-yana/test/pic-routes-test.js @@ -0,0 +1,161 @@ +'use strict'; + +const Pic = require('../model/pic.js'); +const Gallery = require('../model/gallery.js'); +const User = require('../model/user.js'); +const serverToggle = require('./lib/server-toggle.js'); +const expect = require('chai').expect; +const request = require('superagent'); +const server = require('../server.js'); +const url = 'http://localhost:3003'; + +const testUser = { + username: 'testUser', + password: 'word', + email: 'testUser@test.com' +}; + +const testGallery = { + name: 'testGallery name', + desc: 'testGallery description' +}; + +const testPic = { + name: 'testpicname', + desc: 'test pic description', + image: `${__dirname}/data/tester.png` +}; + +describe('Pic Routes', function() { + before(done => serverToggle.serverOn(server, done)); + after(done => serverToggle.serverOff(server, done)); + + beforeEach(done => { + new User(testUser) + .generatePasswordHash(testUser.password) + .then(user => { + user.save(); + this.tempUser = user; + return user.generateToken(); + }) + .then(token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + beforeEach(done => { + testGallery.userID = this.tempUser._id.toString(); + new Gallery(testGallery).save() + .then(gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + + afterEach(done => { + Promise.all([ + Pic.remove({}), + User.remove({}), + Gallery.remove({}) + ]) + .then( () => done()) + .catch(done); + }); + afterEach(done => { + delete testGallery.userID; + done(); + }); + + describe('POST: /api/gallery/:galleryID/pic', () => { + describe('with a valid body', () => { + it('should return a pic', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id.toString()}/pic`) + .set( { Authorization: `Bearer ${this.tempToken}` } ) + .field('name', testPic.name) + .field('desc', testPic.desc) + .attach('image', testPic.image) + .end((err, res) => { + if (err) return done(err); + console.log('res.body', res.body); + expect(res.body.name).to.equal(testPic.name); + expect(res.body.desc).to.equal(testPic.desc); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + expect(res.status).to.equal(200); + done(); + }); + }); + }); + describe('without a valid body', () => { + it('should return a 400 error', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id.toString()}/pic`) + .set( { Authorization: `Bearer ${this.tempToken}`} ) + .end(err => { + expect(err.status).to.equal(400); + done(); + }); + }); + }); + describe('with an invalid galleryID', () => { + it('should return a 404 error', done => { + request.post(`${url}/api/gallery/00p5th1515s0wr0ng/pic`) + .set( { Authorization: `Bearer ${this.tempToken}` } ) + .field('name', testPic.name) + .field('desc', testPic.desc) + .attach('image', testPic.image) + .end(err => { + expect(err.status).to.equal(404); + done(); + }); + }); + }); + }); +}); + // describe('DELETE: /api/gallery/:galleryID/pic/:picID', () => { + // describe('with valid pic, user, and gallery ids', () => { + // before(done => { + // request.post(`${url}/api/gallery/${this.tempGallery._id.toString()}/pic`) + // .set( { Authorization: `Bearer ${this.tempToken}` } ) + // .field('name', testPic.name) + // .field('desc', testPic.desc) + // .attach('image', testPic.image) + // .end((err, res) => { + // if (err) return done(err); + // this.tempPic = res.body; + // console.log('this.tempPic in before', this.tempPic) + // done(); + // }); + // done(); + // }); + // before(done => { + // let picProps = { + // name: testPic.name, + // desc: testPic.desc, + // userID: this.tempUser._id, + // galleryID: this.tempGallery._id, + // objectKey: '077a7aa47fd359ad6d14d95db1bbb31a.png', + // imageURI: 'https://cfgrambackend42.s3.amazonaws.com/077a7aa47fd359ad6d14d95db1bbb31a.png' + // } + // new Pic(picProps).save() + // .then(pic => { + // console.log('PIC', pic); + // this.tempPic = pic; + // done(); + // }) + // .catch(done); + // }); + // it('should return a 204 code', done => { + // request.delete(`${url}/api/gallery/${this.tempGallery._id.toString()}/pic/${this.tempPic._id.toString()}`) + // .set( { Authorization: `Bearer ${this.tempToken}` } ) + // .end((err, res) => { + // if (err) return done(err); + // console.log('res body in it', res.body); + // expect(res.status).to.equal(204); + // done(); + // }); + // }); + // }); + // }); +// });