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;
|
const action = req.query.action;
|
||||||
|
|
||||||
if (action === 'signup' || action === 'signup-paid') {
|
if (action === 'signup' || action === 'signup-paid' || action === 'subscribe') {
|
||||||
let customRedirect = '';
|
let customRedirect = '';
|
||||||
const mostRecentActiveSubscription = subscriptions
|
const mostRecentActiveSubscription = subscriptions
|
||||||
.sort((a, b) => {
|
.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({
|
verificationTrigger = new VerificationTrigger({
|
||||||
configThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
|
configThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
|
||||||
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
|
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
|
||||||
|
@ -181,13 +188,7 @@ module.exports = {
|
||||||
return membersSettings;
|
return membersSettings;
|
||||||
},
|
},
|
||||||
|
|
||||||
ssr: MembersSSR({
|
ssr: null,
|
||||||
cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()),
|
|
||||||
cookieKeys: [settingsCache.get('theme_session_secret')],
|
|
||||||
cookieName: 'ghost-members-ssr',
|
|
||||||
cookieCacheName: 'ghost-members-ssr-cache',
|
|
||||||
getMembersApi: () => module.exports.api
|
|
||||||
}),
|
|
||||||
|
|
||||||
stripeConnect: require('./stripe-connect'),
|
stripeConnect: require('./stripe-connect'),
|
||||||
|
|
||||||
|
|
|
@ -548,7 +548,7 @@ exports[`Members API Can browse 2: [headers] 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
"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",
|
"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",
|
"content-type": "application/json; charset=utf-8",
|
||||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||||
"vary": "Origin, Accept-Encoding",
|
"vary": "Origin, Accept-Encoding",
|
||||||
|
@ -1009,7 +1009,7 @@ exports[`Members API Can filter by paid status 2: [headers] 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
"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",
|
"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",
|
"content-type": "application/json; charset=utf-8",
|
||||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||||
"vary": "Origin, Accept-Encoding",
|
"vary": "Origin, Accept-Encoding",
|
||||||
|
@ -2166,7 +2166,7 @@ exports[`Members API Search for paid members retrieves member with email paid@te
|
||||||
Object {
|
Object {
|
||||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
"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",
|
"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",
|
"content-type": "application/json; charset=utf-8",
|
||||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||||
"vary": "Origin, Accept-Encoding",
|
"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);
|
.expect(assertContentIsAbsent);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cannot read product-only post content', async function () {
|
it('can read product-only post content', async function () {
|
||||||
await request
|
await request
|
||||||
.get('/thou-must-have-default-product/')
|
.get('/thou-must-have-default-product/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect(assertContentIsAbsent);
|
.expect(assertContentIsPresent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -392,11 +392,11 @@ describe('Front-end members behaviour', function () {
|
||||||
.expect(assertContentIsAbsent);
|
.expect(assertContentIsAbsent);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cannot read product-only post content', async function () {
|
it('can read product-only post content', async function () {
|
||||||
await request
|
await request
|
||||||
.get('/thou-must-have-default-product/')
|
.get('/thou-must-have-default-product/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect(assertContentIsAbsent);
|
.expect(assertContentIsPresent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ const settingsCache = require('../../../../../core/shared/settings-cache');
|
||||||
|
|
||||||
describe('Members Service Middleware', function () {
|
describe('Members Service Middleware', function () {
|
||||||
describe('createSessionFromMagicLink', function () {
|
describe('createSessionFromMagicLink', function () {
|
||||||
|
let oldSSR;
|
||||||
let req;
|
let req;
|
||||||
let res;
|
let res;
|
||||||
let next;
|
let next;
|
||||||
|
@ -20,13 +21,17 @@ describe('Members Service Middleware', function () {
|
||||||
res.redirect = sinon.stub().returns('');
|
res.redirect = sinon.stub().returns('');
|
||||||
|
|
||||||
// Stub the members Service, handle this in separate tests
|
// 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, 'getSubdir').returns('/blah');
|
||||||
sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah');
|
sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
membersService.ssr = oldSSR;
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,9 @@ const setupStripe = () => {
|
||||||
if (key === 'stripe_connect_account_name') {
|
if (key === 'stripe_connect_account_name') {
|
||||||
return 'Test Account';
|
return 'Test Account';
|
||||||
}
|
}
|
||||||
|
if (key === 'theme_session_secret') {
|
||||||
|
return '1337_h4xx0r_53cR37';
|
||||||
|
}
|
||||||
return settingsCache.get.wrappedMethod.call(settingsCache, key, ...args);
|
return settingsCache.get.wrappedMethod.call(settingsCache, key, ...args);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -461,15 +461,26 @@ const fixtures = {
|
||||||
return Promise.map(DataGenerator.forKnex.labels, function (label) {
|
return Promise.map(DataGenerator.forKnex.labels, function (label) {
|
||||||
return models.Label.add(label, context.internal);
|
return models.Label.add(label, context.internal);
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
let productsToInsert = fixtureManager.findModelFixtures('Product').entries;
|
let coreProductFixtures = fixtureManager.findModelFixtures('Product').entries;
|
||||||
return Promise.map(productsToInsert, async (product) => {
|
return Promise.map(coreProductFixtures, async (product) => {
|
||||||
const found = await models.Product.findOne(product, context.internal);
|
const found = await models.Product.findOne(product, context.internal);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
await models.Product.add(product, context.internal);
|
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 () {
|
}).then(function () {
|
||||||
return models.Product.findOne({}, context.internal);
|
return models.Product.findOne({type: 'paid'}, context.internal);
|
||||||
}).then(function (product) {
|
}).then(function (product) {
|
||||||
return Promise.props({
|
return Promise.props({
|
||||||
stripeProducts: Promise.each(_.cloneDeep(DataGenerator.forKnex.stripe_products), function (stripeProduct) {
|
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 Promise.each(_.cloneDeep(DataGenerator.forKnex.stripe_customer_subscriptions), function (subscription) {
|
||||||
return models.StripeCustomerSubscription.add(subscription, context.internal);
|
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: [
|
products: [
|
||||||
{
|
{
|
||||||
id: ObjectId().toHexString(),
|
// No ID because these are in the core fixtures.json
|
||||||
name: 'Free',
|
|
||||||
slug: 'free',
|
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(),
|
// No ID because these are in the core fixtures.json
|
||||||
name: 'Ghost Product',
|
slug: 'default-product',
|
||||||
slug: 'ghost-product',
|
// slug is to match the product, the below are updated for the product
|
||||||
type: 'paid'
|
welcome_page_url: '/welcome-paid'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -1271,7 +1271,8 @@ DataGenerator.forKnex = (function () {
|
||||||
];
|
];
|
||||||
|
|
||||||
const products = [
|
const products = [
|
||||||
createBasic(DataGenerator.Content.products[0])
|
DataGenerator.Content.products[0],
|
||||||
|
DataGenerator.Content.products[1]
|
||||||
];
|
];
|
||||||
|
|
||||||
const members_stripe_customers = [
|
const members_stripe_customers = [
|
||||||
|
|
Loading…
Add table
Reference in a new issue