diff --git a/api.test.js b/api.test.js deleted file mode 100644 index e1d5f53..0000000 --- a/api.test.js +++ /dev/null @@ -1,27 +0,0 @@ -const request = require('superagent'); -const path = 'localhost:8000'; - -request.post(`${path}/api/brew`) - .send({ - text : 'new brew' - }) - .end((err, res) => { - console.log(err, res && res.body); - }); - - -///////// - -const db = require('./server/db.js'); -const brewData = require('./server/brew.data.js'); - - -db.connect() - .then(()=>{ - brewData.create({ - text : 'test' - }) - .then((brew)=>{ - console.log(brew); - }) - }) \ No newline at end of file diff --git a/server/brew.api.js b/server/brew.api.js index cf606a1..d7e30bf 100644 --- a/server/brew.api.js +++ b/server/brew.api.js @@ -14,35 +14,36 @@ router.get('/api/brew', (req, res, next) => { //Get router.get('/api/brew/:shareId', mw.viewBrew, (req, res, next) => { - return res.json(req.brew); + return res.json(req.brew.toJSON()); }); //Create router.post('/api/brew', (req, res, next)=>{ - const newBrew = req.body; - if(req.account) newBrew.authors = [req.account.username]; - BrewData.create(newBrew) + const brew = req.body; + if(req.account) brew.authors = [req.account.username]; + BrewData.create(brew) .then((brew) => { - return res.json(brew); + return res.json(brew.toJSON()); }) .catch(next) }); //Update router.put('/api/brew/:editId', mw.loadBrew, (req, res, next)=>{ + const brew = req.body || {}; if(req.account){ - req.brew.authors = _.uniq(_.concat(req.brew.authors, req.account.username)); + brew.authors = _.uniq(_.concat(brew.authors, req.account.username)); } - BrewData.update(req.brew) + BrewData.update(req.params.editId, brew) .then((brew) => { - return res.json(brew); + return res.json(brew.toJSON()); }) .catch(next); }); //Delete router.delete('/api/brew/:editId', mw.loadBrew, (req, res, next) => { - BrewData.remove(req.brew.editId) + BrewData.remove(req.params.editId) .then(()=>{ return res.sendStatus(200); }) diff --git a/server/brew.data.js b/server/brew.data.js index becf788..003205e 100644 --- a/server/brew.data.js +++ b/server/brew.data.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const shortid = require('shortid'); const mongoose = require('./db.js').instance; +const Error = require('./error.js'); const utils = require('./utils.js'); const BrewSchema = mongoose.Schema({ @@ -21,41 +22,15 @@ const BrewSchema = mongoose.Schema({ updatedAt : { type: Date, default: Date.now}, lastViewed : { type: Date, default: Date.now}, views : {type:Number, default:0} -}, { versionKey: false }); - -/* -BrewSchema.methods.sanatize = function(userName, isAdmin, getText = true){ - const brew = this.toJSON(); - delete brew._id; - delete brew.__v; - const isPriviledged = isAdmin || _.contains(this.authors, userName); - if(!isPriviledged) delete brew.editId; - if(!getText) delete brew.text; - return brew; -}; -*/ -/* -BrewSchema.methods.sanatize = function(req, getText = true){ - const brew = this.toJSON(); - delete brew._id; - delete brew.__v; - const isPriviledged = isAdmin || _.contains(this.authors, userName); - if(!isPriviledged) delete brew.editId; - if(!getText) delete brew.text; - return brew; -}; - -BrewSchema.methods.increaseView = function(){ - return new Promise((resolve, reject) => { - this.lastViewed = new Date(); - this.views = this.views + 1; - this.save((err) => { - if(err) return reject(err); - return resolve(this); - }); - }); -}; -*/ +}, { + versionKey: false, + toJSON : { + transform: (doc, ret, options) => { + delete ret._id; + return ret; + } + } +}); BrewSchema.methods.increaseView = function(){ this.views = this.views + 1; @@ -72,32 +47,36 @@ const BrewData = { model : Brew, get : (query) => { - //returns a single brew with the given query - //Start using egads for errors - return Brew.findOne(query).exec(); + return Brew.findOne(query).exec() + .then((brew) => { + if(!brew) throw Error.noBrew(); + return brew; + }); }, create : (brew) => { delete brew.shareId; delete brew.editId; - if(!brew.title) brew.title = utils.getGoodBrewTitle(brew.text); - const newBrew = new Brew(brew); - - return newBrew.save(); + return (new Brew(brew)).save(); }, - update : (newBrew) => { - return Brew.findOneAndUpdate({ editId : newBrew.editId }, - _.merge(newBrew, { updatedAt : Date.now() }), - {new : true, upsert : true} - ).exec(); //TODO: TEST THIS that this returns a record + update : (editId, newBrew) => { + return BrewData.get({ editId }) + .then((brew) => { + delete newBrew.shareId; + delete newBrew.editId; + brew = _.merge(brew, newBrew, { updatedAt : Date.now() }); + return brew.save(); + }); }, remove : (editId) => { return Brew.find({ editId }).remove().exec(); }, + removeAll : ()=>{ + return Brew.find({}).remove().exec(); + }, //////// Special - getByShare : (shareId) => { return BrewData.get({ shareId : shareId}) .then((brew) => { @@ -108,7 +87,7 @@ const BrewData = { }); }, getByEdit : (editId) => { - return Brew.get({ editId }); + return BrewData.get({ editId }); }, search : (query, req={}) => { diff --git a/server/db.js b/server/db.js index 095de2c..a9c5400 100644 --- a/server/db.js +++ b/server/db.js @@ -24,6 +24,5 @@ module.exports = { ); }); }, - instance : mongoose, - clearDatabase : ()=>{} + instance : mongoose } \ No newline at end of file diff --git a/server/error.js b/server/error.js index 993f3e7..7fd3772 100644 --- a/server/error.js +++ b/server/error.js @@ -1,14 +1,12 @@ -const ApiError = require('egads').extend('Server Error', 500, 'Generic Server Error'); +const Error = require('egads').extend('Server Error', 500, 'Generic Server Error'); -ApiError.noBrew = ApiError.extend('Can not find a brew with that id', 404); +Error.noBrew = Error.extend('Can not find a brew with that id', 404, 'No Brew Found'); +Error.noAuth = Error.extend('You can not access this route', 401, 'Unauthorized'); - - - -ApiError.expressHandler = (err, req, res, next) => { - if(err instanceof ApiError){ +Error.expressHandler = (err, req, res, next) => { + if(err instanceof Error){ return res.status(err.status).send({ type : err.name, message : err.message @@ -23,4 +21,4 @@ ApiError.expressHandler = (err, req, res, next) => { -module.exports = ApiError; \ No newline at end of file +module.exports = Error; \ No newline at end of file diff --git a/server/middleware.js b/server/middleware.js index 0d9d235..970f4af 100644 --- a/server/middleware.js +++ b/server/middleware.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const jwt = require('jwt-simple'); const config = require('nconf'); +const Error = require('./error.js'); const BrewData = require('./brew.data.js'); const Middleware = { @@ -15,13 +16,11 @@ const Middleware = { }, admin : (req, res, next) => { if(req.query.admin_key === config.get('admin_key')){ - delete req.admin_key; - req.isAdmin = true; + req.admin = true; } return next(); }, - //Filters devOnly : (req, res, next) => { const env = process.env.NODE_ENV; @@ -29,20 +28,27 @@ const Middleware = { return res.sendStatus(404); }, adminOnly : (req, res, next) => { - if(req.isAdmin) return next(); - return res.sendStatus(401); + if(req.admin) return next(); + return next(Error.noAuth()); }, //Loaders loadBrew : (req, res, next) => { - //Loads a brew by edit id - - //TODO: move validate into hurrr + BrewData.getByEdit(req.params.editId) + .then((brew) => { + req.brew = brew; + return next() + }) + .catch(next); }, viewBrew : (req, res, next) => { - //load by share - //increase view count + BrewData.getByShare(req.params.shareId) + .then((brew) => { + req.brew = brew; + return next() + }) + .catch(next); }, }; diff --git a/server/utils.js b/server/utils.js index 65dd91b..163a61c 100644 --- a/server/utils.js +++ b/server/utils.js @@ -1,17 +1,17 @@ const _ = require('lodash'); - module.exports = { - getGoodBrewTitle : (text) => { + getGoodBrewTitle : (text = '') => { const titlePos = text.indexOf('# '); if(titlePos !== -1){ - const ending = text.indexOf('\n', titlePos); - return text.substring(titlePos + 2, ending); + let ending = text.indexOf('\n', titlePos); + ending = (ending == -1 ? undefined : ending); + return text.substring(titlePos + 2, ending).trim(); }else{ - return _.find(text.split('\n'), (line)=>{ + return (_.find(text.split('\n'), (line)=>{ return line; - }); + }) || '').trim(); } }, replaceByMap : (text, mapping) => { diff --git a/test/api.test.js b/test/api.test.js new file mode 100644 index 0000000..93e353c --- /dev/null +++ b/test/api.test.js @@ -0,0 +1,114 @@ +const testing = require('./test.init.js'); +const request = require('supertest-as-promised'); +const jwt = require('jwt-simple'); +const config = require('nconf'); + +const app = require('app.js'); +const DB = require('db.js'); +const BrewData = require('brew.data.js'); +const Error = require('error.js'); + +const apiPath = '/api/brew'; + +let session_token; +const test_user = { + username : 'cool guy' +}; +let storedBrew = { + title : 'good title', + text : 'original text', + authors : ['your_dm'] +}; + +describe('Brew API', () => { + before('Connect DB', DB.connect); + before('Clear DB', BrewData.removeAll); + before('Create session token', () => { + session_token = jwt.encode(test_user, config.get('secret')); + }); + before('Create brew', ()=>{ + return BrewData.create(storedBrew) + .then((brew)=>{ storedBrew = brew; }); + }); + + + describe('Create', () => { + it('creates a new brew', () => { + return request(app) + .post(apiPath) + .send({ text : 'Brew Text' }) + .expect(200) + .then((res) => { + const brew = res.body; + brew.should.have.property('editId').that.is.a('string'); + brew.should.have.property('shareId').that.is.a('string'); + brew.should.have.property('text').equal('Brew Text'); + brew.should.not.have.property('_id'); + }); + }); + + it('creates a new brew with a session author', () => { + return request(app) + .post(apiPath) + .set('Cookie', `nc_session=${session_token}`) + .send({ text : 'Brew Text' }) + .expect(200) + .then((res) => { + const brew = res.body; + brew.should.have.property('authors').include(test_user.username); + }); + }); + }); + + describe('Update', () => { + it('updates an existing brew', () => { + return request(app) + .put(`${apiPath}/${storedBrew.editId}`) + .send({ text : 'New Text' }) + .expect(200) + .then((res) => { + const brew = res.body; + brew.should.have.property('editId').equal(storedBrew.editId); + brew.should.have.property('text').equal('New Text'); + brew.should.have.property('authors').include('your_dm'); + brew.should.not.have.property('_id'); + }); + }); + + it('adds the user as author', () => { + return request(app) + .put(`${apiPath}/${storedBrew.editId}`) + .set('Cookie', `nc_session=${session_token}`) + .send({ text : 'New Text' }) + .expect(200) + .then((res) => { + const brew = res.body; + brew.should.have.property('authors').include(test_user.username); + brew.should.have.property('authors').include('your_dm'); + }); + }); + it('should throw error on bad edit id', ()=>{ + return request(app) + .put(`${apiPath}/BADEDITID`) + .send({ text : 'New Text' }) + .expect(404) + }); + }); + + describe('Remove', () => { + it('should removes a brew', ()=>{ + return request(app) + .del(`${apiPath}/${storedBrew.editId}`) + .send() + .expect(200) + .then(() => { + BrewData.getByEdit(storedBrew.editId) + .then(() => { throw 'Brew found when one should not have been'; }) + .catch((err) => { + err.should.be.instanceof(Error.noBrew); + }) + }); + }); + }); + +}); \ No newline at end of file diff --git a/test/brew.apitest.js b/test/brew.apitest.js deleted file mode 100644 index 4e03048..0000000 --- a/test/brew.apitest.js +++ /dev/null @@ -1,35 +0,0 @@ -const testing = require('./test.init.js'); -const request = require('supertest-as-promised'); - -const app = require('app.js'); -const BrewDB = require('db.js'); - - -describe('/api/brew', () => { - const apiPath = '/api/brew'; - - before('Await DB', ()=>{ - return BrewDB.connect() - }); - - describe('POST', () => { - it('creates a new brew', () => { - return request(app) - .post(apiPath) - .send({ - text : 'Brew Text' - }) - .expect(200) - .then((res) => { - const brew = res.body; - //should.exist(brew); - brew.should.have.property('editId').that.is.a('string'); - brew.should.have.property('shareId').that.is.a('string'); - brew.should.have.property('text').that.is.a('string'); - }) - }); - - }); - - -}); \ No newline at end of file diff --git a/test/brew.datatest.js b/test/brew.datatest.js deleted file mode 100644 index ad2c205..0000000 --- a/test/brew.datatest.js +++ /dev/null @@ -1,24 +0,0 @@ -const testing = require('./test.init.js'); - -const BrewDB = require('db.js'); -const BrewData = require('brew.data.js'); - - -describe('BrewDB', () => { - before('Await DB', ()=>{ - return BrewDB.connect() - }); - - it('generates ID on save', () => { - return BrewData.create({ - text : "Brew Text" - }).then((brew) => { - //should.exist(brew); - brew.should.have.property('editId').that.is.a('string'); - brew.should.have.property('shareId').that.is.a('string'); - brew.should.have.property('text').that.is.a('string'); - }) - - }); - -}); \ No newline at end of file diff --git a/test/brew.test.js b/test/brew.test.js new file mode 100644 index 0000000..572f48e --- /dev/null +++ b/test/brew.test.js @@ -0,0 +1,108 @@ +const testing = require('./test.init.js'); + +const DB = require('db.js'); +const BrewData = require('brew.data.js'); +const Error = require('error.js'); + + +let storedBrew = { + title : 'good title', + text : 'original text' +}; + +describe('Brew Data', () => { + before('Connect DB', DB.connect); + before('Clear DB', BrewData.removeAll); + before('Create brew', ()=>{ + return BrewData.create(storedBrew) + .then((brew)=>{ storedBrew = brew; }); + }); + + it('generates edit/share ID on create', () => { + return BrewData.create({ + text : 'Brew Text' + }).then((brew) => { + brew.should.have.property('editId').that.is.a('string'); + brew.should.have.property('shareId').that.is.a('string'); + brew.should.have.property('text').that.is.a('string'); + brew.should.have.property('views').equal(0); + }); + }); + + it('generates edit/share ID on create even if given one', () => { + return BrewData.create({ + editId : 'NOPE', + shareId : 'NOTTA' + }).then((brew) => { + brew.should.have.property('editId').not.equal('NOPE'); + brew.should.have.property('shareId').not.equal('NOTTA'); + }); + }); + + + it('can update an existing brew', () => { + return BrewData.update(storedBrew.editId,{ + text : 'New Text' + }).then((brew) => { + brew.should.have.property('editId').equal(storedBrew.editId); + brew.should.have.property('text').equal('New Text'); + brew.should.have.property('title').equal(storedBrew.title); + }) + }); + + + it('properly returns a brew if retrieved by just share', () => { + return BrewData.getByShare(storedBrew.shareId) + .then((brew) => { + brew.should.not.have.property('editId'); + brew.should.have.property('shareId').equal(storedBrew.shareId); + brew.should.have.property('views').equal(1); + }) + }); + + + it('can properly remove a brew', () => { + return BrewData.remove(storedBrew.editId) + .then(() => { + return BrewData.getByEdit(storedBrew.editId) + }) + .then(() => { throw 'Brew found when one should not have been'; }) + .catch((err) => { + err.should.be.an.instanceof(Error.noBrew); + }); + }); + + it('throws the right error if can not find brew', () => { + return BrewData.getByEdit('NOT A REAL ID') + .then(() => { throw 'Brew found when one should not have been'; }) + .catch((err) => { + err.should.be.an.instanceof(Error.noBrew); + }); + }); + + describe('Title Generation', () => { + it('should use the title if given one', () => { + return BrewData.create({ + title : 'Actual Title', + text : '# Not this' + }).then((brew) => { + brew.should.have.property('title').equal('Actual Title'); + }); + }); + it('should use the first header found if no title provided', () => { + return BrewData.create({ + text : 'Not this \n # But This' + }).then((brew) => { + brew.should.have.property('title').equal('But This'); + }) + }); + it('should use the first line if no headers are found', () => { + return BrewData.create({ + text : 'First line \n second line' + }).then((brew) => { + brew.should.have.property('title').equal('First line'); + }); + }); + }); + +}); diff --git a/test/markdown.test.js b/test/markdown.test.js new file mode 100644 index 0000000..e69de29 diff --git a/test/middleware.test.js b/test/middleware.test.js new file mode 100644 index 0000000..8bee892 --- /dev/null +++ b/test/middleware.test.js @@ -0,0 +1,124 @@ +const _ = require('lodash'); +const testing = require('./test.init.js'); +const request = require('supertest-as-promised'); +const jwt = require('jwt-simple'); + +const DB = require('db.js'); +const BrewData = require('brew.data.js'); +const Error = require('error.js'); + +const config = require('nconf'); +const mw = require('middleware.js'); + +const requestHandler = (req, res) => { + return res.status(200).json(_.pick(req, ['brew', 'account', 'admin', 'params', 'query', 'body'])); +}; + +const test_user = { + username : 'cool guy' +}; + +describe('Middleware', () => { + let app = undefined; + let session_token = ''; + + before('create session token', () => { + session_token = jwt.encode(test_user, config.get('secret')); + }); + beforeEach('setup test server', ()=>{ + app = require('express')(); + app.use(require('cookie-parser')()); + }); + + describe('Account', ()=>{ + it('should get the account for a session', () => { + app.use(mw.account); + app.use(requestHandler) + return request(app).get('/') + .set('Cookie', `nc_session=${session_token}`) + .send() + .expect(200) + .then((res) => { + const req = res.body; + req.should.have.property('account').is.a('object'); + req.account.should.have.property('username').equal(test_user.username); + }); + }); + it('should not have an account for an invalid session', () => { + app.use(mw.account); + app.use(requestHandler) + return request(app).get('/') + .set('Cookie', `nc_session=BADSESSION`) + .send() + .expect(200) + .then((res) => { + const req = res.body; + req.should.not.have.property('account'); + }); + }); + }); + + + describe('Brew', ()=>{ + let storedBrew = { + text : 'brew brew', + authors : [test_user.username] + }; + before('Connect DB', DB.connect); + before('Clear DB', BrewData.removeAll); + before('Create brew', ()=>{ + return BrewData.create(storedBrew) + .then((brew)=>{ storedBrew = brew; }); + }); + + it('should load brew with editId params', ()=>{ + app.get('/:editId', mw.loadBrew, requestHandler); + return request(app).get('/' + storedBrew.editId) + .send() + .expect(200) + .then((res) => { + const req = res.body; + req.should.have.property('brew').is.a('object'); + req.brew.should.have.property('editId').equal(storedBrew.editId); + }); + }); + + it('should view brew with shareId params', ()=>{ + app.get('/:shareId', mw.viewBrew, requestHandler); + return request(app).get('/' + storedBrew.shareId) + .send() + .expect(200) + .then((res) => { + const req = res.body; + req.should.have.property('brew').is.a('object'); + req.brew.should.not.have.property('editId'); + req.brew.should.have.property('shareId').equal(storedBrew.shareId); + req.brew.should.have.property('views').equal(1); + }); + }); + }); + + describe('Admin', ()=>{ + it('should detect when you use the admin key', () => { + app.use(mw.admin); + app.use(requestHandler) + return request(app).get(`/?admin_key=${config.get('admin_key')}`) + .send() + .expect(200) + .then((res) => { + const req = res.body; + req.should.have.property('admin').equal(true); + }); + }); + it('should block you if you are not an admin', ()=>{ + app.use(mw.admin); + app.use(mw.adminOnly); + app.get('/', (req, res) => { return res.status(200).send(); }); + app.use(Error.expressHandler); + return request(app).get(`/?admin_key=BADKEY`) + .send() + .expect(401); + }); + }); + +}); diff --git a/test/search.test.js b/test/search.test.js new file mode 100644 index 0000000..e69de29