From aaebbabef9dfed3be977912a4794299d232bb695 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 8 Dec 2016 16:12:44 -0500 Subject: [PATCH] Revert "Revert "Status history"" --- bin/cli-serve | 4 + cache.js | 15 ++ models/action.js | 29 +++ models/asset.js | 26 ++- models/comment.js | 296 ++++++++++++++++++++--------- models/setting.js | 117 ++++++++---- models/user.js | 3 +- package.json | 1 + routes/api/comments/index.js | 87 ++++++--- routes/api/queue/index.js | 68 +++++-- routes/api/settings/index.js | 22 ++- routes/api/stream/index.js | 20 +- services/wordlist.js | 2 +- tests/.eslintrc.json | 6 +- tests/models/action.js | 47 +++-- tests/models/asset.js | 2 +- tests/models/comment.js | 152 ++++++++++++--- tests/models/setting.js | 26 +-- tests/models/user.js | 2 +- tests/routes/api/comments/index.js | 166 +++++++++------- tests/routes/api/queue/index.js | 124 ++++++------ tests/routes/api/settings/index.js | 4 +- tests/routes/api/stream/index.js | 62 +++--- 23 files changed, 846 insertions(+), 435 deletions(-) diff --git a/bin/cli-serve b/bin/cli-serve index a6790c6a0..dd682f5ad 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -96,6 +96,10 @@ function startApp() { server.listen(port); server.on('error', onError); server.on('listening', onListening); + }) + .catch((err) => { + console.error(err); + util.shutdown(1); }); } diff --git a/cache.js b/cache.js index 9345f8cde..1d090e46d 100644 --- a/cache.js +++ b/cache.js @@ -76,6 +76,21 @@ cache.get = (key) => new Promise((resolve, reject) => { }); }); +/** + * This invalidates a cached entry in the cache. + * @param {Mixed} key Either an array of items composing a key or a string + * @return {Promise} + */ +cache.invalidate = (key) => new Promise((resolve, reject) => { + cache.client.del(keyfunc(key), (err) => { + if (err) { + return reject(err); + } + + resolve(); + }); +}); + /** * This sets a value on the key with the expiry and then resolves once it is * done. diff --git a/models/action.js b/models/action.js index 891dbd4f5..b5d2ab8b6 100644 --- a/models/action.js +++ b/models/action.js @@ -1,5 +1,6 @@ const mongoose = require('../mongoose'); const uuid = require('uuid'); +const _ = require('lodash'); const Schema = mongoose.Schema; const ActionSchema = new Schema({ @@ -66,6 +67,34 @@ ActionSchema.statics.findByItemIdArray = function(item_ids) { }); }; +/** + * Fetches the action summaries for the given asset, and comments around the + * given user id. + * @param {[type]} asset_id [description] + * @param {[type]} comments [description] + * @param {String} [current_user_id=''] [description] + * @return {[type]} [description] + */ +ActionSchema.statics.getActionSummariesFromComments = (asset_id = '', comments, current_user_id = '') => { + + // Get the user id's from the author id's as a unique array that gets + // sorted. + let userIDs = _.uniq(comments.map((comment) => comment.author_id)).sort(); + + // Fetch the actions for pretty much everything at this point. + return Action.getActionSummaries(_.uniq([ + + // Actions can be on assets... + asset_id, + + // Comments... + ...comments.map((comment) => comment.id), + + // Or Authors... + ...userIDs + ].filter((e) => e)), current_user_id); +}; + /** * Returns summaries of actions for an array of ids * @param {String} ids array of user identifiers (uuid) diff --git a/models/asset.js b/models/asset.js index 165ebbc6c..a0619576f 100644 --- a/models/asset.js +++ b/models/asset.js @@ -1,6 +1,8 @@ const mongoose = require('../mongoose'); const Schema = mongoose.Schema; +const Setting = require('./setting'); + const uuid = require('uuid'); const AssetSchema = new Schema({ @@ -58,11 +60,6 @@ AssetSchema.index({ 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). @@ -75,6 +72,25 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id}); */ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); +/** + * Retrieves the settings given an asset query and rectifies it against the + * global settings. + * @param {Promise} assetQuery an asset query that returns a single asset. + * @return {Promise} + */ +AssetSchema.statics.rectifySettings = (assetQuery) => Promise.all([ + Setting.retrieve(), + assetQuery +]).then(([settings, asset]) => { + + // If the asset exists and has settings then return the merged object. + if (asset && asset.settings) { + return Object.assign({}, settings, asset.settings); + } + + return settings; +}); + /** * Finds a asset by its url. * diff --git a/models/comment.js b/models/comment.js index c1a32cf1b..af9aa4cb1 100644 --- a/models/comment.js +++ b/models/comment.js @@ -1,9 +1,37 @@ const mongoose = require('../mongoose'); +const Schema = mongoose.Schema; const uuid = require('uuid'); const Action = require('./action'); -const Schema = mongoose.Schema; +/** + * The Mongo schema for a Comment Status. + * @type {Schema} + */ +const StatusSchema = new Schema({ + type: { + type: String, + enum: [ + 'accepted', + 'rejected', + 'premod', + ], + }, + // The User ID of the user that assigned the status. + assigned_by: { + type: String, + default: null + }, + + created_at: Date +}, { + _id: false +}); + +/** + * The Mongo schema for a Comment. + * @type {Schema} + */ const CommentSchema = new Schema({ id: { type: String, @@ -17,11 +45,7 @@ const CommentSchema = new Schema({ }, asset_id: String, author_id: String, - status: { - type: String, - enum: ['accepted', 'rejected', ''], - default: '' - }, + status: [StatusSchema], parent_id: String }, { timestamps: { @@ -30,90 +54,168 @@ const CommentSchema = new Schema({ } }); -//============================================================================== -// Find Statics -//============================================================================== +/** + * toJSON overrides to remove fields from the json + * output. + */ +CommentSchema.options.toJSON = {}; +CommentSchema.options.toJSON.hide = '_id status'; +CommentSchema.options.toJSON.transform = (doc, ret, options) => { + if (options.hide) { + options.hide.split(' ').forEach((prop) => { + delete ret[prop]; + }); + } + + return ret; +}; + +/** + * toJSON overrides to remove fields from the json + * output. + */ +CommentSchema.options.toJSON = {}; +CommentSchema.options.toJSON.hide = '_id'; +CommentSchema.options.toJSON.transform = (doc, ret, options) => { + if (options.hide) { + options.hide.split(' ').forEach((prop) => { + delete ret[prop]; + }); + } + + return ret; +}; + +/** + * Sets up a virtual getter function on a comment such that when you try and + * access the `comment.last_status` it returns the last status in the array + * of status's on the comment, or `null` if there was no status. + */ +CommentSchema.virtual('last_status').get(function() { + if (this.status && this.status.length > 0) { + return this.status[this.status.length - 1].type; + } + + return null; +}); + +/** + * Creates a new Comment that came from a public source. + * @param {Mixed} comment either a single comment or an array of comments. + * @return {Promise} + */ +CommentSchema.statics.publicCreate = (comment) => { + + // Check to see if this is an array of comments, if so map it out. + if (Array.isArray(comment)) { + return Promise.all(comment.map(Comment.publicCreate)); + } + + const { + body, + asset_id, + parent_id, + status = false, + author_id + } = comment; + + comment = new Comment({ + body, + asset_id, + parent_id, + status: status ? [{ + type: status, + created_at: new Date() + }] : [], + author_id + }); + + return comment.save(); +}; /** * Finds a comment by the id. * @param {String} id identifier of comment (uuid) * @return {Promise} */ -CommentSchema.statics.findById = function(id) { - return Comment.findOne({'id': id}); -}; +CommentSchema.statics.findById = (id) => Comment.findOne({id}); /** * Finds ALL the comments by the asset_id. * @param {String} asset_id identifier of the asset which owns this comment (uuid) * @return {Promise} */ -CommentSchema.statics.findByAssetId = function(asset_id) { - return Comment.find({asset_id}); -}; +CommentSchema.statics.findByAssetId = (asset_id) => Comment.find({ + asset_id +}); /** - * Finds the accepted comments by the asset_id. - * get the comments that are accepted. + * Finds the accepted comments by the asset_id get the comments that are + * accepted. * @param {String} asset_id identifier of the asset which owns the comments (uuid) * @return {Promise} */ -CommentSchema.statics.findAcceptedByAssetId = function(asset_id) { - return Comment.find({asset_id: asset_id, status:'accepted'}); -}; +CommentSchema.statics.findAcceptedByAssetId = (asset_id) => Comment.find({ + asset_id, + 'status.type': 'accepted' +}); /** * Finds the new and accepted comments by the asset_id. * @param {String} asset_id identifier of the asset which owns the comments (uuid) * @return {Promise} */ -CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) { - return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}}); -}; +CommentSchema.statics.findAcceptedAndNewByAssetId = (asset_id) => Comment.find({ + asset_id, + $or: [ + { + 'status.type': 'accepted' + }, + { + status: { + $size: 0 + } + } + ] +}); /** * Find comments by an action that was performed on them. * @param {String} action_type the type of action that was performed on the comment * @return {Promise} */ -CommentSchema.statics.findByActionType = function(action_type) { - return Action - .findCommentsIdByActionType(action_type, 'comment') - .then((actions) => { - return Comment.find({'id': {'$in': actions.map(function(a){ - return a.item_id;})} - }); - }); -}; +CommentSchema.statics.findByActionType = (action_type) => Action + .findCommentsIdByActionType(action_type, 'comment') + .then((actions) => Comment.find({ + id: { + $in: actions.map((a) => a.item_id) + } + })); /** - * Find not moderated comments by an action that was performed on them. + * Find comment id's where the action type matches the argument. * @param {String} action_type the type of action that was performed on the comment - * @param {String} status the status of the comment to search for * @return {Promise} */ -CommentSchema.statics.findByStatusByActionType = function(status, action_type) { - return Action - .findCommentsIdByActionType(action_type, 'comment') - .then((actions) => { - return Comment.find({ - status: status, - id: { - $in: actions.map(a => a.item_id) - } - }); - }); -}; +CommentSchema.statics.findIdsByActionType = (action_type) => Action + .findCommentsIdByActionType(action_type, 'comment') + .then((actions) => actions.map(a => a.item_id)); /** * Find comments by their status. * @param {String} status the status of the comment to search for * @return {Promise} */ -CommentSchema.statics.findByStatus = function(status) { - return Comment.find({ - status: status === 'new' ? '' : status - }); +CommentSchema.statics.findByStatus = (status = false) => { + let q = {}; + + if (status) { + q['status.type'] = status; + } else { + q.status = {$size: 0}; + } + + return Comment.find(q); }; /** @@ -121,39 +223,59 @@ CommentSchema.statics.findByStatus = function(status) { * @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings. * @return {Promise} */ -CommentSchema.statics.moderationQueue = function(moderation) { - switch(moderation){ +CommentSchema.statics.moderationQueue = (moderation, asset_id = false) => { - // Pre-moderation: New comments are shown in the moderator queues immediately. - case 'pre': - return Comment.findByStatus('').then((comments) => { - return comments; - }); + /** + * This adds the asset_id requirement to the query if the asset_id is defined. + */ + const assetIDWrap = (query) => { + if (asset_id) { + query = query.where('asset_id', asset_id); + } - // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. - case 'post': - return Comment.findByStatusByActionType('', 'flag').then((comments) => { - return comments; - }); + return query; + }; - default: - return Promise.reject(Error('Moderation setting not found.')); + // Decide on whether or not we need to load extended options for the + // moderation based on the moderation options. + let comments; + + if (moderation === 'pre') { + + // Pre-moderation: New comments are shown in the moderator queues immediately. + comments = assetIDWrap(CommentSchema.statics.findByStatus('premod')); + + } else { + + // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. + comments = CommentSchema.statics.findIdsByActionType('flag') + .then((ids) => assetIDWrap(Comment.find({ + id: { + $in: ids + } + }))); } -}; -//============================================================================== -// Update Statics -//============================================================================== + return comments; +}; /** - * Change the status of a comment. - * @param {String} id identifier of the comment (uuid) - * @param {String} status the new status of the comment + * Pushes a new status in for the user. + * @param {String} id identifier of the comment (uuid) + * @param {String} status the new status of the comment + * @param {String} assigned_by the user id for the user who performed the + * moderation action * @return {Promise} */ -CommentSchema.statics.changeStatus = function(id, status) { - return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}}); -}; +CommentSchema.statics.pushStatus = (id, status, assigned_by = null) => Comment.update({id}, { + $push: { + status: { + type: status, + created_at: new Date(), + assigned_by + } + } +}); /** * Add an action to the comment. @@ -169,19 +291,13 @@ CommentSchema.statics.addAction = (item_id, user_id, action_type) => Action.inse action_type }); -//============================================================================== -// Remove Statics -//============================================================================== - /** * Change the status of a comment. * @param {String} id identifier of the comment (uuid) * @param {String} status the new status of the comment * @return {Promise} */ -CommentSchema.statics.removeById = function(id) { - return Comment.remove({'id': id}); -}; +CommentSchema.statics.removeById = (id) => Comment.remove({id}); /** * Remove an action from the comment. @@ -190,22 +306,18 @@ CommentSchema.statics.removeById = function(id) { * @param {String} user_id the id of the user performing the action * @return {Promise} */ -CommentSchema.statics.removeAction = function(item_id, user_id, action_type) { - return Action.remove({ - action_type, - item_type: 'comment', - item_id, - user_id - }); -}; +CommentSchema.statics.removeAction = (item_id, user_id, action_type) => Action.remove({ + action_type, + item_type: 'comment', + item_id, + user_id +}); /** * Returns all the comments in the collection. * @return {Promise} */ -CommentSchema.statics.all = () => { - return Comment.find(); -}; +CommentSchema.statics.all = () => Comment.find(); // Comment model. const Comment = mongoose.model('Comment', CommentSchema); diff --git a/models/setting.js b/models/setting.js index bf5ac290a..22b2b103f 100644 --- a/models/setting.js +++ b/models/setting.js @@ -1,18 +1,33 @@ const mongoose = require('../mongoose'); const Schema = mongoose.Schema; +const _ = require('lodash'); +const cache = require('../cache'); /** - * this Schema manages application settings that get used on front and backend - * NOTE: when you set a setting here, it will not automatically be exposed to - * the front end. You must add it to the whitelist in the settings route - * in /routes/api/settings/index.js + * SettingSchema manages application settings that get used on front and backend. * @type {Schema} */ const SettingSchema = new Schema({ - id: {type: String, default: '1'}, - moderation: {type: String, enum: ['pre', 'post'], default: 'pre'}, - infoBoxEnable: {type: Boolean, default: false}, - infoBoxContent: {type: String, default: ''}, + id: { + type: String, + default: '1' + }, + moderation: { + type: String, + enum: [ + 'pre', + 'post' + ], + default: 'pre' + }, + infoBoxEnable: { + type: Boolean, + default: false + }, + infoBoxContent: { + type: String, + default: '' + }, wordlist: [String] }, { timestamps: { @@ -22,48 +37,70 @@ const SettingSchema = new Schema({ }); /** - * this is run once when the app starts to ensure settings are populated - * @return {Promise} null initialize the global settings object + * The Mongo Mongoose object. */ -SettingSchema.statics.init = function (defaults) { - return this.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); -}; +const Setting = mongoose.model('Setting', SettingSchema); + +/** + * The Setting Service object exposing the Setting model. + * @type {Object} + */ +const SettingService = module.exports = {}; + +/** + * The selector used to uniquely identify the settings document. + */ +const selector = {id: '1'}; + +/** + * Cache expiry time in seconds for when the cached entry of the settings object + * expires. 2 minutes. + */ +const EXPIRY_TIME = 60 * 2; /** * Gets the entire settings record and sends it back * @return {Promise} settings the whole settings record */ -SettingSchema.statics.getSettings = function () { - return this.findOne({id: '1'}); -}; - -/** - * Gets the settings visible to the public - * @return {Promise} moderation the settings for how to moderate comments - */ -SettingSchema.statics.getPublicSettings = function () { - return this.findOne({id: '1'}).select('moderation infoBoxEnable infoBoxContent'); -}; - -/** - * Gets the info box settings and sends it back - * @return {Promise} content the content of the info Box - */ -SettingSchema.statics.getInfoBoxSetting = function () { - return this.findOne({id: '1'}).select('infoBoxEnable infoBoxContent'); -}; +SettingService.retrieve = () => cache.wrap('settings', EXPIRY_TIME, () => Setting.findOne(selector)); /** * This will update the settings object with whatever you pass in * @param {object} setting a hash of whatever settings you want to update * @return {Promise} settings Promise that resolves to the entire (updated) settings object. */ -SettingSchema.statics.updateSettings = function (setting) { - // There should only ever be one record unless something has gone wrong. - // In the future we may have multiple records for custom settings for objects/users. - return this.findOneAndUpdate({id: '1'}, {$set: setting}, {new: true}); +SettingService.update = (settings) => Setting.findOneAndUpdate(selector, { + $set: settings +}, { + upsert: true, + new: true, + setDefaultsOnInsert: true +}).then((settings) => { + + // Invalidate the settings cache. + return cache + .set('settings', settings, EXPIRY_TIME) + .then(() => settings); +}); + +/** + * Filters the document to ensure that the resulting document is indeed ready + * for non authenticated users. + * @param {Object} settings the source settings object + * @return {Object} the filtered settings object + */ +SettingService.public = (settings) => _.pick(settings, ['moderation', 'infoBoxEnable', 'infoBoxContent']); + +/** + * This is run once when the app starts to ensure settings are populated. + * @return {Promise} null initialize the global settings object + */ +SettingService.init = (defaults) => { + + // Inject the defaults on top of the passed in defaults to ensure that the new + // settings conform to the required selector. + defaults = Object.assign({}, defaults, selector); + + // Actually update the settings collection. + return SettingService.update(defaults); }; - -const Setting = mongoose.model('Setting', SettingSchema); - -module.exports = Setting; diff --git a/models/user.js b/models/user.js index 86908ab42..336486a54 100644 --- a/models/user.js +++ b/models/user.js @@ -9,7 +9,6 @@ const SALT_ROUNDS = 10; // USER_ROLES is the array of roles that is permissible as a user role. const USER_ROLES = [ - '', 'admin', 'moderator' ]; @@ -106,7 +105,7 @@ UserSchema.index({ * output. */ UserSchema.options.toJSON = {}; -UserSchema.options.toJSON.hide = 'password profiles roles disabled'; +UserSchema.options.toJSON.hide = '_id password profiles roles disabled'; UserSchema.options.toJSON.transform = (doc, ret, options) => { if (options.hide) { options.hide.split(' ').forEach((prop) => { diff --git a/package.json b/package.json index be6c2b153..c54bd6723 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "eslint-config-standard": "^6.2.1", "eslint-plugin-flowtype": "^2.25.0", "eslint-plugin-import": "^2.2.0", + "eslint-plugin-mocha": "^4.7.0", "eslint-plugin-promise": "^3.3.1", "eslint-plugin-react": "^6.6.0", "eslint-plugin-standard": "^2.0.1", diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 3bdf4afc6..b82c1faf3 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -1,5 +1,6 @@ const express = require('express'); const Comment = require('../../../models/comment'); +const Asset = require('../../../models/asset'); const User = require('../../../models/user'); const Action = require('../../../models/action'); const wordlist = require('../../../services/wordlist'); @@ -9,24 +10,45 @@ const _ = require('lodash'); const router = express.Router(); router.get('/', authorization.needed('admin'), (req, res, next) => { + + const { + status = null, + action_type = null, + asset_id = null + } = req.query; + + /** + * This adds the asset_id requirement to the query if the asset_id is defined. + */ + const assetIDWrap = (query) => { + if (asset_id) { + query = query.where('asset_id', asset_id); + } + + return query; + }; + let query; - if (req.query.status) { - query = Comment.findByStatus(req.query.status); - } else if (req.query.action_type) { - query = Comment.findByActionType(req.query.action_type); + if (status) { + query = assetIDWrap(Comment.findByStatus(status === 'new' ? null : status)); + } else if (action_type) { + query = Comment + .findIdsByActionType(action_type) + .then((ids) => assetIDWrap(Comment.find({ + id: { + $in: ids + }, + }))); } else { - query = Comment.all(); + query = assetIDWrap(Comment.all()); } query.then((comments) => { return Promise.all([ comments, User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), - Action.getActionSummaries(_.uniq([ - ...comments.map((comment) => comment.id), - ...comments.map((comment) => comment.author_id) - ])) + Action.getActionSummariesFromComments(asset_id, comments, req.user ? req.user.id : false) ]); }) .then(([comments, users, actions])=> @@ -48,21 +70,38 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { parent_id } = req.body; - Comment - .create({ - body, - asset_id, - parent_id, - status: req.wordlist.matched ? 'rejected' : '', - author_id: req.user.id - }) - .then((comment) => { + // Decide the status based on whether or not the current asset/settings + // has pre-mod enabled or not. If the comment was rejected based on the + // wordlist, then reject it, otherwise if the moderation setting is + // premod, set it to `premod`. + let status; - res.status(201).send(comment); - }) - .catch((err) => { - next(err); - }); + if (req.wordlist.matched) { + status = Promise.resolve('rejected'); + } else { + status = Asset + .rectifySettings(Asset.findById(asset_id)) + + // Return `premod` if pre-moderation is enabled and an empty "new" status + // in the event that it is not in pre-moderation mode. + .then(({moderation}) => moderation === 'pre' ? 'premod' : ''); + } + + status.then((status) => Comment.publicCreate({ + body, + asset_id, + parent_id, + status, + author_id: req.user.id + })) + .then((comment) => { + + // The comment was created! Send back the created comment. + res.status(201).send(comment); + }) + .catch((err) => { + next(err); + }); }); router.get('/:comment_id', authorization.needed('admin'), (req, res, next) => { @@ -99,7 +138,7 @@ router.put('/:comment_id/status', authorization.needed('admin'), (req, res, next } = req.body; Comment - .changeStatus(req.params.comment_id, status) + .pushStatus(req.params.comment_id, status, req.user.id) .then(() => { res.status(204).end(); }) diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js index b38d1b763..9c85fbe94 100644 --- a/routes/api/queue/index.js +++ b/routes/api/queue/index.js @@ -3,6 +3,7 @@ const Comment = require('../../../models/comment'); const User = require('../../../models/user'); const Action = require('../../../models/action'); const Setting = require('../../../models/setting'); +const Asset = require('../../../models/asset'); const _ = require('lodash'); const router = express.Router(); @@ -16,27 +17,52 @@ const router = express.Router(); // Pre-moderation: New comments are shown in the moderator queues immediately. // Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users. router.get('/comments/pending', (req, res, next) => { - Setting.getPublicSettings().then(({moderation}) => - Comment.moderationQueue(moderation)) - .then((comments) => { - return Promise.all([ - comments, - User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), - Action.getActionSummaries(_.uniq([ - ...comments.map((comment) => comment.id), - ...comments.map((comment) => comment.author_id) - ])) - ]); - }) - .then(([comments, users, actions])=> - res.status(200).json({ - comments, - users, - actions - })) - .catch(error => { - next(error); - }); + + const { + asset_id + } = req.query; + + let settings = Setting.retrieve(); + + if (asset_id) { + + // In the event that we have an asset_id, we should fetch the asset settings + // in order to actually determine if there is additional comments to parse. + settings = Promise.all([ + settings, + Asset.findById(asset_id).select('settings') + ]).then(([{moderation}, asset]) => { + if (asset.settings && asset.settings.moderation) { + return {moderation: asset.settings.moderation}; + } + + return {moderation}; + }); + } + + settings + .then(({moderation}) => { + return Comment.moderationQueue(moderation); + }).then((comments) => { + return Promise.all([ + comments, + User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), + Action.getActionSummaries(_.uniq([ + ...comments.map((comment) => comment.id), + ...comments.map((comment) => comment.author_id) + ])) + ]); + }) + .then(([comments, users, actions]) => { + res.json({ + comments, + users, + actions + }); + }) + .catch(error => { + next(error); + }); }); module.exports = router; diff --git a/routes/api/settings/index.js b/routes/api/settings/index.js index 910524fb2..76d16c7ec 100644 --- a/routes/api/settings/index.js +++ b/routes/api/settings/index.js @@ -4,19 +4,21 @@ const Setting = require('../../../models/setting'); const router = express.Router(); router.get('/', (req, res, next) => { - Setting - .getSettings() - .then(settings => { - res.json(settings); - }) - .catch(next); + Setting.retrieve().then((settings) => { + res.json(settings); + }) + .catch((err) => { + next(err); + }); }); router.put('/', (req, res, next) => { - Setting - .updateSettings(req.body) - .then(() => res.status(204).end()) - .catch(next); + Setting.update(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 4e8373999..26947be09 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -26,14 +26,14 @@ router.get('/', (req, res, next) => { return asset; }), - // Get the public settings. - Setting.getPublicSettings() + // Get the moderation setting from the settings. + Setting.retrieve() ]) .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) { + if (asset && asset.settings) { settings = Object.assign({}, settings, asset.settings); } @@ -70,17 +70,7 @@ router.get('/', (req, res, next) => { let users = userIDs.length > 0 ? User.findByIdArray(userIDs) : []; // Fetch the actions for pretty much everything at this point. - let actions = Action.getActionSummaries(_.uniq([ - - // Actions can be on assets... - asset.id, - - // Comments... - ...comments.map((comment) => comment.id), - - // Or Authors... - ...userIDs - ]), req.user ? req.user.id : false); + let actions = Action.getActionSummariesFromComments(asset.id, comments, req.user ? req.user.id : false); return Promise.all([ @@ -108,7 +98,7 @@ router.get('/', (req, res, next) => { comments, users, actions, - settings + settings: Setting.public(settings) }); }) .catch(error => { diff --git a/services/wordlist.js b/services/wordlist.js index e6f2ad668..59a842817 100644 --- a/services/wordlist.js +++ b/services/wordlist.js @@ -20,7 +20,7 @@ const wordlist = { */ wordlist.init = () => { return Setting - .getSettings() + .retrieve() .then((settings) => { // Insert the settings wordlist. diff --git a/tests/.eslintrc.json b/tests/.eslintrc.json index 363ef5a28..b188e92cf 100644 --- a/tests/.eslintrc.json +++ b/tests/.eslintrc.json @@ -4,8 +4,12 @@ "node": true, "mocha": true }, + "plugins": [ + "mocha" + ], "extends": "../.eslintrc.json", "rules": { - "no-undef": [0] + "no-undef": [0], + "mocha/no-exclusive-tests": "warn" } } diff --git a/tests/models/action.js b/tests/models/action.js index c354b8f83..5f05dea71 100644 --- a/tests/models/action.js +++ b/tests/models/action.js @@ -1,35 +1,34 @@ const Action = require('../../models/action'); const expect = require('chai').expect; -describe('Action: models', () => { - let mockActions; +describe('models.Action', () => { + let mockActions = []; - beforeEach(() => { - return Action.create([{ - action_type: 'flag', - item_id: '123', - item_type: 'comment', - user_id: 'flagginguserid' - }, { - action_type: 'flag', - item_id: '456', - item_type: 'comment' - }, { - action_type: 'flag', - item_id: '123', - item_type: 'comment' - }, { - action_type: 'like', - item_id: '123', - item_type: 'comment' - }]).then((actions) => { - mockActions = actions; - }); - }); + beforeEach(() => Action.create([{ + action_type: 'flag', + item_id: '123', + item_type: 'comment', + user_id: 'flagginguserid' + }, { + action_type: 'flag', + item_id: '456', + item_type: 'comment' + }, { + action_type: 'flag', + item_id: '123', + item_type: 'comment' + }, { + action_type: 'like', + item_id: '123', + item_type: 'comment' + }]).then((actions) => { + mockActions = actions; + })); describe('#findById()', () => { it('should find an action by id', () => { return Action.findById(mockActions[0].id).then((result) => { + expect(result).to.not.be.null; expect(result).to.have.property('action_type', 'flag'); }); }); diff --git a/tests/models/asset.js b/tests/models/asset.js index 69bf99f9d..c9eff819e 100644 --- a/tests/models/asset.js +++ b/tests/models/asset.js @@ -6,7 +6,7 @@ const expect = chai.expect; // Use the chai should. chai.should(); -describe('Asset: model', () => { +describe('models.Asset', () => { beforeEach(() => { const defaults = {url:'http://test.com'}; diff --git a/tests/models/comment.js b/tests/models/comment.js index 07834a186..a9c2f4787 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -7,35 +7,57 @@ const settings = {id: '1', moderation: 'pre'}; const expect = require('chai').expect; -describe('Comment: models', () => { +describe('models.Comment', () => { const comments = [{ body: 'comment 10', asset_id: '123', - status: '', + status: [], parent_id: '', author_id: '123', id: '1' }, { body: 'comment 20', asset_id: '123', - status: 'accepted', + status: [{ + type: 'accepted' + }], parent_id: '', author_id: '123', id: '2' }, { body: 'comment 30', asset_id: '456', - status: '', + status: [], parent_id: '', author_id: '456', id: '3' }, { body: 'comment 40', asset_id: '123', - status: 'rejected', + status: [{ + type: 'rejected' + }], parent_id: '', author_id: '456', id: '4' + }, { + body: 'comment 50', + asset_id: '1234', + status: [{ + type: 'premod' + }], + parent_id: '', + author_id: '456', + id: '5' + }, { + body: 'comment 60', + asset_id: '1234', + status: [{ + type: 'premod' + }], + parent_id: '', + author_id: '456', + id: '6' }]; const users = [{ @@ -60,25 +82,69 @@ describe('Comment: models', () => { user_id: '456' }]; - beforeEach(() => { - return Promise.all([ - Setting.create(settings), - Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions) - ]); + beforeEach(() => Promise.all([ + Setting.init(settings), + Comment.create(comments), + User.createLocalUsers(users), + Action.create(actions) + ])); + + describe('#publicCreate()', () => { + + it('creates a new comment', () => { + return Comment.publicCreate({ + body: 'This is a comment!', + status: 'accepted' + }).then((c) => { + expect(c).to.not.be.null; + expect(c.id).to.not.be.null; + expect(c.id).to.be.uuid; + expect(c.status).to.have.length(1); + expect(c.status[0]).to.have.property('type', 'accepted'); + }); + }); + + it('creates many new comments', () => { + return Comment.publicCreate([{ + body: 'This is a comment!', + status: 'accepted' + }, { + body: 'This is another comment!' + }, { + body: 'This is a rejected comment!', + status: 'rejected' + }]).then(([c1, c2, c3]) => { + expect(c1).to.not.be.null; + expect(c1.id).to.be.uuid; + expect(c1.status).to.have.length(1); + expect(c1.status[0]).to.have.property('type', 'accepted'); + + expect(c2).to.not.be.null; + expect(c2.id).to.be.uuid; + expect(c2.status).to.have.length(0); + + expect(c3).to.not.be.null; + expect(c3.id).to.be.uuid; + expect(c3.status).to.have.length(1); + expect(c3.status[0]).to.have.property('type', 'rejected'); + }); + }); + }); describe('#findById()', () => { + it('should find a comment by id', () => { return Comment.findById('1').then((result) => { expect(result).to.not.be.null; expect(result).to.have.property('body', 'comment 10'); }); }); + }); describe('#findByAssetId()', () => { + it('should find an array of all comments by asset id', () => { return Comment.findByAssetId('123').then((result) => { expect(result).to.have.length(3); @@ -91,6 +157,7 @@ describe('Comment: models', () => { expect(result[2]).to.have.property('body', 'comment 40'); }); }); + it('should find an array of accepted comments by asset id', () => { return Comment.findAcceptedByAssetId('123').then((result) => { expect(result).to.have.length(1); @@ -101,6 +168,7 @@ describe('Comment: models', () => { expect(result[0]).to.have.property('body', 'comment 20'); }); }); + it('should find an array of new and accepted comments by asset id', () => { return Comment.findAcceptedAndNewByAssetId('123').then((result) => { expect(result).to.have.length(2); @@ -112,13 +180,16 @@ describe('Comment: models', () => { }); }); }); + describe('#moderationQueue()', () => { + it('should find an array of new comments to moderate when pre-moderation', () => { return Comment.moderationQueue('pre').then((result) => { expect(result).to.not.be.null; expect(result).to.have.lengthOf(2); }); }); + it('should find an array of new comments to moderate when post-moderation', () => { return Comment.moderationQueue('post').then((result) => { expect(result).to.not.be.null; @@ -126,21 +197,56 @@ describe('Comment: models', () => { expect(result[0]).to.have.property('body', 'comment 30'); }); }); - // it('should fail when the moderation is not pre or post', () => { - // return Comment.moderationQueue('any').catch(function(error) { - // expect(error).to.not.be.null; - // }); - // }); + }); describe('#removeAction', () => { + it('should remove an action', () => { - return Comment.removeAction('3', '123', 'flag').then(() => { - return Action.findByItemIdArray(['123']); - }) - .then((actions) => { - expect(actions.length).to.equal(0); - }); + return Comment.removeAction('3', '123', 'flag') + .then(() => { + return Action.findByItemIdArray(['123']); + }) + .then((actions) => { + expect(actions.length).to.equal(0); + }); }); }); + + describe('#changeStatus', () => { + + it('should change the status of a comment from no status', () => { + let comment_id = comments[0].id; + + return Comment.findById(comment_id) + .then((c) => { + expect(c).to.have.property('status'); + expect(c.status).to.have.length(0); + + return Comment.pushStatus(comment_id, 'rejected', '123'); + }) + .then(() => Comment.findById(comment_id)) + .then((c) => { + expect(c).to.have.property('status'); + expect(c.status).to.have.length(1); + expect(c.status[0]).to.have.property('type', 'rejected'); + expect(c.status[0]).to.have.property('assigned_by', '123'); + }); + }); + + it('should change the status of a comment from accepted', () => { + return Comment.pushStatus(comments[1].id, 'rejected', '123') + .then(() => Comment.findById(comments[1].id)) + .then((c) => { + expect(c).to.have.property('status'); + expect(c.status).to.have.length(2); + expect(c.status[0]).to.have.property('type', 'accepted'); + expect(c.status[0]).to.have.property('assigned_by', null); + + expect(c.status[1]).to.have.property('type', 'rejected'); + expect(c.status[1]).to.have.property('assigned_by', '123'); + }); + }); + + }); }); diff --git a/tests/models/setting.js b/tests/models/setting.js index 186a65245..3e77297fc 100644 --- a/tests/models/setting.js +++ b/tests/models/setting.js @@ -1,34 +1,29 @@ const Setting = require('../../models/setting'); const expect = require('chai').expect; -describe('Setting: model', () => { +describe('models.Setting', () => { - beforeEach(() => { - const defaults = { - id: 1 - }; - return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); - }); + beforeEach(() => Setting.init({moderation: 'pre'})); - describe('#getSettings()', () => { + describe('#retrieve()', () => { it('should have a moderation field defined', () => { - return Setting.getSettings().then(settings => { + return Setting.retrieve().then(settings => { expect(settings).to.have.property('moderation').and.to.equal('pre'); }); }); it('should have two infoBox fields defined', () => { - return Setting.getSettings().then(settings => { + return Setting.retrieve().then(settings => { expect(settings).to.have.property('infoBoxEnable').and.to.equal(false); expect(settings).to.have.property('infoBoxContent').and.to.equal(''); }); }); }); - describe('#updateSettings()', () => { + describe('#update()', () => { it('should update the settings with a passed object', () => { const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'}; - return Setting.updateSettings(mockSettings).then(updatedSettings => { + return Setting.update(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); @@ -37,13 +32,10 @@ describe('Setting: model', () => { }); }); - describe('#getPublicSettings', () => { + describe('#get', () => { it('should return the moderation settings', () => { - return Setting.getPublicSettings().then(({moderation, infoBoxEnable, infoBoxContent, wordlist}) => { + return Setting.retrieve().then(({moderation}) => { expect(moderation).not.to.be.null; - expect(infoBoxEnable).not.to.be.null; - expect(infoBoxContent).not.to.be.null; - expect(wordlist).to.be.undefined; }); }); }); diff --git a/tests/models/user.js b/tests/models/user.js index c8af92054..5e1677016 100644 --- a/tests/models/user.js +++ b/tests/models/user.js @@ -1,7 +1,7 @@ const User = require('../../models/user'); const expect = require('chai').expect; -describe('User: models', () => { +describe('models.User', () => { let mockUsers; beforeEach(() => { return User.createLocalUsers([{ diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index bc8b67bc1..bf7a06ff6 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -10,6 +10,7 @@ chai.use(require('chai-http')); const wordlist = require('../../../../services/wordlist'); const Comment = require('../../../../models/comment'); +const Asset = require('../../../../models/asset'); const Action = require('../../../../models/action'); const User = require('../../../../models/user'); @@ -17,63 +18,71 @@ const Setting = require('../../../../models/setting'); const settings = {id: '1', moderation: 'pre'}; describe('/api/v1/comments', () => { - 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: 'def-rejected', - body: 'comment 20', - asset_id: 'asset', - author_id: '456', - status: 'rejected' - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456', - author_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', - item_type: 'comment' - }, { - action_type: 'like', - item_id: 'hij', - item_type: 'comment' - }]; - - beforeEach(() => { - return Promise.all([ - Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions), - wordlist.insert([ - 'bad words' - ]), - Setting.create(settings) - ]); - }); describe('#get', () => { + const comments = [{ + body: 'comment 10', + asset_id: 'asset', + author_id: '123' + }, { + body: 'comment 20', + asset_id: 'asset', + author_id: '456' + }, { + body: 'comment 20', + asset_id: 'asset', + author_id: '456', + status: [{ + type: 'rejected' + }] + }, { + body: 'comment 30', + asset_id: '456', + status: [{ + type: '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', + item_type: 'comment' + }, { + action_type: 'like', + item_id: 'hij', + item_type: 'comment' + }]; + + beforeEach(() => { + return Promise.all([ + Comment.create(comments).then((newComments) => { + newComments.forEach((comment, i) => { + comments[i].id = comment.id; + }); + + actions[0].item_id = comments[0].id; + actions[1].item_id = comments[1].id; + + return Action.create(actions); + }), + User.createLocalUsers(users), + wordlist.insert([ + 'bad words' + ]), + Setting.init(settings) + ]); + }); + it('should return all the comments', () => { return chai.request(app) .get('/api/v1/comments') @@ -91,7 +100,9 @@ describe('/api/v1/comments', () => { .set(passport.inject({roles: ['admin']})) .then((res) => { expect(res).to.have.status(200); - expect(res.body.comments[0]).to.have.property('id', 'def-rejected'); + expect(res.body).to.have.property('comments'); + expect(res.body.comments).to.have.length(1); + expect(res.body.comments[0]).to.have.property('id', comments[2].id); }); }); @@ -102,7 +113,7 @@ describe('/api/v1/comments', () => { .then((res) => { expect(res).to.have.status(200); expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', 'hij'); + expect(res.body.comments[0]).to.have.property('id', comments[3].id); }); }); @@ -124,8 +135,7 @@ describe('/api/v1/comments', () => { expect(res).to.have.status(200); expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', 'abc'); - + expect(res.body.comments[0]).to.have.property('id', comments[0].id); }); }); }); @@ -151,7 +161,31 @@ describe('/api/v1/comments', () => { .then((res) => { expect(res).to.have.status(201); expect(res.body).to.have.property('id'); - expect(res.body).to.have.property('status', 'rejected'); + expect(res.body).to.have.property('status').and.to.have.length(1); + expect(res.body.status[0]).to.have.property('type', 'rejected'); + }); + }); + + it('should create a comment with a premod status if it\'s asset is has pre-moderation enabled', () => { + return Asset + .findOrCreateByUrl('https://coralproject.net/article1') + .then((asset) => { + return Asset + .overrideSettings(asset.id, {moderation: 'pre'}) + .then(() => asset); + }) + .then((asset) => { + return chai.request(app) + .post('/api/v1/comments') + .set(passport.inject({roles: []})) + .send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''}); + }) + .then((res) => { + expect(res).to.have.status(201); + expect(res.body).to.have.property('id'); + expect(res.body).to.have.property('asset_id'); + expect(res.body).to.have.property('status').and.to.have.length(1); + expect(res.body.status[0]).to.have.property('type', 'premod'); }); }); }); @@ -267,18 +301,22 @@ describe('/api/v1/comments/:comment_id/actions', () => { body: 'comment 10', asset_id: 'asset', author_id: '123', - status: '' + status: [] }, { id: 'def', body: 'comment 20', asset_id: 'asset', author_id: '456', - status: 'rejected' + status: [{ + type: 'rejected' + }] }, { id: 'hij', body: 'comment 30', asset_id: '456', - status: 'accepted' + status: [{ + type: 'accepted' + }] }]; const users = [{ @@ -316,10 +354,8 @@ describe('/api/v1/comments/:comment_id/actions', () => { .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 1e99ae61e..d64423013 100644 --- a/tests/routes/api/queue/index.js +++ b/tests/routes/api/queue/index.js @@ -15,56 +15,54 @@ const User = require('../../../../models/user'); const Setting = require('../../../../models/setting'); const settings = {id: '1', moderation: 'pre'}; -beforeEach(() => { - return Setting.create(settings); -}); +describe('/api/v1/queue', () => { + const comments = [{ + id: 'abc', + body: 'comment 10', + asset_id: 'asset', + author_id: '123', + status: [{ + type: 'rejected' + }] + }, { + id: 'def', + body: 'comment 20', + asset_id: 'asset', + author_id: '456', + status: [{ + type: 'premod' + }] + }, { + id: 'hij', + body: 'comment 30', + asset_id: '456', + status: [{ + type: 'accepted' + }] + }]; -describe('Get moderation queues rejected, pending, flags', () => { + const users = [{ + displayName: 'Ana', + email: 'ana@gmail.com', + password: '123' + }, { + displayName: 'Maria', + email: 'maria@gmail.com', + password: '123' + }]; - describe('/api/v1/queue', () => { - let comments; + const actions = [{ + action_type: 'flag', + item_id: 'abc', + item_type: 'comment' + }, { + action_type: 'like', + item_id: 'hij', + item_type: 'comment' + }]; - const users = [{ - id: '456', - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123' - }, { - id: '123', - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123' - }]; - - let actions; - - beforeEach(() => { - - comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - status: 'rejected' - }, { - id: 'def', - body: 'comment 20', - asset_id: 'asset' - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456', - status: 'accepted' - }]; - - actions = [{ - action_type: 'flag', - item_type: 'comment' - }, { - action_type: 'like', - item_type: 'comment' - }]; - - return User.createLocalUsers(users) + beforeEach(() => { + return User.createLocalUsers(users) .then((u) => { comments[0].author_id = u[0].id; comments[1].author_id = u[1].id; @@ -76,22 +74,24 @@ describe('Get moderation queues rejected, pending, flags', () => { actions[0].item_id = c[0].id; actions[1].item_id = c[1].id; - return Action.create(actions); + return Promise.all([ + Action.create(actions), + Setting.init(settings) + ]); }); - }); + }); - it('should return all the pending comments, users and actions', 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.comments[0]).to.have.property('body'); - expect(res.body.users[0]).to.have.property('displayName'); - expect(res.body.actions[0]).to.have.property('action_type'); - done(); - }); - }); + it('should return all the pending comments, users and actions', 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.comments[0]).to.have.property('body'); + expect(res.body.users[0]).to.have.property('displayName'); + expect(res.body.actions[0]).to.have.property('action_type'); + done(); + }); }); }); diff --git a/tests/routes/api/settings/index.js b/tests/routes/api/settings/index.js index d1a7ba81b..318af81f4 100644 --- a/tests/routes/api/settings/index.js +++ b/tests/routes/api/settings/index.js @@ -12,7 +12,7 @@ const defaults = {id: '1', moderation: 'pre'}; describe('/api/v1/settings', () => { - beforeEach(() => Setting.create(defaults)); + beforeEach(() => Setting.init(defaults)); describe('#get', () => { @@ -40,7 +40,7 @@ describe('/api/v1/settings', () => { .then((res) => { expect(res).to.have.status(204); - return Setting.getSettings(); + return Setting.retrieve(); }) .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 6484fdc12..a128938ba 100644 --- a/tests/routes/api/stream/index.js +++ b/tests/routes/api/stream/index.js @@ -20,7 +20,37 @@ describe('/api/v1/stream', () => { moderation: 'post' }; - let comments; + const comments = [{ + id: 'abc', + body: 'comment 10', + author_id: '', + parent_id: '', + status: [{ + type: 'accepted' + }] + }, { + id: 'def', + body: 'comment 20', + author_id: '', + parent_id: '', + status: [] + }, { + id: 'uio', + body: 'comment 30', + asset_id: 'asset', + author_id: '456', + parent_id: '', + status: [{ + type: 'accepted' + }] + }, { + id: 'hij', + body: 'comment 40', + asset_id: '456', + status: [{ + type: 'rejected' + }] + }]; const users = [{ displayName: 'Ana', @@ -41,33 +71,6 @@ describe('/api/v1/stream', () => { }]; beforeEach(() => { - - comments = [{ - id: 'abc', - body: 'comment 10', - author_id: '', - parent_id: '', - status: 'accepted' - }, { - id: 'def', - body: 'comment 20', - author_id: '', - parent_id: '', - status: '' - }, { - id: 'uio', - body: 'comment 30', - asset_id: 'asset', - author_id: '456', - parent_id: '', - status: 'accepted' - }, { - id: 'hij', - body: 'comment 40', - asset_id: '456', - status: 'rejected' - }]; - return Promise.all([ User.createLocalUsers(users), Asset.findOrCreateByUrl('http://test.com'), @@ -94,10 +97,11 @@ describe('/api/v1/stream', () => { return Promise.all([ Comment.create(comments), Action.create(actions), - Setting.init().then(() => Setting.updateSettings(settings)) + Setting.init(settings) ]); }); }); + it('should return a stream with comments, users and actions for an existing asset', () => { return chai.request(app) .get('/api/v1/stream')