const sinon = require('sinon') const chai = require('chai') const { expect } = require('chai') chai.use(require('chai-as-promised')) chai.use(require('sinon-chai')) const { main, } = require('../../../scripts/recurly/collect_paypal_past_due_invoice') const RecurlyWrapper = require('../../../app/src/Features/Subscription/RecurlyWrapper') const OError = require('@overleaf/o-error') // from https://recurly.com/developers/api-v2/v2.21/#operation/listInvoices const invoicesXml = invoiceIds => ` ${invoiceIds .map( invoiceId => `
Lon Doner 221B Baker St. London W1K 6AH GB 421f7b7d414e4c6792938e7c49d552e9 paid ${invoiceId} 2000 0 2018-01-30T21:11:50Z 0 charge purchase 2000 0 1200 USD 2016-06-25T12:00:00Z usst CA 0 0 automatic
` ) .join('')}
` // from https://recurly.com/developers/api-v2/v2.21/#operation/lookupAccountsBillingInfo const billingInfoXml = ` PAYPAL_BILLING_AGREEMENT_ID Verena Example 123 Main St. San Francisco CA 94105 US 127.0.0.1 Visa 2019 11 411111 1111 2017-02-17T15:38:53Z ` // from https://recurly.com/developers/api-v2/v2.21/#operation/collectAnInvoice const invoiceCollectXml = `
123 Main St. San Francisco CA 94105 US
374a37924f83c733b9c9814e9580496a pending 1000 5000 438 5438 USD 2016-07-11T19:25:57Z 2016-07-11T19:25:57Z usst CA 0.0875 0 automatic
` const ITEMS_PER_PAGE = 3 const getInvoicePage = fullInvoicesIds => queryOptions => { const cursor = queryOptions.qs.cursor const startEnd = cursor?.split(':').map(Number) || [] const start = startEnd[0] || 0 const end = startEnd[1] || ITEMS_PER_PAGE const body = invoicesXml(fullInvoicesIds.slice(start, end)) const hasMore = end < fullInvoicesIds.length const nextPageCursor = hasMore ? `${end}%3A${end + ITEMS_PER_PAGE}&v=2` : null const response = { status: 200, headers: { link: hasMore ? `https://fakerecurly.com/v2/invoices?cursor=${nextPageCursor}` : undefined, }, } return { response, body } } describe('CollectPayPalPastDueInvoice', function () { let apiRequestStub const fakeApiRequests = invoiceIds => { apiRequestStub = sinon.stub(RecurlyWrapper.promises, 'apiRequest') apiRequestStub.callsFake(options => { if (options.url === 'invoices') { return getInvoicePage(invoiceIds)(options) } if (/accounts\/(\d+)\/billing_info/.test(options.url)) { return { response: { status: 200, headers: {} }, body: billingInfoXml, } } if (/invoices\/(\d+)\/collect/.test(options.url)) { const invoiceId = options.url.match(/invoices\/(\d+)\/collect/)[1] if (invoiceId < 400) { return { response: { status: 200, headers: {} }, body: invoiceCollectXml, } } throw new OError(`Recurly API returned with status code: 404`, { statusCode: 404, }) } }) } afterEach(function () { apiRequestStub?.restore() }) it('collects one valid invoice', async function () { fakeApiRequests([200]) const r = await main() expect(r).to.eql({ INVOICES_COLLECTED: [200], INVOICES_COLLECTED_SUCCESS: [200], USERS_COLLECTED: ['200'], }) }) it('collects several pages', async function () { // 10 invoices, from 200 to 209 fakeApiRequests([...Array(10).keys()].map(i => i + 200)) const r = await main() expect(r).to.eql({ INVOICES_COLLECTED: [200, 201, 202, 203, 204, 205, 206, 207, 208, 209], INVOICES_COLLECTED_SUCCESS: [ 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, ], USERS_COLLECTED: [ '200', '201', '202', '203', '204', '205', '206', '207', '208', '209', ], }) // 4 calls to get the invoices // 10 calls to get the billing info // 10 calls to collect the invoices expect(apiRequestStub.callCount).to.eql(24) }) it("resolves when no invoices are processed so we don't fail in staging", async function () { fakeApiRequests([404]) const r = await main() expect(r).to.eql({ INVOICES_COLLECTED: [404], INVOICES_COLLECTED_SUCCESS: [], USERS_COLLECTED: ['404'], }) }) it('doesnt reject when there are no invoices', async function () { fakeApiRequests([]) const r = await main() expect(r).to.eql({ INVOICES_COLLECTED: [], INVOICES_COLLECTED_SUCCESS: [], USERS_COLLECTED: [], }) }) it("resolves when collection is partially successful so we don't fail in prod", async function () { fakeApiRequests([200, 404]) const r = await main() expect(r).to.eql({ INVOICES_COLLECTED: [200, 404], INVOICES_COLLECTED_SUCCESS: [200], USERS_COLLECTED: ['200', '404'], }) }) })