|
|
@@ -449,10 +449,200 @@ describe('FeaturesUpdater', function () {
|
|
|
ai_plan: 'none',
|
|
|
next_renewal_date: Math.floor(ctx.renewalDate.getTime() / 1000),
|
|
|
expiry_date: '',
|
|
|
+ group: false,
|
|
|
+ commons: false,
|
|
|
+ individual_subscription: true,
|
|
|
+ payment_provider: 'recurly',
|
|
|
features: sinon.match.object,
|
|
|
})
|
|
|
)
|
|
|
})
|
|
|
+
|
|
|
+ it('should not set trial_end_date when no trial is active', function (ctx) {
|
|
|
+ const call = ctx.Modules.promises.hooks.fire
|
|
|
+ .getCalls()
|
|
|
+ .find(c => c.args[0] === 'setUserProperties')
|
|
|
+ expect(call).to.exist
|
|
|
+ expect(call.args[2]).to.not.have.property('trial_end_date')
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the individual subscription is on a trial', function () {
|
|
|
+ beforeEach(async function (ctx) {
|
|
|
+ ctx.trialEndsAt = new Date('2099-05-01T00:00:00Z')
|
|
|
+ const trialingSubscription = {
|
|
|
+ ...ctx.subscriptions.individual,
|
|
|
+ recurlyStatus: {
|
|
|
+ state: 'active',
|
|
|
+ trialEndsAt: ctx.trialEndsAt,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
|
|
|
+ {
|
|
|
+ bestSubscription: { type: 'individual' },
|
|
|
+ individualSubscription: trialingSubscription,
|
|
|
+ memberGroupSubscriptions: [],
|
|
|
+ managedGroupSubscriptions: [],
|
|
|
+ }
|
|
|
+ )
|
|
|
+ ctx.Modules.promises.hooks.fire
|
|
|
+ .withArgs('getPaymentFromRecordPromise', trialingSubscription)
|
|
|
+ .resolves([
|
|
|
+ {
|
|
|
+ subscription: {
|
|
|
+ state: 'active',
|
|
|
+ periodEnd: ctx.renewalDate,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ await ctx.FeaturesUpdater.promises.refreshFeatures(ctx.user._id, 'test')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should sync trial_end_date to customer.io', function (ctx) {
|
|
|
+ expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
|
|
+ 'setUserProperties',
|
|
|
+ ctx.user._id,
|
|
|
+ sinon.match({
|
|
|
+ trial_end_date: Math.floor(ctx.trialEndsAt.getTime() / 1000),
|
|
|
+ })
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the individual subscription uses stripe', function () {
|
|
|
+ beforeEach(async function (ctx) {
|
|
|
+ const stripeSubscription = {
|
|
|
+ ...ctx.subscriptions.individual,
|
|
|
+ paymentProvider: { service: 'stripe-us', state: 'active' },
|
|
|
+ }
|
|
|
+ ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
|
|
|
+ {
|
|
|
+ bestSubscription: { type: 'individual' },
|
|
|
+ individualSubscription: stripeSubscription,
|
|
|
+ memberGroupSubscriptions: [],
|
|
|
+ managedGroupSubscriptions: [],
|
|
|
+ }
|
|
|
+ )
|
|
|
+ ctx.Modules.promises.hooks.fire
|
|
|
+ .withArgs('getPaymentFromRecordPromise', stripeSubscription)
|
|
|
+ .resolves([
|
|
|
+ {
|
|
|
+ subscription: {
|
|
|
+ state: 'active',
|
|
|
+ periodEnd: ctx.renewalDate,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ await ctx.FeaturesUpdater.promises.refreshFeatures(ctx.user._id, 'test')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should report stripe as the payment_provider', function (ctx) {
|
|
|
+ expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
|
|
+ 'setUserProperties',
|
|
|
+ ctx.user._id,
|
|
|
+ sinon.match({
|
|
|
+ payment_provider: 'stripe',
|
|
|
+ individual_subscription: true,
|
|
|
+ })
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the user has a commons institution licence', function () {
|
|
|
+ beforeEach(async function (ctx) {
|
|
|
+ ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
|
|
|
+ {
|
|
|
+ bestSubscription: { type: 'commons' },
|
|
|
+ individualSubscription: null,
|
|
|
+ memberGroupSubscriptions: [],
|
|
|
+ managedGroupSubscriptions: [],
|
|
|
+ currentInstitutionsWithLicence: [{ id: 1, name: 'Uni' }],
|
|
|
+ }
|
|
|
+ )
|
|
|
+ await ctx.FeaturesUpdater.promises.refreshFeatures(ctx.user._id, 'test')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should sync commons=true to customer.io', function (ctx) {
|
|
|
+ expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
|
|
+ 'setUserProperties',
|
|
|
+ ctx.user._id,
|
|
|
+ sinon.match({
|
|
|
+ commons: true,
|
|
|
+ group: false,
|
|
|
+ individual_subscription: false,
|
|
|
+ })
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the user has commons and an individual AI add-on', function () {
|
|
|
+ beforeEach(async function (ctx) {
|
|
|
+ ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
|
|
|
+ {
|
|
|
+ bestSubscription: {
|
|
|
+ type: 'individual',
|
|
|
+ plan: { planCode: 'individual-plan' },
|
|
|
+ },
|
|
|
+ individualSubscription: ctx.subscriptions.individual,
|
|
|
+ memberGroupSubscriptions: [],
|
|
|
+ managedGroupSubscriptions: [],
|
|
|
+ currentInstitutionsWithLicence: [{ id: 1, name: 'Uni' }],
|
|
|
+ }
|
|
|
+ )
|
|
|
+ ctx.Modules.promises.hooks.fire
|
|
|
+ .withArgs('getPaymentFromRecordPromise', ctx.subscriptions.individual)
|
|
|
+ .resolves([
|
|
|
+ {
|
|
|
+ subscription: {
|
|
|
+ state: 'active',
|
|
|
+ periodEnd: ctx.renewalDate,
|
|
|
+ addOns: [{ code: AI_ADD_ON_CODE }],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ await ctx.FeaturesUpdater.promises.refreshFeatures(ctx.user._id, 'test')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should set commons, individual_subscription, and ai-assist-add-on together', function (ctx) {
|
|
|
+ expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
|
|
+ 'setUserProperties',
|
|
|
+ ctx.user._id,
|
|
|
+ sinon.match({
|
|
|
+ commons: true,
|
|
|
+ individual_subscription: true,
|
|
|
+ group: false,
|
|
|
+ ai_plan: 'ai-assist-add-on',
|
|
|
+ })
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the user has no subscription', function () {
|
|
|
+ beforeEach(async function (ctx) {
|
|
|
+ ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
|
|
|
+ {
|
|
|
+ bestSubscription: null,
|
|
|
+ individualSubscription: null,
|
|
|
+ memberGroupSubscriptions: [],
|
|
|
+ managedGroupSubscriptions: [],
|
|
|
+ }
|
|
|
+ )
|
|
|
+ await ctx.FeaturesUpdater.promises.refreshFeatures(ctx.user._id, 'test')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should sync false subscription flags and no payment_provider', function (ctx) {
|
|
|
+ const call = ctx.Modules.promises.hooks.fire
|
|
|
+ .getCalls()
|
|
|
+ .find(c => c.args[0] === 'setUserProperties')
|
|
|
+ expect(call).to.exist
|
|
|
+ expect(call.args[2]).to.include({
|
|
|
+ group: false,
|
|
|
+ commons: false,
|
|
|
+ individual_subscription: false,
|
|
|
+ })
|
|
|
+ expect(call.args[2]).to.not.have.property('payment_provider')
|
|
|
+ expect(call.args[2]).to.not.have.property('trial_end_date')
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
describe('when the individual subscription has a pending cancellation', function () {
|
|
|
@@ -544,6 +734,10 @@ describe('FeaturesUpdater', function () {
|
|
|
group_size: 8,
|
|
|
next_renewal_date: '',
|
|
|
expiry_date: '',
|
|
|
+ group: true,
|
|
|
+ commons: false,
|
|
|
+ individual_subscription: false,
|
|
|
+ payment_provider: 'recurly',
|
|
|
features: sinon.match.object,
|
|
|
overleaf_id: ctx.user._id,
|
|
|
})
|
|
|
@@ -551,6 +745,49 @@ describe('FeaturesUpdater', function () {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+ describe('when the user is in a stripe group subscription', function () {
|
|
|
+ beforeEach(async function (ctx) {
|
|
|
+ ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
|
|
|
+ {
|
|
|
+ bestSubscription: {
|
|
|
+ type: 'group',
|
|
|
+ plan: {
|
|
|
+ planCode: 'group-plan-1',
|
|
|
+ groupPlan: true,
|
|
|
+ membersLimit: 5,
|
|
|
+ },
|
|
|
+ subscription: {
|
|
|
+ teamName: 'Team Alpha',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ memberGroupSubscriptions: [
|
|
|
+ {
|
|
|
+ planCode: 'group-plan-1',
|
|
|
+ teamName: 'Team Alpha',
|
|
|
+ membersLimit: 8,
|
|
|
+ paymentProvider: { service: 'stripe-uk' },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ managedGroupSubscriptions: [],
|
|
|
+ individualSubscription: null,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ await ctx.FeaturesUpdater.promises.refreshFeatures(ctx.user._id, 'test')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should derive payment_provider from the group subscription', function (ctx) {
|
|
|
+ expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
|
|
+ 'setUserProperties',
|
|
|
+ ctx.user._id,
|
|
|
+ sinon.match({
|
|
|
+ payment_provider: 'stripe',
|
|
|
+ group: true,
|
|
|
+ individual_subscription: false,
|
|
|
+ })
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
describe('with a non-standard feature set', async function () {
|
|
|
beforeEach(async function (ctx) {
|
|
|
ctx.SubscriptionLocator.promises.getGroupSubscriptionsMemberOf
|