From 8baf15da3a237e790e4e4ba3adb785ab8c084900 Mon Sep 17 00:00:00 2001 From: Tejiri Matthew <39820795+tejiri4@users.noreply.github.com> Date: Thu, 24 Jan 2019 13:26:19 +0100 Subject: [PATCH] #ft-162414269 Users can report articles --- server/controllers/LikesController.js | 23 +- server/controllers/ReportArticleController.js | 254 +++++++++---- server/middlewares/AuthManager.js | 63 ---- server/middlewares/AuthMiddleware.js | 32 +- .../20190113050147-create-report.js | 4 + server/models/article.js | 24 +- server/models/articleLikesDislike.js | 5 + server/models/comment.js | 6 +- server/models/report.js | 39 +- server/models/user.js | 19 +- server/routes/api/article.js | 2 - server/routes/api/reportArticle.js | 26 +- server/seeders/20190124084130-demoTag.js | 2 +- server/test/controllers/articles.spec.js | 4 +- server/test/controllers/comment.spec.js | 242 ++++++++++++ server/test/controllers/likeDislike.spec.js | 14 + .../test/controllers/reportArticles.spec.js | 356 ++++++++++++++++++ .../test/controllers/userController.spec.js | 4 +- 18 files changed, 936 insertions(+), 183 deletions(-) delete mode 100755 server/middlewares/AuthManager.js create mode 100644 server/test/controllers/reportArticles.spec.js diff --git a/server/controllers/LikesController.js b/server/controllers/LikesController.js index d637c8a5..6b739d58 100755 --- a/server/controllers/LikesController.js +++ b/server/controllers/LikesController.js @@ -2,7 +2,7 @@ import db from '../models'; import response from '../helpers/response'; const { - Article, ArticleLikesDislike, Comment, CommentLike + Article, ArticleLikesDislike, User, Comment, CommentLike } = db; /** @@ -85,19 +85,21 @@ class LikesController { where: { articleId: theArticle.dataValues.id, reaction: 'like' - } + }, + include: [ + { + model: User, + as: 'Liked By', + attributes: ['id', 'userName', 'img'], + } + ], + attributes: [ + ] }); if (!allLikes) { return response(res, 404, 'failure', 'No likes for this Article', null, null); } - return response( - res, - 200, - 'success', - `There are ${allLikes.count} Likes for this article`, - null, - null - ); + return response(res, 200, 'success', `There are ${allLikes.count} Likes for this article`, null, allLikes.rows); } catch (error) { return res.status(401).json({ data: { status: 'failure', message: error } @@ -105,6 +107,7 @@ class LikesController { } } + /** * * @description Method to likes and unlike a comment diff --git a/server/controllers/ReportArticleController.js b/server/controllers/ReportArticleController.js index ed6b5751..fb792fc2 100644 --- a/server/controllers/ReportArticleController.js +++ b/server/controllers/ReportArticleController.js @@ -10,13 +10,13 @@ const { Article, Report, User } = db; */ class ReportArticleController { /** - * @descripion Allow users to report articles that violate terms and agreement - * @static - * @param {*}req Express Request Object - * @param {*}res Express Response object - * @returns {object} Json response - * @memberof ReportArticleController - */ + * @descripion Allow users to report articles that violate terms and agreement + * @static + * @param {*}req Express Request Object + * @param {*}res Express Response object + * @returns {object} Json response + * @memberof ReportArticleController + */ static async reportArticle(req, res) { try { const { userId } = req.user; @@ -32,14 +32,7 @@ class ReportArticleController { return response(res, 404, 'failure', 'Article not found', null, null); } if (userId === getArticle.dataValues.userId) { - return response( - res, - 403, - 'failure', - 'You cannot report your own article. Do you want to Delete the article?', - null, - null - ); + return response(res, 403, 'failure', 'You cannot report your own article. Do you want to Delete the article?', null, null); } const reported = await Report.findOne({ where: { @@ -49,14 +42,7 @@ class ReportArticleController { }); if (reported) { - return response( - res, - 403, - 'failed', - 'You just reported this article! Do you wish to cancel the report or modify your complaint?', - null, - null - ); + return response(res, 403, 'failure', 'You just reported this article! Do you wish to cancel the report or modify your complaint?', null, null); } const submitReport = await Report.create({ userId, @@ -64,67 +50,115 @@ class ReportArticleController { complaint }); if (submitReport) { - return response( - res, - 201, - 'success', - 'Your report has been logged and will be acted on', - null, - null - ); + return response(res, 201, 'success', 'Your complaint has been lodged successfully', null, null); } } catch (error) { - return response(res, 500, 'failure', 'server error', { message: error }, null); + return response( + res, 500, 'failure', 'server error', + { message: 'Something went wrong on the server' }, null + ); } } /** - * - * @description Gets all reported articles - * @static - * @param {*} req - * @param {*} res - * @returns {object} Json response - * @memberof ReportArticleController - */ + * @descripion Allow users to edit complaints on report articles that violate terms and agreement + * @static + * @param {*}req Express Request Object + * @param {*}res Express Response object + * @returns {object} Json response + * @memberof ReportArticleController + */ + static async reportArticleUpdate(req, res) { + try { + const { userId } = req.user; + const { complaint } = req.body; + const { slug } = req.params; + + const getArticle = await Article.findOne({ + where: { + slug + } + }); + if (!getArticle) { + return response(res, 404, 'failure', 'Article not found', null, null); + } + if (userId === getArticle.dataValues.userId) { + return response(res, 403, 'failure', 'You cannot report your own article. Do you want to Delete the article?', null, null); + } + const reported = await Report.findOne({ + where: { + articleId: getArticle.dataValues.id, + userId + } + }); + + + if (reported) { + const submitReport = await reported.update({ + complaint + }); + if (submitReport) { + return response(res, 201, 'success', 'Your complaint has been modified and will be acted on', null, null); + } + } else { + return response(res, 403, 'failure', 'You cannot modify this report', null, null); + } + } catch (error) { + return response( + res, 500, 'failure', 'server error', + { message: 'Something went wrong on the server' }, null + ); + } + } + + + /** + * + * @description Gets all reported articles + * @static + * @param {*} req + * @param {*} res + * @returns {object} Json response + * @memberof ReportArticleController + */ static async getReportedArticles(req, res) { try { const allReportedArticle = await Report.findAndCountAll({ include: [ { model: Article, - attributes: ['title', 'userId'] + attributes: ['id', 'title'] }, { model: User, - attributes: ['userName', 'img'] + as: 'Complainant', + attributes: ['id', 'userName', 'img'], } ], - attributes: ['complaint'] + attributes: [ + 'complaint' + ] }); + return response(res, 200, 'success', `There are ${allReportedArticle.count} reported articles`, null, allReportedArticle.rows); + } catch (error) { return response( - res, - 200, - 'success', - `There are ${allReportedArticle.count} reported articles`, - null, - allReportedArticle.rows + res, 500, 'failure', 'server error', + { message: 'Something went wrong on the server' }, null ); - } catch (error) { - return response(res, 500, 'failure', 'server error', { message: error }, null); } } + /** - * - * @description Gets all reports for articles - * @static - * @param {*} req - * @param {*} res - * @returns {object} Json response - * @memberof ReportArticleController - */ + * + * @description Gets all reports for articles + * @static + * @param {*} req + * @param {*} res + * @returns {object} Json response + * @memberof ReportArticleController + */ static async getReportsOnArticle(req, res) { try { const { slug } = req.params; @@ -142,25 +176,109 @@ class ReportArticleController { include: [ { model: User, - attributes: ['userName', 'img'] + as: 'Complainant', + attributes: ['id', 'userName', 'img'], } ], - attributes: ['complaint'], + attributes: [ + 'complaint' + ], where: { articleId: getArticle.dataValues.id } }); + return response(res, 200, 'success', `There are ${allReportedArticle.count} reports against this articles`, null, allReportedArticle.rows); + } catch (error) { return response( - res, - 200, - 'success', - `There are ${allReportedArticle.count} reports against this articles`, - null, - allReportedArticle.rows + res, 500, 'failure', 'server error', + { message: 'Something went wrong on the server' }, null ); + } + } + + + /** + * + * @description Gets all reports by user + * @static + * @param {*} req + * @param {*} res + * @returns {object} Json response + * @memberof ReportArticleController + */ + static async getAllReportsByUser(req, res) { + try { + const { userId } = req.user; + + const allReportedArticle = await Report.findAndCountAll({ + include: [ + { + model: Article, + attributes: ['id', 'title'] + } + ], + attributes: [ + 'complaint' + ], + where: { + userId + } + }); + + return response(res, 200, 'success', `You have made ${allReportedArticle.count} reports`, null, allReportedArticle.rows); + } catch (error) { + return response( + res, 500, 'failure', 'server error', + { message: 'Something went wrong on the server' }, null + ); + } + } + + /** + * @description For user to delete logged reports + * @static + * @param {*} req Express Object + * @param {*} res Express Object + * @returns {Object} Json Response + * @memberof ReportArticleController + */ + static async deleteReportOnArticle(req, res) { + try { + const { userId } = req.user; + const { slug } = req.params; + + const getArticle = await Article.findOne({ + where: { + slug + } + }); + if (!getArticle) { + return response(res, 404, 'failure', 'Article not found', null, null); + } + const reported = await Report.findOne({ + where: { + articleId: getArticle.dataValues.id + } + }); + if (!reported) { + return response(res, 404, 'failure', 'Report not found', null, null); + } + + if (userId !== reported.dataValues.userId) { + return response(res, 403, 'failure', 'You cannot delete someone else\'s report?', null, null); + } + if (reported) { + const submitReport = await reported.destroy(); + if (submitReport) { + return response(res, 201, 'success', 'Your report has been cancelled', null, null); + } + } } catch (error) { - return response(res, 500, 'failure', 'server error', { message: error }, null); + return response( + res, 500, 'failure', 'server error', + { message: error }, null + ); } } } diff --git a/server/middlewares/AuthManager.js b/server/middlewares/AuthManager.js deleted file mode 100755 index 9b42fa75..00000000 --- a/server/middlewares/AuthManager.js +++ /dev/null @@ -1,63 +0,0 @@ -import TokenManager from '../helpers/TokenManager'; -import response from '../helpers/response'; -/** - * @class AuthMiddleware - * @description class contains function for implementing Authentication middleware - */ -class AuthManager { - /** - * @static - * @description a middleware function checking if a user is authenticated - * @param {object} req HTTP request object - * @param {object} res HTTP response object - * @param {function} next next middleware function - * @returns {object} returns error message if user is not authenticated - */ - static async checkIfUserIsLoggedIn(req, res, next) { - try { - const token = req.headers.authorization; - const decoded = await TokenManager.verify(token); - - if (!token || !decoded || !decoded.userId) { - return response( - res, - 403, - 'failure', - 'User not Authorised!', - null, - null - ); - } - req.user = decoded; - return next(); - } catch (error) { - return response(res, 403, 'failure', 'User not Authorised!', null, null); - } - } - - /** - * @static - * @description checks login status of a request - * @param {*} req - request object - * @param {*} res response object - * @param {*} next - next callback - * @returns {*} calls the next middleware - * @memberof VerifyUser - */ - static async checkAuthStatus(req, res, next) { - try { - const { authorization } = req.headers; - const token = authorization.split(' ')[1]; - const decoded = TokenManager.verify(token); - if (decoded) { - req.user = decoded; - return next(); - } - } catch (error) { - req.isLoggedIn = false; - next(); - } - } -} - -export default AuthManager; diff --git a/server/middlewares/AuthMiddleware.js b/server/middlewares/AuthMiddleware.js index 2440768d..8b302565 100755 --- a/server/middlewares/AuthMiddleware.js +++ b/server/middlewares/AuthMiddleware.js @@ -32,13 +32,37 @@ class AuthMiddleware { } catch (error) { const { name } = error; if (name === 'TokenExpiredError' || name === 'JsonWebTokenError') { - return response(res, 401, 'failure', 'Token is invalid, You need to log in again'); + return response(res, 401, 'failure', 'You need to log in again'); } return response(res, 500, 'failure', 'An error occured on the server', error.message); } } + /** + * @static + * @description checks login status of a request + * @param {*} req - request object + * @param {*} res response object + * @param {*} next - next callback + * @returns {*} calls the next middleware + * @memberof VerifyUser + */ + static async checkAuthStatus(req, res, next) { + try { + const { authorization } = req.headers; + const token = authorization.split(' ')[1]; + const decoded = TokenManager.verify(token); + if (decoded) { + req.user = decoded; + return next(); + } + } catch (error) { + req.isLoggedIn = false; + next(); + } + } + /** * @static * @description - a middleware that checks if the user is an admin @@ -62,14 +86,14 @@ class AuthMiddleware { req.user = decoded; } - if (req.user.role !== '3ceb546e-054d-4c1d-8860-e27c209d4ae4') { + if (req.user.roleId !== '3ceb546e-054d-4c1d-8860-e27c209d4ae4' && req.user.roleId !== '2023afbb-7072-4759-8161-3d149c9589f2') { return response(res, 403, 'failure', 'You are unauthorised to access this page.'); } return next(); } catch (error) { const { name } = error; if (name === 'TokenExpiredError' || name === 'JsonWebTokenError') { - return response(res, 401, 'failure', 'Token is invalid, You need to log in again'); + return response(res, 401, 'failure', 'You need to log in again'); } return response(res, 500, 'failure', 'An error occured on the server', error); @@ -105,7 +129,7 @@ class AuthMiddleware { } catch (error) { const { name } = error; if (name === 'TokenExpiredError' || name === 'JsonWebTokenError') { - return response(res, 401, 'failure', 'Token is invalid, You need to log in again'); + return response(res, 401, 'failure', 'You need to log in again'); } return response(res, 500, 'failure', 'An error occured on the server', error); diff --git a/server/migrations/20190113050147-create-report.js b/server/migrations/20190113050147-create-report.js index 436e492a..27768a82 100755 --- a/server/migrations/20190113050147-create-report.js +++ b/server/migrations/20190113050147-create-report.js @@ -17,6 +17,10 @@ export default { type: Sequelize.UUID, allowNull: false }, + complaint: { + type: Sequelize.TEXT, + allowNull: true + }, createdAt: { allowNull: false, type: Sequelize.DATE, diff --git a/server/models/article.js b/server/models/article.js index 070c2de0..969320f4 100755 --- a/server/models/article.js +++ b/server/models/article.js @@ -59,18 +59,14 @@ export default (sequelize, DataTypes) => { ArticleLikesDislike, Bookmark, Share, - ReadingStats + ReadingStats, + Report } = models; Article.belongsTo(User, { foreignKey: 'userId', onDelete: 'CASCADE', as: 'author' }); - Article.belongsToMany(User, { - through: 'Report', - as: 'reports', - foreignKey: 'articleId' - }); Article.belongsToMany(User, { through: 'Rating', as: 'ratings', @@ -85,9 +81,23 @@ export default (sequelize, DataTypes) => { foreignKey: 'articleId', as: 'comments' }); + Article.hasMany(Report, { + foreignKey: 'articleId', + as: 'report' + }); + Article.belongsToMany(User, { + through: 'Report', + as: 'reports', + foreignKey: 'articleId' + }); + Article.belongsToMany(User, { + through: 'ArticleLikesDislike', + as: 'likes', + foreignKey: 'articleId', + }); Article.hasMany(ArticleLikesDislike, { foreignKey: 'articleId', - as: 'likes' + as: 'like' }); Article.hasOne(Bookmark, { foreignKey: 'articleId', diff --git a/server/models/articleLikesDislike.js b/server/models/articleLikesDislike.js index 4406729d..2240960e 100755 --- a/server/models/articleLikesDislike.js +++ b/server/models/articleLikesDislike.js @@ -18,6 +18,11 @@ export default (sequelize, DataTypes) => { {} ); ArticleLikesDislike.associate = (models) => { + ArticleLikesDislike.belongsTo(models.User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + as: 'Liked By' + }); ArticleLikesDislike.belongsTo(models.Article, { foreignKey: 'articleId', onDelete: 'CASCADE' diff --git a/server/models/comment.js b/server/models/comment.js index 0b92932b..0c780efb 100755 --- a/server/models/comment.js +++ b/server/models/comment.js @@ -28,12 +28,8 @@ export default (sequelize, DataTypes) => { ); Comment.associate = (models) => { const { - Reply, - Article, - User, - CommentLike, + Reply, Article, User, CommentLike } = models; - Comment.hasMany(Reply, { foreignKey: 'commentId', as: 'replies' diff --git a/server/models/report.js b/server/models/report.js index 8669cab8..79fcf63c 100755 --- a/server/models/report.js +++ b/server/models/report.js @@ -1,17 +1,28 @@ -module.exports = (sequelize, DataTypes) => { - const Report = sequelize.define( - 'Report', - { - articleId: { - type: DataTypes.UUID, - allowNull: false - }, - userId: { - type: DataTypes.UUID, - allowNull: false - } +export default (sequelize, DataTypes) => { + const Report = sequelize.define('Report', { + articleId: { + type: DataTypes.UUID, + allowNull: false }, - {} - ); + userId: { + type: DataTypes.UUID, + allowNull: false + }, + complaint: { + type: DataTypes.TEXT, + allowNull: true + } + }, {}); + Report.associate = (models) => { + Report.belongsTo(models.User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + as: 'Complainant' + }); + Report.belongsTo(models.Article, { + foreignKey: 'articleId', + onDelete: 'CASCADE' + }); + }; return Report; }; diff --git a/server/models/user.js b/server/models/user.js index 0edf58de..5b3dc7fb 100755 --- a/server/models/user.js +++ b/server/models/user.js @@ -81,6 +81,8 @@ export default (sequelize, DataTypes) => { Bookmark, Share, Role, + Report, + ArticleLikesDislike, ReadingStats } = models; User.hasMany(Article, { @@ -96,6 +98,14 @@ export default (sequelize, DataTypes) => { foreignKey: 'userId', as: 'comments' }); + User.hasMany(Report, { + foreignKey: 'userId', + as: 'reports' + }); + User.hasMany(ArticleLikesDislike, { + foreignKey: 'userId', + as: 'likes' + }); User.hasMany(Reply, { as: 'replies', foreignKey: 'userId' @@ -117,6 +127,14 @@ export default (sequelize, DataTypes) => { User.hasMany(Share, { foreignKey: 'userId' }); + User.belongsToMany(Article, { + through: 'Report', + foreignKey: 'userId' + }); + User.belongsToMany(Article, { + through: 'ArticleLikesDislike', + foreignKey: 'userId' + }); User.belongsTo(Role, { foreignKey: 'roleId' }); @@ -124,7 +142,6 @@ export default (sequelize, DataTypes) => { through: 'Rating', foreignKey: 'userId' }); - User.belongsToMany(User, { through: 'Follow', as: 'followers', diff --git a/server/routes/api/article.js b/server/routes/api/article.js index 0aec446a..3f7cc4c3 100644 --- a/server/routes/api/article.js +++ b/server/routes/api/article.js @@ -2,7 +2,6 @@ import { Router } from 'express'; import ArticleController from '../../controllers/ArticleController'; import ArticleValidation from '../../middlewares/validations/ArticleValidation'; import AuthMiddleware from '../../middlewares/AuthMiddleware'; -import AuthManager from '../../middlewares/AuthManager'; const articleRoutes = Router(); articleRoutes.get('/search', ArticleController.search); @@ -23,7 +22,6 @@ articleRoutes.get( ); articleRoutes.get( '/articles/:slug', - AuthManager.checkAuthStatus, ArticleController.fetchOne ); articleRoutes.put( diff --git a/server/routes/api/reportArticle.js b/server/routes/api/reportArticle.js index 8480aa5e..3ee7a601 100644 --- a/server/routes/api/reportArticle.js +++ b/server/routes/api/reportArticle.js @@ -7,22 +7,38 @@ import reportArticleSchema from '../../middlewares/validations/ReportArticleVali const reportArticleRoutes = Router(); reportArticleRoutes.post( - '/articles/:slug/report', + '/articles/:slug/reports', AuthMiddleware.checkIfUserIsAuthenticated, - reportArticleSchema, - handleValidationErrors, + reportArticleSchema, handleValidationErrors, ReportArticleController.reportArticle ); +reportArticleRoutes.put( + '/articles/:slug/reports', + AuthMiddleware.checkIfUserIsAuthenticated, + reportArticleSchema, handleValidationErrors, + ReportArticleController.reportArticleUpdate +); reportArticleRoutes.get( '/reports', + AuthMiddleware.checkIfUserIsSuperAdmin, AuthMiddleware.checkIfUserIsAuthenticated, - AuthMiddleware.checkIfUserIsAdmin, ReportArticleController.getReportedArticles ); reportArticleRoutes.get( - '/articles/:slug/report', + '/articles/:slug/reports', + AuthMiddleware.checkIfUserIsAdmin, AuthMiddleware.checkIfUserIsAuthenticated, ReportArticleController.getReportsOnArticle ); +reportArticleRoutes.get( + '/myreports', + AuthMiddleware.checkIfUserIsAuthenticated, + ReportArticleController.getAllReportsByUser +); +reportArticleRoutes.delete( + '/articles/:slug/reports', + AuthMiddleware.checkIfUserIsAuthenticated, + ReportArticleController.deleteReportOnArticle +); export default reportArticleRoutes; diff --git a/server/seeders/20190124084130-demoTag.js b/server/seeders/20190124084130-demoTag.js index d17cdec8..5b1d7e47 100644 --- a/server/seeders/20190124084130-demoTag.js +++ b/server/seeders/20190124084130-demoTag.js @@ -7,7 +7,7 @@ export default { id: '00155c60-6b1a-11e8-9c9c-2d42b21b1a3e', name: 'Technology' } -], {}), + ], {}), down: (queryInterface, Sequelize) => queryInterface.bulkDelete('Tags', null, {}) }; diff --git a/server/test/controllers/articles.spec.js b/server/test/controllers/articles.spec.js index 81cd7e91..15e4210a 100644 --- a/server/test/controllers/articles.spec.js +++ b/server/test/controllers/articles.spec.js @@ -61,7 +61,9 @@ describe('API endpoint /articles/', () => { expect(response.status).to.eqls(401); expect(response.body.status).to.eqls('failure'); - expect(response.body.data.message).to.eqls('Token is invalid, You need to log in again'); + expect(response.body.data.message).to.eqls( + 'You need to log in again' + ); }); it('It should not allow user with invalid token to post an article', async () => { diff --git a/server/test/controllers/comment.spec.js b/server/test/controllers/comment.spec.js index 6fd8fdb0..bbd1803b 100644 --- a/server/test/controllers/comment.spec.js +++ b/server/test/controllers/comment.spec.js @@ -1,3 +1,245 @@ +// import chai, { expect } from 'chai'; +// import chaiHttp from 'chai-http'; +// import sinon from 'sinon'; +// import app from '../../..'; +// import db from '../../models'; +// import { token, tokenTwo } from '../mockData/token'; + +// const { Article, Comment } = db; +// chai.use(chaiHttp); + +// describe('Comment Model', () => { +// describe('Create a comment', () => { +// it('It should be able to create a comment', async () => { +// const response = await chai +// .request(app) +// .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.status).to.eqls(201); +// expect(response.body.status).to.eqls('success'); +// expect(response.body.data.message).to.eqls('Comment created'); +// }); +// it('It should not be able to create a comment when content is empty', async () => { +// const response = await chai +// .request(app) +// .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: '' +// }); +// expect(response.status).to.eqls(422); +// }); +// it('It should be able to handle unexpected errors thrown during creating a comment', async () => { +// const stub = sinon +// .stub(Article, 'findOne') +// .callsFake(() => Promise.reject(new Error('Internal Server Error'))); + +// const response = await chai +// .request(app) +// .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.body.data.statusCode).to.equal(500); +// stub.restore(); +// }); +// it('It should return an error with incorrect token', async () => { +// const response = await chai +// .request(app) +// .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .send({ +// content: 'Nice write up' +// }); +// expect(response.status).to.equal(401); +// expect(response.body.status).to.equal('failure'); +// }); +// it('It should return an error when token is expired', async () => { +// const expiredToken = 'jdjdjdjdjdj'; +// const response = await chai +// .request(app) +// .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .set('Authorization', `Bearer ${expiredToken}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.status).to.equal(401); +// expect(response.body.status).to.equal('failure'); +// }); +// }); +// describe('Get comments for an article', () => { +// it('It should be able to get all comments', async () => { +// const response = await chai +// .request(app) +// .get('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(200); +// expect(response.body.status).to.eqls('success'); +// expect(response.body.data.message).to.eqls('Comment found'); +// }); +// it('It should be able to get a single comment', async () => { +// const response = await chai +// .request(app) +// .get('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(200); +// expect(response.body.status).to.eqls('success'); +// expect(response.body.data.message).to.eqls('Comment found'); +// }); +// it('It should not be able to get a single comment if comment id is wrong', async () => { +// const response = await chai +// .request(app) +// .get('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(404); +// expect(response.body.status).to.eqls('failure'); +// }); +// it('It should throw an error when article id is not found', async () => { +// const response = await chai +// .request(app) +// .get('/api/v1/articles/dhdhdhhdhdhhdhd/comments') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(404); +// expect(response.body.status).to.eqls('failure'); +// }); +// it('It should be able to handle unexpected errors thrown when getting a comment', async () => { +// const stub = sinon +// .stub(Article, 'findOne') +// .callsFake(() => Promise.reject(new Error('Internal Server Error'))); + +// const response = await chai +// .request(app) +// .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.body.data.statusCode).to.equal(500); +// stub.restore(); +// }); +// }); +// describe('Update comment', () => { +// it('It should not be able to update a comment created by another user', async () => { +// const response = await chai +// .request(app) +// .put('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${tokenTwo}`) +// .send({ +// content: 'Nice write up here' +// }); +// expect(response.status).to.eqls(403); +// expect(response.body.status).to.eqls('failure'); +// expect(response.body.data.message).to.eqls('You are not allowed to update another user\'s comment'); +// }); +// it('It should be able to update a comment created by only a user', async () => { +// const response = await chai +// .request(app) +// .put('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.status).to.eqls(200); +// expect(response.body.status).to.eqls('success'); +// expect(response.body.data.message).to.eqls('Comment updated'); +// }); +// it('It should throw an error when you want to update a comment when comment id is wrong', async () => { +// const response = await chai +// .request(app) +// .put('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.status).to.eqls(404); +// expect(response.body.status).to.eqls('failure'); +// }); +// it('It should be able to handle unexpected errors thrown during updating a comment', async () => { +// const stub = sinon +// .stub(Comment, 'findOne') +// .callsFake(() => Promise.reject(new Error('Internal Server Error'))); + +// const response = await chai +// .request(app) +// .put('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.body.data.statusCode).to.equal(500); +// stub.restore(); +// }); +// it('It should not be able to update a comment when article dont has no comment', async () => { +// const response = await chai +// .request(app) +// .put('/api/v1/articles/95745c60-7b1a-11e8-9c9c-2d42b21b1a/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.status).to.eqls(404); +// expect(response.body.status).to.eqls('failure'); +// expect(response.body.data.message).to.eqls('Comment not found for article id'); +// }); +// }); +// describe('Delete comment', () => { +// it('It should not be able to delete a comment created by another user', async () => { +// const response = await chai +// .request(app) +// .delete('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${tokenTwo}`); +// expect(response.status).to.eqls(403); +// expect(response.body.status).to.eqls('failure'); +// expect(response.body.data.message).to.eqls('You are not allowed to delete another user\'s comment'); +// }); +// it('It should not be able to delete a comment when article has no comment', async () => { +// const response = await chai +// .request(app) +// .delete('/api/v1/articles/95745c60-7b1a-11e8-9c9c-2d42b21b1a/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(404); +// expect(response.body.status).to.eqls('failure'); +// expect(response.body.data.message).to.eqls('Comment not found for article id'); +// }); +// it('It should not be able to delete a comment when comment id is wrong', async () => { +// const response = await chai +// .request(app) +// .delete('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(404); +// expect(response.body.status).to.eqls('failure'); +// }); +// it('It should be able to delete a comment created by only a user', async () => { +// const response = await chai +// .request(app) +// .delete('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1a3e') +// .set('Authorization', `Bearer ${token}`); +// expect(response.status).to.eqls(200); +// expect(response.body.status).to.eqls('success'); +// expect(response.body.data.message).to.eqls('Comment deleted'); +// }); +// it('It should be able to handle unexpected errors thrown during deleting a comment', async () => { +// const stub = sinon +// .stub(Comment, 'findOne') +// .callsFake(() => Promise.reject(new Error('Internal Server Error'))); + +// const response = await chai +// .request(app) +// .delete('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/comments/09543c60-7b1a-11e8-9c9c-2d42b21b1') +// .set('Authorization', `Bearer ${token}`) +// .send({ +// content: 'Nice write up' +// }); +// expect(response.body.data.statusCode).to.equal(500); +// stub.restore(); +// }); +// }); +// }); + + import chai, { expect } from 'chai'; import chaiHttp from 'chai-http'; import sinon from 'sinon'; diff --git a/server/test/controllers/likeDislike.spec.js b/server/test/controllers/likeDislike.spec.js index a9b9304d..0c602875 100755 --- a/server/test/controllers/likeDislike.spec.js +++ b/server/test/controllers/likeDislike.spec.js @@ -36,6 +36,20 @@ describe('Likes and Dislike feature', () => { expect(response.body.status).to.equal('success'); }); + it('returns article likes', async () => { + const response = await chai + .request(app) + .get('/api/v1/articles/how-to-say-hello-in-2019/likes') + .set('authorization', `Bearer ${userToken}`); + expect(response.status).to.equal(200); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + + it('unlike articles', async () => { const response = await chai .request(app) diff --git a/server/test/controllers/reportArticles.spec.js b/server/test/controllers/reportArticles.spec.js new file mode 100644 index 00000000..a7d2da46 --- /dev/null +++ b/server/test/controllers/reportArticles.spec.js @@ -0,0 +1,356 @@ +import chai, { expect } from 'chai'; +import chaiHttp from 'chai-http'; +import app from '../../..'; +import db from '../../models'; +import TokenManager from '../../helpers/TokenManager'; +import { superAdminToken, userToken, userToken2, invalidToken } from '../mockData/tokens' + +const { Report } = db; + +chai.use(chaiHttp); + +describe('Report Article Features', () => { + describe('It should report articles', () => { + it('Should return error for unauthorised user', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${invalidToken}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(401); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You need to log in again'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should return error for non existing articles', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(404); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Article not found'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('should return error if author attempts to report own article', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(403); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You cannot report your own article. Do you want to Delete the article?'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('should successfully report article', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(201); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Your complaint has been lodged successfully'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + it('should not allow a user report an article twice', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(403); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You just reported this article! Do you wish to cancel the report or modify your complaint?'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + }); + describe('It should update reports', () => { + it('Should return error for unauthorised user', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${invalidToken}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(401); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You need to log in again'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should return error for non existing articles', async () => { + const response = await chai + .request(app) + .put('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(404); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Article not found'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('should return error if author attempts to report own article', async () => { + const response = await chai + .request(app) + .put('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(403); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You cannot report your own article. Do you want to Delete the article?'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('should successfully report article', async () => { + const response = await chai + .request(app) + .put('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(201); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Your complaint has been modified and will be acted on'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + it('should return error if article and user do not match', async () => { + const response = await chai + .request(app) + .put('/api/v1/articles/how-to-say-hello-in-2019/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(403); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You cannot modify this report'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + }); + describe('It should show all reported articles', () => { + it('Should return error for unauthorised user', async () => { + const response = await chai + .request(app) + .get('/api/v1/reports') + .set('Authorization', `Bearer ${invalidToken}`); + expect(response.status).to.equal(401); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You need to log in again'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should list reported articles', async () => { + const response = await chai + .request(app) + .get('/api/v1/reports') + .set('Authorization', `Bearer ${superAdminToken}`); + expect(response.status).to.equal(200); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + }); + describe('It should get all Reports against an article', () => { + it('Should return error for unauthorised user', async () => { + const response = await chai + .request(app) + .get('/api/v1/reports') + .set('Authorization', `Bearer ${invalidToken}`); + expect(response.status).to.equal(401); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You need to log in again'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should return error for non existing articles', async () => { + const response = await chai + .request(app) + .post('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(404); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Article not found'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should list reports against an article', async () => { + const response = await chai + .request(app) + .get('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${superAdminToken}`); + expect(response.status).to.equal(200); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + }); + describe('It should get all reports logged by a user', () => { + it('Should return error for unauthorised user', async () => { + const response = await chai + .request(app) + .get('/api/v1/myReports') + .set('Authorization', `Bearer ${invalidToken}`); + expect(response.status).to.equal(401); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You need to log in again'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should return all reports by user', async () => { + const response = await chai + .request(app) + .get('/api/v1/myReports') + .set('Authorization', `Bearer ${userToken}`); + expect(response.status).to.equal(200); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + }); + describe('It should delete a report against a single article', () => { + it('Should return error for unauthorised user', async () => { + const response = await chai + .request(app) + .delete('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${invalidToken}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(401); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You need to log in again'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should return error for non existing articles', async () => { + const response = await chai + .request(app) + .delete('/api/v1/articles/What-a-mighy-God/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(404); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Article not found'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should return error for non existing report', async () => { + const response = await chai + .request(app) + .delete('/api/v1/articles/how-to-say-hello-in-2019/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(404); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Report not found'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should not delete other users\' report', async () => { + const response = await chai + .request(app) + .delete('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(403); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('You cannot delete someone else\'s report?'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('failure'); + }); + it('Should delete a single report', async () => { + const response = await chai + .request(app) + .delete('/api/v1/articles/how-to-be-a-10x-dev-sGNYfURm/reports') + .set('Authorization', `Bearer ${userToken2}`) + .send({ + complaint: 'too long' + }); + expect(response.status).to.equal(201); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message).to.be.a('string'); + expect(response.body.data.message).to.be.eql('Your report has been cancelled'); + expect(response.body.data).to.have.property('statusCode'); + expect(response.body.data.statusCode).to.be.a('Number'); + expect(response.body.status).to.equal('success'); + }); + }) +}) diff --git a/server/test/controllers/userController.spec.js b/server/test/controllers/userController.spec.js index 785dc06e..63d89023 100755 --- a/server/test/controllers/userController.spec.js +++ b/server/test/controllers/userController.spec.js @@ -327,7 +327,7 @@ describe('User Model', () => { .send({ bio: 'I am so hungry' }); expect(response.status).to.eqls(401); expect(response.body.status).to.eqls('failure'); - expect(response.body.data.message).to.eqls('Token is invalid, You need to log in again'); + expect(response.body.data.message).to.eqls('You need to log in again'); }); it('User should show conflict error when user tries to edit profile with username that exists', async () => { @@ -452,7 +452,7 @@ describe('User Model', () => { .set('Authorization', `Bearer ${invalidToken}`); expect(response.status).to.eqls(401); expect(response.body.status).to.eqls('failure'); - expect(response.body.data.message).to.eqls('Token is invalid, You need to log in again'); + expect(response.body.data.message).to.eqls('You need to log in again'); }); it('Should throw an error if a Non-Super Admin tries to access the route', async () => {