From 00cff0aece943e7026f3be36de2f97c3919ccf82 Mon Sep 17 00:00:00 2001 From: Sag Date: Wed, 6 Mar 2024 21:30:00 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20free=20tier=20showing=20?= =?UTF-8?q?in=20the=20tiers-only=20paywall=20in=20posts=20(#19807)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs INC-36 fixes https://github.com/TryGhost/Ghost/issues/19796 - The tiers-only paywall was incorrectly rendering "Free". Example: "This post is for subscribers of the Free, Silver and Gold tiers only" - Steps to reproduce the issue: 1. Create a post with public visibility, publish it 2. Then swap the visibility to specific tiers. The default selects all paid tiers. Leave it like that 3. Update the post. The paywall show Free, even though it should be showing only the paid tiers - This fix filters out the "free" tier when visibility is set to tiers, before updating a Post or a Page. The fix includes bulk updates from the list of Posts and Pages (right-click on a Post/Page > Change Access). --- .../utils/serializers/output/mappers/posts.js | 2 +- ghost/core/core/server/models/post.js | 6 + .../admin/__snapshots__/pages.test.js.snap | 188 +++++++++++++++++- .../admin/__snapshots__/posts.test.js.snap | 175 ++++++++++++++++ ghost/core/test/e2e-api/admin/pages.test.js | 56 ++++++ ghost/core/test/e2e-api/admin/posts.test.js | 56 ++++++ 6 files changed, 481 insertions(+), 2 deletions(-) diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/posts.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/posts.js index 5db08da6223..cef1588d643 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/posts.js @@ -62,7 +62,7 @@ module.exports = async (model, frame, options = {}) => { jsonModel.tiers = tiersData || []; } - if (jsonModel.visibility === 'paid' && jsonModel.tiers) { + if (['tiers', 'paid'].includes(jsonModel.visibility) && jsonModel.tiers) { jsonModel.tiers = tiersData ? tiersData.filter(t => t.type === 'paid') : []; } diff --git a/ghost/core/core/server/models/post.js b/ghost/core/core/server/models/post.js index 094a091284b..1651bf5d13e 100644 --- a/ghost/core/core/server/models/post.js +++ b/ghost/core/core/server/models/post.js @@ -1004,6 +1004,12 @@ Post = ghostBookshelf.Model.extend({ this.set('tiers', this.get('tiers').map(t => ({ id: t.id }))); + + // Don't associate the free tier with the post + const freeTier = await ghostBookshelf.model('Product').findOne({type: 'free'}, {require: false, transacting: options.transacting ?? undefined}); + if (freeTier) { + this.set('tiers', this.get('tiers').filter(t => t.id !== freeTier.id)); + } } if (labs.isSet('collectionsCard') && this.get('type') === 'post' && (newStatus === 'published' || olderStatus === 'published')) { diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/pages.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/pages.test.js.snap index 964c9d58896..9af3952e41c 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/pages.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/pages.test.js.snap @@ -799,7 +799,7 @@ Object { "primary_tag": Any, "published_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "show_title_and_feature_image": Any, - "slug": "test-page-2", + "slug": "test-page-3", "status": "published", "tags": Any, "tiers": Array [ @@ -1157,6 +1157,192 @@ Hopefully you don't find it a bore.", } `; +exports[`Pages API Update Access Visibility is set to tiers Does not allow to attach the free tier 1: [body] 1`] = ` +Object { + "pages": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "comment_id": Any, + "count": Object { + "negative_feedback": 0, + "paid_conversions": 0, + "positive_feedback": 0, + "signups": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "lexical": "{\\"root\\":{\\"children\\":[{\\"children\\":[],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"paragraph\\",\\"version\\":1}],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"root\\",\\"version\\":1}}", + "meta_description": null, + "meta_title": null, + "mobiledoc": null, + "og_description": null, + "og_image": null, + "og_title": null, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "show_title_and_feature_image": Any, + "slug": "test-page", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Test Page", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "tiers", + }, + ], +} +`; + +exports[`Pages API Update Access Visibility is set to tiers Does not allow to attach the free tier 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "3504", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": Any, + "x-powered-by": "Express", +} +`; + +exports[`Pages API Update Access Visibility is set to tiers Does not allow to attach the free tier 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "3492", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": Any, + "x-powered-by": "Express", +} +`; + +exports[`Pages API Update Access Visibility is set to tiers Saves only paid tiers 1: [body] 1`] = ` +Object { + "pages": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "comment_id": Any, + "count": Object { + "negative_feedback": 0, + "paid_conversions": 0, + "positive_feedback": 0, + "signups": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "lexical": "{\\"root\\":{\\"children\\":[{\\"children\\":[],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"paragraph\\",\\"version\\":1}],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"root\\",\\"version\\":1}}", + "meta_description": null, + "meta_title": null, + "mobiledoc": null, + "og_description": null, + "og_image": null, + "og_title": null, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "show_title_and_feature_image": Any, + "slug": "test-page-2", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Test Page", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "tiers", + }, + ], +} +`; + +exports[`Pages API Update Access Visibility is set to tiers Saves only paid tiers 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "3494", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": Any, + "x-powered-by": "Express", +} +`; + exports[`Pages API Update Can modify show_title_and_feature_image property 1: [body] 1`] = ` Object { "pages": Array [ diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap index f4628112bf6..dceba661f65 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap @@ -1889,6 +1889,181 @@ Object { } `; +exports[`Posts API Update Access Visibility is set to tiers Does not allow to attach the free tier 1: [body] 1`] = ` +Object { + "pages": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "comment_id": Any, + "count": Object { + "negative_feedback": 0, + "paid_conversions": 0, + "positive_feedback": 0, + "signups": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "lexical": "{\\"root\\":{\\"children\\":[{\\"children\\":[],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"paragraph\\",\\"version\\":1}],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"root\\",\\"version\\":1}}", + "meta_description": null, + "meta_title": null, + "mobiledoc": null, + "og_description": null, + "og_image": null, + "og_title": null, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "show_title_and_feature_image": true, + "slug": "test-page", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Test Page", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "tiers", + }, + ], +} +`; + +exports[`Posts API Update Access Visibility is set to tiers Does not allow to attach the free tier 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "3492", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": Any, + "x-powered-by": "Express", +} +`; + +exports[`Posts API Update Access Visibility is set to tiers Saves only paid tiers 1: [body] 1`] = ` +Object { + "posts": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "comment_id": Any, + "count": Object { + "clicks": 0, + "negative_feedback": 0, + "positive_feedback": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "email": null, + "email_only": false, + "email_segment": "all", + "email_subject": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "lexical": "{\\"root\\":{\\"children\\":[{\\"children\\":[],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"paragraph\\",\\"version\\":1}],\\"direction\\":null,\\"format\\":\\"\\",\\"indent\\":0,\\"type\\":\\"root\\",\\"version\\":1}}", + "meta_description": null, + "meta_title": null, + "mobiledoc": null, + "newsletter": null, + "og_description": null, + "og_image": null, + "og_title": null, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "slug": "test-page", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Test Page", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "tiers", + }, + ], +} +`; + +exports[`Posts API Update Access Visibility is set to tiers Saves only paid tiers 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "3527", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": Any, + "x-powered-by": "Express", +} +`; + exports[`Posts API Update Can add and remove collections 1: [body] 1`] = ` Object { "posts": Array [ diff --git a/ghost/core/test/e2e-api/admin/pages.test.js b/ghost/core/test/e2e-api/admin/pages.test.js index 1c86990005c..88011bc1654 100644 --- a/ghost/core/test/e2e-api/admin/pages.test.js +++ b/ghost/core/test/e2e-api/admin/pages.test.js @@ -285,6 +285,62 @@ describe('Pages API', function () { }) .expectStatus(200); }); + + describe('Access', function () { + describe('Visibility is set to tiers', function () { + it('Saves only paid tiers', async function () { + const page = { + title: 'Test Page', + status: 'draft' + }; + + // @ts-ignore + const products = await models.Product.findAll(); + + const freeTier = products.models[0]; + const paidTier = products.models[1]; + + const {body: pageBody} = await agent + .post('/pages/', { + headers: { + 'content-type': 'application/json' + } + }) + .body({pages: [page]}) + .expectStatus(201); + + const [pageResponse] = pageBody.pages; + + await agent + .put(`/pages/${pageResponse.id}`) + .body({ + pages: [{ + id: pageResponse.id, + updated_at: pageResponse.updated_at, + visibility: 'tiers', + tiers: [ + {id: freeTier.id}, + {id: paidTier.id} + ] + }] + }) + .expectStatus(200) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + 'x-cache-invalidate': anyString + }) + .matchBodySnapshot({ + pages: [Object.assign({}, matchPageShallowIncludes, { + published_at: null, + tiers: [ + {type: paidTier.get('type'), ...tierSnapshot} + ] + })] + }); + }); + }); + }); }); describe('Copy', function () { diff --git a/ghost/core/test/e2e-api/admin/posts.test.js b/ghost/core/test/e2e-api/admin/posts.test.js index 4328260dd01..7b837f9ff11 100644 --- a/ghost/core/test/e2e-api/admin/posts.test.js +++ b/ghost/core/test/e2e-api/admin/posts.test.js @@ -671,6 +671,62 @@ describe('Posts API', function () { should.exist(emptyPageCount); emptyPageCount.should.equal(totalPageCount, 'post-update empty page count'); }); + + describe('Access', function () { + describe('Visibility is set to tiers', function () { + it('Saves only paid tiers', async function () { + const post = { + title: 'Test Page', + status: 'draft' + }; + + // @ts-ignore + const products = await models.Product.findAll(); + + const freeTier = products.models[0]; + const paidTier = products.models[1]; + + const {body: pageBody} = await agent + .post('/posts/', { + headers: { + 'content-type': 'application/json' + } + }) + .body({posts: [post]}) + .expectStatus(201); + + const [pageResponse] = pageBody.posts; + + await agent + .put(`/posts/${pageResponse.id}`) + .body({ + posts: [{ + id: pageResponse.id, + updated_at: pageResponse.updated_at, + visibility: 'tiers', + tiers: [ + {id: freeTier.id}, + {id: paidTier.id} + ] + }] + }) + .expectStatus(200) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + 'x-cache-invalidate': anyString + }) + .matchBodySnapshot({ + posts: [Object.assign({}, matchPostShallowIncludes, { + published_at: null, + tiers: [ + {type: paidTier.get('type'), ...tierSnapshot} + ] + })] + }); + }); + }); + }); }); describe('Delete', function () {