2019-05-29 05:21:06 -04:00
|
|
|
/* eslint-disable
|
|
|
|
max-len,
|
|
|
|
mocha/handle-done-callback,
|
|
|
|
no-return-assign,
|
|
|
|
no-unused-vars,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
|
const sinon = require('sinon')
|
|
|
|
const { expect } = require('chai')
|
|
|
|
const MockRequest = require('../helpers/MockRequest')
|
|
|
|
const MockResponse = require('../helpers/MockResponse')
|
|
|
|
const modulePath =
|
|
|
|
'../../../../app/src/Features/Subscription/SubscriptionController'
|
2019-12-16 05:52:21 -05:00
|
|
|
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
2019-08-22 07:57:50 -04:00
|
|
|
const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
const mockSubscriptions = {
|
|
|
|
'subscription-123-active': {
|
|
|
|
uuid: 'subscription-123-active',
|
|
|
|
plan: {
|
|
|
|
name: 'Gold',
|
2021-04-27 03:52:58 -04:00
|
|
|
plan_code: 'gold',
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
current_period_ends_at: new Date(),
|
|
|
|
state: 'active',
|
|
|
|
unit_amount_in_cents: 999,
|
|
|
|
account: {
|
2021-04-27 03:52:58 -04:00
|
|
|
account_code: 'user-123',
|
|
|
|
},
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('SubscriptionController', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.user = {
|
|
|
|
email: 'tom@yahoo.com',
|
|
|
|
_id: 'one',
|
2021-04-27 03:52:58 -04:00
|
|
|
signUpDate: new Date('2000-10-01'),
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
this.activeRecurlySubscription =
|
|
|
|
mockSubscriptions['subscription-123-active']
|
|
|
|
|
2021-07-28 04:51:20 -04:00
|
|
|
this.SessionManager = {
|
2019-05-29 05:21:06 -04:00
|
|
|
getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
|
|
|
|
getLoggedInUserId: sinon.stub().returns(this.user._id),
|
|
|
|
getSessionUser: sinon.stub().returns(this.user),
|
2021-04-27 03:52:58 -04:00
|
|
|
isUserLoggedIn: sinon.stub().returns(true),
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
this.SubscriptionHandler = {
|
|
|
|
createSubscription: sinon.stub().callsArgWith(3),
|
|
|
|
updateSubscription: sinon.stub().callsArgWith(3),
|
|
|
|
reactivateSubscription: sinon.stub().callsArgWith(1),
|
|
|
|
cancelSubscription: sinon.stub().callsArgWith(1),
|
2019-11-12 03:56:08 -05:00
|
|
|
syncSubscription: sinon.stub().yields(),
|
|
|
|
attemptPaypalInvoiceCollection: sinon.stub().yields(),
|
2021-04-27 03:52:58 -04:00
|
|
|
startFreeTrial: sinon.stub(),
|
2021-05-26 08:26:59 -04:00
|
|
|
promises: {
|
|
|
|
createSubscription: sinon.stub().resolves(),
|
|
|
|
updateSubscription: sinon.stub().resolves(),
|
|
|
|
reactivateSubscription: sinon.stub().resolves(),
|
|
|
|
cancelSubscription: sinon.stub().resolves(),
|
|
|
|
syncSubscription: sinon.stub().resolves(),
|
|
|
|
attemptPaypalInvoiceCollection: sinon.stub().resolves(),
|
|
|
|
startFreeTrial: sinon.stub().resolves(),
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
this.PlansLocator = { findLocalPlanInSettings: sinon.stub() }
|
|
|
|
|
|
|
|
this.LimitationsManager = {
|
|
|
|
hasPaidSubscription: sinon.stub(),
|
|
|
|
userHasV1OrV2Subscription: sinon.stub(),
|
2021-04-27 03:52:58 -04:00
|
|
|
userHasV2Subscription: sinon.stub(),
|
2021-05-26 08:26:59 -04:00
|
|
|
promises: {
|
|
|
|
hasPaidSubscription: sinon.stub().resolves(),
|
|
|
|
userHasV1OrV2Subscription: sinon.stub().resolves(),
|
|
|
|
userHasV2Subscription: sinon.stub().resolves(),
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
this.SubscriptionViewModelBuilder = {
|
|
|
|
buildUsersSubscriptionViewModel: sinon.stub().callsArgWith(1, null, {}),
|
2021-04-27 03:52:58 -04:00
|
|
|
buildPlansList: sinon.stub(),
|
2021-05-26 08:26:59 -04:00
|
|
|
promises: {
|
|
|
|
buildUsersSubscriptionViewModel: sinon.stub().resolves({}),
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
this.settings = {
|
|
|
|
coupon_codes: {
|
|
|
|
upgradeToAnnualPromo: {
|
|
|
|
student: 'STUDENTCODEHERE',
|
2021-04-27 03:52:58 -04:00
|
|
|
collaborator: 'COLLABORATORCODEHERE',
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
apis: {
|
|
|
|
recurly: {
|
2021-04-27 03:52:58 -04:00
|
|
|
subdomain: 'sl',
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
siteUrl: 'http://de.sharelatex.dev:3000',
|
2021-04-27 03:52:58 -04:00
|
|
|
gaExperiments: {},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-05-26 08:26:59 -04:00
|
|
|
this.GeoIpLookup = {
|
2021-08-24 04:54:22 -04:00
|
|
|
isValidCurrencyParam: sinon.stub().returns(true),
|
2021-05-26 08:26:59 -04:00
|
|
|
getCurrencyCode: sinon.stub(),
|
|
|
|
promises: {
|
|
|
|
getCurrencyCode: sinon.stub(),
|
|
|
|
},
|
|
|
|
}
|
2019-08-22 07:57:50 -04:00
|
|
|
this.UserGetter = {
|
2021-04-27 03:52:58 -04:00
|
|
|
getUser: sinon.stub().callsArgWith(2, null, this.user),
|
2021-05-26 08:26:59 -04:00
|
|
|
promises: {
|
|
|
|
getUser: sinon.stub().resolves(this.user),
|
|
|
|
},
|
2019-08-22 07:57:50 -04:00
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
this.SubscriptionController = SandboxedModule.require(modulePath, {
|
|
|
|
requires: {
|
2021-07-28 04:51:20 -04:00
|
|
|
'../Authentication/SessionManager': this.SessionManager,
|
2019-05-29 05:21:06 -04:00
|
|
|
'./SubscriptionHandler': this.SubscriptionHandler,
|
|
|
|
'./PlansLocator': this.PlansLocator,
|
|
|
|
'./SubscriptionViewModelBuilder': this.SubscriptionViewModelBuilder,
|
|
|
|
'./LimitationsManager': this.LimitationsManager,
|
|
|
|
'../../infrastructure/GeoIpLookup': this.GeoIpLookup,
|
2021-07-07 05:38:56 -04:00
|
|
|
'@overleaf/settings': this.settings,
|
2019-05-29 05:21:06 -04:00
|
|
|
'../User/UserGetter': this.UserGetter,
|
2020-02-20 11:08:30 -05:00
|
|
|
'./RecurlyWrapper': (this.RecurlyWrapper = {
|
2021-04-27 03:52:58 -04:00
|
|
|
updateAccountEmailAddress: sinon.stub().yields(),
|
2020-02-20 11:08:30 -05:00
|
|
|
}),
|
2019-05-29 05:21:06 -04:00
|
|
|
'./FeaturesUpdater': (this.FeaturesUpdater = {}),
|
|
|
|
'./GroupPlansData': (this.GroupPlansData = {}),
|
2019-08-22 07:57:50 -04:00
|
|
|
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
|
2020-07-16 02:47:46 -04:00
|
|
|
'../Errors/HttpErrorHandler': (this.HttpErrorHandler = {
|
2021-04-27 03:52:58 -04:00
|
|
|
unprocessableEntity: sinon.stub(),
|
2020-07-16 02:47:46 -04:00
|
|
|
}),
|
2021-04-27 03:52:58 -04:00
|
|
|
'./Errors': SubscriptionErrors,
|
2021-05-19 04:29:21 -04:00
|
|
|
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
2021-09-09 10:45:41 -04:00
|
|
|
recordEventForUser: sinon.stub(),
|
|
|
|
recordEventForSession: sinon.stub(),
|
|
|
|
setUserPropertyForUser: sinon.stub(),
|
2021-05-19 04:29:21 -04:00
|
|
|
}),
|
|
|
|
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
|
|
|
getTestSegmentation: () => {},
|
|
|
|
}),
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
this.res = new MockResponse()
|
|
|
|
this.req = new MockRequest()
|
|
|
|
this.req.body = {}
|
|
|
|
this.req.query = { planCode: '123123' }
|
|
|
|
|
|
|
|
return (this.stubbedCurrencyCode = 'GBP')
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('plansPage', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
|
2021-06-23 09:14:16 -04:00
|
|
|
return this.GeoIpLookup.promises.getCurrencyCode.resolves({
|
|
|
|
currencyCode: this.stubbedCurrencyCode,
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('paymentPage', function () {
|
|
|
|
beforeEach(function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.headers = {}
|
2021-05-26 08:26:59 -04:00
|
|
|
this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly = sinon
|
2019-05-29 05:21:06 -04:00
|
|
|
.stub()
|
2021-05-26 08:26:59 -04:00
|
|
|
.resolves(true)
|
|
|
|
return this.GeoIpLookup.promises.getCurrencyCode.resolves({
|
2021-06-23 09:14:16 -04:00
|
|
|
currencyCode: this.stubbedCurrencyCode,
|
2021-05-26 08:26:59 -04:00
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with a user without a subscription', function () {
|
|
|
|
beforeEach(function () {
|
2021-05-26 08:26:59 -04:00
|
|
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
2019-05-29 05:21:06 -04:00
|
|
|
false
|
|
|
|
)
|
|
|
|
return this.PlansLocator.findLocalPlanInSettings.returns({})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with a valid plan code', function () {
|
|
|
|
it('should render the new subscription page', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.render = (page, opts) => {
|
|
|
|
page.should.equal('subscriptions/new')
|
2021-05-26 08:26:59 -04:00
|
|
|
done()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-05-26 08:26:59 -04:00
|
|
|
this.SubscriptionController.paymentPage(this.req, this.res)
|
2019-08-07 10:04:04 -04:00
|
|
|
})
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with a user with subscription', function () {
|
|
|
|
it('should redirect to the subscription dashboard', function (done) {
|
2020-03-30 14:12:32 -04:00
|
|
|
this.PlansLocator.findLocalPlanInSettings.returns({})
|
2021-05-26 08:26:59 -04:00
|
|
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
2019-05-29 05:21:06 -04:00
|
|
|
true
|
|
|
|
)
|
|
|
|
this.res.redirect = url => {
|
|
|
|
url.should.equal('/user/subscription?hasSubscription=true')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.paymentPage(this.req, this.res)
|
2019-08-07 10:04:04 -04:00
|
|
|
})
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with an invalid plan code', function () {
|
|
|
|
it('should return 422 error - Unprocessable Entity', function (done) {
|
2021-05-26 08:26:59 -04:00
|
|
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
2019-05-29 05:21:06 -04:00
|
|
|
false
|
|
|
|
)
|
|
|
|
this.PlansLocator.findLocalPlanInSettings.returns(null)
|
2020-07-16 02:47:46 -04:00
|
|
|
this.HttpErrorHandler.unprocessableEntity = sinon.spy(
|
|
|
|
(req, res, message) => {
|
|
|
|
expect(req).to.exist
|
|
|
|
expect(res).to.exist
|
|
|
|
expect(message).to.deep.equal('Plan not found')
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
)
|
2021-05-26 08:26:59 -04:00
|
|
|
return this.SubscriptionController.paymentPage(this.req, this.res)
|
2019-08-07 10:04:04 -04:00
|
|
|
})
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('which currency to use', function () {
|
|
|
|
beforeEach(function () {
|
2021-05-26 08:26:59 -04:00
|
|
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
2019-05-29 05:21:06 -04:00
|
|
|
false
|
|
|
|
)
|
|
|
|
return this.PlansLocator.findLocalPlanInSettings.returns({})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should use the set currency from the query string', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.query.currency = 'EUR'
|
|
|
|
this.res.render = (page, opts) => {
|
|
|
|
opts.currency.should.equal('EUR')
|
|
|
|
opts.currency.should.not.equal(this.stubbedCurrencyCode)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.paymentPage(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should upercase the currency code', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.query.currency = 'eur'
|
|
|
|
this.res.render = (page, opts) => {
|
|
|
|
opts.currency.should.equal('EUR')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.paymentPage(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should use the geo ip currency if non is provided', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.query.currency = null
|
|
|
|
this.res.render = (page, opts) => {
|
|
|
|
opts.currency.should.equal(this.stubbedCurrencyCode)
|
2021-08-24 04:54:22 -04:00
|
|
|
done()
|
|
|
|
}
|
|
|
|
this.SubscriptionController.paymentPage(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should use the geo ip currency if not valid', function (done) {
|
|
|
|
this.req.query.currency = 'WAT'
|
|
|
|
this.GeoIpLookup.isValidCurrencyParam.returns(false)
|
|
|
|
this.res.render = (page, opts) => {
|
|
|
|
opts.currency.should.equal(this.stubbedCurrencyCode)
|
2021-05-26 08:26:59 -04:00
|
|
|
done()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-05-26 08:26:59 -04:00
|
|
|
this.SubscriptionController.paymentPage(this.req, this.res)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with a recurly subscription already', function () {
|
|
|
|
it('should redirect to the subscription dashboard', function (done) {
|
2020-03-30 14:12:32 -04:00
|
|
|
this.PlansLocator.findLocalPlanInSettings.returns({})
|
2021-05-26 08:26:59 -04:00
|
|
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
|
|
|
false
|
|
|
|
)
|
|
|
|
this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly.resolves(
|
2019-05-29 05:21:06 -04:00
|
|
|
false
|
|
|
|
)
|
|
|
|
this.res.redirect = url => {
|
|
|
|
url.should.equal('/user/subscription?hasSubscription=true')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.paymentPage(this.req, this.res)
|
2019-08-07 10:04:04 -04:00
|
|
|
})
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
2021-05-26 08:26:59 -04:00
|
|
|
describe('successfulSubscription', function () {
|
2021-04-14 09:17:21 -04:00
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(
|
|
|
|
1,
|
|
|
|
null,
|
|
|
|
{}
|
|
|
|
)
|
|
|
|
this.res.callback = done
|
2021-05-26 08:26:59 -04:00
|
|
|
return this.SubscriptionController.successfulSubscription(
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
2019-08-07 10:04:04 -04:00
|
|
|
})
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('userSubscriptionPage', function () {
|
|
|
|
beforeEach(function (done) {
|
2021-05-26 08:37:15 -04:00
|
|
|
this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel.resolves(
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
|
|
|
personalSubscription: (this.personalSubscription = {
|
2021-04-27 03:52:58 -04:00
|
|
|
'personal-subscription': 'mock',
|
2019-05-29 05:21:06 -04:00
|
|
|
}),
|
|
|
|
memberGroupSubscriptions: (this.memberGroupSubscriptions = {
|
2021-04-27 03:52:58 -04:00
|
|
|
'group-subscriptions': 'mock',
|
|
|
|
}),
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
2020-02-05 11:34:29 -05:00
|
|
|
this.SubscriptionViewModelBuilder.buildPlansList.returns(
|
2019-05-29 05:21:06 -04:00
|
|
|
(this.plans = { plans: 'mock' })
|
|
|
|
)
|
2021-05-26 08:37:15 -04:00
|
|
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(false)
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.render = (view, data) => {
|
|
|
|
this.data = data
|
|
|
|
expect(view).to.equal('subscriptions/dashboard')
|
2021-05-26 08:26:59 -04:00
|
|
|
done()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-05-26 08:26:59 -04:00
|
|
|
this.SubscriptionController.userSubscriptionPage(this.req, this.res)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should load the personal, groups and v1 subscriptions', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
expect(this.data.personalSubscription).to.deep.equal(
|
|
|
|
this.personalSubscription
|
|
|
|
)
|
|
|
|
return expect(this.data.memberGroupSubscriptions).to.deep.equal(
|
|
|
|
this.memberGroupSubscriptions
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should load the user', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return expect(this.data.user).to.deep.equal(this.user)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should load the plans', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return expect(this.data.plans).to.deep.equal(this.plans)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('createSubscription', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res = {
|
|
|
|
sendStatus() {
|
|
|
|
return done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'sendStatus')
|
|
|
|
this.subscriptionDetails = {
|
|
|
|
card: '1234',
|
2021-04-27 03:52:58 -04:00
|
|
|
cvv: '123',
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-08-22 07:57:50 -04:00
|
|
|
this.recurlyTokenIds = {
|
|
|
|
billing: '1234',
|
2021-04-27 03:52:58 -04:00
|
|
|
threeDSecureActionResult: '5678',
|
2019-08-22 07:57:50 -04:00
|
|
|
}
|
|
|
|
this.req.body.recurly_token_id = this.recurlyTokenIds.billing
|
|
|
|
this.req.body.recurly_three_d_secure_action_result_token_id = this.recurlyTokenIds.threeDSecureActionResult
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.body.subscriptionDetails = this.subscriptionDetails
|
|
|
|
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
|
|
|
|
return this.SubscriptionController.createSubscription(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should send the user and subscriptionId to the handler', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.SubscriptionHandler.createSubscription
|
2019-08-22 07:57:50 -04:00
|
|
|
.calledWithMatch(
|
2019-05-29 05:21:06 -04:00
|
|
|
this.user,
|
|
|
|
this.subscriptionDetails,
|
2019-08-22 07:57:50 -04:00
|
|
|
this.recurlyTokenIds
|
2019-05-29 05:21:06 -04:00
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should redurect to the subscription page', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.sendStatus.calledWith(201).should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('createSubscription with errors', function () {
|
|
|
|
it('should handle users with subscription', function (done) {
|
2020-03-05 10:42:19 -05:00
|
|
|
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, true)
|
|
|
|
this.SubscriptionController.createSubscription(this.req, {
|
|
|
|
sendStatus: status => {
|
|
|
|
expect(status).to.equal(409)
|
|
|
|
this.SubscriptionHandler.createSubscription.called.should.equal(false)
|
|
|
|
|
|
|
|
done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2020-03-05 10:42:19 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should handle 3DSecure errors', function (done) {
|
2019-08-22 07:57:50 -04:00
|
|
|
this.next = sinon.stub()
|
|
|
|
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
|
|
|
|
this.SubscriptionHandler.createSubscription.yields(
|
|
|
|
new SubscriptionErrors.RecurlyTransactionError({})
|
|
|
|
)
|
2020-08-11 05:12:49 -04:00
|
|
|
this.HttpErrorHandler.unprocessableEntity = sinon.spy(
|
|
|
|
(req, res, message) => {
|
|
|
|
expect(req).to.exist
|
|
|
|
expect(res).to.exist
|
|
|
|
expect(message).to.deep.equal('Unknown transaction error')
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
this.SubscriptionController.createSubscription(this.req, this.res)
|
2019-12-16 05:52:21 -05:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should handle validation errors', function (done) {
|
2019-12-16 05:52:21 -05:00
|
|
|
this.next = sinon.stub()
|
|
|
|
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
|
|
|
|
this.SubscriptionHandler.createSubscription.yields(
|
2020-08-11 05:12:49 -04:00
|
|
|
new Errors.InvalidError('invalid error test')
|
2019-12-16 05:52:21 -05:00
|
|
|
)
|
2020-08-11 05:12:49 -04:00
|
|
|
this.HttpErrorHandler.unprocessableEntity = sinon.spy(
|
|
|
|
(req, res, message) => {
|
|
|
|
expect(req).to.exist
|
|
|
|
expect(res).to.exist
|
|
|
|
expect(message).to.deep.equal('invalid error test')
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
this.SubscriptionController.createSubscription(this.req, this.res)
|
2019-08-22 07:57:50 -04:00
|
|
|
})
|
2020-07-16 02:47:46 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should handle recurly errors', function (done) {
|
2020-07-16 02:47:46 -04:00
|
|
|
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
|
|
|
|
this.SubscriptionHandler.createSubscription.yields(
|
|
|
|
new SubscriptionErrors.RecurlyTransactionError({})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.HttpErrorHandler.unprocessableEntity = sinon.spy(
|
|
|
|
(req, res, info) => {
|
|
|
|
expect(req).to.exist
|
|
|
|
expect(res).to.exist
|
|
|
|
expect(info).to.deep.equal('Unknown transaction error')
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return this.SubscriptionController.createSubscription(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should handle invalid error', function (done) {
|
2020-07-16 02:47:46 -04:00
|
|
|
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
|
|
|
|
this.SubscriptionHandler.createSubscription.yields(
|
|
|
|
new Errors.InvalidError({})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.HttpErrorHandler.unprocessableEntity = sinon.spy((req, res) => {
|
|
|
|
expect(req).to.exist
|
|
|
|
expect(res).to.exist
|
|
|
|
done()
|
|
|
|
})
|
|
|
|
|
|
|
|
return this.SubscriptionController.createSubscription(this.req, this.res)
|
|
|
|
})
|
2019-08-22 07:57:50 -04:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('updateSubscription via post', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res = {
|
|
|
|
redirect() {
|
|
|
|
return done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'redirect')
|
|
|
|
this.plan_code = '1234'
|
|
|
|
this.req.body.plan_code = this.plan_code
|
|
|
|
return this.SubscriptionController.updateSubscription(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should send the user and subscriptionId to the handler', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.SubscriptionHandler.updateSubscription
|
|
|
|
.calledWith(this.user, this.plan_code)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should redurect to the subscription page', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.redirect.calledWith('/user/subscription').should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('updateAccountEmailAddress via put', function () {
|
|
|
|
it('should send the user and subscriptionId to RecurlyWrapper', function () {
|
2020-07-22 10:08:06 -04:00
|
|
|
this.res.sendStatus = sinon.spy()
|
|
|
|
this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
|
2020-02-20 11:08:30 -05:00
|
|
|
this.RecurlyWrapper.updateAccountEmailAddress
|
|
|
|
.calledWith(this.user._id, this.user.email)
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should respond with 200', function () {
|
2020-07-22 10:08:06 -04:00
|
|
|
this.res.sendStatus = sinon.spy()
|
|
|
|
this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
|
2020-02-20 11:08:30 -05:00
|
|
|
this.res.sendStatus.calledWith(200).should.equal(true)
|
|
|
|
})
|
2020-07-22 10:08:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should send the error to the next handler when updating recurly account email fails', function (done) {
|
2020-07-22 10:08:06 -04:00
|
|
|
this.RecurlyWrapper.updateAccountEmailAddress.yields(new Error())
|
|
|
|
this.next = sinon.spy(error => {
|
|
|
|
expect(error).instanceOf(Error)
|
|
|
|
done()
|
|
|
|
})
|
|
|
|
this.SubscriptionController.updateAccountEmailAddress(
|
|
|
|
this.req,
|
|
|
|
this.res,
|
|
|
|
this.next
|
|
|
|
)
|
|
|
|
})
|
2020-02-20 11:08:30 -05:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('reactivateSubscription', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res = {
|
|
|
|
redirect() {
|
|
|
|
return done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'redirect')
|
|
|
|
return this.SubscriptionController.reactivateSubscription(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should tell the handler to reactivate this user', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.SubscriptionHandler.reactivateSubscription
|
|
|
|
.calledWith(this.user)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should redurect to the subscription page', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.redirect.calledWith('/user/subscription').should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('cancelSubscription', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res = {
|
|
|
|
redirect() {
|
|
|
|
return done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'redirect')
|
|
|
|
return this.SubscriptionController.cancelSubscription(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should tell the handler to cancel this user', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.SubscriptionHandler.cancelSubscription
|
|
|
|
.calledWith(this.user)
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should redurect to the subscription page', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.redirect
|
|
|
|
.calledWith('/user/subscription/canceled')
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('recurly callback', function () {
|
|
|
|
describe('with a sync subscription request', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req = {
|
|
|
|
body: {
|
|
|
|
expired_subscription_notification: {
|
2021-06-10 04:04:30 -04:00
|
|
|
account: {
|
|
|
|
account_code: this.user._id,
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
subscription: {
|
2021-04-27 03:52:58 -04:00
|
|
|
uuid: this.activeRecurlySubscription.uuid,
|
2021-06-10 04:04:30 -04:00
|
|
|
plan: {
|
|
|
|
plan_code: 'collaborator',
|
|
|
|
state: 'active',
|
|
|
|
},
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
this.res = {
|
|
|
|
sendStatus() {
|
|
|
|
return done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'sendStatus')
|
|
|
|
return this.SubscriptionController.recurlyCallback(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should tell the SubscriptionHandler to process the recurly callback', function (done) {
|
2019-11-12 03:56:08 -05:00
|
|
|
this.SubscriptionHandler.syncSubscription.called.should.equal(true)
|
2019-05-29 05:21:06 -04:00
|
|
|
return done()
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should send a 200', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.res.sendStatus.calledWith(200)
|
|
|
|
return done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with a billing info updated request', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-11-12 03:56:08 -05:00
|
|
|
this.req = {
|
|
|
|
body: {
|
|
|
|
billing_info_updated_notification: {
|
|
|
|
account: {
|
2021-04-27 03:52:58 -04:00
|
|
|
account_code: 'mock-account-code',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-11-12 03:56:08 -05:00
|
|
|
}
|
|
|
|
this.res = {
|
|
|
|
sendStatus() {
|
|
|
|
done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-11-12 03:56:08 -05:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'sendStatus')
|
|
|
|
this.SubscriptionController.recurlyCallback(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should call attemptPaypalInvoiceCollection', function (done) {
|
2019-11-12 03:56:08 -05:00
|
|
|
this.SubscriptionHandler.attemptPaypalInvoiceCollection
|
|
|
|
.calledWith('mock-account-code')
|
|
|
|
.should.equal(true)
|
|
|
|
done()
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should send a 200', function (done) {
|
2019-11-12 03:56:08 -05:00
|
|
|
this.res.sendStatus.calledWith(200)
|
|
|
|
done()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('with a non-actionable request', function () {
|
|
|
|
beforeEach(function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.user.id = this.activeRecurlySubscription.account.account_code
|
|
|
|
this.req = {
|
|
|
|
body: {
|
2019-10-15 09:10:46 -04:00
|
|
|
renewed_subscription_notification: {
|
2021-06-10 04:04:30 -04:00
|
|
|
account: {
|
|
|
|
account_code: this.user._id,
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
subscription: {
|
2021-04-27 03:52:58 -04:00
|
|
|
uuid: this.activeRecurlySubscription.uuid,
|
2021-06-10 04:04:30 -04:00
|
|
|
plan: {
|
|
|
|
plan_code: 'collaborator',
|
|
|
|
state: 'active',
|
|
|
|
},
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
this.res = {
|
|
|
|
sendStatus() {
|
|
|
|
return done()
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
sinon.spy(this.res, 'sendStatus')
|
|
|
|
return this.SubscriptionController.recurlyCallback(this.req, this.res)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should not call the subscriptionshandler', function () {
|
2019-11-12 03:56:08 -05:00
|
|
|
this.SubscriptionHandler.syncSubscription.called.should.equal(false)
|
|
|
|
this.SubscriptionHandler.attemptPaypalInvoiceCollection.called.should.equal(
|
2019-05-29 05:21:06 -04:00
|
|
|
false
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should respond with a 200 status', function () {
|
2019-05-29 05:21:06 -04:00
|
|
|
return this.res.sendStatus.calledWith(200)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('renderUpgradeToAnnualPlanPage', function () {
|
|
|
|
it('should redirect to the plans page if the user does not have a subscription', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.LimitationsManager.userHasV2Subscription.callsArgWith(1, null, false)
|
2021-04-14 09:17:21 -04:00
|
|
|
this.res.redirect = function (url) {
|
2019-05-29 05:21:06 -04:00
|
|
|
url.should.equal('/user/subscription/plans')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass the plan code to the view - student', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.LimitationsManager.userHasV2Subscription.callsArgWith(
|
|
|
|
1,
|
|
|
|
null,
|
|
|
|
true,
|
|
|
|
{ planCode: 'Student free trial 14 days' }
|
|
|
|
)
|
2021-04-14 09:17:21 -04:00
|
|
|
this.res.render = function (view, opts) {
|
2019-05-29 05:21:06 -04:00
|
|
|
view.should.equal('subscriptions/upgradeToAnnual')
|
|
|
|
opts.planName.should.equal('student')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass the plan code to the view - collaborator', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.LimitationsManager.userHasV2Subscription.callsArgWith(
|
|
|
|
1,
|
|
|
|
null,
|
|
|
|
true,
|
|
|
|
{ planCode: 'free trial for Collaborator free trial 14 days' }
|
|
|
|
)
|
2021-04-14 09:17:21 -04:00
|
|
|
this.res.render = function (view, opts) {
|
2019-05-29 05:21:06 -04:00
|
|
|
opts.planName.should.equal('collaborator')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should pass annual as the plan name if the user is already on an annual plan', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.LimitationsManager.userHasV2Subscription.callsArgWith(
|
|
|
|
1,
|
|
|
|
null,
|
|
|
|
true,
|
|
|
|
{ planCode: 'student annual with free trial' }
|
|
|
|
)
|
2021-04-14 09:17:21 -04:00
|
|
|
this.res.render = function (view, opts) {
|
2019-05-29 05:21:06 -04:00
|
|
|
opts.planName.should.equal('annual')
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('processUpgradeToAnnualPlan', function () {
|
|
|
|
beforeEach(function () {})
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should tell the subscription handler to update the subscription with the annual plan and apply a coupon code', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.body = { planName: 'student' }
|
|
|
|
|
|
|
|
this.res.sendStatus = () => {
|
|
|
|
this.SubscriptionHandler.updateSubscription
|
|
|
|
.calledWith(this.user, 'student-annual', 'STUDENTCODEHERE')
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.SubscriptionController.processUpgradeToAnnualPlan(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should get the collaborator coupon code', function (done) {
|
2019-05-29 05:21:06 -04:00
|
|
|
this.req.body = { planName: 'collaborator' }
|
|
|
|
|
|
|
|
this.res.sendStatus = url => {
|
|
|
|
this.SubscriptionHandler.updateSubscription
|
|
|
|
.calledWith(this.user, 'collaborator-annual', 'COLLABORATORCODEHERE')
|
|
|
|
.should.equal(true)
|
|
|
|
return done()
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.SubscriptionController.processUpgradeToAnnualPlan(
|
|
|
|
this.req,
|
|
|
|
this.res
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|