From 4282ead3a7dcb491a0cecee9a0a2845d1ddf2b54 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Tue, 30 Aug 2022 17:38:58 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20commenting=20on=20tier-o?= =?UTF-8?q?nly=20posts=20(#15333)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../core/server/services/comments/service.js | 12 +- .../__snapshots__/comments.test.js.snap | 1784 +++++++++++++++++ .../e2e-api/members-comments/comments.test.js | 1517 ++++++++------ 3 files changed, 2643 insertions(+), 670 deletions(-) diff --git a/ghost/core/core/server/services/comments/service.js b/ghost/core/core/server/services/comments/service.js index c8ef5c14ee..3d1732b46a 100644 --- a/ghost/core/core/server/services/comments/service.js +++ b/ghost/core/core/server/services/comments/service.js @@ -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); diff --git a/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap b/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap index bf90d6617b..c8d46d29bc 100644 --- a/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap +++ b/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap @@ -1,5 +1,215 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Comments API Tier only access posts Can comment on a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API Tier only access posts Can comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "373", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API Tier only access posts Can not comment on a post that a member does not have access to 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API Tier only access posts Can not comment on a post that a member does not have access to 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API Tier-only posts Members with access Can comment on a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API Tier-only posts Members with access Can comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "373", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API Tier-only posts Members with access Can reply to a comment 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API Tier-only posts Members with access Can reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "342", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API Tier-only posts Members without access Can not comment on a post 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API Tier-only posts Members without access Can not comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API Tier-only posts Members without access Can not reply to a comment 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API Tier-only posts Members without access Can not reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`Comments API when authenticated Can browse all comments of a post 1: [body] 1`] = ` Object { "comments": Array [ @@ -1456,6 +1666,1300 @@ Object { } `; +exports[`Comments API when commenting enabled for all when authenticated Can browse all comments of a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

First.

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 2, + }, + }, +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can browse all comments of a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1100", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can comment on a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "373", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can edit a comment on a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "html": "Updated comment", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can edit a comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "666", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can fetch counts 1: [body] 1`] = ` +Object { + "618ba1ffbe2896088840a6df": 13, + "618ba1ffbe2896088840a6e1": 0, + "618ba1ffbe2896088840a6e3": 0, +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can fetch counts 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "89", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a comment 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "675", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a comment 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a comment 4: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a comment 5: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "674", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a reply 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a reply 2: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can like a reply 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "343", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can not edit a comment as a member who is not you 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "html": "Illegal comment update", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can not edit a comment as a member who is not you 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "673", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can not edit a comment which does not belong to you 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to edit comments", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot edit comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can not edit a comment which does not belong to you 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "269", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can not reply to a reply 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "Can not reply to a reply", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Request not understood error, cannot save comment.", + "property": null, + "type": "BadRequestError", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can not reply to a reply 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "260", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can remove a like (unlike) 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can remove a like (unlike) 2: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can remove a like (unlike) 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "675", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can reply to a comment 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "342", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can reply to your own comment 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can reply to your own comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "342", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can report a comment 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can request second page of replies 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 3, + "next": null, + "page": 2, + "pages": 2, + "prev": 1, + "total": 5, + }, + }, +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can request second page of replies 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "708", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can return replies 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 5, + }, + }, +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Can return replies 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1629", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Cannot like a comment multiple times 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "This comment was liked already", + "property": null, + "type": "BadRequestError", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Cannot like a comment multiple times 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "218", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Cannot report a comment twice 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Cannot unlike a comment if it has not been liked 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Unable to find like", + "property": null, + "type": "NotFoundError", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Cannot unlike a comment if it has not been liked 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "205", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

First.

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "956", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 3: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 4: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "344", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 5: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 6: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "344", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 7: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 8: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "344", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 9: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

First.

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when authenticated Limits returned replies to 3 10: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1261", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when not authenticated Can browse all comments of a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

