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 5db08da622..cef1588d64 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 094a091284..1651bf5d13 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 964c9d5889..9af3952e41 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 f4628112bf..dceba661f6 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 1c86990005..88011bc165 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 4328260dd0..7b837f9ff1 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 () {