const expect = require('chai').expect; const {graphql} = require('graphql'); const timekeeper = require('timekeeper'); const schema = require('../../../../graph/schema'); const Context = require('../../../../graph/context'); const UsersService = require('../../../../services/users'); const AssetModel = require('../../../../models/asset'); const SettingsService = require('../../../../services/settings'); const CommentsService = require('../../../../services/comments'); describe('graph.mutations.editComment', () => { let asset; let user; let settings; beforeEach(async () => { timekeeper.reset(); settings = await SettingsService.init(); asset = await AssetModel.create({}); user = await UsersService.createLocalUser( 'usernameA@example.com', 'password', 'usernameA'); }); afterEach(async () => { await asset.remove(); await user.remove(); await settings.remove(); }); const editCommentMutation = ` mutation EditComment ($id: ID!, $asset_id: ID!, $edit: EditCommentInput) { editComment(id:$id, asset_id:$asset_id, edit:$edit) { errors { translation_key } } } `; it('a user can edit their own comment', async () => { const context = new Context({user}); const testStartedAt = new Date(); const comment = await CommentsService.publicCreate({ asset_id: asset.id, author_id: user.id, body: `hello there! ${String(Math.random()).slice(2)}`, }); // body_history should be there expect(comment.body_history.length).to.equal(1); expect(comment.body_history[0].body).to.equal(comment.body); expect(comment.body_history[0].created_at).to.be.instanceOf(Date); expect(comment.body_history[0].created_at).to.be.at.least(testStartedAt); // now edit const newBody = 'I have been edited.'; const response = await graphql(schema, editCommentMutation, {}, context, { id: comment.id, asset_id: asset.id, edit: { body: newBody } }); if (response.errors && response.errors.length) { console.error(response.errors); } expect(response.errors).to.be.empty; if (response.data.editComment.errors && response.data.editComment.errors.length > 0) { console.error(response.data.editComment.errors); } expect(response.data.editComment.errors).to.be.null; // assert body has changed const commentAfterEdit = await CommentsService.findById(comment.id); expect(commentAfterEdit.body).to.equal(newBody); expect(commentAfterEdit.body_history).to.be.instanceOf(Array); expect(commentAfterEdit.body_history.length).to.equal(2); expect(commentAfterEdit.body_history[1].body).to.equal(newBody); expect(commentAfterEdit.body_history[1].created_at).to.be.instanceOf(Date); expect(commentAfterEdit.body_history[1].created_at).to.be.at.least(testStartedAt); expect(commentAfterEdit.status).to.equal('NONE'); }); it('A user can\'t edit their comment outside of the edit comment time window', async () => { const comment = await CommentsService.publicCreate({ asset_id: asset.id, author_id: user.id, body: `hello there! ${String(Math.random()).slice(2)}`, }); const now = new Date(); const oneHourFromNow = new Date(new Date(now).setHours(now.getHours() + 1)); timekeeper.travel(oneHourFromNow); const newBody = 'This body should never be set'; const context = new Context({user}); const response = await graphql(schema, editCommentMutation, {}, context, { id: comment.id, asset_id: asset.id, edit: { body: newBody } }); if (response.errors && response.errors.length > 0) { console.error(response.errors); } expect(response.errors).to.be.empty; expect(response.data.editComment.errors).to.not.be.empty; expect(response.data.editComment.errors[0].translation_key).to.equal('EDIT_WINDOW_ENDED'); const commentAfterEdit = await CommentsService.findById(comment.id); // it *hasn't* changed from the original expect(commentAfterEdit.body).to.equal(comment.body); }); it('A user can\'t edit someone else\'s comment', async () => { const comment = await CommentsService.publicCreate({ asset_id: asset.id, author_id: user.id, body: `hello there! ${String(Math.random()).slice(2)}`, }); const userB = await UsersService.createLocalUser( 'usernameB@example.com', 'password', 'usernameB'); const newBody = 'This body should never be set'; const context = new Context({user: userB}); const response = await graphql(schema, editCommentMutation, {}, context, { id: comment.id, asset_id: asset.id, edit: { body: newBody } }); expect(response.errors).to.be.empty; expect(response.data.editComment.errors).to.not.be.empty; expect(response.data.editComment.errors[0].translation_key).to.equal('NOT_AUTHORIZED'); const commentAfterEdit = await CommentsService.findById(comment.id); // it *hasn't* changed from the original expect(commentAfterEdit.body).to.equal(comment.body); }); it('A user Can\'t edit a comment id that doesn\'t exist', async () => { const fakeCommentId = 'nooooope'; const newBody = 'This body should never be set'; const context = new Context({user}); const response = await graphql(schema, editCommentMutation, {}, context, { id: fakeCommentId, asset_id: asset.id, edit: { body: newBody } }); if (response.errors && response.errors.length > 0) { console.error(response.errors); } expect(response.errors).to.be.empty; expect(response.data.editComment.errors[0].translation_key).to.equal('NOT_FOUND'); }); const bannedWord = 'BANNED_WORD'; [ { description: 'premod: editing a REJECTED comment sets back to PREMOD', settings: { moderation: 'PRE', }, beforeEdit: { body: 'I was offensive and thus REJECTED', status: 'REJECTED', }, edit: { body: 'I have been edited to be less offensive', }, afterEdit: { status: 'PREMOD', }, }, { description: 'editing an ACCEPTED comment to add a bad word sets status to REJECTED', settings: { moderation: 'POST', wordlist: { banned: [bannedWord] } }, beforeEdit: { body: 'I\'m a perfectly acceptable comment', status: 'ACCEPTED', }, edit: { body: `I have been sneakily edited to add a banned word: ${bannedWord}` }, afterEdit: { status: 'REJECTED', }, }, { description: 'postmod: editing a REJECTED comment with banned word to remove banned word sets status to NONE', settings: { moderation: 'POST', wordlist: { banned: [bannedWord] } }, beforeEdit: { body: `I'm a rejected comment with bad word ${bannedWord}`, status: 'REJECTED', }, edit: { body: 'I have been edited to remove the bad word' }, afterEdit: { status: 'NONE', }, }, { description: 'postmod + premodLinksEnable: editing an ACCEPTED comment to add a link sets status to PREMOD', settings: { moderation: 'POST', premodLinksEnable: true, }, beforeEdit: { body: 'I\'m a perfectly acceptable comment', status: 'ACCEPTED', }, edit: { body: 'I have been edited to add a link: https://coralproject.net/' }, afterEdit: { status: 'PREMOD', }, }, ].forEach(({description, settings, beforeEdit, edit, afterEdit, only}) => { const test = only ? it.only : it; test(description, async () => { await SettingsService.update(settings); const context = new Context({user}); const comment = await CommentsService.publicCreate(Object.assign( { asset_id: asset.id, author_id: user.id, }, beforeEdit, )); // now edit const newBody = edit.body; const response = await graphql(schema, editCommentMutation, {}, context, { id: comment.id, asset_id: asset.id, edit: { body: newBody } }); if (response.errors && response.errors.length) {console.error(response.errors);} expect(response.errors).to.be.empty; const commentAfterEdit = await CommentsService.findById(comment.id); expect(commentAfterEdit.body).to.equal(newBody); expect(commentAfterEdit.status).to.equal(afterEdit.status); }); }); });