First.

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 1, + }, + }, +} +`; + +exports[`Comments API when commenting enabled for all when not authenticated Can browse all comments of a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "741", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when commenting enabled for all when not authenticated cannot report a comment 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Unable to find member", + "property": null, + "type": "UnauthorizedError", + }, + ], +} +`; + +exports[`Comments API when commenting enabled for all when not authenticated cannot report a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "211", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`Comments API when not authenticated but enabled Can browse all comments of a post 1: [body] 1`] = ` Object { "comments": Array [ @@ -1571,3 +3075,283 @@ Object { "x-powered-by": "Express", } `; + +exports[`Comments API when paid only Members with access Can comment on a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when paid only Members with access Can comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "373", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only Members with access Can reply to a comment 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when paid only Members with access Can reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "342", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only Members without access Can not comment on a post 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API when paid only Members without access Can not comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only Members without access Can not reply to a comment 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API when paid only Members without access Can not reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only commenting Members with access Can comment on a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

This is a message

New line

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when paid only commenting Members with access Can comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "373", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only commenting Members with access Can reply to a comment 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when paid only commenting Members with access Can reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "342", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only commenting Members without access Can not comment on a post 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API when paid only commenting Members without access Can not comment on a post 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when paid only commenting Members without access Can not reply to a comment 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": null, + "context": "You do not have permission to comment on this post.", + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Permission error, cannot save comment.", + "property": null, + "type": "NoPermissionError", + }, + ], +} +`; + +exports[`Comments API when paid only commenting Members without access Can not reply to a comment 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "277", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; diff --git a/ghost/core/test/e2e-api/members-comments/comments.test.js b/ghost/core/test/e2e-api/members-comments/comments.test.js index 507a0468db..2e86082484 100644 --- a/ghost/core/test/e2e-api/members-comments/comments.test.js +++ b/ghost/core/test/e2e-api/members-comments/comments.test.js @@ -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: '

This is a message

New line

' + }]}) + .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 tag is removed by the sanitizer + html: new RegExp(escapeRegExp('

This is a message

New line

')) + }); + + // 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: '

This is a message

New line

' + }]}) + .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,52 +198,630 @@ describe('Comments API', function () { mockManager.restore(); }); - describe('when not authenticated but enabled', 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); + 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); + }); + }); + + after(async function () { + sinon.restore(); + }); + + it('Can browse all comments of a post', async function () { + const {body} = await membersAgent + .get(`/api/comments/?filter=post_id:${postId}`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: [commentMatcherWithReplies({replies: 1})] + }); + }); + + 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 + }] + }); }); }); - after(async function () { - sinon.restore(); - }); - - it('Can browse all comments of a post', async function () { - const {body} = await membersAgent - .get(`/api/comments/?filter=post_id:${postId}`) - .expectStatus(200) - .matchHeaderSnapshot({ - etag: anyEtag - }) - .matchBodySnapshot({ - comments: [commentMatcherWithReplies({replies: 1})] + describe('when authenticated', function () { + 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 () { + const getStub = sinon.stub(settingsCache, 'get'); + getStub.callsFake((key, options) => { + if (key === 'comments_enabled') { + return 'all'; + } + return getStub.wrappedMethod.call(settingsCache, key, options); }); - }); - - 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 - }] + }); + + afterEach(async function () { + sinon.restore(); + }); + + it('Can comment on a post', async function () { + await testCanCommentOnPost(member); + }); + + it('Can browse all comments of a post', async function () { + const {body} = await membersAgent + .get(`/api/comments/?filter=post_id:${postId}`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: [commentMatcherWithReplies({replies: 1}), commentMatcher] + }); + }); + + 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')}); + + const {body} = 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') + }) + .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 sleep(200); + + // 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 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(5).fill(commentMatcher) + }) + .expect(({body}) => { + should(body.comments[0].count.replies).be.undefined(); + should(body.meta.pagination.total).eql(5); + 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 second 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=2&limit=3`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: new Array(2).fill(commentMatcher) + }) + .expect(({body}) => { + should(body.comments[0].count.replies).be.undefined(); + should(body.meta.pagination.total).eql(5); + 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('

