From c052652559c43b9110ec92731e96ad7f42016ff0 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Tue, 24 May 2022 16:42:15 +0200 Subject: [PATCH] Updated settings API to return all settings in edit endpoint (#14889) refs https://github.com/TryGhost/Team/issues/1650 Since we introduced calculated settings, we need to return all settings when editing a setting, because those calculated settings can change. - When editing settings, return all the settings. Previously we didn't include the calculated settings (which caused issues with admin not having up to date values for those) - Updated Stripe script to be injected based on the calculated settings Required for https://github.com/TryGhost/Admin/pull/2405 --- core/frontend/helpers/ghost_head.js | 7 +- core/server/api/canary/settings.js | 4 +- .../services/settings/settings-service.js | 4 +- .../admin/__snapshots__/settings.test.js.snap | 495 +++++++++++++++++- test/e2e-api/admin/settings.test.js | 12 +- test/regression/api/admin/settings.test.js | 259 ++++----- test/unit/frontend/helpers/ghost_head.test.js | 66 +-- 7 files changed, 598 insertions(+), 249 deletions(-) diff --git a/core/frontend/helpers/ghost_head.js b/core/frontend/helpers/ghost_head.js index 53a1b61af5..c26e02116e 100644 --- a/core/frontend/helpers/ghost_head.js +++ b/core/frontend/helpers/ghost_head.js @@ -44,17 +44,14 @@ function finaliseStructuredData(meta) { } function getMembersHelper(data, frontendKey) { - if (settingsCache.get('members_signup_access') === 'none') { + if (!settingsCache.get('members_enabled')) { return ''; } - const stripeDirectSecretKey = settingsCache.get('stripe_secret_key'); - const stripeDirectPublishableKey = settingsCache.get('stripe_publishable_key'); - const stripeConnectAccountId = settingsCache.get('stripe_connect_account_id'); const colorString = _.has(data, 'site._preview') && data.site.accent_color ? ` data-accent-color="${data.site.accent_color}"` : ''; let membersHelper = ``; membersHelper += (``); - if ((!!stripeDirectSecretKey && !!stripeDirectPublishableKey) || !!stripeConnectAccountId) { + if (settingsCache.get('paid_members_enabled')) { membersHelper += ''; } return membersHelper; diff --git a/core/server/api/canary/settings.js b/core/server/api/canary/settings.js index 5c9b3ce1f3..3d3acfae5c 100644 --- a/core/server/api/canary/settings.js +++ b/core/server/api/canary/settings.js @@ -195,7 +195,9 @@ module.exports = { this.headers.cacheInvalidate = true; } - return result; + // We need to return all settings here, because we have calculated settings that might change + const browse = await settingsBREADService.browse(frame.options.context); + return browse; } }, diff --git a/core/server/services/settings/settings-service.js b/core/server/services/settings/settings-service.js index da118134c6..ceaef6edfe 100644 --- a/core/server/services/settings/settings-service.js +++ b/core/server/services/settings/settings-service.js @@ -118,7 +118,7 @@ module.exports = { return connectKeys; }, - isStripeConnected() { + arePaidMembersEnabled() { return this.isMembersEnabled() && this.getActiveStripeKeys() !== null; }, @@ -137,7 +137,7 @@ module.exports = { fields.push(new CalculatedField({key: 'members_enabled', type: 'boolean', group: 'members', fn: this.isMembersEnabled.bind(this), dependents: ['members_signup_access']})); fields.push(new CalculatedField({key: 'members_invite_only', type: 'boolean', group: 'members', fn: this.isMembersInviteOnly.bind(this), dependents: ['members_signup_access']})); - fields.push(new CalculatedField({key: 'paid_members_enabled', type: 'boolean', group: 'members', fn: this.isStripeConnected.bind(this), dependents: ['members_signup_access', 'stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']})); + fields.push(new CalculatedField({key: 'paid_members_enabled', type: 'boolean', group: 'members', fn: this.arePaidMembersEnabled.bind(this), dependents: ['members_signup_access', 'stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']})); fields.push(new CalculatedField({key: 'firstpromoter_account', type: 'string', group: 'firstpromoter', fn: this.getFirstpromoterId.bind(this), dependents: ['firstpromoter', 'firstpromoter_id']})); return fields; diff --git a/test/e2e-api/admin/__snapshots__/settings.test.js.snap b/test/e2e-api/admin/__snapshots__/settings.test.js.snap index 38c2ce802d..d9c3f93ecf 100644 --- a/test/e2e-api/admin/__snapshots__/settings.test.js.snap +++ b/test/e2e-api/admin/__snapshots__/settings.test.js.snap @@ -1286,21 +1286,57 @@ Object { "key": "title", "value": "[]", }, + Object { + "key": "description", + "value": "Thoughts, stories and ideas", + }, + Object { + "key": "logo", + "value": "", + }, + Object { + "key": "cover_image", + "value": "https://static.ghost.org/v4.0.0/images/publication-cover.jpg", + }, + Object { + "key": "icon", + "value": "", + }, + Object { + "key": "accent_color", + "value": "#FF1A75", + }, + Object { + "key": "locale", + "value": "ua", + }, + Object { + "key": "timezone", + "value": "Pacific/Auckland", + }, Object { "key": "codeinjection_head", "value": null, }, + Object { + "key": "codeinjection_foot", + "value": "", + }, + Object { + "key": "facebook", + "value": "ghost", + }, + Object { + "key": "twitter", + "value": "@ghost", + }, Object { "key": "navigation", "value": "[{\\"label\\":\\"label1\\"}]", }, Object { - "key": "slack_username", - "value": "New Slack Username", - }, - Object { - "key": "is_private", - "value": false, + "key": "secondary_navigation", + "value": "[{\\"label\\":\\"Data & privacy\\",\\"url\\":\\"/privacy/\\"},{\\"label\\":\\"Contact\\",\\"url\\":\\"/contact/\\"},{\\"label\\":\\"Contribute →\\",\\"url\\":\\"/contribute/\\"}]", }, Object { "key": "meta_title", @@ -1335,21 +1371,185 @@ Object { "value": "twitter description", }, Object { - "key": "locale", - "value": "ua", + "key": "active_theme", + "value": "casper", + }, + Object { + "key": "is_private", + "value": false, + }, + Object { + "key": "password", + "value": "", + }, + Object { + "key": "public_hash", + "value": StringMatching /\\[a-z0-9\\]\\{30\\}/, + }, + Object { + "key": "default_content_visibility", + "value": "public", + }, + Object { + "key": "default_content_visibility_tiers", + "value": "[]", + }, + Object { + "key": "members_signup_access", + "value": "all", + }, + Object { + "key": "members_support_address", + "value": "noreply", + }, + Object { + "key": "stripe_secret_key", + "value": null, + }, + Object { + "key": "stripe_publishable_key", + "value": null, + }, + Object { + "key": "stripe_plans", + "value": "[]", + }, + Object { + "key": "stripe_connect_publishable_key", + "value": "pk_test_for_stripe", + }, + Object { + "key": "stripe_connect_secret_key", + "value": "••••••••", + }, + Object { + "key": "stripe_connect_livemode", + "value": null, + }, + Object { + "key": "stripe_connect_display_name", + "value": null, + }, + Object { + "key": "stripe_connect_account_id", + "value": null, + }, + Object { + "key": "members_monthly_price_id", + "value": null, + }, + Object { + "key": "members_yearly_price_id", + "value": null, + }, + Object { + "key": "portal_name", + "value": true, + }, + Object { + "key": "portal_button", + "value": true, + }, + Object { + "key": "portal_plans", + "value": "[\\"free\\"]", + }, + Object { + "key": "portal_products", + "value": "[]", + }, + Object { + "key": "portal_button_style", + "value": "icon-and-text", + }, + Object { + "key": "portal_button_icon", + "value": null, + }, + Object { + "key": "portal_button_signup_text", + "value": "Subscribe", + }, + Object { + "key": "mailgun_domain", + "value": null, + }, + Object { + "key": "mailgun_api_key", + "value": null, + }, + Object { + "key": "mailgun_base_url", + "value": null, + }, + Object { + "key": "email_track_opens", + "value": true, + }, + Object { + "key": "email_verification_required", + "value": false, + }, + Object { + "key": "amp", + "value": false, + }, + Object { + "key": "amp_gtag_id", + "value": null, + }, + Object { + "key": "firstpromoter", + "value": false, + }, + Object { + "key": "firstpromoter_id", + "value": null, }, Object { "key": "labs", "value": "{\\"multipleProducts\\":true,\\"tierWelcomePages\\":true,\\"tierName\\":true,\\"selectablePortalLinks\\":true,\\"membersTableStatus\\":true,\\"multipleNewsletters\\":true,\\"multipleNewslettersUI\\":true,\\"membersActivityFeed\\":true,\\"dashboardV5\\":true,\\"members\\":true}", }, Object { - "key": "timezone", - "value": "Pacific/Auckland", + "key": "slack_url", + "value": "", + }, + Object { + "key": "slack_username", + "value": "New Slack Username", }, Object { "key": "unsplash", "value": false, }, + Object { + "key": "shared_views", + "value": "[]", + }, + Object { + "key": "editor_default_email_recipients", + "value": "visibility", + }, + Object { + "key": "editor_default_email_recipients_filter", + "value": "all", + }, + Object { + "key": "members_enabled", + "value": true, + }, + Object { + "key": "members_invite_only", + "value": false, + }, + Object { + "key": "paid_members_enabled", + "value": true, + }, + Object { + "key": "firstpromoter_account", + "value": null, + }, ], } `; @@ -1358,7 +1558,7 @@ exports[`Settings API Edit Can edit a setting 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": "1112", + "content-length": "3536", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1370,7 +1570,276 @@ Object { exports[`Settings API Edit cannot edit uneditable settings 1: [body] 1`] = ` Object { "meta": Object {}, - "settings": Array [], + "settings": Array [ + Object { + "key": "title", + "value": "[]", + }, + Object { + "key": "description", + "value": "Thoughts, stories and ideas", + }, + Object { + "key": "logo", + "value": "", + }, + Object { + "key": "cover_image", + "value": "https://static.ghost.org/v4.0.0/images/publication-cover.jpg", + }, + Object { + "key": "icon", + "value": "", + }, + Object { + "key": "accent_color", + "value": "#FF1A75", + }, + Object { + "key": "locale", + "value": "ua", + }, + Object { + "key": "timezone", + "value": "Pacific/Auckland", + }, + Object { + "key": "codeinjection_head", + "value": null, + }, + Object { + "key": "codeinjection_foot", + "value": "", + }, + Object { + "key": "facebook", + "value": "ghost", + }, + Object { + "key": "twitter", + "value": "@ghost", + }, + Object { + "key": "navigation", + "value": "[{\\"label\\":\\"label1\\"}]", + }, + Object { + "key": "secondary_navigation", + "value": "[{\\"label\\":\\"Data & privacy\\",\\"url\\":\\"/privacy/\\"},{\\"label\\":\\"Contact\\",\\"url\\":\\"/contact/\\"},{\\"label\\":\\"Contribute →\\",\\"url\\":\\"/contribute/\\"}]", + }, + Object { + "key": "meta_title", + "value": "SEO title", + }, + Object { + "key": "meta_description", + "value": "SEO description", + }, + Object { + "key": "og_image", + "value": "http://127.0.0.1:2369/content/images/2019/07/facebook.png", + }, + Object { + "key": "og_title", + "value": "facebook title", + }, + Object { + "key": "og_description", + "value": "facebook description", + }, + Object { + "key": "twitter_image", + "value": "http://127.0.0.1:2369/content/images/2019/07/twitter.png", + }, + Object { + "key": "twitter_title", + "value": "twitter title", + }, + Object { + "key": "twitter_description", + "value": "twitter description", + }, + Object { + "key": "active_theme", + "value": "casper", + }, + Object { + "key": "is_private", + "value": false, + }, + Object { + "key": "password", + "value": "", + }, + Object { + "key": "public_hash", + "value": StringMatching /\\[a-z0-9\\]\\{30\\}/, + }, + Object { + "key": "default_content_visibility", + "value": "public", + }, + Object { + "key": "default_content_visibility_tiers", + "value": "[]", + }, + Object { + "key": "members_signup_access", + "value": "all", + }, + Object { + "key": "members_support_address", + "value": "noreply", + }, + Object { + "key": "stripe_secret_key", + "value": null, + }, + Object { + "key": "stripe_publishable_key", + "value": null, + }, + Object { + "key": "stripe_plans", + "value": "[]", + }, + Object { + "key": "stripe_connect_publishable_key", + "value": "pk_test_for_stripe", + }, + Object { + "key": "stripe_connect_secret_key", + "value": "••••••••", + }, + Object { + "key": "stripe_connect_livemode", + "value": null, + }, + Object { + "key": "stripe_connect_display_name", + "value": null, + }, + Object { + "key": "stripe_connect_account_id", + "value": null, + }, + Object { + "key": "members_monthly_price_id", + "value": null, + }, + Object { + "key": "members_yearly_price_id", + "value": null, + }, + Object { + "key": "portal_name", + "value": true, + }, + Object { + "key": "portal_button", + "value": true, + }, + Object { + "key": "portal_plans", + "value": "[\\"free\\"]", + }, + Object { + "key": "portal_products", + "value": "[]", + }, + Object { + "key": "portal_button_style", + "value": "icon-and-text", + }, + Object { + "key": "portal_button_icon", + "value": null, + }, + Object { + "key": "portal_button_signup_text", + "value": "Subscribe", + }, + Object { + "key": "mailgun_domain", + "value": null, + }, + Object { + "key": "mailgun_api_key", + "value": null, + }, + Object { + "key": "mailgun_base_url", + "value": null, + }, + Object { + "key": "email_track_opens", + "value": true, + }, + Object { + "key": "email_verification_required", + "value": false, + }, + Object { + "key": "amp", + "value": false, + }, + Object { + "key": "amp_gtag_id", + "value": null, + }, + Object { + "key": "firstpromoter", + "value": false, + }, + Object { + "key": "firstpromoter_id", + "value": null, + }, + Object { + "key": "labs", + "value": "{\\"multipleProducts\\":true,\\"tierWelcomePages\\":true,\\"tierName\\":true,\\"selectablePortalLinks\\":true,\\"membersTableStatus\\":true,\\"multipleNewsletters\\":true,\\"multipleNewslettersUI\\":true,\\"membersActivityFeed\\":true,\\"dashboardV5\\":true,\\"members\\":true}", + }, + Object { + "key": "slack_url", + "value": "", + }, + Object { + "key": "slack_username", + "value": "New Slack Username", + }, + Object { + "key": "unsplash", + "value": false, + }, + Object { + "key": "shared_views", + "value": "[]", + }, + Object { + "key": "editor_default_email_recipients", + "value": "visibility", + }, + Object { + "key": "editor_default_email_recipients_filter", + "value": "all", + }, + Object { + "key": "members_enabled", + "value": true, + }, + Object { + "key": "members_invite_only", + "value": false, + }, + Object { + "key": "paid_members_enabled", + "value": true, + }, + Object { + "key": "firstpromoter_account", + "value": null, + }, + ], } `; @@ -1390,7 +1859,7 @@ exports[`Settings API Edit cannot edit uneditable settings 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": "25", + "content-length": "3536", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", diff --git a/test/e2e-api/admin/settings.test.js b/test/e2e-api/admin/settings.test.js index ffa2c646f3..acae92289c 100644 --- a/test/e2e-api/admin/settings.test.js +++ b/test/e2e-api/admin/settings.test.js @@ -157,7 +157,7 @@ describe('Settings API', function () { }) .expectStatus(200) .matchBodySnapshot({ - settings: matchSettingsArray(settingsToChange.length) + settings: matchSettingsArray(CURRENT_SETTINGS_COUNT) }) .matchHeaderSnapshot({ etag: anyEtag @@ -167,12 +167,18 @@ describe('Settings API', function () { it('cannot edit uneditable settings', async function () { await agent.put('settings/') .body({ - settings: [{key: 'email_verification_required', value: false}] + settings: [{key: 'email_verification_required', value: true}] }) .expectStatus(200) - .matchBodySnapshot() + .matchBodySnapshot({ + settings: matchSettingsArray(CURRENT_SETTINGS_COUNT) + }) .matchHeaderSnapshot({ etag: anyEtag + }) + .expect(({body}) => { + const emailVerificationRequired = body.settings.find(setting => setting.key === 'email_verification_required'); + assert.strictEqual(emailVerificationRequired.value, false); }); }); }); diff --git a/test/regression/api/admin/settings.test.js b/test/regression/api/admin/settings.test.js index 424fcd31ff..f9805289fc 100644 --- a/test/regression/api/admin/settings.test.js +++ b/test/regression/api/admin/settings.test.js @@ -5,10 +5,63 @@ const config = require('../../../../core/shared/config'); const testUtils = require('../../../utils'); const localUtils = require('./utils'); const db = require('../../../../core/server/data/db'); +const settingsCache = require('../../../../core/shared/settings-cache'); describe('Settings API (canary)', function () { let request; + async function checkCanEdit(key, value, expectedValue) { + if (!expectedValue) { + expectedValue = value; + } + + const settingToChange = { + settings: [{key, value}] + }; + + await request.put(localUtils.API.getApiQuery('settings/')) + .set('Origin', config.get('url')) + .send(settingToChange) + .expect('Content-Type', /json/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(200) + .expect((response) => { + should.exist(response.headers['x-cache-invalidate']); + response.headers['x-cache-invalidate'].should.eql('/*'); + }); + + // Check if not changed (also check internal ones) + const afterValue = settingsCache.get(key); + should.deepEqual(afterValue, expectedValue); + } + + async function checkCantEdit(key, value) { + // Get current value (internal) + const currentValue = settingsCache.get(key); + + const settingToChange = { + settings: [{key, value}] + }; + + if (currentValue === value) { + throw new Error('This test requires a different value than the current one'); + } + + await request.put(localUtils.API.getApiQuery('settings/')) + .set('Origin', config.get('url')) + .send(settingToChange) + .expect('Content-Type', /json/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(200) + .expect((response) => { + should.not.exist(response.headers['x-cache-invalidate']); + }); + + // Check if not changed (also check internal ones) + const afterValue = settingsCache.get(key); + should.deepEqual(afterValue, currentValue); + } + describe('As Owner', function () { before(async function () { await localUtils.startGhost(); @@ -16,152 +69,32 @@ describe('Settings API (canary)', function () { await localUtils.doAuth(request); }); - it('Can edit newly introduced locale setting', function () { - return request.put(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .send({ - settings: [{key: 'locale', value: 'ge'}] - }) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then(function (res) { - should.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.settings); - - jsonResponse.settings.length.should.eql(1); - - testUtils.API.checkResponseValue(jsonResponse.settings[0], ['key', 'value']); - jsonResponse.settings[0].key.should.eql('locale'); - jsonResponse.settings[0].value.should.eql('ge'); - }); + it('Can edit newly introduced locale setting', async function () { + await checkCanEdit('locale', 'ge'); }); it('Can\'t edit permalinks', async function () { - const settingToChange = { - settings: [{key: 'permalinks', value: '/:primary_author/:slug/'}] - }; - - await request.put(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .send(settingToChange) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .expect(({body, headers}) => { - // it didn't actually edit anything, but we don't error anymore - body.should.eql({ - settings: [], - meta: {} - }); - - should.not.exist(headers['x-cache-invalidate']); - }); + await checkCantEdit('permalinks', '/:primary_author/:slug/'); }); it('Can edit only allowed labs keys', async function () { - const settingToChange = { - settings: [{ - key: 'labs', - value: JSON.stringify({ - activitypub: true, - gibberish: true - }) - }] - }; - - const res = await request.put(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .send(settingToChange) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200); - - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.settings); - - jsonResponse.settings.length.should.eql(1); - testUtils.API.checkResponseValue(jsonResponse.settings[0], ['key', 'value']); - jsonResponse.settings[0].key.should.eql('labs'); - - const responseObj = JSON.parse(jsonResponse.settings[0].value); - - should.not.exist(responseObj.gibberish); + await checkCanEdit('labs', + JSON.stringify({ + activitypub: true, + gibberish: true + }), + { + activitypub: true + } + ); }); - it('Can\'t edit non existent setting', function () { - return request.get(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .then(function (res) { - let jsonResponse = res.body; - const newValue = 'new value'; - should.exist(jsonResponse); - should.exist(jsonResponse.settings); - jsonResponse.settings = [{key: 'testvalue', value: newValue}]; - - return request.put(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .send(jsonResponse) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .expect(({body, headers}) => { - // it didn't actually edit anything, but we don't error anymore - body.should.eql({ - settings: [], - meta: {} - }); - - should.not.exist(headers['x-cache-invalidate']); - }); - }); + it('Can\'t edit non existent setting', async function () { + await checkCantEdit('non-existent-setting', 'value'); }); it('Will transform "1"', function () { - return request.get(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .then(function (res) { - const jsonResponse = res.body; - - const settingToChange = { - settings: [ - { - key: 'is_private', - value: '1' - } - ] - }; - - should.exist(jsonResponse); - should.exist(jsonResponse.settings); - - return request.put(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .send(settingToChange) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then(function ({body, headers}) { - const putBody = body; - headers['x-cache-invalidate'].should.eql('/*'); - should.exist(putBody); - - putBody.settings[0].key.should.eql('is_private'); - putBody.settings[0].value.should.eql(true); - - localUtils.API.checkResponse(putBody, 'settings'); - }); - }); + return checkCanEdit('is_private', '1', true); }); it('Can edit multiple setting along with a deprecated one from v4', async function () { @@ -194,13 +127,11 @@ describe('Settings API (canary)', function () { headers['x-cache-invalidate'].should.eql('/*'); should.exist(putBody); - putBody.settings.length.should.equal(2); + let setting = putBody.settings.find(s => s.key === 'unsplash'); + should.equal(setting.value, true); - putBody.settings[0].key.should.eql('unsplash'); - should.equal(putBody.settings[0].value, true); - - putBody.settings[1].key.should.eql('title'); - should.equal(putBody.settings[1].value, 'New Value'); + setting = putBody.settings.find(s => s.key === 'title'); + should.equal(setting.value, 'New Value'); localUtils.API.checkResponse(putBody, 'settings'); }); @@ -226,11 +157,9 @@ describe('Settings API (canary)', function () { headers['x-cache-invalidate'].should.eql('/*'); should.exist(putBody); - putBody.settings.length.should.equal(1); - localUtils.API.checkResponse(putBody, 'settings'); - putBody.settings[0].key.should.eql('slack_username'); - putBody.settings[0].value.should.eql('can edit me'); + const setting = putBody.settings.find(s => s.key === 'slack_username'); + setting.value.should.eql('can edit me'); }); it('Can edit URLs without internal storage format leaking', async function () { @@ -297,27 +226,7 @@ describe('Settings API (canary)', function () { }); it('Cannot edit notifications key through API', async function () { - const settingsToChange = { - settings: [ - {key: 'notifications', value: JSON.stringify(['do not touch me'])} - ] - }; - - await request.put(localUtils.API.getApiQuery('settings/')) - .set('Origin', config.get('url')) - .send(settingsToChange) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .expect(({body, headers}) => { - // it didn't actually edit anything, but we don't error anymore - body.should.eql({ - settings: [], - meta: {} - }); - - should.not.exist(headers['x-cache-invalidate']); - }); + await checkCantEdit('notifications', JSON.stringify(['do not touch me'])); }); }); @@ -452,14 +361,22 @@ describe('Settings API (canary)', function () { }); it('allows editing settings that cannot be edited via HTTP', async function () { + // Get current value + const {settings} = await api.settings.browse({}, testUtils.context.internal); + + const currentValue = settings.find(s => s.key === 'email_verification_required'); + + if (!currentValue || currentValue.value === true) { + throw new Error('Invalid key or unchanged value'); + } + let jsonResponse = await api.settings.edit({ - settings: [{key: 'email_verification_required', value: false}] + settings: [{key: 'email_verification_required', value: true}] }, testUtils.context.internal); - jsonResponse.should.eql({ - settings: [{key: 'email_verification_required', value: false}], - meta: {} - }); + const setting = jsonResponse.settings.find(s => s.key === 'email_verification_required'); + should.exist(setting); + setting.value.should.eql(true); }); }); }); diff --git a/test/unit/frontend/helpers/ghost_head.test.js b/test/unit/frontend/helpers/ghost_head.test.js index ac6a0002eb..7cd694f076 100644 --- a/test/unit/frontend/helpers/ghost_head.test.js +++ b/test/unit/frontend/helpers/ghost_head.test.js @@ -1577,6 +1577,8 @@ describe('{{ghost_head}} helper', function () { }); it('attaches style tag to existing script/style tag', function (done) { + settingsCache.get.withArgs('members_enabled').returns(true); + const renderObject = { post: posts[1] }; @@ -1656,8 +1658,8 @@ describe('{{ghost_head}} helper', function () { }); describe('members scripts', function () { - it('includes portal when signup is "all"', function (done) { - settingsCache.get.withArgs('members_signup_access').returns('all'); + it('includes portal when members enabled', function (done) { + settingsCache.get.withArgs('members_enabled').returns(true); ghost_head(testUtils.createHbsResponse({ locals: { @@ -1674,29 +1676,9 @@ describe('{{ghost_head}} helper', function () { }).catch(done); }); - it('includes portal when signup is "invite"', function (done) { - settingsCache.get.withArgs('members_signup_access').returns('invite'); - - ghost_head(testUtils.createHbsResponse({ - locals: { - relativeUrl: '/', - context: ['home', 'index'], - safeVersion: '4.3' - } - })).then(function (rendered) { - should.exist(rendered); - rendered.string.should.containEql('