From a00befafcb29776af7e3020e57ab9f81aee1ca54 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 15:48:46 -0700 Subject: [PATCH 1/4] Initial graph tests --- bin/cli-setup | 1 + errors.js | 3 +- graph/mutators/comment.js | 17 +- graph/resolvers/root_mutation.js | 20 +- models/comment.js | 5 +- routes/api/actions/index.js | 20 -- routes/api/comments/index.js | 130 ------------- routes/api/index.js | 4 - routes/api/queue/index.js | 101 ---------- services/comments.js | 24 +-- test/graph/context.js | 64 ++++++ test/graph/mutations/createComment.js | 233 ++++++++++++++++++++++ test/routes/api/comments/index.js | 269 -------------------------- test/routes/api/queue/index.js | 123 ------------ 14 files changed, 337 insertions(+), 677 deletions(-) delete mode 100644 routes/api/actions/index.js delete mode 100644 routes/api/comments/index.js delete mode 100644 routes/api/queue/index.js create mode 100644 test/graph/context.js create mode 100644 test/graph/mutations/createComment.js delete mode 100644 test/routes/api/comments/index.js delete mode 100644 test/routes/api/queue/index.js diff --git a/bin/cli-setup b/bin/cli-setup index 7985bf220..b8e8538ed 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -184,6 +184,7 @@ const performSetup = () => { console.log('Settings created.'); console.log(`User ${user.id} created.`); console.log('\nTalk is now installed!'); + console.log('\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.'); util.shutdown(); }) .catch((err) => { diff --git a/errors.js b/errors.js index ee68b67fd..ad8288dad 100644 --- a/errors.js +++ b/errors.js @@ -80,7 +80,8 @@ const ErrMissingToken = new APIError('token is required', { class ErrAssetCommentingClosed extends APIError { constructor(closedMessage = null) { super('asset commenting is closed', { - status: 400 + status: 400, + translation_key: 'COMMENTING_CLOSED' }, { // Include the closedMessage in the metadata piece of the error. diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 5007a3039..33d2c4f85 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -15,11 +15,18 @@ const Wordlist = require('../../services/wordlist'); * @return {Promise} resolves to the created comment */ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = null}, status = 'NONE') => { + + let tags = []; + if (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR')) { + tags = [{name: 'STAFF'}]; + } + return CommentsService.publicCreate({ body, asset_id, parent_id, status, + tags, author_id: user.id }) .then((comment) => { @@ -36,12 +43,6 @@ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = } } - if (user.hasRoles('ADMIN')) { - return CommentsService - .addTag(comment.id, 'STAFF', user.id) - .then(() => comment); - } - return comment; }); }; @@ -140,12 +141,12 @@ const createPublicComment = (context, commentInput) => { // Otherwise just return the new comment. // TODO: Check why the wordlist is undefined - if (wordlist != null) { + if (wordlist != null && wordlist.suspect != null) { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'COMMENTS', 'FLAG' to be // defined in a checkable schema. - return context.mutators.Action.createAction(null, { + return context.mutators.Action.create({ item_id: comment.id, item_type: 'COMMENTS', action_type: 'FLAG', diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 4285f900d..71ac8b8a6 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -1,3 +1,6 @@ +const {Error: {ValidationError}} = require('mongoose'); +const errors = require('../../errors'); + /** * Wraps up a promise to return an object with the resolution of the promise * keyed at `key` or an error caught at `errors`. @@ -9,9 +12,20 @@ const wrapResponse = (key) => (promise) => { res[key] = value; } return res; - }).catch((err) => ({ - errors: [err] - })); + }).catch((err) => { + + if (err instanceof errors.APIError) { + return { + errors: [err] + }; + } else if (err instanceof ValidationError) { + + // TODO: wrap this with one of our internal errors. + throw err; + } + + throw err; + }); }; const RootMutation = { diff --git a/models/comment.js b/models/comment.js index c88041c8a..e6523e45f 100644 --- a/models/comment.js +++ b/models/comment.js @@ -43,7 +43,10 @@ const TagSchema = new Schema({ default: null }, - created_at: Date + created_at: { + type: Date, + default: Date + } }, { _id: false }); diff --git a/routes/api/actions/index.js b/routes/api/actions/index.js deleted file mode 100644 index 20316d93b..000000000 --- a/routes/api/actions/index.js +++ /dev/null @@ -1,20 +0,0 @@ -const express = require('express'); -const ActionsService = require('../../../services/actions'); - -const router = express.Router(); - -router.delete('/:action_id', (req, res, next) => { - ActionsService - .findOneAndRemove({ - id: req.params.action_id, - user_id: req.user.id - }) - .then(() => { - res.status(204).end(); - }) - .catch(error => { - next(error); - }); -}); - -module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js deleted file mode 100644 index e77ca0c30..000000000 --- a/routes/api/comments/index.js +++ /dev/null @@ -1,130 +0,0 @@ -const express = require('express'); -const errors = require('../../../errors'); - -const CommentModel = require('../../../models/comment'); -const CommentsService = require('../../../services/comments'); -const AssetsService = require('../../../services/assets'); -const UsersService = require('../../../services/users'); -const ActionsService = require('../../../services/actions'); - -const authorization = require('../../../middleware/authorization'); -const _ = require('lodash'); - -const router = express.Router(); - -router.get('/', (req, res, next) => { - - const { - status = null, - action_type = null, - asset_id = null, - user_id = null - } = req.query; - - // everything on this route requires admin privileges besides listing comments for owner of said comments - if (!authorization.has(req.user, 'ADMIN') && !user_id) { - next(errors.ErrNotAuthorized); - return; - } - - // if the user is not an admin, only return comment list for the owner of the comments - if (req.user.id !== user_id && !authorization.has(req.user, 'ADMIN')) { - next(errors.ErrNotAuthorized); - return; - } - - /** - * 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; - - // the check for user_id MUST be first here. - // otherwise this will be a vulnerability if you pass user_id and something else, - // the app will return admin-level data without the proper checks - if (user_id) { - query = CommentsService.findByUserId(user_id, authorization.has(req.user, 'ADMIN')); - } else if (status) { - query = assetIDWrap(CommentsService.findByStatus(status === 'NEW' ? 'NONE' : status)); - } else if (action_type) { - query = CommentsService - .findIdsByActionType(action_type) - .then((ids) => assetIDWrap(CommentModel.find({ - id: { - $in: ids - } - }))); - } else { - query = assetIDWrap(CommentsService.all()); - } - - query.then((comments) => { - return Promise.all([ - comments, - AssetsService.findMultipleById(comments.map(comment => comment.asset_id)), - UsersService.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), - ActionsService.getActionSummariesFromComments(asset_id, comments, req.user ? req.user.id : false) - ]); - }) - .then(([comments, assets, users, actions]) => - res.status(200).json({ - comments, - assets, - users, - actions - })) - .catch((err) => { - next(err); - }); -}); - -router.get('/:comment_id', authorization.needed('ADMIN'), (req, res, next) => { - CommentsService - .findById(req.params.comment_id) - .then(comment => { - if (!comment) { - res.status(404).end(); - return; - } - - res.status(200).json(comment); - }) - .catch((err) => { - next(err); - }); -}); - -router.delete('/:comment_id', authorization.needed('ADMIN'), (req, res, next) => { - CommentsService - .removeById(req.params.comment_id) - .then(() => { - res.status(204).end(); - }) - .catch((err) => { - next(err); - }); -}); - -router.put('/:comment_id/status', authorization.needed('ADMIN'), (req, res, next) => { - const { - status - } = req.body; - - CommentsService - .pushStatus(req.params.comment_id, status, req.user.id) - .then(() => { - res.status(204).end(); - }) - .catch((err) => { - next(err); - }); -}); - -module.exports = router; diff --git a/routes/api/index.js b/routes/api/index.js index 9913d8ad2..56846acde 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -5,10 +5,6 @@ const router = express.Router(); router.use('/assets', authorization.needed('ADMIN'), require('./assets')); router.use('/settings', authorization.needed('ADMIN'), require('./settings')); -router.use('/queue', authorization.needed('ADMIN'), require('./queue')); - -router.use('/comments', authorization.needed(), require('./comments')); -router.use('/actions', authorization.needed(), require('./actions')); router.use('/auth', require('./auth')); router.use('/users', require('./users')); diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js deleted file mode 100644 index b3cbb1a52..000000000 --- a/routes/api/queue/index.js +++ /dev/null @@ -1,101 +0,0 @@ -const express = require('express'); -const CommentsService = require('../../../services/comments'); -const CommentModel = require('../../../models/comment'); -const UsersService = require('../../../services/users'); -const ActionsService = require('../../../services/actions'); -const authorization = require('../../../middleware/authorization'); -const _ = require('lodash'); - -const router = express.Router(); - -function gatherActionsAndUsers (comments) { - return Promise.all([ - comments, - UsersService.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), - ActionsService.getActionSummaries(_.uniq([ - ...comments.map((comment) => comment.id), - ...comments.map((comment) => comment.author_id) - ])) - ]); -} - -//============================================================================== -// Get Routes -//============================================================================== - -// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated, -// depending on the settings. The :moderation overwrites this settings. -// 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/premod', authorization.needed('ADMIN'), (req, res, next) => { - - const {asset_id} = req.query; - - CommentsService.moderationQueue('PREMOD', asset_id) - .then(gatherActionsAndUsers) - .then(([comments, users, actions]) => { - res.json({comments, users, actions}); - }) - .catch(error => { - next(error); - }); -}); - -router.get('/comments/rejected', authorization.needed('ADMIN'), (req, res, next) => { - const {asset_id} = req.query; - - CommentsService.moderationQueue('REJECTED', asset_id) - .then(gatherActionsAndUsers) - .then(([comments, users, actions]) => { - res.json({comments, users, actions}); - }) - .catch(error => { - next(error); - }); -}); - -router.get('/comments/flagged', authorization.needed('ADMIN'), (req, res, next) => { - const {asset_id} = req.query; - - const assetIDWrap = (query) => { - if (asset_id) { - query = query.where('asset_id', asset_id); - } - - return query; - }; - - CommentsService.findIdsByActionType('FLAG') - .then(ids => assetIDWrap(CommentModel.find({ - id: {$in: ids} - }))) - .then(gatherActionsAndUsers) - .then(([comments, users, actions]) => { - res.json({comments, users, actions}); - }) - .catch(error => { - next(error); - }); -}); - -// Returns back all the users that are in the moderation queue. -router.get('/users/flagged', (req, res, next) => { - UsersService.moderationQueue() - .then((users) => { - return Promise.all([ - users, - ActionsService.getActionSummaries(users.map((user) => user.id)) - ]); - }) - .then(([users, actions]) => { - res.json({ - users, - actions - }); - }) - .catch(error => { - next(error); - }); -}); - -module.exports = router; diff --git a/services/comments.js b/services/comments.js index ad9ce18db..4dda82b04 100644 --- a/services/comments.js +++ b/services/comments.js @@ -29,27 +29,17 @@ module.exports = class CommentsService { } const { - body, - asset_id, - parent_id, status = 'NONE', - author_id } = comment; - comment = new CommentModel({ - body, - asset_id, - parent_id, - status_history: status ? [{ - type: status, - created_at: new Date() - }] : [], - tags: [], - status, - author_id - }); + comment.status_history = status ? [{ + type: status, + created_at: new Date() + }] : []; - return comment.save(); + let commentModel = new CommentModel(comment); + + return commentModel.save(); } /** diff --git a/test/graph/context.js b/test/graph/context.js new file mode 100644 index 000000000..413622678 --- /dev/null +++ b/test/graph/context.js @@ -0,0 +1,64 @@ +const expect = require('chai').expect; + +const User = require('../../models/user'); +const Context = require('../../graph/context'); +const errors = require('../../errors'); + +describe('graph.Context', () => { + + describe('#constructor: with a user', () => { + let c; + + beforeEach(() => { + c = new Context({user: new User({id: '1'})}); + }); + + it('creates a context with a user', (done) => { + expect(c).to.have.property('user'); + expect(c.user).to.have.property('id', '1'); + + done(); + }); + + it('does have access to mutators', () => { + return c.mutators.Action.create({ + item_id: '1', + item_type: 'COMMENTS', + action_type: 'LIKE' + }) + .then((action) => { + expect(action).to.have.property('item_id', '1'); + expect(action).to.have.property('item_type', 'COMMENTS'); + expect(action).to.have.property('action_type', 'LIKE'); + }); + }); + }); + + describe('#constructor: without a user', () => { + let c; + + beforeEach(() => { + c = new Context({user: undefined}); + }); + + it('creates a context without a user', (done) => { + expect(c).to.not.have.property('user'); + + done(); + }); + + it('does not have access to mutators', () => { + return c.mutators.Action.create({ + item_id: '1', + item_type: 'COMMENTS', + action_type: 'LIKE' + }) + .then((action) => { + expect(action).to.be.null; + }) + .catch((err) => { + expect(err).to.be.equal(errors.ErrNotAuthorized); + }); + }); + }); +}); diff --git a/test/graph/mutations/createComment.js b/test/graph/mutations/createComment.js new file mode 100644 index 000000000..b80cdbab2 --- /dev/null +++ b/test/graph/mutations/createComment.js @@ -0,0 +1,233 @@ +const expect = require('chai').expect; +const {graphql} = require('graphql'); + +const schema = require('../../../graph/schema'); +const Context = require('../../../graph/context'); +const UserModel = require('../../../models/user'); +const AssetModel = require('../../../models/asset'); +const SettingsService = require('../../../services/settings'); +const ActionModel = require('../../../models/action'); + +describe('graph.mutation.createComment', () => { + beforeEach(() => SettingsService.init()); + + const query = ` + mutation CreateComment($body: String = "Here's my comment!") { + createComment(asset_id: "123", body: $body) { + comment { + id + status + tags { + name + } + } + errors { + translation_key + } + } + } + `; + + describe('context with different user properties', () => { + + beforeEach(() => AssetModel.create({id: '123'})); + + [ + {user: null, error: 'NOT_AUTHORIZED'}, + {user: new UserModel({}), error: null} + ].forEach(({user, error}) => { + describe(user != null ? 'with user' : 'without user', () => { + + it(error ? 'does not create the comment' : 'creates the comment', () => { + const context = new Context({user}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not.null; + expect(data.createComment.errors[0]).to.have.property('translation_key', error); + } else { + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + } + }); + }); + + }); + }); + + }); + + describe('users with different statuses', () => { + + beforeEach(() => AssetModel.create({id: '123'})); + + [ + {user: new UserModel({status: 'ACTIVE'}), error: null}, + {user: new UserModel({status: 'BANNED'}), error: 'NOT_AUTHORIZED'}, + {user: new UserModel({status: 'PENDING'}), error: null}, + {user: new UserModel({status: 'APPROVED'}), error: null} + ].forEach(({user, error}) => { + describe(`user.status=${user.status}`, () => { + it(error ? 'does not create the comment' : 'creates the comment', () => { + const context = new Context({user}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not.null; + expect(data.createComment.errors[0]).to.have.property('translation_key', error); + } else { + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + } + }); + }); + }); + }); + + }); + + describe('assets with different statuses', () => { + + [ + {asset: new AssetModel({id: '123', closedAt: new Date((new Date()).getTime() + (10 * 86400000))}), error: null}, + {asset: new AssetModel({id: '123', closedAt: new Date((new Date()).getTime() - (10 * 86400000))}), error: 'COMMENTING_CLOSED'} + ].forEach(({asset, error}) => { + describe(`asset.isClosed=${asset.isClosed}`, () => { + + beforeEach(() => asset.save()); + + it(error ? 'does not create the comment' : 'creates the comment', () => { + const context = new Context({user: new UserModel({status: 'ACTIVE'})}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not.null; + expect(data.createComment.errors[0]).to.have.property('translation_key', error); + } else { + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + } + }); + }); + + }); + + }); + + }); + + describe('comments made with different asset moderation settings', () => { + + [ + {moderation: 'PRE', status: 'PREMOD'}, + {moderation: 'POST', status: 'NONE'} + ].forEach(({moderation, status}) => { + describe(`moderation=${moderation}`, () => { + + beforeEach(() => AssetModel.create({id: '123', settings: {moderation}})); + + it(`creates comment with status=${status}`, () => { + const context = new Context({user: new UserModel({status: 'ACTIVE'})}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + expect(data.createComment.comment).to.have.property('status', status); + }); + }); + + }); + }); + + }); + + describe('comments with/without banned words', () => { + + beforeEach(() => Promise.all([ + AssetModel.create({id: '123'}), + SettingsService.update({wordlist: {banned: ['WORST'], suspect: ['EH']}}) + ])); + + [ + {message: 'comment does not contain banned/suspect words', body: 'This is such a nice comment!', status: 'NONE', flagged: false}, + {message: 'comment contains banned words', body: 'This is the WORST comment!', status: 'REJECTED', flagged: false}, + {message: 'comment contains suspect words', body: 'This is the EH comment!', status: 'NONE', flagged: true} + ].forEach(({message, body, status, flagged}) => { + describe(message, () => { + + it(`should create a comment with status=${status} and it ${flagged ? 'should' : 'should not'} be flagged`, () => { + const context = new Context({user: new UserModel({status: 'ACTIVE'})}); + + return graphql(schema, query, {}, context, { + body + }) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment.comment).to.have.property('status', status); + expect(data.createComment).to.have.property('errors').null; + + return ActionModel.find({ + item_id: data.createComment.comment.id, + action_type: 'FLAG' + }); + }) + .then((actions) => { + if (flagged) { + expect(actions).to.have.length(1); + } else { + expect(actions).to.have.length(0); + } + }); + }); + + }); + }); + }); + + describe('users with different roles', () => { + + beforeEach(() => AssetModel.create({id: '123'})); + + [ + {roles: [], tag: null}, + {roles: ['ADMIN'], tag: 'STAFF'}, + {roles: ['MODERATOR'], tag: 'STAFF'}, + {roles: ['ADMIN', 'MODERATOR'], tag: 'STAFF'} + ].forEach(({roles, tag}) => { + describe(`user.roles=${JSON.stringify(roles)}`, () => { + + it(`creates comment ${tag ? `with tag=${tag}` : 'without tags'}`, () => { + const context = new Context({user: new UserModel({roles})}); + + return graphql(schema, query, {}, context) + .then(({data, errors}) => { + expect(errors).to.be.undefined; + expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment).to.have.property('errors').null; + + if (tag) { + expect(data.createComment.comment).to.have.property('tags').length(1); + expect(data.createComment.comment.tags[0]).to.have.property('name', tag); + } else { + expect(data.createComment.comment).to.have.property('tags').length(0); + } + }); + }); + + }); + }); + + }); +}); diff --git a/test/routes/api/comments/index.js b/test/routes/api/comments/index.js deleted file mode 100644 index 50431159b..000000000 --- a/test/routes/api/comments/index.js +++ /dev/null @@ -1,269 +0,0 @@ -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 CommentModel = require('../../../../models/comment'); -const ActionModel = require('../../../../models/action'); - -const CommentsService = require('../../../../services/comments'); -const UsersService = require('../../../../services/users'); -const SettingsService = require('../../../../services/settings'); - -const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}}; - -describe('/api/v1/comments', () => { - - // Ensure that the settings are always available. - beforeEach(() => SettingsService.init(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: 'REJECTED', - status_history: [{ - type: 'REJECTED' - }] - }, { - body: 'comment 30', - asset_id: '456', - status: 'ACCEPTED', - status_history: [{ - type: 'ACCEPTED' - }] - }]; - - const users = [{ - username: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - username: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; - - const actions = [{ - action_type: 'FLAG', - item_id: 'abc', - item_type: 'COMMENTS' - }, { - action_type: 'LIKE', - item_id: 'hij', - item_type: 'COMMENTS' - }]; - - beforeEach(() => { - return Promise.all([ - CommentModel.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 ActionModel.create(actions); - }), - UsersService.createLocalUsers(users) - ]); - }); - - it('should return only the owner’s published comments if the user is not an admin', () => { - return chai.request(app) - .get('/api/v1/comments?user_id=456') - .set(passport.inject({id: '456', roles: []})) - .then(res => { - expect(res).to.have.status(200); - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('author_id', '456'); - }); - }); - - it('should fail if a non-admin requests comments not owned by them', () => { - return chai.request(app) - .get('/api/v1/comments?user_id=456') - .set(passport.inject({id: '123', roles: []})) - .then((res) => { - expect(res).to.be.empty; - }) - .catch((err) => { - expect(err).to.have.status(401); - }); - }); - - 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).to.have.property('comments'); - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', comments[2].id); - }); - }); - - 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.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', comments[3].id); - }); - }); - - 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.comments).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.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('id', comments[0].id); - }); - }); - }); -}); - -describe('/api/v1/comments/: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 = [{ - username: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - username: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; - - const actions = [{ - action_type: 'FLAG', - item_id: 'abc', - item_type: 'COMMENTS' - }, { - action_type: 'LIKE', - item_id: 'hij', - item_type: 'COMMENTS' - }]; - - beforeEach(() => { - return SettingsService.init(settings).then(() => { - return Promise.all([ - CommentModel.create(comments), - UsersService.createLocalUsers(users), - ActionModel.create(actions) - ]); - }); - }); - - 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 CommentsService.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); - }); - }); - }); -}); diff --git a/test/routes/api/queue/index.js b/test/routes/api/queue/index.js deleted file mode 100644 index bca68f355..000000000 --- a/test/routes/api/queue/index.js +++ /dev/null @@ -1,123 +0,0 @@ -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 Comment = require('../../../../models/comment'); -const Action = require('../../../../models/action'); -const UsersService = require('../../../../services/users'); - -const SettingsService = require('../../../../services/settings'); -const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['banned'], suspect: ['suspect']}}; - -describe('/api/v1/queue', () => { - const comments = [{ - id: 'abc', - body: 'comment 10', - asset_id: 'asset', - author_id: '123', - status: 'REJECTED', - status_history: [{ - type: 'REJECTED' - }] - }, { - id: 'def', - body: 'comment 20', - asset_id: 'asset', - author_id: '456', - status: 'PREMOD', - status_history: [{ - type: 'PREMOD' - }] - }, { - id: 'hij', - body: 'comment 30', - asset_id: '456', - status: 'ACCEPTED', - status_history: [{ - type: 'ACCEPTED' - }] - }]; - - const users = [{ - username: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - username: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; - - const actions = [{ - action_type: 'FLAG', - item_id: 'abc', - item_type: 'COMMENTS' - }, { - action_type: 'LIKE', - item_id: 'hij', - item_type: 'COMMENTS' - }, { - action_type: 'FLAG', - item_id: '123', - item_type: 'USERS' - }]; - - beforeEach(() => { - return SettingsService.init(settings).then(() => { - return UsersService.createLocalUsers(users) - .then((u) => { - comments[0].author_id = u[0].id; - comments[1].author_id = u[1].id; - comments[2].author_id = u[1].id; - - return Promise.all([ - Comment.create(comments), - u, - ...u.map((user) => UsersService.setStatus(user.id, 'PENDING')) - ]); - }) - .then(([c, u]) => { - actions[0].item_id = c[0].id; - actions[1].item_id = c[1].id; - actions[2].item_id = u[0].id; - - return Promise.all([ - Action.create(actions), - SettingsService.init(settings) - ]); - }); - }); - }); - - it('should return all the pending comments, users and actions', () => { - return chai.request(app) - .get('/api/v1/queue/comments/premod') - .set(passport.inject({roles: ['ADMIN']})) - .then((res) => { - expect(res).to.have.status(200); - expect(res.body.comments).to.have.length(1); - expect(res.body.comments[0]).to.have.property('body'); - expect(res.body.users[0]).to.have.property('username'); - expect(res.body.actions[0]).to.have.property('action_type'); - }); - }); - - it('should return all pending users and actions', function(done){ - chai.request(app) - .get('/api/v1/queue/users/flagged') - .set(passport.inject({roles: ['ADMIN']})) - .end(function(err, res){ - expect(err).to.be.null; - expect(res).to.have.status(200); - expect(res.body.users[0]).to.have.property('username'); - expect(res.body.actions[0]).to.have.property('action_type'); - done(); - }); - }); -}); From 4cccf09a872d4b7bad5f2495669a3553a98ab1f6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 15:53:23 -0700 Subject: [PATCH 2/4] spelling... --- test/graph/mutations/createComment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/graph/mutations/createComment.js b/test/graph/mutations/createComment.js index b80cdbab2..26775933c 100644 --- a/test/graph/mutations/createComment.js +++ b/test/graph/mutations/createComment.js @@ -8,7 +8,7 @@ const AssetModel = require('../../../models/asset'); const SettingsService = require('../../../services/settings'); const ActionModel = require('../../../models/action'); -describe('graph.mutation.createComment', () => { +describe('graph.mutations.createComment', () => { beforeEach(() => SettingsService.init()); const query = ` From afcdacff0dcbdb6be9d9594da70d4d6e43afce4a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 17:49:59 -0700 Subject: [PATCH 3/4] Added yarn.lock to Dockerfile --- Dockerfile | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a01aae254..11587cafb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,8 +14,8 @@ ENV TALK_PORT 5000 EXPOSE 5000 # Install app dependencies -COPY package.json /usr/src/app/ -RUN yarn install --production +COPY package.json yarn.lock /usr/src/app/ +RUN yarn install # Bundle app source COPY . /usr/src/app diff --git a/package.json b/package.json index 09ce50aa9..70b0e17b1 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ }, "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { - "apollo-client": "^0.8.3", "bcrypt": "^0.8.7", "body-parser": "^1.15.2", "cli-table": "^0.3.1", @@ -66,7 +65,6 @@ "graphql": "^0.8.2", "graphql-errors": "^2.1.0", "graphql-server-express": "^0.5.0", - "graphql-tag": "^1.2.3", "graphql-tools": "^0.9.0", "helmet": "^3.1.0", "inquirer": "^3.0.1", @@ -88,6 +86,7 @@ "uuid": "^2.0.3" }, "devDependencies": { + "apollo-client": "^0.8.3", "autoprefixer": "^6.5.2", "babel-core": "^6.21.0", "babel-eslint": "^7.1.0", @@ -122,6 +121,7 @@ "exports-loader": "^0.6.3", "fetch-mock": "^5.5.0", "graphql-docs": "^0.2.0", + "graphql-tag": "^1.2.3", "hammerjs": "^2.0.8", "ignore-styles": "^5.0.1", "immutable": "^3.8.1", From 301c8e829f7e985d66c6fbc7b0c1f571d6173b33 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Feb 2017 17:56:44 -0700 Subject: [PATCH 4/4] Added production flag back --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 11587cafb..14d606c0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ EXPOSE 5000 # Install app dependencies COPY package.json yarn.lock /usr/src/app/ -RUN yarn install +RUN yarn install --production # Bundle app source COPY . /usr/src/app