This is a message

New line

')), + 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 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 () { + await membersAgent + .post(`api/comments/counts`) + .body({ + ids: [ + fixtureManager.get('posts', 0).id, + fixtureManager.get('posts', 1).id, + fixtureManager.get('posts', 2).id + ] + }) + .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 not enabled', function () { + describe('when commenting disabled', function () { beforeEach(async function () { await membersAgent.loginAs('member@example.com'); const getStub = sinon.stub(settingsCache, 'get'); @@ -134,7 +837,7 @@ describe('Comments API', function () { sinon.restore(); }); - it('Can comment on a post', async function () { + it('Can not comment on a post', async function () { const {body} = await membersAgent .post(`/api/comments/`) .body({comments: [{ @@ -145,12 +848,84 @@ describe('Comments API', function () { }); }); - describe('when authenticated', function () { - 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'); + 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) => { @@ -165,633 +940,43 @@ describe('Comments API', function () { sinon.restore(); }); - 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: '

This is a message

New line

' - }]}) - .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 tag is removed by the sanitizer - html: new RegExp(escapeRegExp('

This is a message

New line

')) - }); - - // 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.'); - }); - - it('Can browse all comments of a post', async function () { - const {body} = await membersAgent - .get(`/api/comments/?filter=post_id:${postId}`) - .expectStatus(200) - .matchHeaderSnapshot({ - etag: anyEtag - }) - .matchBodySnapshot({ - comments: [commentMatcherWithReplies({replies: 1}), commentMatcher] - }); - }); - - 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')}); - - const {body} = 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') - }) - .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 sleep(200); - - // 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 () { - 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.'); - }); - - 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 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(5).fill(commentMatcher) - }) - .expect(({body}) => { - should(body.comments[0].count.replies).be.undefined(); - should(body.meta.pagination.total).eql(5); - 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 second 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=2&limit=3`) - .expectStatus(200) - .matchHeaderSnapshot({ - etag: anyEtag - }) - .matchBodySnapshot({ - comments: new Array(2).fill(commentMatcher) - }) - .expect(({body}) => { - should(body.comments[0].count.replies).be.undefined(); - should(body.meta.pagination.total).eql(5); - 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('

This is a message

New line

')), - 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 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 + 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 + } ] - } - } = await membersAgent.get(`/api/comments/${commentId}`) - .expectStatus(200) - .matchHeaderSnapshot({ - etag: anyEtag - }) - .matchBodySnapshot({ - comments: [{ - ...commentMatcherWithReplies({replies: 1}), - edited_at: anyISODateTime - }] - }); + }, {id: member.id}); + }); - assert(comment.member.id !== memberId); + it('Can comment on a post', async function () { + await testCanCommentOnPost(member); + }); + + it('Can reply to a comment', async function () { + await testCanReply(member); + }); }); - 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' - }]}); + describe('Members without access', function () { + before(async function () { + await membersAgent.loginAs('member-not-premium@example.com'); + }); - const { - body: { - comments: [{ - id: replyId - }] - } - } = await membersAgent - .post(`/api/comments/`) - .body({comments: [{ - post_id: postId, - parent_id: parentId, - html: 'Reply' - }]}); + it('Can not comment on a post', async function () { + await testCannotCommentOnPost(); + }); - 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 () { - await membersAgent - .post(`api/comments/counts`) - .body({ - ids: [ - postId = fixtureManager.get('posts', 0).id, - postId = fixtureManager.get('posts', 1).id, - postId = fixtureManager.get('posts', 2).id - ] - }) - .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); + it('Can not reply to a comment', async function () { + await testCannotReply(); + }); }); }); });