mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-29 07:09:48 +03:00
🐛 Fixed commenting on tier-only posts (#15333)
fixes https://github.com/TryGhost/Team/issues/1860 **Problem:** Members were not able to comment on a post that was only visible for members with a specific tier. **Causes:** Content gating was done on models with missing relations. - The products relation was not loaded on the member when doing content gating - The tiers relation was not loaded on the post when doing content gating **Tests:** - Added for tier-only posts - Added for paid-only commenting
This commit is contained in:
parent
e7786ca482
commit
4282ead3a7
@ -160,7 +160,8 @@ class CommentsService {
|
||||
id: member
|
||||
}, {
|
||||
require: true,
|
||||
...options
|
||||
...options,
|
||||
withRelated: ['products']
|
||||
});
|
||||
|
||||
this.checkCommentAccess(memberModel);
|
||||
@ -169,7 +170,8 @@ class CommentsService {
|
||||
id: post
|
||||
}, {
|
||||
require: true,
|
||||
...options
|
||||
...options,
|
||||
withRelated: ['tiers']
|
||||
});
|
||||
|
||||
this.checkPostAccess(postModel, memberModel);
|
||||
@ -208,7 +210,8 @@ class CommentsService {
|
||||
id: member
|
||||
}, {
|
||||
require: true,
|
||||
...options
|
||||
...options,
|
||||
withRelated: ['products']
|
||||
});
|
||||
|
||||
this.checkCommentAccess(memberModel);
|
||||
@ -229,7 +232,8 @@ class CommentsService {
|
||||
id: parentComment.get('post_id')
|
||||
}, {
|
||||
require: true,
|
||||
...options
|
||||
...options,
|
||||
withRelated: ['tiers']
|
||||
});
|
||||
|
||||
this.checkPostAccess(postModel, memberModel);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,11 @@ const moment = require('moment-timezone');
|
||||
const settingsCache = require('../../../core/shared/settings-cache');
|
||||
const sinon = require('sinon');
|
||||
|
||||
let membersAgent, membersAgent2, member, postId, postTitle, commentId;
|
||||
let membersAgent, membersAgent2, postId, postTitle, commentId;
|
||||
|
||||
async function getPaidProduct() {
|
||||
return await models.Product.findOne({type: 'paid'});
|
||||
}
|
||||
|
||||
const commentMatcher = {
|
||||
id: anyObjectId,
|
||||
@ -54,7 +58,128 @@ 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 sleep(200);
|
||||
|
||||
// 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) {
|
||||
const date = new Date(0);
|
||||
await models.Member.edit({last_seen_at: date, last_commented_at: date}, {id: member.get('id')});
|
||||
|
||||
const {body} = 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({
|
||||
subject: '↪️ New reply to your comment on Ghost',
|
||||
to: fixtureManager.get('members', 0).email
|
||||
});
|
||||
|
||||
// Wait for the dispatched events (because this happens async)
|
||||
await sleep(250);
|
||||
|
||||
// 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() {
|
||||
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(403)
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
})
|
||||
.matchBodySnapshot({
|
||||
errors: [{
|
||||
id: anyErrorId
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
async function testCannotReply() {
|
||||
await membersAgent
|
||||
.post(`/api/comments/`)
|
||||
.body({comments: [{
|
||||
post_id: postId,
|
||||
parent_id: fixtureManager.get('comments', 0).id,
|
||||
html: 'This is a reply'
|
||||
}]})
|
||||
.expectStatus(403)
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
})
|
||||
.matchBodySnapshot({
|
||||
errors: [{
|
||||
id: anyErrorId
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
describe('Comments API', function () {
|
||||
let member;
|
||||
|
||||
before(async function () {
|
||||
membersAgent = await agentProvider.getMembersAPIAgent();
|
||||
membersAgent2 = await agentProvider.getMembersAPIAgent();
|
||||
@ -73,7 +198,8 @@ describe('Comments API', function () {
|
||||
mockManager.restore();
|
||||
});
|
||||
|
||||
describe('when not authenticated but enabled', function () {
|
||||
describe('when commenting enabled for all', function () {
|
||||
describe('when not authenticated', function () {
|
||||
beforeEach(function () {
|
||||
const getStub = sinon.stub(settingsCache, 'get');
|
||||
getStub.callsFake((key, options) => {
|
||||
@ -118,33 +244,6 @@ describe('Comments API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not enabled', 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 comment on a post', async function () {
|
||||
const {body} = 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 authenticated', function () {
|
||||
before(async function () {
|
||||
await membersAgent.loginAs('member@example.com');
|
||||
@ -166,43 +265,7 @@ describe('Comments API', function () {
|
||||
});
|
||||
|
||||
it('Can comment on a post', async function () {
|
||||
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 sleep(200);
|
||||
|
||||
// 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.');
|
||||
await testCanCommentOnPost(member);
|
||||
});
|
||||
|
||||
it('Can browse all comments of a post', async function () {
|
||||
@ -258,45 +321,7 @@ describe('Comments API', function () {
|
||||
});
|
||||
|
||||
it('Can reply to a comment', async function () {
|
||||
const date = new Date(0);
|
||||
await models.Member.edit({last_seen_at: date, last_commented_at: date}, {id: member.get('id')});
|
||||
|
||||
const {body} = 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({
|
||||
subject: '↪️ New reply to your comment on Ghost',
|
||||
to: fixtureManager.get('members', 0).email
|
||||
});
|
||||
|
||||
// Wait for the dispatched events (because this happens async)
|
||||
await sleep(250);
|
||||
|
||||
// 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.');
|
||||
await testCanReply(member);
|
||||
});
|
||||
|
||||
let testReplyId;
|
||||
@ -755,9 +780,9 @@ describe('Comments API', function () {
|
||||
.post(`api/comments/counts`)
|
||||
.body({
|
||||
ids: [
|
||||
postId = fixtureManager.get('posts', 0).id,
|
||||
postId = fixtureManager.get('posts', 1).id,
|
||||
postId = fixtureManager.get('posts', 2).id
|
||||
fixtureManager.get('posts', 0).id,
|
||||
fixtureManager.get('posts', 1).id,
|
||||
fixtureManager.get('posts', 2).id
|
||||
]
|
||||
})
|
||||
.expectStatus(200)
|
||||
@ -795,3 +820,163 @@ describe('Comments API', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 () {
|
||||
const {body} = 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user