370c6b465b
fixes https://github.com/TryGhost/Product/issues/4108 - Updates filters behind a new alpha feature flag so you can also filter on members who have email disabled (because the email had a permanent bounce, they reported spam or the email address is invalid) - When returning members, we now also use the email_disabled flag to set email_suppression.suppressed correctly (in case they are out of sync, which should normally never happen).
291 lines
9.7 KiB
JavaScript
291 lines
9.7 KiB
JavaScript
const assert = require('assert/strict');
|
|
const sinon = require('sinon');
|
|
const MemberBreadService = require('../../../../lib/services/MemberBREADService');
|
|
const moment = require('moment');
|
|
|
|
describe('MemberBreadService', function () {
|
|
describe('read', function () {
|
|
const MEMBER_ID = 123;
|
|
const DEFAULT_RELATIONS = [
|
|
'labels',
|
|
'stripeSubscriptions',
|
|
'stripeSubscriptions.customer',
|
|
'stripeSubscriptions.stripePrice',
|
|
'stripeSubscriptions.stripePrice.stripeProduct',
|
|
'stripeSubscriptions.stripePrice.stripeProduct.product',
|
|
'products',
|
|
'newsletters',
|
|
'productEvents'
|
|
];
|
|
|
|
let memberModelStub,
|
|
memberModelJSON,
|
|
memberRepositoryStub,
|
|
memberAttributionServiceStub,
|
|
emailSuppressionListStub;
|
|
|
|
const getService = () => {
|
|
return new MemberBreadService({
|
|
memberRepository: memberRepositoryStub,
|
|
memberAttributionService: memberAttributionServiceStub,
|
|
emailSuppressionList: emailSuppressionListStub
|
|
});
|
|
};
|
|
|
|
beforeEach(function () {
|
|
memberModelJSON = {
|
|
id: MEMBER_ID,
|
|
name: 'foo bar',
|
|
email: 'foo@bar.baz',
|
|
subscriptions: []
|
|
};
|
|
memberModelStub = {
|
|
id: MEMBER_ID,
|
|
related: sinon.stub().returns([]),
|
|
toJSON: sinon.stub().returns({...memberModelJSON}),
|
|
get: function (key) {
|
|
return this[key];
|
|
}
|
|
};
|
|
memberRepositoryStub = {
|
|
get: sinon.stub().resolves(null),
|
|
related: sinon.stub().returns([])
|
|
};
|
|
memberAttributionServiceStub = {
|
|
getMemberCreatedAttribution: sinon.stub().resolves(null)
|
|
};
|
|
emailSuppressionListStub = {
|
|
getSuppressionData: sinon.stub().resolves({})
|
|
};
|
|
|
|
memberRepositoryStub.get
|
|
.withArgs(
|
|
{id: MEMBER_ID},
|
|
{withRelated: DEFAULT_RELATIONS}
|
|
)
|
|
.resolves(memberModelStub);
|
|
});
|
|
|
|
it('returns a member', async function () {
|
|
const memberBreadService = getService();
|
|
const member = await memberBreadService.read({id: MEMBER_ID});
|
|
|
|
assert.equal(member.id, memberModelJSON.id);
|
|
assert.equal(member.email, memberModelJSON.email);
|
|
});
|
|
|
|
it('returns a member with subscriptions', async function () {
|
|
const subscriptionsJSON = [
|
|
{
|
|
subscription_id: 'sub_123',
|
|
price: {}
|
|
}
|
|
];
|
|
const subscriptionModels = [
|
|
{
|
|
get: sinon.stub()
|
|
}
|
|
];
|
|
|
|
subscriptionModels[0].get
|
|
.withArgs('subscription_id')
|
|
.returns(subscriptionsJSON[0].subscription_id);
|
|
|
|
memberModelStub.related
|
|
.withArgs('stripeSubscriptions')
|
|
.returns(subscriptionModels);
|
|
|
|
memberModelStub.toJSON.returns({
|
|
...memberModelJSON,
|
|
subscriptions: subscriptionsJSON
|
|
});
|
|
|
|
const memberBreadService = getService();
|
|
const member = await memberBreadService.read({id: MEMBER_ID});
|
|
|
|
assert.deepEqual(member.subscriptions, subscriptionsJSON);
|
|
});
|
|
|
|
it('returns a member with subscriptions that only have a price', async function () {
|
|
const subscriptionsJSON = [
|
|
{
|
|
subscription_id: 'sub_123',
|
|
price: {}
|
|
},
|
|
{
|
|
subscription_id: 'sub_456'
|
|
}
|
|
];
|
|
const subscriptionModels = subscriptionsJSON.map((subscription) => {
|
|
const model = {
|
|
get: sinon.stub()
|
|
};
|
|
|
|
model.get.withArgs('subscription_id').returns(subscription.subscription_id);
|
|
model.get.withArgs('offer_id').returns(undefined);
|
|
|
|
return model;
|
|
});
|
|
|
|
memberModelStub.related
|
|
.withArgs('stripeSubscriptions')
|
|
.returns(subscriptionModels);
|
|
|
|
memberModelStub.toJSON.returns({
|
|
...memberModelJSON,
|
|
subscriptions: subscriptionsJSON
|
|
});
|
|
|
|
const memberBreadService = getService();
|
|
const member = await memberBreadService.read({id: MEMBER_ID});
|
|
|
|
assert.deepEqual(member.subscriptions, [
|
|
subscriptionsJSON[0]
|
|
]);
|
|
});
|
|
|
|
it('returns a member with a comped subscription', async function () {
|
|
const productsJSON = [
|
|
{
|
|
id: 'prod_123',
|
|
expiry_at: new Date('2023-10-13T15:15:00')
|
|
},
|
|
{
|
|
id: 'prod_456',
|
|
expiry_at: new Date('2023-10-13T15:15:00')
|
|
}
|
|
];
|
|
const productEventsJSON = [
|
|
{
|
|
product_id: productsJSON[0].id,
|
|
created_at: new Date('2023-09-13T15:15:00'),
|
|
action: 'added'
|
|
},
|
|
{
|
|
product_id: productsJSON[1].id,
|
|
created_at: new Date('2023-09-13T15:20:00'),
|
|
action: 'added'
|
|
}
|
|
];
|
|
const subscriptionsJSON = [
|
|
{
|
|
subscription_id: 'sub_123',
|
|
price: {
|
|
product: {
|
|
product_id: productsJSON[0].id
|
|
}
|
|
},
|
|
status: 'cancelled',
|
|
product_id: productsJSON[0].id
|
|
},
|
|
// Comped subscription - Has no price
|
|
{
|
|
subscription_id: 'sub_456',
|
|
status: 'active',
|
|
product_id: productsJSON[1].id
|
|
}
|
|
];
|
|
const subscriptionModels = subscriptionsJSON.map((subscription) => {
|
|
const model = {
|
|
get: sinon.stub()
|
|
};
|
|
|
|
model.get.withArgs('subscription_id').returns(subscription.subscription_id);
|
|
model.get.withArgs('offer_id').returns(undefined);
|
|
|
|
return model;
|
|
});
|
|
|
|
memberModelStub.related
|
|
.withArgs('stripeSubscriptions')
|
|
.returns(subscriptionModels);
|
|
|
|
memberModelStub.toJSON.returns({
|
|
...memberModelJSON,
|
|
subscriptions: subscriptionsJSON,
|
|
products: productsJSON,
|
|
productEvents: productEventsJSON
|
|
});
|
|
|
|
memberRepositoryStub.isActiveSubscriptionStatus = sinon.stub().returns(true);
|
|
|
|
const memberBreadService = getService();
|
|
const member = await memberBreadService.read({id: MEMBER_ID});
|
|
|
|
// Ensure only 2 subscriptions are returned
|
|
assert.equal(member.subscriptions.length, 2);
|
|
|
|
const compedSubscription = member.subscriptions[1];
|
|
|
|
// Ensure the 2nd subscription is the comped one
|
|
sinon.assert.match(compedSubscription, {
|
|
id: '',
|
|
tier: productsJSON[1],
|
|
customer: {
|
|
id: '',
|
|
name: memberModelJSON.name,
|
|
email: memberModelJSON.email
|
|
},
|
|
plan: {
|
|
id: '',
|
|
nickname: 'Complimentary',
|
|
interval: 'year',
|
|
currency: 'USD',
|
|
amount: 0
|
|
},
|
|
status: 'active',
|
|
start_date: moment(productEventsJSON[1].created_at),
|
|
default_payment_card_last4: '****',
|
|
cancel_at_period_end: false,
|
|
cancellation_reason: null,
|
|
current_period_end: moment(productsJSON[1].expiry_at),
|
|
price: {
|
|
id: '',
|
|
price_id: '',
|
|
nickname: 'Complimentary',
|
|
amount: 0,
|
|
interval: 'year',
|
|
type: 'recurring',
|
|
currency: 'USD',
|
|
product: {
|
|
id: '',
|
|
product_id: productsJSON[1].id
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('returns a member with attribution data', async function () {
|
|
const attributionData = {
|
|
url: 'https://example.com'
|
|
};
|
|
|
|
memberAttributionServiceStub.getMemberCreatedAttribution
|
|
.withArgs(MEMBER_ID)
|
|
.resolves(attributionData);
|
|
|
|
const memberBreadService = getService();
|
|
const member = await memberBreadService.read({id: MEMBER_ID});
|
|
|
|
assert.deepEqual(member.attribution, attributionData);
|
|
});
|
|
|
|
it('returns a member with suppression data', async function () {
|
|
emailSuppressionListStub.getSuppressionData
|
|
.withArgs(memberModelJSON.email)
|
|
.resolves({
|
|
suppressed: true,
|
|
info: 'bounce'
|
|
});
|
|
|
|
const memberBreadService = getService();
|
|
const member = await memberBreadService.read({id: MEMBER_ID});
|
|
|
|
assert.deepEqual(member.email_suppression, {
|
|
suppressed: true,
|
|
info: 'bounce'
|
|
});
|
|
});
|
|
});
|
|
});
|