From ab46c020d9c1ea1d58003d176840f8add0e8ea15 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 28 Nov 2016 15:01:13 -0700 Subject: [PATCH 1/2] Added asset searching --- bin/cli-assets | 38 +++++++++++++++ bin/cli-jobs | 69 ++++++++++++++++++++++++++++ models/asset.js | 32 +++++++++++-- package.json | 17 +++++-- routes/api/asset/index.js | 9 ++-- tests/mongoose.js | 4 +- tests/routes/api/assets/index.js | 79 +++++++++++++++++++++++++++++++- 7 files changed, 234 insertions(+), 14 deletions(-) diff --git a/bin/cli-assets b/bin/cli-assets index 99965c6d9..9ab36685a 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -12,9 +12,11 @@ process.env.DEBUG = process.env.TALK_DEBUG; const program = require('commander'); const pkg = require('../package.json'); +const parseDuration = require('parse-duration'); const Table = require('cli-table'); const Asset = require('../models/asset'); const mongoose = require('../mongoose'); +const scraper = require('../services/scraper'); const util = require('../util'); // Register the shutdown criteria. @@ -55,6 +57,37 @@ function listAssets() { }); } +function refreshAssets(ageString) { + const now = new Date().getTime(); + const ageMs = parseDuration(ageString); + const age = new Date(now - ageMs); + + Asset.find({ + $or: [ + { + scraped: { + $lte: age + } + }, + { + scraped: null + } + ] + }) + + // Queue all the assets for scraping. + .then((assets) => Promise.all(assets.map(scraper.create))) + + .then(() => { + console.log('Assets were queued to be scraped'); + util.shutdown(); + }) + .catch((err) => { + console.error(err); + util.shutdown(1); + }); +} + //============================================================================== // Setting up the program command line arguments. //============================================================================== @@ -67,6 +100,11 @@ program .description('list all the assets in the database') .action(listAssets); +program + .command('refresh ') + .description('queues the assets that exceed the age requested') + .action(refreshAssets); + program.parse(process.argv); // If there is no command listed, output help. diff --git a/bin/cli-jobs b/bin/cli-jobs index f14276d38..bc320d14b 100755 --- a/bin/cli-jobs +++ b/bin/cli-jobs @@ -14,11 +14,15 @@ const program = require('commander'); const scraper = require('../services/scraper'); const util = require('../util'); const mongoose = require('../mongoose'); +const kue = require('../kue'); util.onshutdown([ () => mongoose.disconnect() ]); +/** + * Starts the job processor. + */ function processJobs() { // Start the processor. @@ -31,6 +35,65 @@ function processJobs() { ]); } +/** + * Removes a single job. + * @param {Object} job the job to be removed + * @return {Promise} + */ +function removeJob(job) { + return new Promise((resolve, reject) => job.remove((err) => { + if (err) { + return reject(err); + } + + return resolve(job); + })); +} + +/** + * Removes the jobs passed in and returns a promise. + * @param {Array} jobs array of jobs + * @return {Promise} + */ +function removeJobs(jobs) { + return Promise.all(jobs.map(removeJob)); +} + +/** + * Get the top n jobs with a specific state. + * @param {String} [state='complete'] state to list jobs by + * @param {Number} limit limit of jobs to load + * @return {Promise} + */ +function rangeJobsByState(state = 'complete', limit) { + return new Promise((resolve, reject) => { + kue.Job.rangeByState(state, 0, limit, 'asc', (err, jobs) => { + if (err) { + return reject(err); + } + + resolve(jobs); + }); + }); +} + +/** + * Cleans up the jobs that are in the queue. + */ +function cleanupJobs(options) { + const n = 100; + + Promise.all([ + rangeJobsByState('complete', n), + options.stuck ? rangeJobsByState('failed', n) : false + ]) + .then((joblists) => joblists.filter((jobs) => jobs).map(removeJobs)) + .then(() => { + util.shutdown(); + console.log('Removed old jobs'); + }); +} + //============================================================================== // Setting up the program command line arguments. //============================================================================== @@ -40,6 +103,12 @@ program .description('starts job processing') .action(processJobs); +program + .command('cleanup') + .option('-s, --stuck', 'cleans up jobs that have been stuck', false) + .description('cleans up inactive jobs') + .action(cleanupJobs); + program.parse(process.argv); // If there is no command listed, output help. diff --git a/models/asset.js b/models/asset.js index b5321cec3..14d5675df 100644 --- a/models/asset.js +++ b/models/asset.js @@ -38,21 +38,32 @@ const AssetSchema = new Schema({ } }); +AssetSchema.index({ + title: 'text', + url: 'text', + description: 'text', + section: 'text', + subsection: 'text', + author: 'text' +}, { + background: true +}); + /** * Search for assets. Currently only returns all. -*/ + */ AssetSchema.statics.search = (query) => Asset.find(query); /** * Finds an asset by its id. * @param {String} id identifier of the asset (uuid). -*/ + */ AssetSchema.statics.findById = (id) => Asset.findOne({id}); /** * Finds a asset by its url. * @param {String} url identifier of the asset (uuid). -*/ + */ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); /** @@ -65,7 +76,8 @@ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); * is not possible with the mongoose driver. * * @param {String} url identifier of the asset (uuid). -*/ + * @return {Promise} + */ AssetSchema.statics.findOrCreateByUrl = (url) => Asset.findOneAndUpdate({url}, {url}, { // Ensure that if it's new, we return the new object created. @@ -78,6 +90,18 @@ AssetSchema.statics.findOrCreateByUrl = (url) => Asset.findOneAndUpdate({url}, { setDefaultsOnInsert: true }); +/** + * Finds assets matching keywords on the model. If `value` is an empty string, + * then it will not even perform a text search query. + * @param {String} value string to search by. + * @return {Promise} + */ +AssetSchema.statics.search = (value) => value.length === 0 ? Asset.find({}) : Asset.find({ + $text: { + $search: value + } +}); + const Asset = mongoose.model('Asset', AssetSchema); module.exports = Asset; diff --git a/package.json b/package.json index bc2f72f20..8d0256af2 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,13 @@ "config": { "pre-git": { "commit-msg": [], - "pre-commit": ["npm run lint", "npm test"], - "pre-push": ["npm test"], + "pre-commit": [ + "npm run lint", + "npm test" + ], + "pre-push": [ + "npm test" + ], "post-commit": [], "post-merge": [] } @@ -26,7 +31,12 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": ["talk", "coral", "coralproject", "ask"], + "keywords": [ + "talk", + "coral", + "coralproject", + "ask" + ], "author": "", "license": "Apache-2.0", "bugs": { @@ -52,6 +62,7 @@ "morgan": "^1.7.0", "natural": "^0.4.0", "nodemailer": "^2.6.4", + "parse-duration": "^0.1.1", "passport": "^0.3.2", "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 18fd0b7ec..0c740d32f 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -11,17 +11,20 @@ router.get('/', (req, res, next) => { limit = 20, skip = 0, sort = 'asc', - field = 'created_at' + field = 'created_at', + search = '' } = req.query; // Find all the assets. Promise.all([ Asset - .find({}) + .search(search) .sort({[field]: (sort === 'asc') ? 1 : -1}) .skip(skip) .limit(limit), - Asset.count() + Asset + .search(search) + .count() ]) .then(([result, count]) => { diff --git a/tests/mongoose.js b/tests/mongoose.js index 9d3ba1195..74a0b04f7 100644 --- a/tests/mongoose.js +++ b/tests/mongoose.js @@ -2,8 +2,8 @@ const mongoose = require('../mongoose'); beforeEach(function (done) { function clearDB() { - for (let i in mongoose.connection.collections) { - mongoose.connection.collections[i].remove(function() {}); + for (let collection in mongoose.connection.collections) { + mongoose.connection.collections[collection].remove(function() {}); } return done(); } diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js index fb67b7b48..667bec0ed 100644 --- a/tests/routes/api/assets/index.js +++ b/tests/routes/api/assets/index.js @@ -1,9 +1,84 @@ +const passport = require('../../../passport'); + +const app = require('../../../../app'); +const chai = require('chai'); +const expect = chai.expect; + +// Setup chai. +chai.should(); +chai.use(require('chai-http')); + +const Asset = require('../../../../models/asset'); + describe('/assets', () => { + beforeEach(() => { + return Asset.create([ + { + url: 'https://coralproject.net/news/asset1', + title: 'Asset 1', + description: 'term1' + }, + { + url: 'https://coralproject.net/news/asset2', + title: 'Asset 2', + description: 'term2' + } + ]); + }); + describe('GET', () => { - it('should return assets that we search for'); - it('should not return assets that we do not search for'); + it('should return all assets without a search query', () => { + return chai.request(app) + .get('/api/v1/asset') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + const body = res.body; + + expect(body).to.have.property('count', 2); + expect(body).to.have.property('result'); + + const assets = body.result; + + expect(assets).to.have.length(2); + }); + }); + + it('should return assets that we search for', () => { + return chai.request(app) + .get('/api/v1/asset?search=term2') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + const body = res.body; + + expect(body).to.have.property('count', 1); + expect(body).to.have.property('result'); + + const assets = body.result; + + expect(assets).to.have.length(1); + + const asset = assets[0]; + + expect(asset).to.have.property('url', 'https://coralproject.net/news/asset2'); + expect(asset).to.have.property('title', 'Asset 2'); + }); + }); + + it('should not return assets that we do not search for', () => { + return chai.request(app) + .get('/api/v1/asset?search=term3') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + const body = res.body; + + expect(body).to.have.property('count', 0); + expect(body).to.have.property('result'); + + expect(body.result).to.be.empty; + }); + }); }); From 3448752f6fbd2dd142bfa1fe72aaf4cbc92503ff Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 28 Nov 2016 17:13:11 -0700 Subject: [PATCH 2/2] Added asset settings + improved tests for routes --- models/asset.js | 19 +- models/user.js | 1 + routes/api/asset/index.js | 15 + routes/api/stream/index.js | 29 +- swagger.yaml | 22 ++ tests/models/asset.js | 28 +- tests/models/setting.js | 2 + tests/routes/api/assets/index.js | 4 +- tests/routes/api/auth/index.js | 57 ++-- tests/routes/api/comments/index.js | 459 ++++++++++------------------- tests/routes/api/queue/index.js | 31 +- tests/routes/api/settings/index.js | 71 ++--- tests/routes/api/stream/index.js | 66 +++-- 13 files changed, 396 insertions(+), 408 deletions(-) diff --git a/models/asset.js b/models/asset.js index 14d5675df..00251985a 100644 --- a/models/asset.js +++ b/models/asset.js @@ -1,7 +1,8 @@ const mongoose = require('../mongoose'); -const uuid = require('uuid'); const Schema = mongoose.Schema; +const uuid = require('uuid'); + const AssetSchema = new Schema({ id: { type: String, @@ -22,6 +23,10 @@ const AssetSchema = new Schema({ type: Date, default: null }, + settings: { + type: Schema.Types.Mixed, + default: null + }, title: String, description: String, image: String, @@ -90,6 +95,18 @@ AssetSchema.statics.findOrCreateByUrl = (url) => Asset.findOneAndUpdate({url}, { setDefaultsOnInsert: true }); +/** + * Updates the settings for the asset. + * @param {[type]} id [description] + * @param {[type]} settings [description] + * @return {[type]} [description] + */ +AssetSchema.statics.overrideSettings = (id, settings) => Asset.update({id}, { + $set: { + settings + } +}); + /** * Finds assets matching keywords on the model. If `value` is an empty string, * then it will not even perform a text search query. diff --git a/models/user.js b/models/user.js index bde2363c2..845b84d19 100644 --- a/models/user.js +++ b/models/user.js @@ -9,6 +9,7 @@ const SALT_ROUNDS = 10; // USER_ROLES is the array of roles that is permissible as a user role. const USER_ROLES = [ + '', 'admin', 'moderator' ]; diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 0c740d32f..96b83a969 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -81,4 +81,19 @@ router.post('/:asset_id/scrape', (req, res, next) => { }); }); +router.put('/:asset_id/settings', (req, res, next) => { + + // Override the settings for the asset. + Asset + .overrideSettings(req.params.asset_id, req.body) + .then(() => { + + res.status(204).end(); + }) + .catch((err) => { + next(err); + }); + +}); + module.exports = router; diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 0ef97aad2..95ec4774f 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -30,10 +30,18 @@ router.get('/', (req, res, next) => { // Get the moderation setting from the settings. Setting.getModerationSetting() ]) - .then(([asset, {moderation}]) => { + .then(([asset, settings]) => { + + // Merge the asset specific settings with the returned settings object in + // the event that the asset that was returned also had settings. + if (asset.settings) { + settings = Object.assign(settings, asset.settings); + } + + // Fetch the appropriate comments stream. let comments; - if (moderation === 'post') { + if (settings.moderation === 'post') { comments = Comment.findAcceptedByAssetId(asset.id); } else { @@ -48,11 +56,14 @@ router.get('/', (req, res, next) => { comments, // Send back the reference to the asset. - asset + asset, + + // Send back the settings to the stream. + settings ]); }) // Get all the users and actions for those comments. - .then(([comments, asset]) => { + .then(([comments, asset, settings]) => { // Get the user id's from the author id's as a unique array that gets // sorted. @@ -86,17 +97,21 @@ router.get('/', (req, res, next) => { users, // And all actions about the asset, comments, and users. - actions + actions, + + // Pass back the settings that we loaded. + settings ]); }) - .then(([asset, comments, users, actions]) => { + .then(([asset, comments, users, actions, settings]) => { // Send back the payload containing all this data. res.json({ assets: [asset], comments, users, - actions + actions, + settings }); }) .catch(error => { diff --git a/swagger.yaml b/swagger.yaml index 09adb97d6..e819f451c 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -335,6 +335,28 @@ paths: schema: $ref: '#/definitions/Error' + /asset/{asset_id}/settings: + put: + parameters: + - name: asset_id + in: path + required: true + type: string + format: uuid + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Settings' + responses: + 204: + description: The asset settings were updated. + 404: + description: The asset was not found. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' /stream: get: diff --git a/tests/models/asset.js b/tests/models/asset.js index a5c4ae62b..69bf99f9d 100644 --- a/tests/models/asset.js +++ b/tests/models/asset.js @@ -1,5 +1,10 @@ const Asset = require('../../models/asset'); -const expect = require('chai').expect; + +const chai = require('chai'); +const expect = chai.expect; + +// Use the chai should. +chai.should(); describe('Asset: model', () => { @@ -53,6 +58,27 @@ describe('Asset: model', () => { }); }); + describe('#overrideSettings', () => { + it('should update the settings', () => { + return Asset + .findOrCreateByUrl('https://override.test.com/asset') + .then((asset) => { + expect(asset).to.have.property('settings'); + expect(asset.settings).to.be.null; + + return Asset.overrideSettings(asset.id, {moderation: 'pre'}); + }) + .then(() => { + return Asset.findOrCreateByUrl('https://override.test.com/asset'); + }) + .then((asset) => { + expect(asset).to.have.property('settings'); + expect(asset.settings).is.an('object'); + expect(asset.settings).to.have.property('moderation', 'pre'); + }); + }); + }); + describe('#findOrCreateByUrl', ()=> { it('should find an asset by a url', () => { return Asset.findOrCreateByUrl('http://test.com') diff --git a/tests/models/setting.js b/tests/models/setting.js index a36b363fb..08a47d7a1 100644 --- a/tests/models/setting.js +++ b/tests/models/setting.js @@ -14,6 +14,7 @@ describe('Setting: model', () => { expect(settings).to.have.property('moderation').and.to.equal('pre'); }); }); + it('should have two infoBox fields defined', () => { return Setting.getSettings().then(settings => { expect(settings).to.have.property('infoBoxEnable').and.to.equal(false); @@ -26,6 +27,7 @@ describe('Setting: model', () => { it('should update the settings with a passed object', () => { const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'}; return Setting.updateSettings(mockSettings).then(updatedSettings => { + expect(updatedSettings).to.be.an('object'); expect(updatedSettings).to.have.property('moderation').and.to.equal('post'); expect(updatedSettings).to.have.property('infoBoxEnable', true); expect(updatedSettings).to.have.property('infoBoxContent', 'yeah'); diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js index 667bec0ed..c56e5b1ba 100644 --- a/tests/routes/api/assets/index.js +++ b/tests/routes/api/assets/index.js @@ -10,7 +10,7 @@ chai.use(require('chai-http')); const Asset = require('../../../../models/asset'); -describe('/assets', () => { +describe('/api/v1/assets', () => { beforeEach(() => { return Asset.create([ @@ -27,7 +27,7 @@ describe('/assets', () => { ]); }); - describe('GET', () => { + describe('#get', () => { it('should return all assets without a search query', () => { return chai.request(app) diff --git a/tests/routes/api/auth/index.js b/tests/routes/api/auth/index.js index ddca63fe8..dd408d135 100644 --- a/tests/routes/api/auth/index.js +++ b/tests/routes/api/auth/index.js @@ -6,32 +6,47 @@ chai.use(require('chai-http')); const User = require('../../../../models/user'); -describe('POST /auth/local', () => { +describe('/api/v1/auth', () => { + describe('#get', () => { + it('should return nothing when no user is logged in', () => { + return chai.request(app) + .get('/api/v1/auth') + .then((res) => { + expect(res.status).to.be.equal(204); + expect(res.body).to.be.empty; + }); + }); + }); +}); + +describe('/api/v1/auth/local', () => { beforeEach(() => { return User.createLocalUser('maria@gmail.com', 'password!', 'Maria'); }); - it('should send back the user on a successful login', () => { - return chai.request(app) - .post('/api/v1/auth/local') - .send({email: 'maria@gmail.com', password: 'password!'}) - .catch((res) => { - expect(res).to.have.status(200); - expect(res).to.be.json; - expect(res.body).to.have.property('user'); - expect(res.body.user).to.have.property('displayName', 'Maria'); - }); - }); + describe('#post', () => { + it('should send back the user on a successful login', () => { + return chai.request(app) + .post('/api/v1/auth/local') + .send({email: 'maria@gmail.com', password: 'password!'}) + .catch((res) => { + expect(res).to.have.status(200); + expect(res).to.be.json; + expect(res.body).to.have.property('user'); + expect(res.body.user).to.have.property('displayName', 'Maria'); + }); + }); - it('should not send back the user on a unsuccessful login', () => { - return chai.request(app) - .post('/api/v1/auth/local') - .send({email: 'maria@gmail.com', password: 'password!3'}) - .catch((err) => { - expect(err).to.not.be.null; - expect(err.response).to.have.status(401); - expect(err.response.body).to.have.property('message', 'not authorized'); - }); + it('should not send back the user on a unsuccessful login', () => { + return chai.request(app) + .post('/api/v1/auth/local') + .send({email: 'maria@gmail.com', password: 'password!3'}) + .catch((err) => { + expect(err).to.not.be.null; + expect(err.response).to.have.status(401); + expect(err.response.body).to.have.property('message', 'not authorized'); + }); + }); }); }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index ff7f3a9d4..5ac9af7b7 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -16,11 +16,7 @@ const User = require('../../../../models/user'); const Setting = require('../../../../models/setting'); const settings = {id: '1', moderation: 'pre'}; -beforeEach(() => { - return Setting.create(settings); -}); - -describe('Get /comments', () => { +describe('/api/v1/comments', () => { const comments = [{ id: 'abc', body: 'comment 10', @@ -32,61 +28,11 @@ describe('Get /comments', () => { asset_id: 'asset', author_id: '456' }, { - id: 'hij', - body: 'comment 30', - asset_id: '456' - }]; - - const users = [{ - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123' - }, { - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123' - }]; - - const actions = [{ - action_type: 'flag', - item_id: 'abc' - }, { - action_type: 'like', - item_id: 'hij' - }]; - - beforeEach(() => { - return Promise.all([ - Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions) - ]); - }); - - it('should return all the comments', () => { - return chai.request(app) - .get('/api/v1/comments') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - - expect(res).to.have.status(200); - - }); - }); -}); - -describe('Get comments by status and action', () => { - const comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - author_id: '123', - status: 'rejected' - }, { - id: 'def', + id: 'def-rejected', body: 'comment 20', asset_id: 'asset', - author_id: '456' + author_id: '456', + status: 'rejected' }, { id: 'hij', body: 'comment 30', @@ -117,109 +63,100 @@ describe('Get comments by status and action', () => { beforeEach(() => { return Promise.all([ Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions) - ]); - }); - - it('should return all the rejected comments', () => { - return chai.request(app) - .get('/api/v1/comments?status=rejected') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('id', 'abc'); - }); - }); - - it('should return all the approved comments', () => { - return chai.request(app) - .get('/api/v1/comments?status=accepted') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('id', 'hij'); - }); - }); - - it('should return all the new comments', () => { - return chai.request(app) - .get('/api/v1/comments?status=new') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('id', 'def'); - }); - }); - - it('should return all the flagged comments', () => { - return chai.request(app) - .get('/api/v1/comments?action_type=flag') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - expect(res).to.have.status(200); - - expect(res.body.length).to.equal(1); - expect(res.body[0]).to.have.property('id', 'abc'); - - }); - }); -}); - -describe('Post /comments', () => { - const users = [{ - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123' - }, { - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123' - }]; - - const actions = [{ - action_type: 'flag', - item_id: 'abc' - }, { - action_type: 'like', - item_id: 'hij' - }]; - - beforeEach(() => { - return Promise.all([ User.createLocalUsers(users), Action.create(actions), wordlist.insert([ 'bad words' - ]) + ]), + Setting.create(settings) ]); }); - it('should create a comment', () => { - return chai.request(app) - .post('/api/v1/comments') - .set(passport.inject({roles: []})) - .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) - .then((res) => { - expect(res).to.have.status(201); - expect(res.body).to.have.property('id'); - }); + describe('#get', () => { + it('should return all the comments', () => { + return chai.request(app) + .get('/api/v1/comments') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + + expect(res).to.have.status(200); + + }); + }); + + it('should return all the rejected comments', () => { + return chai.request(app) + .get('/api/v1/comments?status=rejected') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body[0]).to.have.property('id', 'def-rejected'); + }); + }); + + it('should return all the approved comments', () => { + return chai.request(app) + .get('/api/v1/comments?status=accepted') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.have.length(1); + expect(res.body[0]).to.have.property('id', 'hij'); + }); + }); + + it('should return all the new comments', () => { + return chai.request(app) + .get('/api/v1/comments?status=new') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.have.length(2); + }); + }); + + it('should return all the flagged comments', () => { + return chai.request(app) + .get('/api/v1/comments?action_type=flag') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + expect(res).to.have.status(200); + + expect(res.body).to.have.length(1); + expect(res.body[0]).to.have.property('id', 'abc'); + + }); + }); }); - it('should create a comment with a rejected status if it contains a bad word', () => { - return chai.request(app) - .post('/api/v1/comments') - .set(passport.inject({roles: []})) - .send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) - .then((res) => { - expect(res).to.have.status(201); - expect(res.body).to.have.property('id'); - expect(res.body).to.have.property('status', 'rejected'); - }); + describe('#post', () => { + + it('should create a comment', () => { + return chai.request(app) + .post('/api/v1/comments') + .set(passport.inject({roles: []})) + .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) + .then((res) => { + expect(res).to.have.status(201); + expect(res.body).to.have.property('id'); + }); + }); + + it('should create a comment with a rejected status if it contains a bad word', () => { + return chai.request(app) + .post('/api/v1/comments') + .set(passport.inject({roles: []})) + .send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) + .then((res) => { + expect(res).to.have.status(201); + expect(res.body).to.have.property('id'); + expect(res.body).to.have.property('status', 'rejected'); + }); + }); }); }); -describe('Get /:comment_id', () => { +describe('/api/v1/comments/:comment_id', () => { const comments = [{ id: 'abc', body: 'comment 10', @@ -264,79 +201,65 @@ describe('Get /:comment_id', () => { ]); }); - it('should return the right comment for the comment_id', () => { - return chai.request(app) - .get('/api/v1/comments/abc') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res).to.have.property('body'); - expect(res.body).to.have.property('body', 'comment 10'); + describe('#get', () => { - }); + it('should return the right comment for the comment_id', () => { + return chai.request(app) + .get('/api/v1/comments/abc') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + expect(res).to.have.status(200); + expect(res).to.have.property('body'); + expect(res.body).to.have.property('body', 'comment 10'); + + }); + }); + }); + + describe('#delete', () => { + it('it should remove comment', () => { + return chai.request(app) + .delete('/api/v1/comments/abc') + .set(passport.inject({roles: ['admin']})) + .then((res) => { + expect(res).to.have.status(204); + + return Comment.findById('abc'); + }) + .then((comment) => { + expect(comment).to.be.null; + }); + }); + }); + + describe('#put', () => { + it('it should update status', function() { + return chai.request(app) + .put('/api/v1/comments/abc/status') + .set(passport.inject({roles: ['admin']})) + .send({status: 'accepted'}) + .then((res) => { + expect(res).to.have.status(204); + expect(res.body).to.be.empty; + }); + }); + + it('it should not allow a non-admin to update status', () => { + return chai.request(app) + .put('/api/v1/comments/abc/status') + .set(passport.inject({roles: []})) + .send({status: 'accepted'}) + .then((res) => { + expect(res).to.be.empty; + }) + .catch((err) => { + expect(err).to.have.property('status', 401); + }); + }); }); }); -describe('Remove /:comment_id', () => { - - const comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - author_id: '123' - }, { - id: 'def', - body: 'comment 20', - asset_id: 'asset', - author_id: '456' - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456' - }]; - - const users = [{ - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123' - }, { - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123' - }]; - - const actions = [{ - action_type: 'flag', - item_id: 'abc' - }, { - action_type: 'like', - item_id: 'hij' - }]; - - beforeEach(() => { - return Promise.all([ - Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions) - ]); - }); - - it('it should remove comment', () => { - return chai.request(app) - .delete('/api/v1/comments/abc') - .set(passport.inject({roles: ['admin']})) - .then((res) => { - expect(res).to.have.status(204); - - return Comment.findById('abc'); - }) - .then((comment) => { - expect(comment).to.be.null; - }); - }); -}); - -describe('Put /:comment_id/status', () => { +describe('/api/v1/comments/:comment_id/actions', () => { const comments = [{ id: 'abc', @@ -383,90 +306,20 @@ describe('Put /:comment_id/status', () => { ]); }); - it('it should update status', function() { - return chai.request(app) - .put('/api/v1/comments/abc/status') - .set(passport.inject({roles: ['admin']})) - .send({status: 'accepted'}) - .then((res) => { - expect(res).to.have.status(204); - expect(res.body).to.be.empty; - }); - }); - - it('it should not allow a non-admin to update status', () => { - return chai.request(app) - .put('/api/v1/comments/abc/status') - .set(passport.inject({roles: []})) - .send({status: 'accepted'}) - .then((res) => { - expect(res).to.be.empty; - }) - .catch((err) => { - expect(err).to.have.property('status', 401); - }); - }); -}); - -describe('Post /:comment_id/actions', () => { - - const comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - author_id: '123', - status: '' - }, { - id: 'def', - body: 'comment 20', - asset_id: 'asset', - author_id: '456', - status: 'rejected' - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456', - status: 'accepted' - }]; - - const users = [{ - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123' - }, { - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123' - }]; - - const actions = [{ - action_type: 'flag', - item_id: 'abc' - }, { - action_type: 'like', - item_id: 'hij' - }]; - - beforeEach(() => { - return Promise.all([ - Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions) - ]); - }); - - it('it should update actions', () => { - return chai.request(app) - .post('/api/v1/comments/abc/actions') - .set(passport.inject({id: '456', roles: ['admin']})) - .send({'user_id': '456', 'action_type': 'flag'}) - .then((res) => { - expect(res).to.have.status(201); - expect(res).to.have.body; - expect(res.body).to.have.property('item_type', 'comment'); - expect(res.body).to.have.property('action_type', 'flag'); - expect(res.body).to.have.property('item_id', 'abc'); - expect(res.body).to.have.property('user_id', '456'); - }); + describe('#post', () => { + it('it should update actions', () => { + return chai.request(app) + .post('/api/v1/comments/abc/actions') + .set(passport.inject({id: '456', roles: ['admin']})) + .send({'user_id': '456', 'action_type': 'flag'}) + .then((res) => { + expect(res).to.have.status(201); + expect(res).to.have.body; + expect(res.body).to.have.property('item_type', 'comment'); + expect(res.body).to.have.property('action_type', 'flag'); + expect(res.body).to.have.property('item_id', 'abc'); + expect(res.body).to.have.property('user_id', '456'); + }); + }); }); }); diff --git a/tests/routes/api/queue/index.js b/tests/routes/api/queue/index.js index 74137a49a..733e5a1be 100644 --- a/tests/routes/api/queue/index.js +++ b/tests/routes/api/queue/index.js @@ -15,11 +15,7 @@ const User = require('../../../../models/user'); const Setting = require('../../../../models/setting'); const settings = {id: '1', moderation: 'pre'}; -beforeEach(() => { - return Setting.create(settings); -}); - -describe('Get moderation queues rejected, pending, flags', () => { +describe('/api/v1/queue', () => { const comments = [{ id: 'abc', body: 'comment 10', @@ -62,19 +58,22 @@ describe('Get moderation queues rejected, pending, flags', () => { return Promise.all([ Comment.create(comments), User.createLocalUsers(users), - Action.create(actions) + Action.create(actions), + Setting.create(settings) ]); }); - it('should return all the pending comments', function(done){ - chai.request(app) - .get('/api/v1/queue/comments/pending') - .set(passport.inject({roles: ['admin']})) - .end(function(err, res){ - expect(err).to.be.null; - expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('id', 'def'); - done(); - }); + describe('#get', () => { + it('should return all the pending comments', function(done){ + chai.request(app) + .get('/api/v1/queue/comments/pending') + .set(passport.inject({roles: ['admin']})) + .end(function(err, res){ + expect(err).to.be.null; + expect(res).to.have.status(200); + expect(res.body[0]).to.have.property('id', 'def'); + done(); + }); + }); }); }); diff --git a/tests/routes/api/settings/index.js b/tests/routes/api/settings/index.js index 9f4466a7f..d1a7ba81b 100644 --- a/tests/routes/api/settings/index.js +++ b/tests/routes/api/settings/index.js @@ -10,49 +10,42 @@ chai.use(require('chai-http')); const Setting = require('../../../../models/setting'); const defaults = {id: '1', moderation: 'pre'}; -describe('GET /settings', () => { +describe('/api/v1/settings', () => { - beforeEach(() => { - return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); + beforeEach(() => Setting.create(defaults)); + + describe('#get', () => { + + it('should return a settings object', () => { + return chai.request(app) + .get('/api/v1/settings') + .set(passport.inject({ + roles: ['admin'] + })) + .then((res) => { + expect(res).to.have.status(200); + expect(res).to.be.json; + expect(res.body).to.have.property('moderation', 'pre'); + }); + }); }); - it('should return a settings object', () => { - return chai.request(app) - .get('/api/v1/settings') - .set(passport.inject({ - roles: ['admin'] - })) - .then((res) => { - expect(res).to.have.status(200); - expect(res).to.be.json; - expect(res.body).to.have.property('moderation', 'pre'); - }); - }); -}); + describe('#put', () => { -// update the settings. -describe('update settings', () => { - it('should respond ok to a PUT', () => { - return Setting - .update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}) - .then(() => { - return chai.request(app) - .put('/api/v1/settings') - .set(passport.inject({ - roles: ['admin'] - })) - .send({moderation: 'post'}); - }) - .then(res => { - expect(res).to.have.status(204); + it('should update the settings', () => { + return chai.request(app) + .put('/api/v1/settings') + .set(passport.inject({roles: ['admin']})) + .send({moderation: 'post'}) + .then((res) => { + expect(res).to.have.status(204); - return Setting.getSettings(); - }) - .then(settings => { - - // confirm updated settings in db - expect(settings).to.have.property('moderation'); - expect(settings.moderation).to.equal('post'); - }); + return Setting.getSettings(); + }) + .then((settings) => { + expect(settings).to.have.property('moderation', 'post'); + }); + }); }); + }); diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js index 7d05c9708..2b86f00ff 100644 --- a/tests/routes/api/stream/index.js +++ b/tests/routes/api/stream/index.js @@ -13,9 +13,12 @@ const Asset = require('../../../../models/asset'); const Setting = require('../../../../models/setting'); -describe('api/stream: routes', () => { +describe('/api/v1/stream', () => { - const settings = {id: '1', moderation: 'pre'}; + const settings = { + id: '1', + moderation: 'pre' + }; const comments = [{ id: 'abc', @@ -35,7 +38,7 @@ describe('api/stream: routes', () => { asset_id: 'asset', author_id: '456', parent_id: '', - status: '' + status: 'accepted' }, { id: 'hij', body: 'comment 40', @@ -65,15 +68,26 @@ describe('api/stream: routes', () => { return Promise.all([ User.createLocalUsers(users), - Asset.findOrCreateByUrl('http://test.com') + Asset.findOrCreateByUrl('http://test.com'), + Asset + .findOrCreateByUrl('http://coralproject.net/asset2') + .then((asset) => { + return Asset + .overrideSettings(asset.id, {moderation: 'post'}) + .then(() => asset); + }) ]) - .then(([users, asset]) => { + .then(([users, asset1, asset2]) => { comments[0].author_id = users[0].id; comments[1].author_id = users[1].id; + comments[2].author_id = users[0].id; + comments[3].author_id = users[1].id; - comments[0].asset_id = asset.id; - comments[1].asset_id = asset.id; + comments[0].asset_id = asset1.id; + comments[1].asset_id = asset1.id; + comments[2].asset_id = asset2.id; + comments[3].asset_id = asset2.id; return Promise.all([ Comment.create(comments), @@ -83,16 +97,32 @@ describe('api/stream: routes', () => { }); }); - it('should return a stream with comments, users and actions for an existing asset', () => { - return chai.request(app) - .get('/api/v1/stream') - .query({'asset_url': 'http://test.com'}) - .then(res => { - expect(res).to.have.status(200); - expect(res.body.assets.length).to.equal(1); - expect(res.body.comments.length).to.equal(2); - expect(res.body.users.length).to.equal(2); - expect(res.body.actions.length).to.equal(1); - }); + describe('#get', () => { + it('should return a stream with comments, users and actions for an existing asset', () => { + return chai.request(app) + .get('/api/v1/stream') + .query({'asset_url': 'http://test.com'}) + .then(res => { + expect(res).to.have.status(200); + expect(res.body.assets.length).to.equal(1); + expect(res.body.comments.length).to.equal(2); + expect(res.body.users.length).to.equal(2); + expect(res.body.actions.length).to.equal(1); + expect(res.body.settings).to.have.property('moderation', 'pre'); + }); + }); + + it('should merge the settings when the asset contains settings to override it with', () => { + return chai.request(app) + .get('/api/v1/stream') + .query({'asset_url': 'http://coralproject.net/asset2'}) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body.assets.length).to.equal(1); + expect(res.body.comments.length).to.equal(1); + expect(res.body.users.length).to.equal(1); + expect(res.body.settings).to.have.property('moderation', 'post'); + }); + }); }); });