Files
Verso/services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.node.test.mjs
T
Simon Gardner 4d338efe56 updates to recurly -> stripe customer upsert script
GitOrigin-RevId: dfe277807f9282804fed5a9cdf86e654a719de96
2026-02-06 09:05:15 +00:00

484 lines
16 KiB
JavaScript

/* eslint-disable @overleaf/require-script-runner */
import test from 'node:test'
import assert from 'node:assert/strict'
/*
* This test can be run from the services/web directory with:
*
* node --test scripts/helpers/migrate_recurly_customers_to_stripe.helpers.node.test.mjs
*
* It can be deleted once the Recurly to Stripe migration is complete.
*/
import {
coalesceOrEqualOrThrow,
coalesceOrEqualOrThrowAddress,
coalesceOrEqualOrThrowName,
coalesceOrThrowVATNumber,
getCanadaTaxIdType,
getAustraliaTaxIdType,
getBrazilTaxIdType,
getBulgariaTaxIdType,
getCroatiaTaxIdType,
getGermanyTaxIdType,
getHungaryTaxIdType,
getJapanTaxIdType,
getLiechtensteinTaxIdType,
getMalaysiaTaxIdType,
getNorwayTaxIdType,
getPolandTaxIdType,
getRomaniaTaxIdType,
getRussiaTaxIdType,
getSingaporeTaxIdType,
getSloveniaTaxIdType,
getSpainTaxIdType,
getSwitzerlandTaxIdType,
getUkTaxIdType,
getUzbekistanTaxIdType,
getTaxIdType,
coalesceOrThrowPaymentMethod,
} from './migrate_recurly_customers_to_stripe.helpers.mjs'
test('coalesceOrEqualOrThrow returns primary when set', () => {
assert.equal(coalesceOrEqualOrThrow('a', undefined, 'field'), 'a')
})
test('coalesceOrEqualOrThrow returns fallback when primary is unset', () => {
assert.equal(coalesceOrEqualOrThrow(undefined, 'b', 'field'), 'b')
})
test('coalesceOrEqualOrThrow returns value when both are set and equal', () => {
assert.equal(coalesceOrEqualOrThrow('same', 'same', 'field'), 'same')
})
test('coalesceOrEqualOrThrow throws when both are set but differ', () => {
assert.throws(
() => coalesceOrEqualOrThrow('a', 'b', 'field'),
/Primary and fallback values are both set but differ/
)
})
test('coalesceOrEqualOrThrowAddress returns null when neither is valid', () => {
assert.equal(coalesceOrEqualOrThrowAddress({}, null), null)
assert.equal(
coalesceOrEqualOrThrowAddress(
{ address: { street1: '', postalCode: '', country: '' } },
{ address: { street1: '', postalCode: '', country: '' } }
),
null
)
assert.equal(
coalesceOrEqualOrThrowAddress(
{ address: { street1: ' ', postalCode: ' ', country: ' ' } },
null
),
null
)
})
test('coalesceOrEqualOrThrowAddress returns account when billing invalid', () => {
const account = {
address: { street1: '1 Road', postalCode: 'ABC', country: 'GB' },
}
const billingInfo = {
address: { street1: '', postalCode: 'ABC', country: 'GB' },
}
assert.deepEqual(coalesceOrEqualOrThrowAddress(account, billingInfo), {
line1: '1 Road',
postal_code: 'ABC',
country: 'GB',
})
})
test('coalesceOrEqualOrThrowAddress returns billing when account invalid', () => {
const account = {
address: { street1: '', postalCode: 'ABC', country: 'GB' },
}
const billingInfo = {
address: { street1: '1 Road', postalCode: 'ABC', country: 'GB' },
}
assert.deepEqual(coalesceOrEqualOrThrowAddress(account, billingInfo), {
line1: '1 Road',
postal_code: 'ABC',
country: 'GB',
})
})
test('coalesceOrEqualOrThrowAddress returns billing when both valid+equal', () => {
const addr = { street1: '1 Road', postalCode: 'ABC', country: 'GB' }
assert.deepEqual(
coalesceOrEqualOrThrowAddress({ address: { ...addr } }, { address: addr }),
{ line1: '1 Road', postal_code: 'ABC', country: 'GB' }
)
})
test('coalesceOrEqualOrThrowAddress normalizes Recurly-style address fields', () => {
const billingInfo = {
address: {
street1: 'as',
street2: '',
city: '',
region: '',
postalCode: '12312',
country: 'AI',
},
}
assert.deepEqual(coalesceOrEqualOrThrowAddress({}, billingInfo), {
line1: 'as',
postal_code: '12312',
country: 'AI',
})
})
test('coalesceOrEqualOrThrowAddress throws when both valid but differ', () => {
const account = {
address: { street1: '1 Road', postalCode: 'ABC', country: 'GB' },
}
const billingInfo = {
address: { street1: '2 Road', postalCode: 'ABC', country: 'GB' },
}
assert.throws(
() => coalesceOrEqualOrThrowAddress(account, billingInfo),
/Billing address and account address differ/
)
})
test('coalesceOrEqualOrThrowName returns billingInfo name when both sources match', () => {
const account = { firstName: 'Alice', lastName: 'Billing' }
const billingInfo = { firstName: 'Alice', lastName: 'Billing' }
assert.equal(
coalesceOrEqualOrThrowName(account, billingInfo),
'Alice Billing'
)
})
test('coalesceOrEqualOrThrowName prefers billingInfo when billingInfo is full but account is not', () => {
const account = { firstName: 'Alice', lastName: '' }
const billingInfo = { firstName: 'Alice', lastName: 'Billing' }
assert.equal(
coalesceOrEqualOrThrowName(account, billingInfo),
'Alice Billing'
)
})
test('coalesceOrEqualOrThrowName falls back to account when billingInfo missing last name', () => {
const account = { firstName: 'Alice', lastName: 'Account' }
const billingInfo = { firstName: 'Alice', lastName: '' }
assert.equal(
coalesceOrEqualOrThrowName(account, billingInfo),
'Alice Account'
)
})
test('coalesceOrEqualOrThrowName returns null when both sources are empty', () => {
assert.equal(coalesceOrEqualOrThrowName({}, null), null)
assert.equal(
coalesceOrEqualOrThrowName({ firstName: '', lastName: '' }, null),
null
)
})
test('coalesceOrEqualOrThrowName throws when both full names are present but differ', () => {
const account = { firstName: 'Alice', lastName: 'Account' }
const billingInfo = { firstName: 'Alice', lastName: 'Billing' }
assert.throws(
() => coalesceOrEqualOrThrowName(account, billingInfo),
/Name differs between billingInfo and account/
)
})
test('coalesceOrThrowVATNumber returns billingInfo VAT when set', () => {
const account = { vatNumber: '' }
const billingInfo = { vatNumber: 'BILL456' }
assert.equal(coalesceOrThrowVATNumber(account, billingInfo), 'BILL456')
})
test('coalesceOrThrowVATNumber returns account VAT when billingInfo VAT unset', () => {
const account = { vatNumber: 'ACCT123' }
const billingInfo = { vatNumber: '' }
assert.equal(coalesceOrThrowVATNumber(account, billingInfo), 'ACCT123')
})
test('coalesceOrThrowVATNumber returns null when neither is set', () => {
assert.equal(coalesceOrThrowVATNumber({}, null), null)
assert.equal(
coalesceOrThrowVATNumber({ vatNumber: '' }, { vatNumber: '' }),
null
)
})
test('coalesceOrThrowVATNumber treats trimmed values as equal', () => {
const account = { vatNumber: ' GB123 ' }
const billingInfo = { vatNumber: 'GB123' }
assert.equal(coalesceOrThrowVATNumber(account, billingInfo), 'GB123')
})
test('coalesceOrThrowVATNumber throws when both are set but differ', () => {
const account = { vatNumber: 'GB123' }
const billingInfo = { vatNumber: 'DE999' }
assert.throws(
() => coalesceOrThrowVATNumber(account, billingInfo),
/Field vatNumber: Primary and fallback values are both set but differ/
)
})
test('getCanadaTaxIdType returns ca_gst_hst for GST/HST format', () => {
assert.equal(getCanadaTaxIdType('123456789RT0002', null), 'ca_gst_hst')
assert.equal(getCanadaTaxIdType(' 123456789rt0002 ', null), 'ca_gst_hst')
})
test('getCanadaTaxIdType returns ca_qst for Quebec QST format', () => {
assert.equal(getCanadaTaxIdType('1234567890TQ1234', null), 'ca_qst')
assert.equal(getCanadaTaxIdType('1234567890tq1234', null), 'ca_qst')
})
test('getCanadaTaxIdType returns PST types for documented provincial formats', () => {
assert.equal(getCanadaTaxIdType('PST-1234-5678', null), 'ca_pst_bc')
assert.equal(getCanadaTaxIdType('123456-7', 'R3C 4T3'), 'ca_pst_mb')
assert.equal(getCanadaTaxIdType('1234567', 'S7K 3J8'), 'ca_pst_sk')
})
test('getCanadaTaxIdType returns ca_bn for 9-digit BN format', () => {
// assert.equal(getCanadaTaxIdType('123456789', null), 'ca_bn')
assert.equal(getCanadaTaxIdType('123456789', null), null) // TODO: improve function get definitive ca_bn vs ca_gst_hst
})
test('getCanadaTaxIdType returns null when format is unknown/ambiguous', () => {
assert.equal(getCanadaTaxIdType('', null), null)
assert.equal(getCanadaTaxIdType(null, null), null)
assert.equal(getCanadaTaxIdType('RT0002', null), null)
assert.equal(getCanadaTaxIdType('PST12345678', null), null)
})
test('getAustraliaTaxIdType distinguishes ABN vs ARN', () => {
assert.equal(getAustraliaTaxIdType('12345678912'), 'au_abn')
assert.equal(getAustraliaTaxIdType('123456789123'), 'au_arn')
})
test('getBrazilTaxIdType distinguishes CNPJ vs CPF', () => {
assert.equal(getBrazilTaxIdType('01.234.456/5432-10'), 'br_cnpj')
assert.equal(getBrazilTaxIdType('123.456.789-87'), 'br_cpf')
})
test('getBulgariaTaxIdType distinguishes EU VAT vs UIC', () => {
assert.equal(getBulgariaTaxIdType('BG0123456789'), 'eu_vat')
assert.equal(getBulgariaTaxIdType('123456789'), 'bg_uic')
})
test('getCroatiaTaxIdType distinguishes EU VAT vs OIB', () => {
assert.equal(getCroatiaTaxIdType('HR12345678912'), 'eu_vat')
assert.equal(getCroatiaTaxIdType('12345678901'), 'hr_oib')
})
test('getGermanyTaxIdType distinguishes EU VAT vs Steuer Nummer', () => {
assert.equal(getGermanyTaxIdType('DE123456789'), 'eu_vat')
assert.equal(getGermanyTaxIdType('1234567890'), 'de_stn')
})
test('getHungaryTaxIdType distinguishes EU VAT vs HU tax number', () => {
assert.equal(getHungaryTaxIdType('HU12345678'), 'eu_vat')
assert.equal(getHungaryTaxIdType('12345678-1-23'), 'hu_tin')
})
test('getJapanTaxIdType distinguishes TRN, CN, RN', () => {
assert.equal(getJapanTaxIdType('T1234567891234'), 'jp_trn')
assert.equal(getJapanTaxIdType('1234567891234'), 'jp_cn')
assert.equal(getJapanTaxIdType('12345'), 'jp_rn')
})
test('getLiechtensteinTaxIdType distinguishes UID vs VAT', () => {
assert.equal(getLiechtensteinTaxIdType('CHE123456789'), 'li_uid')
assert.equal(getLiechtensteinTaxIdType('12345'), 'li_vat')
})
test('getMalaysiaTaxIdType distinguishes FRP, ITN, SST', () => {
assert.equal(getMalaysiaTaxIdType('12345678'), 'my_frp')
assert.equal(getMalaysiaTaxIdType('C 1234567890'), 'my_itn')
assert.equal(getMalaysiaTaxIdType('A12-3456-78912345'), 'my_sst')
})
test('getNorwayTaxIdType distinguishes VAT vs VOEC', () => {
assert.equal(getNorwayTaxIdType('123456789MVA'), 'no_vat')
assert.equal(getNorwayTaxIdType('1234567'), 'no_voec')
})
test('getPolandTaxIdType distinguishes EU VAT vs NIP', () => {
assert.equal(getPolandTaxIdType('PL1234567890'), 'eu_vat')
assert.equal(getPolandTaxIdType('1234567890'), 'pl_nip')
})
test('getRomaniaTaxIdType distinguishes EU VAT vs TIN', () => {
assert.equal(getRomaniaTaxIdType('RO1234567891'), 'eu_vat')
assert.equal(getRomaniaTaxIdType('1234567890123'), 'ro_tin')
})
test('getRussiaTaxIdType distinguishes INN vs KPP', () => {
assert.equal(getRussiaTaxIdType('1234567891'), 'ru_inn')
assert.equal(getRussiaTaxIdType('123456789'), 'ru_kpp')
})
test('getSingaporeTaxIdType distinguishes GST vs UEN', () => {
assert.equal(getSingaporeTaxIdType('M12345678X'), 'sg_gst')
assert.equal(getSingaporeTaxIdType('123456789F'), 'sg_uen')
})
test('getSloveniaTaxIdType distinguishes EU VAT vs TIN', () => {
assert.equal(getSloveniaTaxIdType('SI12345678'), 'eu_vat')
assert.equal(getSloveniaTaxIdType('12345678'), 'si_tin')
})
test('getSpainTaxIdType distinguishes EU VAT vs CIF', () => {
assert.equal(getSpainTaxIdType('ESA1234567Z'), 'eu_vat')
assert.equal(getSpainTaxIdType('A12345678'), 'es_cif')
})
test('getSwitzerlandTaxIdType distinguishes UID vs VAT', () => {
assert.equal(getSwitzerlandTaxIdType('CHE-123.456.789 HR'), 'ch_uid')
assert.equal(getSwitzerlandTaxIdType('CHE-123.456.789 MWST'), 'ch_vat')
})
test('getUkTaxIdType distinguishes GB VAT vs EU VAT (NI)', () => {
assert.equal(getUkTaxIdType('GB123456789'), 'gb_vat')
assert.equal(getUkTaxIdType('XI123456789'), 'eu_vat')
})
test('getUzbekistanTaxIdType distinguishes TIN vs VAT', () => {
assert.equal(getUzbekistanTaxIdType('123456789'), 'uz_tin')
assert.equal(getUzbekistanTaxIdType('123456789012'), 'uz_vat')
})
test('getTaxIdType handles EU OSS VAT and EU VAT defaults', () => {
assert.equal(getTaxIdType('EU', 'EU123456789'), 'eu_oss_vat')
assert.equal(getTaxIdType('AT', 'ATU12345678'), 'eu_vat')
})
test('coalesceOrThrowPaymentMethod throws when payment methods array is empty', () => {
assert.throws(
() => coalesceOrThrowPaymentMethod([], 'cus_123', {}),
/Stripe customer cus_123 has no usable payment method/
)
})
test('coalesceOrThrowPaymentMethod throws when no payment methods match billing info', () => {
const paymentMethods = [
{
id: 'pm_1',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
},
]
const billingInfo = {
paymentMethod: { lastFour: '5678', expMonth: 12, expYear: 2030 },
}
assert.throws(
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', billingInfo),
/Stripe customer cus_123 has no usable payment method/
)
})
test('coalesceOrThrowPaymentMethod returns matching payment method', () => {
const paymentMethod = {
id: 'pm_match',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
}
const paymentMethods = [paymentMethod]
const billingInfo = {
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
}
const result = coalesceOrThrowPaymentMethod(
paymentMethods,
'cus_123',
billingInfo
)
assert.equal(result, paymentMethod)
})
test('coalesceOrThrowPaymentMethod matches first of multiple matching methods', () => {
const paymentMethod1 = {
id: 'pm_1',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
}
const paymentMethod2 = {
id: 'pm_2',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
}
const paymentMethods = [paymentMethod1, paymentMethod2]
const billingInfo = {
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
}
const result = coalesceOrThrowPaymentMethod(
paymentMethods,
'cus_123',
billingInfo
)
assert.equal(result, paymentMethod1)
})
test('coalesceOrThrowPaymentMethod filters out non-matching methods', () => {
const matchingMethod = {
id: 'pm_match',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
}
const nonMatchingMethod = {
id: 'pm_no_match',
card: { last4: '5678', exp_month: 12, exp_year: 2030 },
}
const paymentMethods = [nonMatchingMethod, matchingMethod]
const billingInfo = {
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
}
const result = coalesceOrThrowPaymentMethod(
paymentMethods,
'cus_123',
billingInfo
)
assert.equal(result, matchingMethod)
})
test('coalesceOrThrowPaymentMethod handles null billingInfo', () => {
const paymentMethods = [
{
id: 'pm_1',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
},
]
assert.throws(
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', null),
/Stripe customer cus_123 has no usable payment method/
)
})
test('coalesceOrThrowPaymentMethod handles missing paymentMethod in billingInfo', () => {
const paymentMethods = [
{
id: 'pm_1',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
},
]
const billingInfo = {}
assert.throws(
() => coalesceOrThrowPaymentMethod(paymentMethods, 'cus_123', billingInfo),
/Stripe customer cus_123 has no usable payment method/
)
})
test('coalesceOrThrowPaymentMethod handles missing card in payment method', () => {
const paymentMethods = [
{ id: 'pm_1' }, // no card property
{
id: 'pm_2',
card: { last4: '1234', exp_month: 12, exp_year: 2030 },
},
]
const billingInfo = {
paymentMethod: { lastFour: '1234', expMonth: 12, expYear: 2030 },
}
const result = coalesceOrThrowPaymentMethod(
paymentMethods,
'cus_123',
billingInfo
)
assert.equal(result.id, 'pm_2')
})