mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
🐛 Fixed welcome pages not working for "subscribe" links (#14176)
- Fixed test fixtures so that members with subscriptions also have products/tiers - Fixed test fixtures so that default&free tiers can be updated for tests - Added tests for the signin functionality and welcome page redirects - Extended `setupStripe` to setup other Members settings - this needs some more thought around how we proceed
This commit is contained in:
parent
c09a81aabe
commit
9c5c41b927
9 changed files with 148 additions and 27 deletions
|
@ -198,7 +198,7 @@ const createSessionFromMagicLink = async function (req, res, next) {
|
|||
|
||||
const action = req.query.action;
|
||||
|
||||
if (action === 'signup' || action === 'signup-paid') {
|
||||
if (action === 'signup' || action === 'signup-paid' || action === 'subscribe') {
|
||||
let customRedirect = '';
|
||||
const mostRecentActiveSubscription = subscriptions
|
||||
.sort((a, b) => {
|
||||
|
|
|
@ -125,6 +125,13 @@ module.exports = {
|
|||
});
|
||||
}
|
||||
|
||||
module.exports.ssr = MembersSSR({
|
||||
cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()),
|
||||
cookieKeys: [settingsCache.get('theme_session_secret')],
|
||||
cookieName: 'ghost-members-ssr',
|
||||
getMembersApi: () => module.exports.api
|
||||
});
|
||||
|
||||
verificationTrigger = new VerificationTrigger({
|
||||
configThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
|
||||
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
|
||||
|
@ -181,13 +188,7 @@ module.exports = {
|
|||
return membersSettings;
|
||||
},
|
||||
|
||||
ssr: MembersSSR({
|
||||
cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()),
|
||||
cookieKeys: [settingsCache.get('theme_session_secret')],
|
||||
cookieName: 'ghost-members-ssr',
|
||||
cookieCacheName: 'ghost-members-ssr-cache',
|
||||
getMembersApi: () => module.exports.api
|
||||
}),
|
||||
ssr: null,
|
||||
|
||||
stripeConnect: require('./stripe-connect'),
|
||||
|
||||
|
|
|
@ -548,7 +548,7 @@ exports[`Members API Can browse 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": "7008",
|
||||
"content-length": "8051",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -1009,7 +1009,7 @@ exports[`Members API Can filter by paid status 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": "5533",
|
||||
"content-length": "6576",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -2166,7 +2166,7 @@ exports[`Members API Search for paid members retrieves member with email paid@te
|
|||
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": "1296",
|
||||
"content-length": "1640",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
|
79
test/e2e-api/members/signin.test.js
Normal file
79
test/e2e-api/members/signin.test.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
const membersService = require('../../../core/server/services/members');
|
||||
const {agentProvider, mockManager, fixtureManager} = require('../../utils/e2e-framework');
|
||||
|
||||
let membersAgent;
|
||||
|
||||
describe('Members Signin', function () {
|
||||
before(async function () {
|
||||
// Weird - most of the mocks happen after getting the agent
|
||||
// but to mock stripe we want to fake the stripe keys in the settings.
|
||||
// And it's initialised at boot - so mocking it before
|
||||
// Probably wanna replace this with a settinfs fixture mock or smth??
|
||||
mockManager.setupStripe();
|
||||
|
||||
const agents = await agentProvider.getAgentsForMembers();
|
||||
membersAgent = agents.membersAgent;
|
||||
|
||||
await fixtureManager.init('members');
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
mockManager.mockLabsEnabled('multipleProducts');
|
||||
mockManager.mockLabsEnabled('tierWelcomePages');
|
||||
mockManager.mockStripe();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
mockManager.restore();
|
||||
});
|
||||
|
||||
it('Will not set a cookie if the token is invalid', async function () {
|
||||
await membersAgent.get('/?token=blah')
|
||||
.expectStatus(302)
|
||||
.expectHeader('Location', /\?\w*success=false/);
|
||||
});
|
||||
|
||||
it('Will set a cookie if the token is valid', async function () {
|
||||
const magicLink = await membersService.api.getMagicLink('member1@test.com');
|
||||
const magicLinkUrl = new URL(magicLink);
|
||||
const token = magicLinkUrl.searchParams.get('token');
|
||||
|
||||
await membersAgent.get(`/?token=${token}`)
|
||||
.expectStatus(302)
|
||||
.expectHeader('Location', /\?\w*success=true/)
|
||||
.expectHeader('Set-Cookie', /members-ssr.*/);
|
||||
});
|
||||
|
||||
it('Will redirect to the free welcome page for signup', async function () {
|
||||
const magicLink = await membersService.api.getMagicLink('member1@test.com');
|
||||
const magicLinkUrl = new URL(magicLink);
|
||||
const token = magicLinkUrl.searchParams.get('token');
|
||||
|
||||
await membersAgent.get(`/?token=${token}&action=signup`)
|
||||
.expectStatus(302)
|
||||
.expectHeader('Location', /\/welcome-free\/$/)
|
||||
.expectHeader('Set-Cookie', /members-ssr.*/);
|
||||
});
|
||||
|
||||
it('Will redirect to the paid welcome page for signup-paid', async function () {
|
||||
const magicLink = await membersService.api.getMagicLink('paid@test.com');
|
||||
const magicLinkUrl = new URL(magicLink);
|
||||
const token = magicLinkUrl.searchParams.get('token');
|
||||
|
||||
await membersAgent.get(`/?token=${token}&action=signup-paid`)
|
||||
.expectStatus(302)
|
||||
.expectHeader('Location', /\/welcome-paid\/$/)
|
||||
.expectHeader('Set-Cookie', /members-ssr.*/);
|
||||
});
|
||||
|
||||
it('Will redirect to the free welcome page for subscribe', async function () {
|
||||
const magicLink = await membersService.api.getMagicLink('member1@test.com');
|
||||
const magicLinkUrl = new URL(magicLink);
|
||||
const token = magicLinkUrl.searchParams.get('token');
|
||||
|
||||
await membersAgent.get(`/?token=${token}&action=subscribe`)
|
||||
.expectStatus(302)
|
||||
.expectHeader('Location', /\/welcome-free\/$/)
|
||||
.expectHeader('Set-Cookie', /members-ssr.*/);
|
||||
});
|
||||
});
|
|
@ -338,11 +338,11 @@ describe('Front-end members behaviour', function () {
|
|||
.expect(assertContentIsAbsent);
|
||||
});
|
||||
|
||||
it('cannot read product-only post content', async function () {
|
||||
it('can read product-only post content', async function () {
|
||||
await request
|
||||
.get('/thou-must-have-default-product/')
|
||||
.expect(200)
|
||||
.expect(assertContentIsAbsent);
|
||||
.expect(assertContentIsPresent);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -392,11 +392,11 @@ describe('Front-end members behaviour', function () {
|
|||
.expect(assertContentIsAbsent);
|
||||
});
|
||||
|
||||
it('cannot read product-only post content', async function () {
|
||||
it('can read product-only post content', async function () {
|
||||
await request
|
||||
.get('/thou-must-have-default-product/')
|
||||
.expect(200)
|
||||
.expect(assertContentIsAbsent);
|
||||
.expect(assertContentIsPresent);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ const settingsCache = require('../../../../../core/shared/settings-cache');
|
|||
|
||||
describe('Members Service Middleware', function () {
|
||||
describe('createSessionFromMagicLink', function () {
|
||||
let oldSSR;
|
||||
let req;
|
||||
let res;
|
||||
let next;
|
||||
|
@ -20,13 +21,17 @@ describe('Members Service Middleware', function () {
|
|||
res.redirect = sinon.stub().returns('');
|
||||
|
||||
// Stub the members Service, handle this in separate tests
|
||||
membersService.ssr.exchangeTokenForSession = sinon.stub();
|
||||
oldSSR = membersService.ssr;
|
||||
membersService.ssr = {
|
||||
exchangeTokenForSession: sinon.stub()
|
||||
};
|
||||
|
||||
sinon.stub(urlUtils, 'getSubdir').returns('/blah');
|
||||
sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
membersService.ssr = oldSSR;
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ const setupStripe = () => {
|
|||
if (key === 'stripe_connect_account_name') {
|
||||
return 'Test Account';
|
||||
}
|
||||
if (key === 'theme_session_secret') {
|
||||
return '1337_h4xx0r_53cR37';
|
||||
}
|
||||
return settingsCache.get.wrappedMethod.call(settingsCache, key, ...args);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -461,15 +461,26 @@ const fixtures = {
|
|||
return Promise.map(DataGenerator.forKnex.labels, function (label) {
|
||||
return models.Label.add(label, context.internal);
|
||||
}).then(function () {
|
||||
let productsToInsert = fixtureManager.findModelFixtures('Product').entries;
|
||||
return Promise.map(productsToInsert, async (product) => {
|
||||
let coreProductFixtures = fixtureManager.findModelFixtures('Product').entries;
|
||||
return Promise.map(coreProductFixtures, async (product) => {
|
||||
const found = await models.Product.findOne(product, context.internal);
|
||||
if (!found) {
|
||||
await models.Product.add(product, context.internal);
|
||||
}
|
||||
});
|
||||
}).then(async function () {
|
||||
let testProductFixtures = DataGenerator.forKnex.products;
|
||||
for (const productFixture of testProductFixtures) {
|
||||
if (productFixture.id) { // Not currently used - this is used to add new text fixtures, e.g. a Bronze/Silver/Gold Tier
|
||||
await models.Product.add(productFixture, context.internal);
|
||||
} else { // Used to update the core fixtures
|
||||
// If it doesn't exist we have invalid fixtures, so require: true to ensure we throw
|
||||
const existing = await models.Product.findOne({slug: productFixture.slug}, {...context.internal, require: true});
|
||||
await models.Product.edit(productFixture, {...context.internal, id: existing.id});
|
||||
}
|
||||
}
|
||||
}).then(function () {
|
||||
return models.Product.findOne({}, context.internal);
|
||||
return models.Product.findOne({type: 'paid'}, context.internal);
|
||||
}).then(function (product) {
|
||||
return Promise.props({
|
||||
stripeProducts: Promise.each(_.cloneDeep(DataGenerator.forKnex.stripe_products), function (stripeProduct) {
|
||||
|
@ -513,6 +524,27 @@ const fixtures = {
|
|||
return Promise.each(_.cloneDeep(DataGenerator.forKnex.stripe_customer_subscriptions), function (subscription) {
|
||||
return models.StripeCustomerSubscription.add(subscription, context.internal);
|
||||
});
|
||||
}).then(async function () {
|
||||
const members = (await models.Member.findAll({
|
||||
withRelated: [
|
||||
'labels',
|
||||
'stripeSubscriptions',
|
||||
'stripeSubscriptions.customer',
|
||||
'stripeSubscriptions.stripePrice',
|
||||
'stripeSubscriptions.stripePrice.stripeProduct',
|
||||
'products',
|
||||
'offerRedemptions'
|
||||
]
|
||||
})).toJSON();
|
||||
|
||||
for (const member of members) {
|
||||
for (const subscription of member.subscriptions) {
|
||||
const product = subscription.price.product.product_id;
|
||||
await models.Member.edit({products: member.products.concat({
|
||||
id: product
|
||||
})}, {id: member.id});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -367,16 +367,16 @@ DataGenerator.Content = {
|
|||
|
||||
products: [
|
||||
{
|
||||
id: ObjectId().toHexString(),
|
||||
name: 'Free',
|
||||
// No ID because these are in the core fixtures.json
|
||||
slug: 'free',
|
||||
type: 'free'
|
||||
// slug is to match the product, the below are updated for the product
|
||||
welcome_page_url: '/welcome-free'
|
||||
},
|
||||
{
|
||||
id: ObjectId().toHexString(),
|
||||
name: 'Ghost Product',
|
||||
slug: 'ghost-product',
|
||||
type: 'paid'
|
||||
// No ID because these are in the core fixtures.json
|
||||
slug: 'default-product',
|
||||
// slug is to match the product, the below are updated for the product
|
||||
welcome_page_url: '/welcome-paid'
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -1271,7 +1271,8 @@ DataGenerator.forKnex = (function () {
|
|||
];
|
||||
|
||||
const products = [
|
||||
createBasic(DataGenerator.Content.products[0])
|
||||
DataGenerator.Content.products[0],
|
||||
DataGenerator.Content.products[1]
|
||||
];
|
||||
|
||||
const members_stripe_customers = [
|
||||
|
|
Loading…
Add table
Reference in a new issue