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'],
})
})
})