SubscriptionControllerTests.js 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const { assert, expect } = require('chai')
  4. const MockRequest = require('../helpers/MockRequest')
  5. const MockResponse = require('../helpers/MockResponse')
  6. const modulePath =
  7. '../../../../app/src/Features/Subscription/SubscriptionController'
  8. const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
  9. const mockSubscriptions = {
  10. 'subscription-123-active': {
  11. uuid: 'subscription-123-active',
  12. plan: {
  13. name: 'Gold',
  14. plan_code: 'gold',
  15. },
  16. current_period_ends_at: new Date(),
  17. state: 'active',
  18. unit_amount_in_cents: 999,
  19. account: {
  20. account_code: 'user-123',
  21. },
  22. },
  23. }
  24. describe('SubscriptionController', function () {
  25. beforeEach(function () {
  26. this.user = {
  27. email: 'tom@yahoo.com',
  28. _id: 'one',
  29. signUpDate: new Date('2000-10-01'),
  30. emails: [{ email: 'tom@yahoo.com', confirmedAt: new Date('2000-10-02') }],
  31. }
  32. this.activeRecurlySubscription =
  33. mockSubscriptions['subscription-123-active']
  34. this.SessionManager = {
  35. getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
  36. getLoggedInUserId: sinon.stub().returns(this.user._id),
  37. getSessionUser: sinon.stub().returns(this.user),
  38. isUserLoggedIn: sinon.stub().returns(true),
  39. }
  40. this.SubscriptionHandler = {
  41. createSubscription: sinon.stub().callsArgWith(3),
  42. updateSubscription: sinon.stub().callsArgWith(3),
  43. reactivateSubscription: sinon.stub().callsArgWith(1),
  44. cancelSubscription: sinon.stub().callsArgWith(1),
  45. syncSubscription: sinon.stub().yields(),
  46. attemptPaypalInvoiceCollection: sinon.stub().yields(),
  47. startFreeTrial: sinon.stub(),
  48. promises: {
  49. createSubscription: sinon.stub().resolves(),
  50. updateSubscription: sinon.stub().resolves(),
  51. reactivateSubscription: sinon.stub().resolves(),
  52. cancelSubscription: sinon.stub().resolves(),
  53. syncSubscription: sinon.stub().resolves(),
  54. attemptPaypalInvoiceCollection: sinon.stub().resolves(),
  55. startFreeTrial: sinon.stub().resolves(),
  56. },
  57. }
  58. this.LimitationsManager = {
  59. hasPaidSubscription: sinon.stub(),
  60. userHasV1OrV2Subscription: sinon.stub(),
  61. userHasV2Subscription: sinon.stub(),
  62. promises: {
  63. hasPaidSubscription: sinon.stub().resolves(),
  64. userHasV1OrV2Subscription: sinon.stub().resolves(),
  65. userHasV2Subscription: sinon.stub().resolves(),
  66. },
  67. }
  68. this.SubscriptionViewModelBuilder = {
  69. buildUsersSubscriptionViewModel: sinon.stub().callsArgWith(1, null, {}),
  70. buildPlansList: sinon.stub(),
  71. promises: {
  72. buildUsersSubscriptionViewModel: sinon.stub().resolves({}),
  73. getBestSubscription: sinon.stub().resolves({}),
  74. },
  75. buildPlansListForSubscriptionDash: sinon
  76. .stub()
  77. .returns({ plans: [], planCodesChangingAtTermEnd: [] }),
  78. }
  79. this.settings = {
  80. coupon_codes: {
  81. upgradeToAnnualPromo: {
  82. student: 'STUDENTCODEHERE',
  83. collaborator: 'COLLABORATORCODEHERE',
  84. },
  85. },
  86. groupPlanModalOptions: {
  87. plan_codes: [],
  88. currencies: [
  89. {
  90. display: 'GBP (£)',
  91. code: 'GBP',
  92. },
  93. ],
  94. sizes: ['42'],
  95. usages: [{ code: 'foo', display: 'Foo' }],
  96. },
  97. apis: {
  98. recurly: {
  99. subdomain: 'sl',
  100. },
  101. },
  102. siteUrl: 'http://de.overleaf.dev:3000',
  103. }
  104. this.AuthorizationManager = {
  105. promises: {
  106. isUserSiteAdmin: sinon.stub().resolves(false),
  107. },
  108. }
  109. this.GeoIpLookup = {
  110. isValidCurrencyParam: sinon.stub().returns(true),
  111. getCurrencyCode: sinon.stub().yields('USD', 'US'),
  112. promises: {
  113. getCurrencyCode: sinon.stub().resolves({
  114. countryCode: 'US',
  115. currencyCode: 'USD',
  116. }),
  117. },
  118. }
  119. this.UserGetter = {
  120. getUser: sinon.stub().callsArgWith(2, null, this.user),
  121. promises: {
  122. getUser: sinon.stub().resolves(this.user),
  123. },
  124. }
  125. this.SplitTestV2Hander = {
  126. promises: {
  127. getAssignment: sinon.stub().resolves({ variant: 'default' }),
  128. isSplitTestActive: sinon.stub().resolves(true),
  129. },
  130. }
  131. this.SubscriptionHelper = {
  132. generateInitialLocalizedGroupPrice: sinon.stub(),
  133. }
  134. this.Features = {
  135. hasFeature: sinon.stub().returns(false),
  136. }
  137. this.SubscriptionController = SandboxedModule.require(modulePath, {
  138. requires: {
  139. '../Authorization/AuthorizationManager': this.AuthorizationManager,
  140. '../SplitTests/SplitTestHandler': this.SplitTestV2Hander,
  141. '../Authentication/SessionManager': this.SessionManager,
  142. './SubscriptionHandler': this.SubscriptionHandler,
  143. './SubscriptionHelper': this.SubscriptionHelper,
  144. './SubscriptionViewModelBuilder': this.SubscriptionViewModelBuilder,
  145. './LimitationsManager': this.LimitationsManager,
  146. '../../infrastructure/GeoIpLookup': this.GeoIpLookup,
  147. '@overleaf/settings': this.settings,
  148. '../User/UserGetter': this.UserGetter,
  149. './RecurlyWrapper': (this.RecurlyWrapper = {
  150. updateAccountEmailAddress: sinon.stub().yields(),
  151. }),
  152. './RecurlyEventHandler': {
  153. sendRecurlyAnalyticsEvent: sinon.stub().resolves(),
  154. },
  155. './FeaturesUpdater': (this.FeaturesUpdater = {}),
  156. './GroupPlansData': (this.GroupPlansData = {}),
  157. './V1SubscriptionManager': (this.V1SubscriptionManager = {}),
  158. '../Errors/HttpErrorHandler': (this.HttpErrorHandler = {
  159. unprocessableEntity: sinon.stub().callsFake((req, res, message) => {
  160. res.status(422)
  161. res.json({ message })
  162. }),
  163. }),
  164. './Errors': SubscriptionErrors,
  165. '../Analytics/AnalyticsManager': (this.AnalyticsManager = {
  166. recordEventForUser: sinon.stub(),
  167. recordEventForSession: sinon.stub(),
  168. setUserPropertyForUser: sinon.stub(),
  169. }),
  170. '../../infrastructure/Modules': {
  171. promises: { hooks: { fire: sinon.stub().resolves() } },
  172. },
  173. '../../infrastructure/Features': this.Features,
  174. '../../util/currency': (this.currency = {
  175. formatCurrencyLocalized: sinon.stub(),
  176. }),
  177. },
  178. })
  179. this.res = new MockResponse()
  180. this.req = new MockRequest()
  181. this.req.body = {}
  182. this.req.query = { planCode: '123123' }
  183. this.stubbedCurrencyCode = 'GBP'
  184. })
  185. describe('plansPage', function () {
  186. beforeEach(function () {
  187. this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
  188. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  189. currencyCode: this.stubbedCurrencyCode,
  190. })
  191. })
  192. describe('ip override', function () {
  193. beforeEach(function () {
  194. this.req.ip = '1.2.3.4'
  195. this.req.query = { ip: '5.6.7.8' }
  196. this.GeoIpLookup.promises.getCurrencyCode.withArgs('1.2.3.4').resolves({
  197. currencyCode: 'GBP',
  198. })
  199. this.GeoIpLookup.promises.getCurrencyCode.withArgs('5.6.7.8').resolves({
  200. currencyCode: 'USD',
  201. })
  202. })
  203. it('should ignore override for non admin', function (done) {
  204. this.res.render = (page, opts) => {
  205. opts.recommendedCurrency.should.equal('GBP')
  206. done()
  207. }
  208. this.AuthorizationManager.promises.isUserSiteAdmin.resolves(false)
  209. this.SubscriptionController.plansPage(this.req, this.res)
  210. })
  211. it('should accept override for admin', function (done) {
  212. this.res.render = (page, opts) => {
  213. opts.recommendedCurrency.should.equal('USD')
  214. done()
  215. }
  216. this.AuthorizationManager.promises.isUserSiteAdmin.resolves(true)
  217. this.SubscriptionController.plansPage(this.req, this.res)
  218. })
  219. })
  220. describe('groupPlanModal data', function () {
  221. it('should pass local currency if valid', function (done) {
  222. this.res.render = (page, opts) => {
  223. page.should.equal('subscriptions/plans')
  224. opts.groupPlanModalDefaults.currency.should.equal('GBP')
  225. done()
  226. }
  227. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  228. currencyCode: 'GBP',
  229. })
  230. this.SubscriptionController.plansPage(this.req, this.res)
  231. })
  232. it('should fallback to USD when valid', function (done) {
  233. this.res.render = (page, opts) => {
  234. page.should.equal('subscriptions/plans')
  235. opts.groupPlanModalDefaults.currency.should.equal('USD')
  236. done()
  237. }
  238. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  239. currencyCode: 'FOO',
  240. })
  241. this.SubscriptionController.plansPage(this.req, this.res)
  242. })
  243. it('should pass valid options for group plan modal and discard invalid', function (done) {
  244. this.res.render = (page, opts) => {
  245. page.should.equal('subscriptions/plans')
  246. opts.groupPlanModalDefaults.size.should.equal('42')
  247. opts.groupPlanModalDefaults.plan_code.should.equal('collaborator')
  248. opts.groupPlanModalDefaults.currency.should.equal('GBP')
  249. opts.groupPlanModalDefaults.usage.should.equal('foo')
  250. done()
  251. }
  252. this.GeoIpLookup.isValidCurrencyParam.returns(false)
  253. this.req.query = {
  254. number: '42',
  255. currency: 'ABC',
  256. plan: 'does-not-exist',
  257. usage: 'foo',
  258. }
  259. this.SubscriptionController.plansPage(this.req, this.res)
  260. })
  261. })
  262. describe('showInrGeoBanner data', function () {
  263. it('should return true for Indian Users', function (done) {
  264. this.res.render = (page, opts) => {
  265. page.should.equal('subscriptions/plans')
  266. opts.showInrGeoBanner.should.equal(true)
  267. done()
  268. }
  269. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  270. countryCode: 'IN',
  271. })
  272. this.SubscriptionController.plansPage(this.req, this.res)
  273. })
  274. it('should return false for US Users', function (done) {
  275. this.res.render = (page, opts) => {
  276. page.should.equal('subscriptions/plans')
  277. opts.showInrGeoBanner.should.equal(false)
  278. done()
  279. }
  280. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  281. countryCode: 'US',
  282. })
  283. this.SubscriptionController.plansPage(this.req, this.res)
  284. })
  285. })
  286. describe('showBrlGeoBanner data', function () {
  287. it('should return true for Brazilian users', function (done) {
  288. this.res.render = (page, opts) => {
  289. page.should.equal('subscriptions/plans')
  290. opts.showBrlGeoBanner.should.equal(true)
  291. done()
  292. }
  293. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  294. countryCode: 'BR',
  295. })
  296. this.SubscriptionController.plansPage(this.req, this.res)
  297. })
  298. it('should return false for US users', function (done) {
  299. this.res.render = (page, opts) => {
  300. page.should.equal('subscriptions/plans')
  301. opts.showBrlGeoBanner.should.equal(false)
  302. done()
  303. }
  304. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  305. countryCode: 'US',
  306. })
  307. this.SubscriptionController.plansPage(this.req, this.res)
  308. })
  309. })
  310. describe('showLATAMBanner', function () {
  311. describe('latam variant', function () {
  312. beforeEach(function () {
  313. this.SplitTestV2Hander.promises.getAssignment
  314. .withArgs(this.req, this.res, 'geo-pricing-latam-v2')
  315. .resolves({
  316. variant: 'latam',
  317. })
  318. })
  319. it('should return true for Mexican users', function (done) {
  320. this.res.render = (page, opts) => {
  321. page.should.equal('subscriptions/plans')
  322. opts.showLATAMBanner.should.equal(true)
  323. done()
  324. }
  325. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  326. countryCode: 'MX',
  327. })
  328. this.SubscriptionController.plansPage(this.req, this.res)
  329. })
  330. it('should return true for Colombian users', function (done) {
  331. this.res.render = (page, opts) => {
  332. page.should.equal('subscriptions/plans')
  333. opts.showLATAMBanner.should.equal(true)
  334. done()
  335. }
  336. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  337. countryCode: 'CO',
  338. })
  339. this.SubscriptionController.plansPage(this.req, this.res)
  340. })
  341. it('should return true for Chilean users', function (done) {
  342. this.res.render = (page, opts) => {
  343. page.should.equal('subscriptions/plans')
  344. opts.showLATAMBanner.should.equal(true)
  345. done()
  346. }
  347. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  348. countryCode: 'CL',
  349. })
  350. this.SubscriptionController.plansPage(this.req, this.res)
  351. })
  352. it('should return true for Peruvian users', function (done) {
  353. this.res.render = (page, opts) => {
  354. page.should.equal('subscriptions/plans')
  355. opts.showLATAMBanner.should.equal(true)
  356. done()
  357. }
  358. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  359. countryCode: 'PE',
  360. })
  361. this.SubscriptionController.plansPage(this.req, this.res)
  362. })
  363. it('should return true for US users', function (done) {
  364. this.res.render = (page, opts) => {
  365. page.should.equal('subscriptions/plans')
  366. opts.showLATAMBanner.should.equal(false)
  367. done()
  368. }
  369. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  370. countryCode: 'US',
  371. })
  372. this.SubscriptionController.plansPage(this.req, this.res)
  373. })
  374. })
  375. describe('default variant', function () {
  376. beforeEach(function () {
  377. this.SplitTestV2Hander.promises.getAssignment
  378. .withArgs(this.req, this.res, 'geo-pricing-latam-v2')
  379. .resolves({
  380. variant: 'default',
  381. })
  382. })
  383. it('should return false', function (done) {
  384. this.res.render = (page, opts) => {
  385. page.should.equal('subscriptions/plans')
  386. opts.showLATAMBanner.should.equal(false)
  387. done()
  388. }
  389. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  390. countryCode: 'MX',
  391. })
  392. this.SubscriptionController.plansPage(this.req, this.res)
  393. })
  394. })
  395. })
  396. describe('localCcyAssignment', function () {
  397. it('uses formatCurrencyLocalized when variant is enabled', function (done) {
  398. this.SplitTestV2Hander.promises.getAssignment
  399. .withArgs(this.req, this.res, 'local-ccy-format-v2')
  400. .resolves({
  401. variant: 'enabled',
  402. })
  403. this.res.render = (page, opts) => {
  404. expect(opts.formatCurrency).to.equal(
  405. this.currency.formatCurrencyLocalized
  406. )
  407. done()
  408. }
  409. this.SubscriptionController.plansPage(this.req, this.res)
  410. })
  411. it('uses formatCurrencyDefault when variant is default', function (done) {
  412. this.res.render = (page, opts) => {
  413. expect(opts.formatCurrency).to.equal(
  414. this.SubscriptionHelper.formatCurrencyDefault
  415. )
  416. done()
  417. }
  418. this.SubscriptionController.plansPage(this.req, this.res)
  419. })
  420. })
  421. describe('website-redesign-plans test', function () {
  422. beforeEach(function () {
  423. this.req.query = {}
  424. })
  425. describe('"default" variant', function () {
  426. // note: if test is not active, default variant is assigned
  427. beforeEach(function () {
  428. this.SplitTestV2Hander.promises.getAssignment
  429. .withArgs(this.req, this.res, 'website-redesign-plans')
  430. .resolves({
  431. variant: 'default',
  432. })
  433. })
  434. it('renders "default" variant', function (done) {
  435. this.res.render = page => {
  436. page.should.equal('subscriptions/plans')
  437. expect(this.res.redirect).to.not.have.been.called
  438. done()
  439. }
  440. this.SubscriptionController.plansPage(this.req, this.res)
  441. })
  442. })
  443. describe('"new-design" variant', function () {
  444. beforeEach(function () {
  445. this.SplitTestV2Hander.promises.getAssignment
  446. .withArgs(this.req, this.res, 'website-redesign-plans')
  447. .resolves({
  448. variant: 'new-design',
  449. })
  450. })
  451. it('redirects to "new-design" variant', function (done) {
  452. this.res.callback = () => {
  453. expect(this.res.redirect).to.have.been.calledWith(
  454. 302,
  455. '/user/subscription/plans-2'
  456. )
  457. done()
  458. }
  459. this.SubscriptionController.plansPage(this.req, this.res)
  460. })
  461. it('passes query params when redirecting to new design variant', function (done) {
  462. this.req.query = { currency: 'USD' }
  463. this.res.callback = () => {
  464. expect(this.res.redirect).to.have.been.calledWith(
  465. 302,
  466. '/user/subscription/plans-2?currency=USD'
  467. )
  468. done()
  469. }
  470. this.SubscriptionController.plansPage(this.req, this.res)
  471. })
  472. })
  473. describe('"light-design" variant', function () {
  474. beforeEach(function () {
  475. this.SplitTestV2Hander.promises.getAssignment
  476. .withArgs(this.req, this.res, 'website-redesign-plans')
  477. .resolves({
  478. variant: 'light-design',
  479. })
  480. })
  481. it('renders "light-design" variant', function (done) {
  482. this.res.callback = () => {
  483. expect(this.res.redirect).to.have.been.calledWith(
  484. 302,
  485. '/user/subscription/plans-3'
  486. )
  487. done()
  488. }
  489. this.SubscriptionController.plansPage(this.req, this.res)
  490. })
  491. it('passes query params when redirecting to new design variant', function (done) {
  492. this.req.query = { currency: 'USD' }
  493. this.res.callback = () => {
  494. expect(this.res.redirect).to.have.been.calledWith(
  495. 302,
  496. '/user/subscription/plans-3?currency=USD'
  497. )
  498. done()
  499. }
  500. this.SubscriptionController.plansPage(this.req, this.res)
  501. })
  502. })
  503. })
  504. it('should return correct countryCode', function (done) {
  505. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  506. countryCode: 'MX',
  507. })
  508. this.res.render = (page, opts) => {
  509. expect(opts.countryCode).to.equal('MX')
  510. done()
  511. }
  512. this.SubscriptionController.plansPage(this.req, this.res)
  513. })
  514. })
  515. describe('plansPage light touch redesign', function () {
  516. beforeEach(function () {
  517. this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
  518. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  519. currencyCode: this.stubbedCurrencyCode,
  520. })
  521. })
  522. describe('ip override', function () {
  523. beforeEach(function () {
  524. this.req.ip = '1.2.3.4'
  525. this.req.query = { ip: '5.6.7.8' }
  526. this.GeoIpLookup.promises.getCurrencyCode.withArgs('1.2.3.4').resolves({
  527. currencyCode: 'GBP',
  528. })
  529. this.GeoIpLookup.promises.getCurrencyCode.withArgs('5.6.7.8').resolves({
  530. currencyCode: 'USD',
  531. })
  532. })
  533. it('should ignore override for non admin', function (done) {
  534. this.res.render = (page, opts) => {
  535. opts.recommendedCurrency.should.equal('GBP')
  536. done()
  537. }
  538. this.AuthorizationManager.promises.isUserSiteAdmin.resolves(false)
  539. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  540. })
  541. it('should accept override for admin', function (done) {
  542. this.res.render = (page, opts) => {
  543. opts.recommendedCurrency.should.equal('USD')
  544. done()
  545. }
  546. this.AuthorizationManager.promises.isUserSiteAdmin.resolves(true)
  547. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  548. })
  549. })
  550. describe('groupPlanModal data', function () {
  551. it('should pass local currency if valid', function (done) {
  552. this.res.render = (page, opts) => {
  553. page.should.equal('subscriptions/plans-light-design')
  554. opts.groupPlanModalDefaults.currency.should.equal('GBP')
  555. done()
  556. }
  557. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  558. currencyCode: 'GBP',
  559. })
  560. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  561. })
  562. it('should fallback to USD when valid', function (done) {
  563. this.res.render = (page, opts) => {
  564. page.should.equal('subscriptions/plans-light-design')
  565. opts.groupPlanModalDefaults.currency.should.equal('USD')
  566. done()
  567. }
  568. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  569. currencyCode: 'FOO',
  570. })
  571. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  572. })
  573. it('should pass valid options for group plan modal and discard invalid', function (done) {
  574. this.res.render = (page, opts) => {
  575. page.should.equal('subscriptions/plans-light-design')
  576. opts.groupPlanModalDefaults.size.should.equal('42')
  577. opts.groupPlanModalDefaults.plan_code.should.equal('collaborator')
  578. opts.groupPlanModalDefaults.currency.should.equal('GBP')
  579. opts.groupPlanModalDefaults.usage.should.equal('foo')
  580. done()
  581. }
  582. this.GeoIpLookup.isValidCurrencyParam.returns(false)
  583. this.req.query = {
  584. number: '42',
  585. currency: 'ABC',
  586. plan: 'does-not-exist',
  587. usage: 'foo',
  588. }
  589. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  590. })
  591. })
  592. describe('showInrGeoBanner data', function () {
  593. it('should return true for Indian Users', function (done) {
  594. this.res.render = (page, opts) => {
  595. page.should.equal('subscriptions/plans-light-design')
  596. opts.showInrGeoBanner.should.equal(true)
  597. done()
  598. }
  599. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  600. countryCode: 'IN',
  601. })
  602. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  603. })
  604. it('should return false for US Users', function (done) {
  605. this.res.render = (page, opts) => {
  606. page.should.equal('subscriptions/plans-light-design')
  607. opts.showInrGeoBanner.should.equal(false)
  608. done()
  609. }
  610. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  611. countryCode: 'US',
  612. })
  613. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  614. })
  615. })
  616. describe('showBrlGeoBanner data', function () {
  617. it('should return true for Brazilian users', function (done) {
  618. this.res.render = (page, opts) => {
  619. page.should.equal('subscriptions/plans-light-design')
  620. opts.showBrlGeoBanner.should.equal(true)
  621. done()
  622. }
  623. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  624. countryCode: 'BR',
  625. })
  626. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  627. })
  628. it('should return false for US users', function (done) {
  629. this.res.render = (page, opts) => {
  630. page.should.equal('subscriptions/plans-light-design')
  631. opts.showBrlGeoBanner.should.equal(false)
  632. done()
  633. }
  634. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  635. countryCode: 'US',
  636. })
  637. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  638. })
  639. })
  640. describe('showLATAMBanner', function () {
  641. describe('latam variant', function () {
  642. beforeEach(function () {
  643. this.SplitTestV2Hander.promises.getAssignment
  644. .withArgs(this.req, this.res, 'geo-pricing-latam-v2')
  645. .resolves({
  646. variant: 'latam',
  647. })
  648. })
  649. it('should return true for Mexican users', function (done) {
  650. this.res.render = (page, opts) => {
  651. page.should.equal('subscriptions/plans-light-design')
  652. opts.showLATAMBanner.should.equal(true)
  653. done()
  654. }
  655. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  656. countryCode: 'MX',
  657. })
  658. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  659. })
  660. it('should return true for Colombian users', function (done) {
  661. this.res.render = (page, opts) => {
  662. page.should.equal('subscriptions/plans-light-design')
  663. opts.showLATAMBanner.should.equal(true)
  664. done()
  665. }
  666. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  667. countryCode: 'CO',
  668. })
  669. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  670. })
  671. it('should return true for Chilean users', function (done) {
  672. this.res.render = (page, opts) => {
  673. page.should.equal('subscriptions/plans-light-design')
  674. opts.showLATAMBanner.should.equal(true)
  675. done()
  676. }
  677. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  678. countryCode: 'CL',
  679. })
  680. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  681. })
  682. it('should return true for Peruvian users', function (done) {
  683. this.res.render = (page, opts) => {
  684. page.should.equal('subscriptions/plans-light-design')
  685. opts.showLATAMBanner.should.equal(true)
  686. done()
  687. }
  688. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  689. countryCode: 'PE',
  690. })
  691. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  692. })
  693. it('should return true for US users', function (done) {
  694. this.res.render = (page, opts) => {
  695. page.should.equal('subscriptions/plans-light-design')
  696. opts.showLATAMBanner.should.equal(false)
  697. done()
  698. }
  699. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  700. countryCode: 'US',
  701. })
  702. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  703. })
  704. })
  705. describe('default variant', function () {
  706. beforeEach(function () {
  707. this.SplitTestV2Hander.promises.getAssignment
  708. .withArgs(this.req, this.res, 'geo-pricing-latam-v2')
  709. .resolves({
  710. variant: 'default',
  711. })
  712. })
  713. it('should return false', function (done) {
  714. this.res.render = (page, opts) => {
  715. page.should.equal('subscriptions/plans-light-design')
  716. opts.showLATAMBanner.should.equal(false)
  717. done()
  718. }
  719. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  720. countryCode: 'MX',
  721. })
  722. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  723. })
  724. })
  725. })
  726. describe('localCcyAssignment', function () {
  727. it('uses formatCurrencyLocalized when variant is enabled', function (done) {
  728. this.SplitTestV2Hander.promises.getAssignment
  729. .withArgs(this.req, this.res, 'local-ccy-format-v2')
  730. .resolves({
  731. variant: 'enabled',
  732. })
  733. this.res.render = (page, opts) => {
  734. expect(opts.formatCurrency).to.equal(
  735. this.currency.formatCurrencyLocalized
  736. )
  737. done()
  738. }
  739. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  740. })
  741. it('uses formatCurrencyDefault when variant is default', function (done) {
  742. this.res.render = (page, opts) => {
  743. expect(opts.formatCurrency).to.equal(
  744. this.SubscriptionHelper.formatCurrencyDefault
  745. )
  746. done()
  747. }
  748. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  749. })
  750. })
  751. it('should return correct countryCode', function (done) {
  752. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  753. countryCode: 'MX',
  754. })
  755. this.res.render = (page, opts) => {
  756. expect(opts.countryCode).to.equal('MX')
  757. done()
  758. }
  759. this.SubscriptionController.plansPageLightDesign(this.req, this.res)
  760. })
  761. })
  762. describe('interstitialPaymentPage', function () {
  763. beforeEach(function () {
  764. this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
  765. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  766. currencyCode: this.stubbedCurrencyCode,
  767. })
  768. })
  769. describe('with a user without subscription', function () {
  770. it('should render the interstitial payment page', function (done) {
  771. this.res.render = (page, opts) => {
  772. page.should.equal('subscriptions/interstitial-payment')
  773. done()
  774. }
  775. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  776. })
  777. })
  778. describe('with a user with subscription', function () {
  779. it('should redirect to the subscription dashboard', function (done) {
  780. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
  781. true
  782. )
  783. this.res.redirect = url => {
  784. url.should.equal('/user/subscription?hasSubscription=true')
  785. done()
  786. }
  787. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  788. })
  789. })
  790. describe('showInrGeoBanner data', function () {
  791. it('should return true for Indian users', function (done) {
  792. this.res.render = (page, opts) => {
  793. page.should.equal('subscriptions/interstitial-payment')
  794. opts.showInrGeoBanner.should.equal(true)
  795. done()
  796. }
  797. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  798. countryCode: 'IN',
  799. })
  800. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  801. })
  802. it('should be false for US users', function (done) {
  803. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  804. countryCode: 'US',
  805. })
  806. this.res.render = (page, opts) => {
  807. page.should.equal('subscriptions/interstitial-payment')
  808. opts.showInrGeoBanner.should.equal(false)
  809. done()
  810. }
  811. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  812. })
  813. })
  814. describe('showBrlGeoBanner data', function () {
  815. it('should return true for Brazilian users', function (done) {
  816. this.res.render = (page, opts) => {
  817. page.should.equal('subscriptions/interstitial-payment')
  818. opts.showBrlGeoBanner.should.equal(true)
  819. done()
  820. }
  821. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  822. countryCode: 'BR',
  823. })
  824. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  825. })
  826. it('should return false for US users', function (done) {
  827. this.res.render = (page, opts) => {
  828. page.should.equal('subscriptions/interstitial-payment')
  829. opts.showBrlGeoBanner.should.equal(false)
  830. done()
  831. }
  832. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  833. countryCode: 'US',
  834. })
  835. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  836. })
  837. })
  838. describe('showLATAMBanner', function () {
  839. describe('latam variant', function () {
  840. beforeEach(function () {
  841. this.SplitTestV2Hander.promises.getAssignment
  842. .withArgs(this.req, this.res, 'geo-pricing-latam-v2')
  843. .resolves({
  844. variant: 'latam',
  845. })
  846. })
  847. it('should return true for Mexican users', function (done) {
  848. this.res.render = (page, opts) => {
  849. page.should.equal('subscriptions/interstitial-payment')
  850. opts.showLATAMBanner.should.equal(true)
  851. done()
  852. }
  853. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  854. countryCode: 'MX',
  855. })
  856. this.SubscriptionController.interstitialPaymentPage(
  857. this.req,
  858. this.res
  859. )
  860. })
  861. it('should return true for Colombian users', function (done) {
  862. this.res.render = (page, opts) => {
  863. page.should.equal('subscriptions/interstitial-payment')
  864. opts.showLATAMBanner.should.equal(true)
  865. done()
  866. }
  867. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  868. countryCode: 'CO',
  869. })
  870. this.SubscriptionController.interstitialPaymentPage(
  871. this.req,
  872. this.res
  873. )
  874. })
  875. it('should return true for Chilean users', function (done) {
  876. this.res.render = (page, opts) => {
  877. page.should.equal('subscriptions/interstitial-payment')
  878. opts.showLATAMBanner.should.equal(true)
  879. done()
  880. }
  881. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  882. countryCode: 'CL',
  883. })
  884. this.SubscriptionController.interstitialPaymentPage(
  885. this.req,
  886. this.res
  887. )
  888. })
  889. it('should return true for Peruvian users', function (done) {
  890. this.res.render = (page, opts) => {
  891. page.should.equal('subscriptions/interstitial-payment')
  892. opts.showLATAMBanner.should.equal(true)
  893. done()
  894. }
  895. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  896. countryCode: 'PE',
  897. })
  898. this.SubscriptionController.interstitialPaymentPage(
  899. this.req,
  900. this.res
  901. )
  902. })
  903. it('should return true for US users', function (done) {
  904. this.res.render = (page, opts) => {
  905. page.should.equal('subscriptions/interstitial-payment')
  906. opts.showLATAMBanner.should.equal(false)
  907. done()
  908. }
  909. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  910. countryCode: 'US',
  911. })
  912. this.SubscriptionController.interstitialPaymentPage(
  913. this.req,
  914. this.res
  915. )
  916. })
  917. })
  918. describe('default variant', function () {
  919. beforeEach(function () {
  920. this.SplitTestV2Hander.promises.getAssignment
  921. .withArgs(this.req, this.res, 'website-redesign-plan')
  922. .resolves({
  923. variant: 'default',
  924. })
  925. })
  926. it('should return false', function (done) {
  927. this.res.render = (page, opts) => {
  928. page.should.equal('subscriptions/interstitial-payment')
  929. opts.showLATAMBanner.should.equal(false)
  930. done()
  931. }
  932. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  933. countryCode: 'MX',
  934. })
  935. this.SubscriptionController.interstitialPaymentPage(
  936. this.req,
  937. this.res
  938. )
  939. })
  940. })
  941. })
  942. it('should return correct countryCode', function (done) {
  943. this.GeoIpLookup.promises.getCurrencyCode.resolves({
  944. countryCode: 'MX',
  945. })
  946. this.res.render = (page, opts) => {
  947. expect(opts.countryCode).to.equal('MX')
  948. done()
  949. }
  950. this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
  951. })
  952. })
  953. describe('successfulSubscription', function () {
  954. it('without a personal subscription', function (done) {
  955. this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel.resolves(
  956. {}
  957. )
  958. this.res.redirect = url => {
  959. url.should.equal('/user/subscription/plans')
  960. done()
  961. }
  962. this.SubscriptionController.successfulSubscription(this.req, this.res)
  963. })
  964. it('with a personal subscription', function (done) {
  965. this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel.resolves(
  966. {
  967. personalSubscription: 'foo',
  968. }
  969. )
  970. this.res.render = (url, variables) => {
  971. url.should.equal('subscriptions/successful-subscription-react')
  972. assert.deepEqual(variables, {
  973. title: 'thank_you',
  974. personalSubscription: 'foo',
  975. postCheckoutRedirect: undefined,
  976. user: this.user,
  977. })
  978. done()
  979. }
  980. this.SubscriptionController.successfulSubscription(this.req, this.res)
  981. })
  982. it('with an error', function (done) {
  983. this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel.resolves(
  984. undefined
  985. )
  986. this.SubscriptionController.successfulSubscription(
  987. this.req,
  988. this.res,
  989. error => {
  990. assert.isNotNull(error)
  991. done()
  992. }
  993. )
  994. })
  995. })
  996. describe('userSubscriptionPage', function () {
  997. beforeEach(function (done) {
  998. this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel.resolves(
  999. {
  1000. personalSubscription: (this.personalSubscription = {
  1001. 'personal-subscription': 'mock',
  1002. }),
  1003. memberGroupSubscriptions: (this.memberGroupSubscriptions = {
  1004. 'group-subscriptions': 'mock',
  1005. }),
  1006. }
  1007. )
  1008. this.SubscriptionViewModelBuilder.buildPlansList.returns(
  1009. (this.plans = { plans: 'mock' })
  1010. )
  1011. this.SubscriptionViewModelBuilder.buildPlansListForSubscriptionDash.returns(
  1012. {
  1013. plans: this.plans,
  1014. planCodesChangingAtTermEnd: [],
  1015. }
  1016. )
  1017. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(false)
  1018. this.res.render = (view, data) => {
  1019. this.data = data
  1020. expect(view).to.equal('subscriptions/dashboard-react')
  1021. done()
  1022. }
  1023. this.SubscriptionController.userSubscriptionPage(this.req, this.res, done)
  1024. })
  1025. it('should load the personal, groups and v1 subscriptions', function () {
  1026. expect(this.data.personalSubscription).to.deep.equal(
  1027. this.personalSubscription
  1028. )
  1029. expect(this.data.memberGroupSubscriptions).to.deep.equal(
  1030. this.memberGroupSubscriptions
  1031. )
  1032. })
  1033. it('should load the user', function () {
  1034. expect(this.data.user).to.deep.equal(this.user)
  1035. })
  1036. it('should load the plans', function () {
  1037. expect(this.data.plans).to.deep.equal(this.plans)
  1038. })
  1039. it('should load an empty list of groups with settings available', function () {
  1040. expect(this.data.groupSettingsEnabledFor).to.deep.equal([])
  1041. })
  1042. })
  1043. describe('updateSubscription via post', function () {
  1044. beforeEach(function (done) {
  1045. this.res = {
  1046. redirect() {
  1047. done()
  1048. },
  1049. }
  1050. sinon.spy(this.res, 'redirect')
  1051. this.plan_code = '1234'
  1052. this.req.body.plan_code = this.plan_code
  1053. this.SubscriptionController.updateSubscription(this.req, this.res)
  1054. })
  1055. it('should send the user and subscriptionId to the handler', function (done) {
  1056. this.SubscriptionHandler.updateSubscription
  1057. .calledWith(this.user, this.plan_code)
  1058. .should.equal(true)
  1059. done()
  1060. })
  1061. it('should redurect to the subscription page', function (done) {
  1062. this.res.redirect.calledWith('/user/subscription').should.equal(true)
  1063. done()
  1064. })
  1065. })
  1066. describe('updateAccountEmailAddress via put', function () {
  1067. it('should send the user and subscriptionId to RecurlyWrapper', function () {
  1068. this.res.sendStatus = sinon.spy()
  1069. this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
  1070. this.RecurlyWrapper.updateAccountEmailAddress
  1071. .calledWith(this.user._id, this.user.email)
  1072. .should.equal(true)
  1073. })
  1074. it('should respond with 200', function () {
  1075. this.res.sendStatus = sinon.spy()
  1076. this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
  1077. this.res.sendStatus.calledWith(200).should.equal(true)
  1078. })
  1079. it('should send the error to the next handler when updating recurly account email fails', function (done) {
  1080. this.RecurlyWrapper.updateAccountEmailAddress.yields(new Error())
  1081. this.next = sinon.spy(error => {
  1082. expect(error).instanceOf(Error)
  1083. done()
  1084. })
  1085. this.SubscriptionController.updateAccountEmailAddress(
  1086. this.req,
  1087. this.res,
  1088. this.next
  1089. )
  1090. })
  1091. })
  1092. describe('reactivateSubscription', function () {
  1093. describe('when the user has permission', function () {
  1094. beforeEach(function (done) {
  1095. this.res = {
  1096. redirect() {
  1097. done()
  1098. },
  1099. }
  1100. this.req.assertPermission = sinon.stub()
  1101. this.next = sinon.stub().callsFake(error => {
  1102. done(error)
  1103. })
  1104. sinon.spy(this.res, 'redirect')
  1105. this.SubscriptionController.reactivateSubscription(
  1106. this.req,
  1107. this.res,
  1108. this.next
  1109. )
  1110. })
  1111. it('should assert the user has permission to reactivate their subscription', function (done) {
  1112. this.req.assertPermission
  1113. .calledWith('reactivate-subscription')
  1114. .should.equal(true)
  1115. done()
  1116. })
  1117. it('should tell the handler to reactivate this user', function (done) {
  1118. this.SubscriptionHandler.reactivateSubscription
  1119. .calledWith(this.user)
  1120. .should.equal(true)
  1121. done()
  1122. })
  1123. it('should redurect to the subscription page', function (done) {
  1124. this.res.redirect.calledWith('/user/subscription').should.equal(true)
  1125. done()
  1126. })
  1127. })
  1128. describe('when the user does not have permission', function () {
  1129. beforeEach(function (done) {
  1130. this.res = {
  1131. redirect() {
  1132. done()
  1133. },
  1134. }
  1135. this.req.assertPermission = sinon.stub().throws()
  1136. this.next = sinon.stub().callsFake(() => {
  1137. done()
  1138. })
  1139. sinon.spy(this.res, 'redirect')
  1140. this.SubscriptionController.reactivateSubscription(
  1141. this.req,
  1142. this.res,
  1143. this.next
  1144. )
  1145. })
  1146. it('should not reactivate the user', function (done) {
  1147. this.req.assertPermission = sinon.stub().throws()
  1148. this.SubscriptionHandler.reactivateSubscription.called.should.equal(
  1149. false
  1150. )
  1151. done()
  1152. })
  1153. it('should call next with an error', function (done) {
  1154. this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
  1155. done()
  1156. })
  1157. })
  1158. })
  1159. describe('cancelSubscription', function () {
  1160. beforeEach(function (done) {
  1161. this.res = {
  1162. redirect() {
  1163. done()
  1164. },
  1165. }
  1166. sinon.spy(this.res, 'redirect')
  1167. this.SubscriptionController.cancelSubscription(this.req, this.res)
  1168. })
  1169. it('should tell the handler to cancel this user', function (done) {
  1170. this.SubscriptionHandler.cancelSubscription
  1171. .calledWith(this.user)
  1172. .should.equal(true)
  1173. done()
  1174. })
  1175. it('should redurect to the subscription page', function (done) {
  1176. this.res.redirect
  1177. .calledWith('/user/subscription/canceled')
  1178. .should.equal(true)
  1179. done()
  1180. })
  1181. })
  1182. describe('recurly callback', function () {
  1183. describe('with a sync subscription request', function () {
  1184. beforeEach(function (done) {
  1185. this.req = {
  1186. body: {
  1187. expired_subscription_notification: {
  1188. account: {
  1189. account_code: this.user._id,
  1190. },
  1191. subscription: {
  1192. uuid: this.activeRecurlySubscription.uuid,
  1193. plan: {
  1194. plan_code: 'collaborator',
  1195. state: 'active',
  1196. },
  1197. },
  1198. },
  1199. },
  1200. }
  1201. this.res = {
  1202. sendStatus() {
  1203. done()
  1204. },
  1205. }
  1206. sinon.spy(this.res, 'sendStatus')
  1207. this.SubscriptionController.recurlyCallback(this.req, this.res)
  1208. })
  1209. it('should tell the SubscriptionHandler to process the recurly callback', function (done) {
  1210. this.SubscriptionHandler.syncSubscription.called.should.equal(true)
  1211. done()
  1212. })
  1213. it('should send a 200', function (done) {
  1214. this.res.sendStatus.calledWith(200)
  1215. done()
  1216. })
  1217. })
  1218. describe('with a billing info updated request', function () {
  1219. beforeEach(function (done) {
  1220. this.req = {
  1221. body: {
  1222. billing_info_updated_notification: {
  1223. account: {
  1224. account_code: 'mock-account-code',
  1225. },
  1226. },
  1227. },
  1228. }
  1229. this.res = {
  1230. sendStatus() {
  1231. done()
  1232. },
  1233. }
  1234. sinon.spy(this.res, 'sendStatus')
  1235. this.SubscriptionController.recurlyCallback(this.req, this.res)
  1236. })
  1237. it('should call attemptPaypalInvoiceCollection', function (done) {
  1238. this.SubscriptionHandler.attemptPaypalInvoiceCollection
  1239. .calledWith('mock-account-code')
  1240. .should.equal(true)
  1241. done()
  1242. })
  1243. it('should send a 200', function (done) {
  1244. this.res.sendStatus.calledWith(200)
  1245. done()
  1246. })
  1247. })
  1248. describe('with a non-actionable request', function () {
  1249. beforeEach(function (done) {
  1250. this.user.id = this.activeRecurlySubscription.account.account_code
  1251. this.req = {
  1252. body: {
  1253. renewed_subscription_notification: {
  1254. account: {
  1255. account_code: this.user._id,
  1256. },
  1257. subscription: {
  1258. uuid: this.activeRecurlySubscription.uuid,
  1259. plan: {
  1260. plan_code: 'collaborator',
  1261. state: 'active',
  1262. },
  1263. },
  1264. },
  1265. },
  1266. }
  1267. this.res = {
  1268. sendStatus() {
  1269. done()
  1270. },
  1271. }
  1272. sinon.spy(this.res, 'sendStatus')
  1273. this.SubscriptionController.recurlyCallback(this.req, this.res)
  1274. })
  1275. it('should not call the subscriptionshandler', function () {
  1276. this.SubscriptionHandler.syncSubscription.called.should.equal(false)
  1277. this.SubscriptionHandler.attemptPaypalInvoiceCollection.called.should.equal(
  1278. false
  1279. )
  1280. })
  1281. it('should respond with a 200 status', function () {
  1282. this.res.sendStatus.calledWith(200)
  1283. })
  1284. })
  1285. })
  1286. })