/* eslint-disable camelcase, handle-callback-err, max-len, no-return-assign, no-unused-vars, standard/no-callback-literal, */ // 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 should = require('chai').should() const { expect } = require('chai') const sinon = require('sinon') const crypto = require('crypto') const querystring = require('querystring') const modulePath = '../../../../app/src/Features/Subscription/RecurlyWrapper' const SandboxedModule = require('sandboxed-module') const tk = require('timekeeper') const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors') const fixtures = { 'subscriptions/44f83d7cba354d5b84812419f923ea96': '' + '' + ' ' + ' ' + ' gold' + ' Gold plan' + ' ' + ' 44f83d7cba354d5b84812419f923ea96' + ' active' + ' 800' + ' EUR' + ' 1' + ' 2011-05-27T07:00:00Z' + ' ' + ' ' + ' 2011-06-27T07:00:00Z' + ' 2011-07-27T07:00:00Z' + ' ' + ' ' + ' ' + ' ' + ' ipaddresses' + ' 10' + ' 150' + ' ' + ' ' + ' ' + ' ' + ' ' + '', 'recurly_js/result/70db44b10f5f4b238669480c9903f6f5': '' + '' + ' ' + ' ' + ' gold' + ' Gold plan' + ' ' + ' 44f83d7cba354d5b84812419f923ea96' + ' active' + ' 800' + ' EUR' + ' 1' + ' 2011-05-27T07:00:00Z' + ' ' + ' ' + ' 2011-06-27T07:00:00Z' + ' 2011-07-27T07:00:00Z' + ' ' + ' ' + ' ' + ' ' + ' ipaddresses' + ' 10' + ' 150' + ' ' + ' ' + ' ' + ' ' + ' ' + '', 'accounts/104': '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' 104' + ' active' + ' ' + ' verena@example.com' + ' Verena' + ' Example' + ' ' + ' a92468579e9c4231a6c0031c4716c01d' + ' 2011-10-25T12:00:00' + '' } const mockApiRequest = function(options, callback) { if (fixtures[options.url]) { return callback(null, { statusCode: 200 }, fixtures[options.url]) } else { return callback(new Error('Not found'), { statusCode: 404 }) } } describe('RecurlyWrapper', function() { before(function() { let RecurlyWrapper this.settings = { plans: [ { planCode: 'collaborator', name: 'Collaborator', features: { collaborators: -1, versioning: true } } ], defaultPlanCode: { collaborators: 0, versioning: false }, apis: { recurly: { apiKey: 'nonsense', privateKey: 'private_nonsense' } } } tk.freeze(Date.now()) // freeze the time for these tests return (this.RecurlyWrapper = RecurlyWrapper = SandboxedModule.require( modulePath, { globals: { console: console }, requires: { 'settings-sharelatex': this.settings, 'logger-sharelatex': { err: sinon.stub(), error: sinon.stub(), warn: sinon.stub(), log: sinon.stub() }, request: sinon.stub(), xml2js: require('xml2js'), './Errors': SubscriptionErrors } } )) }) after(function() { return tk.reset() }) describe('getSubscription', function() { describe('with proper subscription id', function() { before(function() { this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', mockApiRequest ) return this.RecurlyWrapper.getSubscription( '44f83d7cba354d5b84812419f923ea96', (error, recurlySubscription) => { return (this.recurlySubscription = recurlySubscription) } ) }) after(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('should look up the subscription at the normal API end point', function() { return this.apiRequest.args[0][0].url.should.equal( 'subscriptions/44f83d7cba354d5b84812419f923ea96' ) }) it('should return the subscription', function() { return this.recurlySubscription.uuid.should.equal( '44f83d7cba354d5b84812419f923ea96' ) }) }) describe('with ReculyJS token', function() { before(function() { this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', mockApiRequest ) return this.RecurlyWrapper.getSubscription( '70db44b10f5f4b238669480c9903f6f5', { recurlyJsResult: true }, (error, recurlySubscription) => { return (this.recurlySubscription = recurlySubscription) } ) }) after(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('should return the subscription', function() { return this.recurlySubscription.uuid.should.equal( '44f83d7cba354d5b84812419f923ea96' ) }) it('should look up the subscription at the RecurlyJS API end point', function() { return this.apiRequest.args[0][0].url.should.equal( 'recurly_js/result/70db44b10f5f4b238669480c9903f6f5' ) }) }) describe('with includeAccount', function() { beforeEach(function() { this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', mockApiRequest ) return this.RecurlyWrapper.getSubscription( '44f83d7cba354d5b84812419f923ea96', { includeAccount: true }, (error, recurlySubscription) => { return (this.recurlySubscription = recurlySubscription) } ) }) afterEach(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('should request the account from the API', function() { return this.apiRequest.args[1][0].url.should.equal('accounts/104') }) it('should populate the account attribute', function() { return this.recurlySubscription.account.account_code.should.equal('104') }) }) }) describe('updateSubscription', function() { beforeEach(function(done) { this.recurlySubscriptionId = 'subscription-id-123' this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', (options, callback) => { this.requestOptions = options return callback( null, {}, fixtures['subscriptions/44f83d7cba354d5b84812419f923ea96'] ) } ) return this.RecurlyWrapper.updateSubscription( this.recurlySubscriptionId, { plan_code: 'silver', timeframe: 'now' }, (error, recurlySubscription) => { this.recurlySubscription = recurlySubscription return done() } ) }) afterEach(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('sends correct XML', function() { this.apiRequest.called.should.equal(true) const { body } = this.apiRequest.lastCall.args[0] expect(body).to.equal(`\ silver now \ `) this.requestOptions.url.should.equal( `subscriptions/${this.recurlySubscriptionId}` ) return this.requestOptions.method.should.equal('put') }) it('should return the updated subscription', function() { should.exist(this.recurlySubscription) return this.recurlySubscription.plan.plan_code.should.equal('gold') }) }) describe('cancelSubscription', function() { beforeEach(function(done) { this.recurlySubscriptionId = 'subscription-id-123' this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', (options, callback) => { options.url.should.equal( `subscriptions/${this.recurlySubscriptionId}/cancel` ) options.method.should.equal('put') return callback() } ) return this.RecurlyWrapper.cancelSubscription( this.recurlySubscriptionId, done ) }) afterEach(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('should send a cancel request to the API', function() { return this.apiRequest.called.should.equal(true) }) describe('when the subscription is already cancelled', function() { beforeEach(function() { this.RecurlyWrapper.apiRequest.restore() this.recurlySubscriptionId = 'subscription-id-123' return (this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', (options, callback) => { return callback( new Error('woops'), {}, "A canceled subscription can't transition to canceled" ) } )) }) it('should not produce an error', function(done) { return this.RecurlyWrapper.cancelSubscription( this.recurlySubscriptionId, err => { expect(err).to.equal(null) return done() } ) }) }) }) describe('reactivateSubscription', function() { beforeEach(function(done) { this.recurlySubscriptionId = 'subscription-id-123' this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', (options, callback) => { options.url.should.equal( `subscriptions/${this.recurlySubscriptionId}/reactivate` ) options.method.should.equal('put') return callback() } ) return this.RecurlyWrapper.reactivateSubscription( this.recurlySubscriptionId, done ) }) afterEach(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('should send a cancel request to the API', function() { return this.apiRequest.called.should.equal(true) }) }) describe('redeemCoupon', function() { beforeEach(function(done) { this.recurlyAccountId = 'account-id-123' this.coupon_code = '312321312' this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', (options, callback) => { options.url.should.equal(`coupons/${this.coupon_code}/redeem`) options.method.should.equal('post') return callback() } ) return this.RecurlyWrapper.redeemCoupon( this.recurlyAccountId, this.coupon_code, done ) }) afterEach(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('sends correct XML', function() { this.apiRequest.called.should.equal(true) const { body } = this.apiRequest.lastCall.args[0] return expect(body).to.equal(`\ account-id-123 USD \ `) }) }) describe('createFixedAmmountCoupon', function() { beforeEach(function(done) { this.couponCode = 'a-coupon-code' this.couponName = 'a-coupon-name' this.currencyCode = 'EUR' this.discount = 1337 this.planCode = 'a-plan-code' this.apiRequest = sinon.stub( this.RecurlyWrapper, 'apiRequest', (options, callback) => { return callback() } ) return this.RecurlyWrapper.createFixedAmmountCoupon( this.couponCode, this.couponName, this.currencyCode, this.discount, this.planCode, done ) }) afterEach(function() { return this.RecurlyWrapper.apiRequest.restore() }) it('sends correct XML', function() { this.apiRequest.called.should.equal(true) const { body } = this.apiRequest.lastCall.args[0] return expect(body).to.equal(`\ a-coupon-code a-coupon-name dollars 1337 a-plan-code false \ `) }) }) describe('createSubscription', function() { beforeEach(function() { this.user = { _id: 'some_id', email: 'user@example.com' } this.subscriptionDetails = { currencyCode: 'EUR', plan_code: 'some_plan_code', coupon_code: '', isPaypal: true, address: { address1: 'addr_one', address2: 'addr_two', country: 'some_country', state: 'some_state', zip: 'some_zip' } } this.subscription = {} this.recurlyTokenIds = { billing: 'a-token-id', threeDSecureActionResult: 'a-3d-token-id' } return (this.call = callback => { return this.RecurlyWrapper.createSubscription( this.user, this.subscriptionDetails, this.recurlyTokenIds, callback ) }) }) describe('when paypal', function() { beforeEach(function() { this.subscriptionDetails.isPaypal = true this._createPaypalSubscription = sinon.stub( this.RecurlyWrapper, '_createPaypalSubscription' ) return this._createPaypalSubscription.callsArgWith( 3, null, this.subscription ) }) afterEach(function() { return this._createPaypalSubscription.restore() }) it('should not produce an error', function(done) { return this.call((err, sub) => { expect(err).to.equal(null) expect(err).to.not.be.instanceof(Error) return done() }) }) it('should produce a subscription object', function(done) { return this.call((err, sub) => { expect(sub).to.deep.equal(this.subscription) return done() }) }) it('should call _createPaypalSubscription', function(done) { return this.call((err, sub) => { this._createPaypalSubscription.callCount.should.equal(1) return done() }) }) describe('when _createPaypalSubscription produces an error', function() { beforeEach(function() { return this._createPaypalSubscription.callsArgWith( 3, new Error('woops') ) }) it('should produce an error', function(done) { return this.call((err, sub) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) describe('when not paypal', function() { beforeEach(function() { this.subscriptionDetails.isPaypal = false this._createCreditCardSubscription = sinon.stub( this.RecurlyWrapper, '_createCreditCardSubscription' ) return this._createCreditCardSubscription.callsArgWith( 3, null, this.subscription ) }) afterEach(function() { return this._createCreditCardSubscription.restore() }) it('should not produce an error', function(done) { return this.call((err, sub) => { expect(err).to.equal(null) expect(err).to.not.be.instanceof(Error) return done() }) }) it('should produce a subscription object', function(done) { return this.call((err, sub) => { expect(sub).to.deep.equal(this.subscription) return done() }) }) it('should call _createCreditCardSubscription', function(done) { return this.call((err, sub) => { this._createCreditCardSubscription.callCount.should.equal(1) return done() }) }) describe('when _createCreditCardSubscription produces an error', function() { beforeEach(function() { return this._createCreditCardSubscription.callsArgWith( 3, new Error('woops') ) }) it('should produce an error', function(done) { return this.call((err, sub) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) }) describe('_createCreditCardSubscription', function() { beforeEach(function() { this.user = { _id: 'some_id', email: 'user@example.com', first_name: 'Foo', last_name: 'Johnson' } this.subscriptionDetails = { currencyCode: 'EUR', plan_code: 'some_plan_code', coupon_code: '', isPaypal: true, first_name: 'Prairie', address: { address1: 'addr_one', address2: 'addr_two', country: 'some_country', state: 'some_state', zip: 'some_zip' } } this.subscription = {} this.recurlyTokenIds = { billing: 'a-token-id', threeDSecureActionResult: 'a-3d-token-id' } this.apiRequest = sinon.stub(this.RecurlyWrapper, 'apiRequest') this.response = { statusCode: 200 } this.body = 'is_bad' this.apiRequest.callsArgWith(1, null, this.response, this.body) this._parseSubscriptionXml = sinon.stub( this.RecurlyWrapper, '_parseSubscriptionXml' ) this._parseSubscriptionXml.callsArgWith(1, null, this.subscription) return (this.call = callback => { return this.RecurlyWrapper._createCreditCardSubscription( this.user, this.subscriptionDetails, this.recurlyTokenIds, callback ) }) }) afterEach(function() { this.apiRequest.restore() return this._parseSubscriptionXml.restore() }) it('sends correct XML', function(done) { return this.call((err, result) => { const { body } = this.apiRequest.lastCall.args[0] expect(body).to.equal(`\ some_plan_code EUR some_id user@example.com Prairie Johnson a-token-id a-3d-token-id \ `) return done() }) }) it('should not produce an error', function(done) { return this.call((err, sub) => { expect(err).to.not.be.instanceof(Error) expect(err).to.equal(null) return done() }) }) it('should produce a subscription', function(done) { return this.call((err, sub) => { expect(sub).to.equal(this.subscription) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, sub) => { this.apiRequest.callCount.should.equal(1) return done() }) }) it('should call _parseSubscriptionXml', function(done) { return this.call((err, sub) => { this._parseSubscriptionXml.callCount.should.equal(1) return done() }) }) describe('when api request returns 422', function() { beforeEach(function() { const body = `\ three_d_secure_action_required 3d_secure_action_required Your payment gateway is requesting that the transaction be completed with 3D Secure in accordance with PSD2. Your card must be authenticated with 3D Secure before continuing. mock_three_d_secure_action_token Your card must be authenticated with 3D Secure before continuing. ` this.apiRequest.yields(null, { statusCode: 422 }, body) }) it('should produce an error', function(done) { return this.call((err, sub) => { expect(err).to.be.instanceof( SubscriptionErrors.RecurlyTransactionError ) expect(err.info.public.message).to.be.equal( 'Your card must be authenticated with 3D Secure before continuing.' ) expect(err.info.public.threeDSecureActionTokenId).to.be.equal( 'mock_three_d_secure_action_token' ) return done() }) }) }) describe('when api request produces an error', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, new Error('woops')) }) it('should produce an error', function(done) { return this.call((err, sub) => { expect(err).to.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, sub) => { this.apiRequest.callCount.should.equal(1) return done() }) }) it('should not _parseSubscriptionXml', function(done) { return this.call((err, sub) => { this._parseSubscriptionXml.callCount.should.equal(0) return done() }) }) }) describe('when parse xml produces an error', function() { beforeEach(function() { return this._parseSubscriptionXml.callsArgWith(1, new Error('woops')) }) it('should produce an error', function(done) { return this.call((err, sub) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) describe('_createPaypalSubscription', function() { beforeEach(function() { this.checkAccountExists = sinon.stub( this.RecurlyWrapper._paypal, 'checkAccountExists' ) this.createAccount = sinon.stub( this.RecurlyWrapper._paypal, 'createAccount' ) this.createBillingInfo = sinon.stub( this.RecurlyWrapper._paypal, 'createBillingInfo' ) this.setAddress = sinon.stub(this.RecurlyWrapper._paypal, 'setAddress') this.createSubscription = sinon.stub( this.RecurlyWrapper._paypal, 'createSubscription' ) this.user = { _id: 'some_id', email: 'user@example.com' } this.subscriptionDetails = { currencyCode: 'EUR', plan_code: 'some_plan_code', coupon_code: '', isPaypal: true, address: { address1: 'addr_one', address2: 'addr_two', country: 'some_country', state: 'some_state', zip: 'some_zip' } } this.subscription = {} this.recurlyTokenIds = { billing: 'a-token-id', threeDSecureActionResult: 'a-3d-token-id' } // set up data callbacks const { user } = this const { subscriptionDetails } = this const { recurlyTokenIds } = this this.checkAccountExists.callsArgWith(1, null, { user, subscriptionDetails, recurlyTokenIds, userExists: false, account: { accountCode: 'xx' } }) this.createAccount.callsArgWith(1, null, { user, subscriptionDetails, recurlyTokenIds, userExists: false, account: { accountCode: 'xx' } }) this.createBillingInfo.callsArgWith(1, null, { user, subscriptionDetails, recurlyTokenIds, userExists: false, account: { accountCode: 'xx' }, billingInfo: { token_id: 'abc' } }) this.setAddress.callsArgWith(1, null, { user, subscriptionDetails, recurlyTokenIds, userExists: false, account: { accountCode: 'xx' }, billingInfo: { token_id: 'abc' } }) this.createSubscription.callsArgWith(1, null, { user, subscriptionDetails, recurlyTokenIds, userExists: false, account: { accountCode: 'xx' }, billingInfo: { token_id: 'abc' }, subscription: this.subscription }) return (this.call = callback => { return this.RecurlyWrapper._createPaypalSubscription( this.user, this.subscriptionDetails, this.recurlyTokenIds, callback ) }) }) afterEach(function() { this.checkAccountExists.restore() this.createAccount.restore() this.createBillingInfo.restore() this.setAddress.restore() return this.createSubscription.restore() }) it('should not produce an error', function(done) { return this.call((err, sub) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should produce a subscription object', function(done) { return this.call((err, sub) => { expect(sub).to.not.equal(null) expect(sub).to.equal(this.subscription) return done() }) }) it('should call each of the paypal stages', function(done) { return this.call((err, sub) => { this.checkAccountExists.callCount.should.equal(1) this.createAccount.callCount.should.equal(1) this.createBillingInfo.callCount.should.equal(1) this.setAddress.callCount.should.equal(1) this.createSubscription.callCount.should.equal(1) return done() }) }) describe('when one of the paypal stages produces an error', function() { beforeEach(function() { return this.createAccount.callsArgWith(1, new Error('woops')) }) it('should produce an error', function(done) { return this.call((err, sub) => { expect(err).to.be.instanceof(Error) return done() }) }) it('should stop calling the paypal stages after the error', function(done) { return this.call((err, sub) => { this.checkAccountExists.callCount.should.equal(1) this.createAccount.callCount.should.equal(1) this.createBillingInfo.callCount.should.equal(0) this.setAddress.callCount.should.equal(0) this.createSubscription.callCount.should.equal(0) return done() }) }) }) }) describe('paypal actions', function() { beforeEach(function() { this.apiRequest = sinon.stub(this.RecurlyWrapper, 'apiRequest') this._parseAccountXml = sinon.spy(this.RecurlyWrapper, '_parseAccountXml') this._parseBillingInfoXml = sinon.spy( this.RecurlyWrapper, '_parseBillingInfoXml' ) this._parseSubscriptionXml = sinon.spy( this.RecurlyWrapper, '_parseSubscriptionXml' ) return (this.cache = { user: (this.user = { _id: 'some_id', email: 'foo@bar.com', first_name: 'Foo', last_name: 'Bar' }), recurlyTokenIds: (this.recurlyTokenIds = { billing: 'a-token-id', threeDSecureActionResult: 'a-3d-token-id' }), subscriptionDetails: (this.subscriptionDetails = { currencyCode: 'EUR', plan_code: 'some_plan_code', coupon_code: '', isPaypal: true, address: { address1: 'addr_one', address2: 'addr_two', city: 'some_city', country: 'some_country', state: 'some_state', zip: 'some_zip' } }) }) }) afterEach(function() { this.apiRequest.restore() this._parseAccountXml.restore() this._parseBillingInfoXml.restore() return this._parseSubscriptionXml.restore() }) describe('_paypal.checkAccountExists', function() { beforeEach(function() { return (this.call = callback => { return this.RecurlyWrapper._paypal.checkAccountExists( this.cache, callback ) }) }) describe('when the account exists', function() { beforeEach(function() { const resultXml = 'abc' return this.apiRequest.callsArgWith( 1, null, { statusCode: 200 }, resultXml ) }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(1) return done() }) }) it('should call _parseAccountXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseAccountXml.callCount.should.equal(1) return done() }) }) it('should add the account to the cumulative result', function(done) { return this.call((err, result) => { expect(result.account).to.not.equal(null) expect(result.account).to.not.equal(undefined) expect(result.account).to.deep.equal({ account_code: 'abc' }) return done() }) }) it('should set userExists to true', function(done) { return this.call((err, result) => { expect(result.userExists).to.equal(true) return done() }) }) }) describe('when the account does not exist', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, null, { statusCode: 404 }, '') }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(1) this.apiRequest.firstCall.args[0].method.should.equal('GET') return done() }) }) it('should not call _parseAccountXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseAccountXml.callCount.should.equal(0) return done() }) }) it('should not add the account to result', function(done) { return this.call((err, result) => { expect(result.account).to.equal(undefined) return done() }) }) it('should set userExists to false', function(done) { return this.call((err, result) => { expect(result.userExists).to.equal(false) return done() }) }) }) describe('when apiRequest produces an error', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, new Error('woops'), { statusCode: 500 }) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) describe('_paypal.createAccount', function() { beforeEach(function() { return (this.call = callback => { return this.RecurlyWrapper._paypal.createAccount(this.cache, callback) }) }) describe('when address is missing from subscriptionDetails', function() { beforeEach(function() { return (this.cache.subscriptionDetails.address = null) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) describe('when account already exists', function() { beforeEach(function() { this.cache.userExists = true return (this.cache.account = { account_code: 'abc' }) }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should produce cache object', function(done) { return this.call((err, result) => { expect(result).to.deep.equal(this.cache) expect(result.account).to.deep.equal({ account_code: 'abc' }) return done() }) }) it('should not call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(0) return done() }) }) it('should not call _parseAccountXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseAccountXml.callCount.should.equal(0) return done() }) }) }) describe('when account does not exist', function() { beforeEach(function() { this.cache.userExists = false const resultXml = 'abc' return this.apiRequest.callsArgWith( 1, null, { statusCode: 200 }, resultXml ) }) it('sends correct XML', function(done) { return this.call((err, result) => { const { body } = this.apiRequest.lastCall.args[0] expect(body).to.equal(`\ some_id foo@bar.com Foo Bar
addr_one addr_two some_city some_state some_zip some_country
\ `) return done() }) }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(1) this.apiRequest.firstCall.args[0].method.should.equal('POST') return done() }) }) it('should call _parseAccountXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseAccountXml.callCount.should.equal(1) return done() }) }) describe('when apiRequest produces an error', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, new Error('woops'), { statusCode: 500 }) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) }) describe('_paypal.createBillingInfo', function() { beforeEach(function() { this.cache.account = { account_code: 'abc' } return (this.call = callback => { return this.RecurlyWrapper._paypal.createBillingInfo( this.cache, callback ) }) }) describe('when account_code is missing from cache', function() { beforeEach(function() { return (this.cache.account.account_code = null) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) describe('when all goes well', function() { beforeEach(function() { const resultXml = '
1' return this.apiRequest.callsArgWith( 1, null, { statusCode: 200 }, resultXml ) }) it('sends correct XML', function(done) { return this.call((err, result) => { const { body } = this.apiRequest.lastCall.args[0] expect(body).to.equal(`\ a-token-id \ `) return done() }) }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(1) this.apiRequest.firstCall.args[0].method.should.equal('POST') return done() }) }) it('should call _parseBillingInfoXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseBillingInfoXml.callCount.should.equal(1) return done() }) }) it('should set billingInfo on cache', function(done) { return this.call((err, result) => { expect(result.billingInfo).to.deep.equal({ a: '1' }) return done() }) }) }) describe('when apiRequest produces an error', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, new Error('woops'), { statusCode: 500 }) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) describe('_paypal.setAddress', function() { beforeEach(function() { this.cache.account = { account_code: 'abc' } this.cache.billingInfo = {} return (this.call = callback => { return this.RecurlyWrapper._paypal.setAddress(this.cache, callback) }) }) describe('when account_code is missing from cache', function() { beforeEach(function() { return (this.cache.account.account_code = null) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) describe('when address is missing from subscriptionDetails', function() { beforeEach(function() { return (this.cache.subscriptionDetails.address = null) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) describe('when all goes well', function() { beforeEach(function() { const resultXml = 'London' return this.apiRequest.callsArgWith( 1, null, { statusCode: 200 }, resultXml ) }) it('sends correct XML', function(done) { return this.call((err, result) => { const { body } = this.apiRequest.lastCall.args[0] expect(body).to.equal(`\ addr_one addr_two some_city some_state some_zip some_country \ `) return done() }) }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(1) this.apiRequest.firstCall.args[0].method.should.equal('PUT') return done() }) }) it('should call _parseBillingInfoXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseBillingInfoXml.callCount.should.equal(1) return done() }) }) it('should set billingInfo on cache', function(done) { return this.call((err, result) => { expect(result.billingInfo).to.deep.equal({ city: 'London' }) return done() }) }) }) describe('when apiRequest produces an error', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, new Error('woops'), { statusCode: 500 }) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) describe('_paypal.createSubscription', function() { beforeEach(function() { this.cache.account = { account_code: 'abc' } this.cache.billingInfo = {} return (this.call = callback => { return this.RecurlyWrapper._paypal.createSubscription( this.cache, callback ) }) }) describe('when all goes well', function() { beforeEach(function() { const resultXml = '1' return this.apiRequest.callsArgWith( 1, null, { statusCode: 200 }, resultXml ) }) it('sends correct XML', function(done) { return this.call((err, result) => { const { body } = this.apiRequest.lastCall.args[0] expect(body).to.equal(`\ some_plan_code EUR some_id \ `) return done() }) }) it('should not produce an error', function(done) { return this.call((err, result) => { expect(err).to.not.be.instanceof(Error) return done() }) }) it('should call apiRequest', function(done) { return this.call((err, result) => { this.apiRequest.callCount.should.equal(1) this.apiRequest.firstCall.args[0].method.should.equal('POST') return done() }) }) it('should call _parseSubscriptionXml', function(done) { return this.call((err, result) => { this.RecurlyWrapper._parseSubscriptionXml.callCount.should.equal(1) return done() }) }) it('should set subscription on cache', function(done) { return this.call((err, result) => { expect(result.subscription).to.deep.equal({ a: '1' }) return done() }) }) }) describe('when apiRequest produces an error', function() { beforeEach(function() { return this.apiRequest.callsArgWith(1, new Error('woops'), { statusCode: 500 }) }) it('should produce an error', function(done) { return this.call((err, result) => { expect(err).to.be.instanceof(Error) return done() }) }) }) }) }) describe('listAccountActiveSubscriptions', function() { beforeEach(function() { this.user_id = 'mock-user-id' this.callback = sinon.stub() this.RecurlyWrapper.apiRequest = sinon .stub() .yields( null, (this.response = { mock: 'response' }), (this.body = '') ) return (this.RecurlyWrapper._parseSubscriptionsXml = sinon .stub() .yields(null, (this.subscriptions = ['mock', 'subscriptions']))) }) describe('with an account', function() { beforeEach(function() { return this.RecurlyWrapper.listAccountActiveSubscriptions( this.user_id, this.callback ) }) it('should send a request to Recurly', function() { return this.RecurlyWrapper.apiRequest .calledWith({ url: `accounts/${this.user_id}/subscriptions`, qs: { state: 'active' }, expect404: true }) .should.equal(true) }) it('should return the subscriptions', function() { return this.callback .calledWith(null, this.subscriptions) .should.equal(true) }) }) describe('without an account', function() { beforeEach(function() { this.response.statusCode = 404 return this.RecurlyWrapper.listAccountActiveSubscriptions( this.user_id, this.callback ) }) it('should return an empty array of subscriptions', function() { return this.callback.calledWith(null, []).should.equal(true) }) }) }) })