mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-19 00:11:49 +03:00
a489d5a3d8
refs https://linear.app/tryghost/issue/ENG-682/ This should allow us to bust both endpoints cache when write operations are made to comments.
1089 lines
40 KiB
JavaScript
1089 lines
40 KiB
JavaScript
const assert = require('assert/strict');
|
|
const {agentProvider, mockManager, fixtureManager, matchers, configUtils} = require('../../utils/e2e-framework');
|
|
const {anyEtag, anyObjectId, anyLocationFor, anyISODateTime, anyErrorId, anyUuid, anyNumber, anyBoolean} = matchers;
|
|
const should = require('should');
|
|
const models = require('../../../core/server/models');
|
|
const moment = require('moment-timezone');
|
|
const settingsCache = require('../../../core/shared/settings-cache');
|
|
const sinon = require('sinon');
|
|
const DomainEvents = require('@tryghost/domain-events');
|
|
|
|
let membersAgent, membersAgent2, postId, postTitle, commentId;
|
|
|
|
async function getPaidProduct() {
|
|
return await models.Product.findOne({type: 'paid'});
|
|
}
|
|
|
|
const commentMatcher = {
|
|
id: anyObjectId,
|
|
created_at: anyISODateTime,
|
|
member: {
|
|
id: anyObjectId,
|
|
uuid: anyUuid
|
|
},
|
|
count: {
|
|
likes: anyNumber
|
|
},
|
|
liked: anyBoolean
|
|
};
|
|
|
|
/**
|
|
* @param {Object} [options]
|
|
* @param {number} [options.replies]
|
|
* @returns
|
|
*/
|
|
function commentMatcherWithReplies(options = {replies: 0}) {
|
|
return {
|
|
id: anyObjectId,
|
|
created_at: anyISODateTime,
|
|
member: {
|
|
id: anyObjectId,
|
|
uuid: anyUuid
|
|
},
|
|
replies: new Array(options?.replies ?? 0).fill(commentMatcher),
|
|
count: {
|
|
likes: anyNumber,
|
|
replies: anyNumber
|
|
},
|
|
liked: anyBoolean
|
|
};
|
|
}
|
|
|
|
function escapeRegExp(string) {
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|
|
|
|
async function testCanCommentOnPost(member) {
|
|
await models.Member.edit({last_seen_at: null, last_commented_at: null}, {id: member.get('id')});
|
|
|
|
const {body} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: '<div></div><p></p><p>This is a <strong>message</strong></p><p></p><p></p><p>New line</p><p></p>'
|
|
}]})
|
|
.expectStatus(201)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
location: anyLocationFor('comments')
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcher]
|
|
});
|
|
// Save for other tests
|
|
commentId = body.comments[0].id;
|
|
|
|
// Check if author got an email
|
|
mockManager.assert.sentEmailCount(1);
|
|
mockManager.assert.sentEmail({
|
|
subject: '💬 New comment on your post: ' + postTitle,
|
|
to: fixtureManager.get('users', 0).email,
|
|
// Note that the <strong> tag is removed by the sanitizer
|
|
html: new RegExp(escapeRegExp('<p>This is a message</p><p></p><p>New line</p>'))
|
|
});
|
|
|
|
// Wait for the dispatched events (because this happens async)
|
|
await DomainEvents.allSettled();
|
|
|
|
// Check last_updated_at changed?
|
|
member = await models.Member.findOne({id: member.id});
|
|
should.notEqual(member.get('last_seen_at'), null, 'The member should have a `last_seen_at` property after posting a comment.');
|
|
|
|
// Check last_commented_at changed?
|
|
should.notEqual(member.get('last_commented_at'), null, 'The member should have a `last_commented_at` property after posting a comment.');
|
|
}
|
|
|
|
async function testCanReply(member, emailMatchers = {}) {
|
|
const date = new Date(0);
|
|
await models.Member.edit({last_seen_at: date, last_commented_at: date}, {id: member.get('id')});
|
|
|
|
await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: fixtureManager.get('comments', 0).id,
|
|
html: 'This is a reply'
|
|
}]})
|
|
.expectStatus(201)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
location: anyLocationFor('comments')
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcher]
|
|
});
|
|
|
|
mockManager.assert.sentEmailCount(2);
|
|
mockManager.assert.sentEmail({
|
|
subject: '💬 New comment on your post: ' + postTitle,
|
|
to: fixtureManager.get('users', 0).email
|
|
});
|
|
|
|
mockManager.assert.sentEmail({
|
|
...emailMatchers,
|
|
subject: '↪️ New reply to your comment on Ghost',
|
|
to: fixtureManager.get('members', 0).email
|
|
});
|
|
|
|
// Wait for the dispatched events (because this happens async)
|
|
await DomainEvents.allSettled();
|
|
|
|
// Check last_updated_at changed?
|
|
member = await models.Member.findOne({id: member.id});
|
|
should.notEqual(member.get('last_seen_at').getTime(), date.getTime(), 'Should update `last_seen_at` property after posting a comment.');
|
|
|
|
// Check last_commented_at changed?
|
|
should.notEqual(member.get('last_commented_at').getTime(), date.getTime(), 'Should update `last_commented_at` property after posting a comment.');
|
|
}
|
|
|
|
async function testCannotCommentOnPost(status = 403) {
|
|
await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: '<div></div><p></p><p>This is a <strong>message</strong></p><p></p><p></p><p>New line</p><p></p>'
|
|
}]})
|
|
.expectStatus(status)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyErrorId
|
|
}]
|
|
});
|
|
}
|
|
|
|
async function testCannotReply(status = 403) {
|
|
await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: fixtureManager.get('comments', 0).id,
|
|
html: 'This is a reply'
|
|
}]})
|
|
.expectStatus(status)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyErrorId
|
|
}]
|
|
});
|
|
}
|
|
|
|
describe('Comments API', function () {
|
|
let member;
|
|
|
|
before(async function () {
|
|
membersAgent = await agentProvider.getMembersAPIAgent();
|
|
membersAgent2 = membersAgent.duplicate();
|
|
|
|
await fixtureManager.init('posts', 'members', 'comments');
|
|
|
|
postId = fixtureManager.get('posts', 0).id;
|
|
postTitle = fixtureManager.get('posts', 0).title;
|
|
});
|
|
|
|
beforeEach(function () {
|
|
mockManager.mockMail();
|
|
});
|
|
|
|
afterEach(async function () {
|
|
await configUtils.restore();
|
|
mockManager.restore();
|
|
});
|
|
|
|
describe('when commenting enabled for all', function () {
|
|
describe('when not authenticated', function () {
|
|
beforeEach(function () {
|
|
const getStub = sinon.stub(settingsCache, 'get');
|
|
getStub.callsFake((key, options) => {
|
|
if (key === 'comments_enabled') {
|
|
return 'all';
|
|
}
|
|
return getStub.wrappedMethod.call(settingsCache, key, options);
|
|
});
|
|
});
|
|
|
|
afterEach(async function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
it('Can browse all comments of a post (legacy)', async function () {
|
|
await membersAgent
|
|
.get(`/api/comments/?filter=post_id:'${postId}'`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcherWithReplies({replies: 1})]
|
|
});
|
|
});
|
|
|
|
it('Can browse all comments of a post', async function () {
|
|
await membersAgent
|
|
.get(`/api/comments/post/${postId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcherWithReplies({replies: 1})]
|
|
});
|
|
});
|
|
|
|
it('cannot comment on a post', async function () {
|
|
await testCannotCommentOnPost(401);
|
|
});
|
|
|
|
it('cannot reply on a post', async function () {
|
|
await testCannotReply(401);
|
|
});
|
|
|
|
it('cannot report a comment', async function () {
|
|
commentId = fixtureManager.get('comments', 0).id;
|
|
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.post(`/api/comments/${commentId}/report/`)
|
|
.expectStatus(401)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyUuid
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('cannot like a comment', async function () {
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.post(`/api/comments/${commentId}/like/`)
|
|
.expectStatus(401)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyUuid
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('cannot unlike a comment', async function () {
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.delete(`/api/comments/${commentId}/like/`)
|
|
.expectStatus(401)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyUuid
|
|
}]
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when authenticated', function () {
|
|
let getStub;
|
|
|
|
before(async function () {
|
|
await membersAgent.loginAs('member@example.com');
|
|
member = await models.Member.findOne({email: 'member@example.com'}, {require: true});
|
|
await membersAgent2.loginAs('member2@example.com');
|
|
});
|
|
|
|
beforeEach(function () {
|
|
getStub = sinon.stub(settingsCache, 'get');
|
|
getStub.callsFake((key, options) => {
|
|
if (key === 'comments_enabled') {
|
|
return 'all';
|
|
}
|
|
return getStub.wrappedMethod.call(settingsCache, key, options);
|
|
});
|
|
});
|
|
|
|
afterEach(async function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
it('Can comment on a post', async function () {
|
|
await testCanCommentOnPost(member);
|
|
});
|
|
|
|
it('Can browse all comments of a post (legacy)', async function () {
|
|
// uses explicit order to match db ordering
|
|
await membersAgent
|
|
.get(`/api/comments/?filter=post_id:'${postId}'&order=id%20ASC`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [
|
|
commentMatcherWithReplies({replies: 1}),
|
|
commentMatcher
|
|
]
|
|
});
|
|
});
|
|
|
|
it('Can browse all comments of a post', async function () {
|
|
// uses explicit order to match db ordering
|
|
await membersAgent
|
|
.get(`/api/comments/post/${postId}/?order=id%20ASC`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [
|
|
commentMatcherWithReplies({replies: 1}),
|
|
commentMatcher
|
|
]
|
|
});
|
|
});
|
|
|
|
it('Can browse all comments of a post with default order', async function () {
|
|
await membersAgent
|
|
.get(`/api/comments/post/${postId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [
|
|
commentMatcher,
|
|
commentMatcherWithReplies({replies: 1})
|
|
]
|
|
});
|
|
});
|
|
|
|
it('Can reply to your own comment', async function () {
|
|
// Should not update last_seen_at or last_commented_at when both are already set to a value on the same day
|
|
const timezone = settingsCache.get('timezone');
|
|
const date = moment.utc(new Date()).tz(timezone).startOf('day').toDate();
|
|
await models.Member.edit({last_seen_at: date, last_commented_at: date}, {id: member.get('id')});
|
|
|
|
await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: commentId,
|
|
html: 'This is a reply'
|
|
}]})
|
|
.expectStatus(201)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
location: anyLocationFor('comments'),
|
|
'x-cache-invalidate': matchers.stringMatching(
|
|
new RegExp('/api/members/comments/post/[0-9a-f]{24}/, /api/members/comments/[0-9a-f]{24}/replies/')
|
|
)
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcher]
|
|
});
|
|
|
|
// Check only the author got an email (because we are the author of this parent comment)
|
|
mockManager.assert.sentEmailCount(1);
|
|
mockManager.assert.sentEmail({
|
|
subject: '💬 New comment on your post: ' + postTitle,
|
|
to: fixtureManager.get('users', 0).email
|
|
});
|
|
|
|
// Wait for the dispatched events (because this happens async)
|
|
await DomainEvents.allSettled();
|
|
|
|
// Check last updated_at is not changed?
|
|
member = await models.Member.findOne({id: member.id});
|
|
should.equal(member.get('last_seen_at').getTime(), date.getTime(), 'The member should not update `last_seen_at` if last seen at is same day');
|
|
|
|
// Check last_commented_at changed?
|
|
should.equal(member.get('last_commented_at').getTime(), date.getTime(), 'The member should not update `last_commented_at` f last seen at is same day');
|
|
});
|
|
|
|
it('Can reply to a comment', async function () {
|
|
await testCanReply(member);
|
|
});
|
|
|
|
let testReplyId;
|
|
it('Limits returned replies to 3', async function () {
|
|
const parentId = fixtureManager.get('comments', 0).id;
|
|
|
|
// Check initial status: two replies before test
|
|
await membersAgent
|
|
.get(`/api/comments/${parentId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcherWithReplies({replies: 2})]
|
|
})
|
|
.expect(({body}) => {
|
|
body.comments[0].count.replies.should.eql(2);
|
|
});
|
|
|
|
// Add some replies
|
|
for (let index = 0; index < 3; index++) {
|
|
const {body: reply} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: parentId,
|
|
html: 'This is a reply ' + index
|
|
}]})
|
|
.expectStatus(201)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag,
|
|
location: anyLocationFor('comments')
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcher]
|
|
});
|
|
if (index === 0) {
|
|
testReplyId = reply.comments[0].id;
|
|
}
|
|
}
|
|
|
|
// Check if we have count.replies = 4, and replies.length == 3
|
|
await membersAgent
|
|
.get(`/api/comments/${parentId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [commentMatcherWithReplies({replies: 3})]
|
|
})
|
|
.expect(({body}) => {
|
|
body.comments[0].count.replies.should.eql(5);
|
|
});
|
|
});
|
|
|
|
it('Can reply to a comment with www domain', async function () {
|
|
// Test that the www. is stripped from the default
|
|
configUtils.set('url', 'http://www.domain.example/');
|
|
await testCanReply(member, {from: '"Ghost" <noreply@domain.example>'});
|
|
});
|
|
|
|
it('Can reply to a comment with custom support email', async function () {
|
|
// Test that the www. is stripped from the default
|
|
getStub.callsFake((key, options) => {
|
|
if (key === 'members_support_address') {
|
|
return 'support@example.com';
|
|
}
|
|
if (key === 'comments_enabled') {
|
|
return 'all';
|
|
}
|
|
return getStub.wrappedMethod.call(settingsCache, key, options);
|
|
});
|
|
await testCanReply(member, {from: '"Ghost" <support@example.com>'});
|
|
});
|
|
|
|
it('Can like a comment', async function () {
|
|
// Check not liked
|
|
await membersAgent
|
|
.get(`/api/comments/${commentId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: new Array(1).fill(commentMatcherWithReplies({replies: 1}))
|
|
})
|
|
.expect(({body}) => {
|
|
body.comments[0].liked.should.eql(false);
|
|
body.comments[0].count.likes.should.eql(0);
|
|
});
|
|
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.post(`/api/comments/${commentId}/like/`)
|
|
.expectStatus(204)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.expectEmptyBody();
|
|
|
|
// Check liked
|
|
await membersAgent
|
|
.get(`/api/comments/${commentId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: new Array(1).fill(commentMatcherWithReplies({replies: 1}))
|
|
})
|
|
.expect(({body}) => {
|
|
body.comments[0].liked.should.eql(true);
|
|
body.comments[0].count.likes.should.eql(1);
|
|
});
|
|
});
|
|
|
|
it('Cannot like a comment multiple times', async function () {
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.post(`/api/comments/${commentId}/like/`)
|
|
.expectStatus(400)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyUuid
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('Can like a reply', async function () {
|
|
// Check initial status: two replies before test
|
|
await membersAgent
|
|
.post(`/api/comments/${testReplyId}/like/`)
|
|
.expectStatus(204)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.expectEmptyBody();
|
|
|
|
// Check liked
|
|
await membersAgent
|
|
.get(`/api/comments/${testReplyId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: new Array(1).fill(commentMatcherWithReplies({replies: 0}))
|
|
})
|
|
.expect(({body}) => {
|
|
body.comments[0].liked.should.eql(true);
|
|
body.comments[0].count.likes.should.eql(1);
|
|
});
|
|
});
|
|
|
|
it('Can return replies', async function () {
|
|
const parentId = fixtureManager.get('comments', 0).id;
|
|
|
|
// Check initial status: two replies before test
|
|
await membersAgent
|
|
.get(`/api/comments/${parentId}/replies/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: new Array(7).fill(commentMatcher)
|
|
})
|
|
.expect(({body}) => {
|
|
should(body.comments[0].count.replies).be.undefined();
|
|
should(body.meta.pagination.total).eql(7);
|
|
should(body.meta.pagination.next).eql(null);
|
|
|
|
// Check liked + likes working for replies too
|
|
should(body.comments[2].id).eql(testReplyId);
|
|
should(body.comments[2].count.likes).eql(1);
|
|
should(body.comments[2].liked).eql(true);
|
|
});
|
|
});
|
|
|
|
it('Can request last page of replies', async function () {
|
|
const parentId = fixtureManager.get('comments', 0).id;
|
|
|
|
// Check initial status: two replies before test
|
|
await membersAgent
|
|
.get(`/api/comments/${parentId}/replies/?page=3&limit=3`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: new Array(1).fill(commentMatcher)
|
|
})
|
|
.expect(({body}) => {
|
|
should(body.comments[0].count.replies).be.undefined();
|
|
should(body.meta.pagination.total).eql(7);
|
|
should(body.meta.pagination.next).eql(null);
|
|
});
|
|
});
|
|
|
|
it('Can remove a like (unlike)', async function () {
|
|
// Unlike
|
|
await membersAgent
|
|
.delete(`/api/comments/${commentId}/like/`)
|
|
.expectStatus(204)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.expectEmptyBody();
|
|
|
|
// Check not liked
|
|
await membersAgent
|
|
.get(`/api/comments/${commentId}/`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: new Array(1).fill(commentMatcherWithReplies({replies: 1}))
|
|
})
|
|
.expect(({body}) => {
|
|
body.comments[0].liked.should.eql(false);
|
|
body.comments[0].count.likes.should.eql(0);
|
|
});
|
|
});
|
|
|
|
it('Cannot unlike a comment if it has not been liked', async function () {
|
|
// Remove like
|
|
await membersAgent
|
|
.delete(`/api/comments/${commentId}/like/`)
|
|
//.expectStatus(404)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
id: anyErrorId
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('Can report a comment', async function () {
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.post(`/api/comments/${commentId}/report/`)
|
|
.expectStatus(204)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.expectEmptyBody();
|
|
|
|
// Check report
|
|
const reports = await models.CommentReport.findAll({filter: 'comment_id:\'' + commentId + '\''});
|
|
reports.models.length.should.eql(1);
|
|
|
|
const report = reports.models[0];
|
|
report.get('member_id').should.eql(member.id);
|
|
|
|
mockManager.assert.sentEmail({
|
|
subject: '🚩 A comment has been reported on your post',
|
|
to: fixtureManager.get('users', 0).email,
|
|
html: new RegExp(escapeRegExp('<p>This is a message</p><p></p><p>New line</p>')),
|
|
text: new RegExp(escapeRegExp('This is a message\n\nNew line'))
|
|
});
|
|
});
|
|
|
|
it('Cannot report a comment twice', async function () {
|
|
// Create a temporary comment
|
|
await membersAgent
|
|
.post(`/api/comments/${commentId}/report/`)
|
|
.expectStatus(204)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.expectEmptyBody();
|
|
|
|
// Check report should be the same (no extra created)
|
|
const reports = await models.CommentReport.findAll({filter: 'comment_id:\'' + commentId + '\''});
|
|
reports.models.length.should.eql(1);
|
|
|
|
const report = reports.models[0];
|
|
report.get('member_id').should.eql(member.id);
|
|
|
|
mockManager.assert.sentEmailCount(0);
|
|
});
|
|
|
|
it('Can edit a comment on a post', async function () {
|
|
const {body} = await membersAgent
|
|
.put(`/api/comments/${commentId}`)
|
|
.body({comments: [{
|
|
html: 'Updated comment'
|
|
}]})
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [{
|
|
...commentMatcherWithReplies({replies: 1}),
|
|
edited_at: anyISODateTime
|
|
}]
|
|
});
|
|
|
|
assert(body.comments[0].edited_at, 'The edited_at field should be populated');
|
|
});
|
|
|
|
it('Can not edit a comment post_id', async function () {
|
|
const anotherPostId = fixtureManager.get('posts', 1).id;
|
|
await membersAgent
|
|
.put(`/api/comments/${commentId}`)
|
|
.body({comments: [{
|
|
post_id: anotherPostId
|
|
}]});
|
|
|
|
const {body} = await membersAgent
|
|
.get(`/api/comments/?filter=post_id:'${anotherPostId}'`);
|
|
|
|
assert(!body.comments.find(comment => comment.id === commentId), 'The comment should not have moved post');
|
|
});
|
|
|
|
it('Can not edit a comment which does not belong to you', async function () {
|
|
await membersAgent2
|
|
.put(`/api/comments/${commentId}`)
|
|
.body({comments: [{
|
|
html: 'Illegal comment update'
|
|
}]})
|
|
.expectStatus(403)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
type: 'NoPermissionError',
|
|
id: anyUuid
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('Can not edit a comment as a member who is not you', async function () {
|
|
const memberId = fixtureManager.get('members', 1).id;
|
|
await membersAgent
|
|
.put(`/api/comments/${commentId}`)
|
|
.body({comments: [{
|
|
html: 'Illegal comment update',
|
|
member_id: memberId
|
|
}]});
|
|
|
|
const {
|
|
body: {
|
|
comments: [
|
|
comment
|
|
]
|
|
}
|
|
} = await membersAgent.get(`/api/comments/${commentId}`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
comments: [{
|
|
...commentMatcherWithReplies({replies: 1}),
|
|
edited_at: anyISODateTime
|
|
}]
|
|
});
|
|
|
|
assert(comment.member.id !== memberId);
|
|
});
|
|
|
|
it('Can not reply to a reply', async function () {
|
|
const {
|
|
body: {
|
|
comments: [{
|
|
id: parentId
|
|
}]
|
|
}
|
|
} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: 'Parent'
|
|
}]});
|
|
|
|
const {
|
|
body: {
|
|
comments: [{
|
|
id: replyId
|
|
}]
|
|
}
|
|
} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: parentId,
|
|
html: 'Reply'
|
|
}]});
|
|
|
|
await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: replyId,
|
|
html: 'Reply to a reply!'
|
|
}]})
|
|
.expectStatus(400)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot({
|
|
errors: [{
|
|
type: 'BadRequestError',
|
|
id: anyUuid
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('Can not edit a replies parent', async function () {
|
|
const {
|
|
body: {
|
|
comments: [{
|
|
id: parentId
|
|
}]
|
|
}
|
|
} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: 'Parent'
|
|
}]});
|
|
|
|
const {
|
|
body: {
|
|
comments: [{
|
|
id: newParentId
|
|
}]
|
|
}
|
|
} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: 'New Parent'
|
|
}]});
|
|
|
|
const {
|
|
body: {
|
|
comments: [{
|
|
id: replyId
|
|
}]
|
|
}
|
|
} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
parent_id: parentId,
|
|
html: 'Reply'
|
|
}]});
|
|
|
|
// Attempt to edit the parent
|
|
await membersAgent
|
|
.put(`/api/comments/${replyId}/`)
|
|
.body({comments: [{
|
|
parent_id: newParentId,
|
|
html: 'Changed parent'
|
|
}]});
|
|
|
|
const {body: {comments: [comment]}} = await membersAgent.get(`api/comments/${newParentId}`);
|
|
|
|
assert(comment.replies.length === 0, 'The parent comment should not have changed');
|
|
});
|
|
|
|
it('Can fetch counts', async function () {
|
|
const ids = [
|
|
fixtureManager.get('posts', 0).id,
|
|
fixtureManager.get('posts', 1).id,
|
|
fixtureManager.get('posts', 2).id
|
|
];
|
|
await membersAgent
|
|
.get(`api/comments/counts/?ids=${ids.join(',')}`)
|
|
.expectStatus(200)
|
|
.matchHeaderSnapshot({
|
|
etag: anyEtag
|
|
})
|
|
.matchBodySnapshot();
|
|
});
|
|
|
|
it('Can delete a comment, and it is redacted from', async function () {
|
|
const {
|
|
body: {
|
|
comments: [{
|
|
id: commentToDeleteId
|
|
}]
|
|
}
|
|
} = await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: 'Comment to delete'
|
|
}]});
|
|
|
|
const {
|
|
body: {
|
|
comments: [deletedComment]
|
|
}
|
|
} = await membersAgent
|
|
.put(`/api/comments/${commentToDeleteId}`)
|
|
.body({comments: [{
|
|
status: 'deleted'
|
|
}]});
|
|
|
|
assert(!deletedComment.html);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when commenting disabled', function () {
|
|
beforeEach(async function () {
|
|
await membersAgent.loginAs('member@example.com');
|
|
const getStub = sinon.stub(settingsCache, 'get');
|
|
getStub.callsFake((key, options) => {
|
|
if (key === 'comments_enabled') {
|
|
return 'off';
|
|
}
|
|
return getStub.wrappedMethod.call(settingsCache, key, options);
|
|
});
|
|
});
|
|
|
|
afterEach(async function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
it('Can not comment on a post', async function () {
|
|
await membersAgent
|
|
.post(`/api/comments/`)
|
|
.body({comments: [{
|
|
post_id: postId,
|
|
html: '<p>This is a <strong>message</strong></p><p>New line</p>'
|
|
}]})
|
|
.expectStatus(405);
|
|
});
|
|
});
|
|
|
|
describe('when paid only commenting', function () {
|
|
beforeEach(async function () {
|
|
const getStub = sinon.stub(settingsCache, 'get');
|
|
getStub.callsFake((key, options) => {
|
|
if (key === 'comments_enabled') {
|
|
return 'paid';
|
|
}
|
|
return getStub.wrappedMethod.call(settingsCache, key, options);
|
|
});
|
|
});
|
|
|
|
afterEach(async function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
describe('Members with access', function () {
|
|
before(async function () {
|
|
await membersAgent.loginAs('paid@example.com');
|
|
member = await models.Member.findOne({email: 'paid@example.com'}, {require: true});
|
|
|
|
const product = await getPaidProduct();
|
|
|
|
// Attach comped subscription to this member
|
|
await models.Member.edit({
|
|
status: 'comped',
|
|
products: [
|
|
{
|
|
id: product.id
|
|
}
|
|
]
|
|
}, {id: member.id});
|
|
});
|
|
|
|
it('Can comment on a post', async function () {
|
|
await testCanCommentOnPost(member);
|
|
});
|
|
|
|
it('Can reply to a comment', async function () {
|
|
await testCanReply(member);
|
|
});
|
|
});
|
|
|
|
describe('Members without access', function () {
|
|
before(async function () {
|
|
await membersAgent.loginAs('free@example.com');
|
|
});
|
|
|
|
it('Can not comment on a post', async function () {
|
|
await testCannotCommentOnPost();
|
|
});
|
|
|
|
it('Can not reply to a comment', async function () {
|
|
await testCannotReply();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Only allow members with access to a given post to comment on that post
|
|
describe('Tier-only posts', function () {
|
|
let post;
|
|
let product;
|
|
|
|
before(async function () {
|
|
product = await getPaidProduct();
|
|
|
|
// Limit post access
|
|
post = await models.Post.findOne({id: postId}, {require: true});
|
|
|
|
await models.Post.edit({
|
|
visibility: 'tiers',
|
|
tiers: [
|
|
{
|
|
id: product.id
|
|
}
|
|
]
|
|
}, {id: post.id});
|
|
});
|
|
|
|
beforeEach(function () {
|
|
const getStub = sinon.stub(settingsCache, 'get');
|
|
getStub.callsFake((key, options) => {
|
|
if (key === 'comments_enabled') {
|
|
return 'all';
|
|
}
|
|
return getStub.wrappedMethod.call(settingsCache, key, options);
|
|
});
|
|
});
|
|
|
|
afterEach(async function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
describe('Members with access', function () {
|
|
before(async function () {
|
|
await membersAgent.loginAs('member-premium@example.com');
|
|
member = await models.Member.findOne({email: 'member-premium@example.com'}, {require: true});
|
|
|
|
// Attach comped subscription to this member
|
|
await models.Member.edit({
|
|
status: 'comped',
|
|
products: [
|
|
{
|
|
id: product.id
|
|
}
|
|
]
|
|
}, {id: member.id});
|
|
});
|
|
|
|
it('Can comment on a post', async function () {
|
|
await testCanCommentOnPost(member);
|
|
});
|
|
|
|
it('Can reply to a comment', async function () {
|
|
await testCanReply(member);
|
|
});
|
|
});
|
|
|
|
describe('Members without access', function () {
|
|
before(async function () {
|
|
await membersAgent.loginAs('member-not-premium@example.com');
|
|
});
|
|
|
|
it('Can not comment on a post', async function () {
|
|
await testCannotCommentOnPost();
|
|
});
|
|
|
|
it('Can not reply to a comment', async function () {
|
|
await testCannotReply();
|
|
});
|
|
});
|
|
});
|
|
});
|