/* eslint-disable max-len, 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 should = require('chai').should() const sinon = require('sinon') const querystring = require('querystring') const modulePath = '../../../../app/src/Features/Subscription/SubscriptionHandler' const mockRecurlySubscriptions = { 'subscription-123-active': { uuid: 'subscription-123-active', plan: { name: 'Gold', plan_code: 'gold' }, current_period_ends_at: new Date(), state: 'active', unit_amount_in_cents: 999, account: { account_code: 'user-123' } } } describe('SubscriptionHandler', function() { beforeEach(function() { this.Settings = { plans: [ { planCode: 'collaborator', name: 'Collaborator', features: { collaborators: -1, versioning: true } } ], defaultPlanCode: { collaborators: 0, versioning: false } } this.activeRecurlySubscription = mockRecurlySubscriptions['subscription-123-active'] this.User = {} this.user = { _id: (this.user_id = 'user_id_here_') } this.subscription = { recurlySubscription_id: this.activeRecurlySubscription.uuid } this.RecurlyWrapper = { getSubscription: sinon .stub() .callsArgWith(2, null, this.activeRecurlySubscription), updateSubscription: sinon .stub() .callsArgWith(2, null, this.activeRecurlySubscription), cancelSubscription: sinon.stub().callsArgWith(1), reactivateSubscription: sinon.stub().callsArgWith(1), redeemCoupon: sinon.stub().callsArgWith(2), createSubscription: sinon .stub() .callsArgWith(3, null, this.activeRecurlySubscription), getBillingInfo: sinon.stub().yields(), getAccountPastDueInvoices: sinon.stub().yields(), attemptInvoiceCollection: sinon.stub().yields() } this.DropboxHandler = { unlinkAccount: sinon.stub().callsArgWith(1) } this.SubscriptionUpdater = { syncSubscription: sinon.stub().yields(), startFreeTrial: sinon.stub().callsArgWith(1) } this.LimitationsManager = { userHasV2Subscription: sinon.stub() } this.EmailHandler = { sendEmail: sinon.stub() } this.AnalyticsManager = { recordEvent: sinon.stub() } this.SubscriptionHandler = SandboxedModule.require(modulePath, { globals: { console: console }, requires: { './RecurlyWrapper': this.RecurlyWrapper, 'settings-sharelatex': this.Settings, '../../models/User': { User: this.User }, './SubscriptionUpdater': this.SubscriptionUpdater, 'logger-sharelatex': { log() {} }, './LimitationsManager': this.LimitationsManager, '../Email/EmailHandler': this.EmailHandler, '../Dropbox/DropboxHandler': this.DropboxHandler, '../../infrastructure/Events': (this.Events = { emit: sinon.stub() }), '../Analytics/AnalyticsManager': this.AnalyticsManager } }) return (this.SubscriptionHandler.syncSubscriptionToUser = sinon .stub() .callsArgWith(2)) }) describe('createSubscription', function() { beforeEach(function() { this.callback = sinon.stub() this.subscriptionDetails = { cvv: '123', number: '12345' } this.recurlyTokenIds = { billing: '45555666' } return (this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon .stub() .yields(null, true)) }) describe('successfully', function() { beforeEach(function() { return this.SubscriptionHandler.createSubscription( this.user, this.subscriptionDetails, this.recurlyTokenIds, this.callback ) }) it('should create the subscription with the wrapper', function() { return this.RecurlyWrapper.createSubscription .calledWith(this.user, this.subscriptionDetails, this.recurlyTokenIds) .should.equal(true) }) it('should sync the subscription to the user', function() { this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(true) this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal( this.activeRecurlySubscription ) return this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal( this.user._id ) }) }) describe('when there is already a subscription in Recurly', function() { beforeEach(function() { this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon .stub() .yields(null, false) return this.SubscriptionHandler.createSubscription( this.user, this.subscriptionDetails, this.recurlyTokenIds, this.callback ) }) it('should return an error', function() { return this.callback.calledWith( new Error('user already has subscription in recurly') ) }) }) }) describe('updateSubscription', function() { describe('with a user with a subscription', function() { describe('with a valid plan code', function() { beforeEach(function(done) { this.plan_code = 'collaborator' this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, true, this.subscription ) return this.SubscriptionHandler.updateSubscription( this.user, this.plan_code, null, done ) }) it('should update the subscription', function() { this.RecurlyWrapper.updateSubscription .calledWith(this.subscription.recurlySubscription_id) .should.equal(true) const updateOptions = this.RecurlyWrapper.updateSubscription .args[0][1] return updateOptions.plan_code.should.equal(this.plan_code) }) it('should update immediately', function() { const updateOptions = this.RecurlyWrapper.updateSubscription .args[0][1] return updateOptions.timeframe.should.equal('now') }) it('should sync the new subscription to the user', function() { this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal( true ) this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal( this.activeRecurlySubscription ) return this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal( this.user._id ) }) }) }) describe('with a user without a subscription', function() { beforeEach(function(done) { this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, false ) return this.SubscriptionHandler.updateSubscription( this.user, this.plan_code, null, done ) }) it('should redirect to the subscription dashboard', function() { this.RecurlyWrapper.updateSubscription.called.should.equal(false) return this.SubscriptionHandler.syncSubscriptionToUser.called.should.equal( false ) }) }) describe('with a coupon code', function() { beforeEach(function(done) { this.plan_code = 'collaborator' this.coupon_code = '1231312' this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, true, this.subscription ) return this.SubscriptionHandler.updateSubscription( this.user, this.plan_code, this.coupon_code, done ) }) it('should get the users account', function() { return this.RecurlyWrapper.getSubscription .calledWith(this.activeRecurlySubscription.uuid) .should.equal(true) }) it('should redeme the coupon', function(done) { this.RecurlyWrapper.redeemCoupon .calledWith( this.activeRecurlySubscription.account.account_code, this.coupon_code ) .should.equal(true) return done() }) it('should update the subscription', function() { this.RecurlyWrapper.updateSubscription .calledWith(this.subscription.recurlySubscription_id) .should.equal(true) const updateOptions = this.RecurlyWrapper.updateSubscription.args[0][1] return updateOptions.plan_code.should.equal(this.plan_code) }) }) }) describe('cancelSubscription', function() { describe('with a user without a subscription', function() { beforeEach(function(done) { this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, false, this.subscription ) return this.SubscriptionHandler.cancelSubscription(this.user, done) }) it('should redirect to the subscription dashboard', function() { return this.RecurlyWrapper.cancelSubscription.called.should.equal(false) }) }) describe('with a user with a subscription', function() { beforeEach(function(done) { this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, true, this.subscription ) return this.SubscriptionHandler.cancelSubscription(this.user, done) }) it('should cancel the subscription', function() { this.RecurlyWrapper.cancelSubscription.called.should.equal(true) return this.RecurlyWrapper.cancelSubscription .calledWith(this.subscription.recurlySubscription_id) .should.equal(true) }) it('should trigger the cancel subscription event', function() { return this.Events.emit .calledWith('cancelSubscription', this.user._id) .should.equal(true) }) }) }) describe('reactiveRecurlySubscription', function() { describe('with a user without a subscription', function() { beforeEach(function(done) { this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, false, this.subscription ) return this.SubscriptionHandler.reactivateSubscription(this.user, done) }) it('should redirect to the subscription dashboard', function() { return this.RecurlyWrapper.reactivateSubscription.called.should.equal( false ) }) it('should not send a notification email', function() { return sinon.assert.notCalled(this.EmailHandler.sendEmail) }) }) describe('with a user with a subscription', function() { beforeEach(function(done) { this.LimitationsManager.userHasV2Subscription.callsArgWith( 1, null, true, this.subscription ) return this.SubscriptionHandler.reactivateSubscription(this.user, done) }) it('should reactivate the subscription', function() { this.RecurlyWrapper.reactivateSubscription.called.should.equal(true) return this.RecurlyWrapper.reactivateSubscription .calledWith(this.subscription.recurlySubscription_id) .should.equal(true) }) it('should send a notification email', function() { return sinon.assert.calledWith( this.EmailHandler.sendEmail, 'reactivatedSubscription' ) }) }) }) describe('syncSubscription', function() { describe('with an actionable request', function() { beforeEach(function(done) { this.user.id = this.activeRecurlySubscription.account.account_code this.User.findById = (userId, callback) => { userId.should.equal(this.user.id) return callback(null, this.user) } return this.SubscriptionHandler.syncSubscription( this.activeRecurlySubscription, {}, done ) }) it('should request the affected subscription from the API', function() { return this.RecurlyWrapper.getSubscription .calledWith(this.activeRecurlySubscription.uuid) .should.equal(true) }) it('should request the account details of the subscription', function() { const options = this.RecurlyWrapper.getSubscription.args[0][1] return options.includeAccount.should.equal(true) }) it('should sync the subscription to the user', function() { this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(true) this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal( this.activeRecurlySubscription ) return this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal( this.user._id ) }) }) }) describe('attemptPaypalInvoiceCollection', function() { describe('for credit card users', function() { beforeEach(function(done) { this.RecurlyWrapper.getBillingInfo.yields(null, { paypal_billing_agreement_id: null }) this.SubscriptionHandler.attemptPaypalInvoiceCollection( this.activeRecurlySubscription.account.account_code, done ) }) it('gets billing infos', function() { sinon.assert.calledWith( this.RecurlyWrapper.getBillingInfo, this.activeRecurlySubscription.account.account_code ) }) it('skips user', function() { sinon.assert.notCalled(this.RecurlyWrapper.getAccountPastDueInvoices) }) }) describe('for paypal users', function() { beforeEach(function(done) { this.RecurlyWrapper.getBillingInfo.yields(null, { paypal_billing_agreement_id: 'mock-billing-agreement' }) this.RecurlyWrapper.getAccountPastDueInvoices.yields(null, [ { invoice_number: 'mock-invoice-number' } ]) this.SubscriptionHandler.attemptPaypalInvoiceCollection( this.activeRecurlySubscription.account.account_code, done ) }) it('gets past due invoices', function() { sinon.assert.calledWith( this.RecurlyWrapper.getAccountPastDueInvoices, this.activeRecurlySubscription.account.account_code ) }) it('calls attemptInvoiceCollection', function() { sinon.assert.calledWith( this.RecurlyWrapper.attemptInvoiceCollection, 'mock-invoice-number' ) }) }) }) describe('validateNoSubscriptionInRecurly', function() { beforeEach(function() { this.subscriptions = [] this.RecurlyWrapper.listAccountActiveSubscriptions = sinon .stub() .yields(null, this.subscriptions) this.SubscriptionUpdater.syncSubscription = sinon.stub().yields() return (this.callback = sinon.stub()) }) describe('with no subscription in recurly', function() { beforeEach(function() { this.subscriptions.push((this.subscription = { mock: 'subscription' })) return this.SubscriptionHandler.validateNoSubscriptionInRecurly( this.user_id, this.callback ) }) it('should call RecurlyWrapper.listAccountActiveSubscriptions with the user id', function() { return this.RecurlyWrapper.listAccountActiveSubscriptions .calledWith(this.user_id) .should.equal(true) }) it('should sync the subscription', function() { return this.SubscriptionUpdater.syncSubscription .calledWith(this.subscription, this.user_id) .should.equal(true) }) it('should call the callback with valid == false', function() { return this.callback.calledWith(null, false).should.equal(true) }) }) describe('with a subscription in recurly', function() { beforeEach(function() { return this.SubscriptionHandler.validateNoSubscriptionInRecurly( this.user_id, this.callback ) }) it('should not sync the subscription', function() { return this.SubscriptionUpdater.syncSubscription.called.should.equal( false ) }) it('should call the callback with valid == true', function() { return this.callback.calledWith(null, true).should.equal(true) }) }) }) })