PayPal transaction filter + payments mapping + external-id dedupe were inline and untested, and auth was a hand-rolled select instead of the shared helper. Extracted to pure lib/paypal-logic.ts (also fixes a latent throw on a missing/invalid PayPal date by falling back to now); route now uses requireMemberWithRole. 7 tests; suite 521. Behavior otherwise unchanged.
109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
mapPayPalTransactions,
|
|
filterUnseenByExternalId,
|
|
externalIdsOf,
|
|
type PayPalPaymentRow,
|
|
} from '@/lib/paypal-logic'
|
|
|
|
const NOW = '2026-01-01T00:00:00.000Z'
|
|
|
|
describe('mapPayPalTransactions', () => {
|
|
it('keeps only incoming (amount > 0) and maps fields', () => {
|
|
const rows = mapPayPalTransactions(
|
|
[
|
|
{
|
|
transaction_info: {
|
|
transaction_id: 't1',
|
|
transaction_amount: { value: '25.00' },
|
|
transaction_subject: 'Dues',
|
|
transaction_initiation_date: '2026-02-03T10:00:00Z',
|
|
},
|
|
payer_info: { email_address: '[email protected]' },
|
|
},
|
|
{ transaction_info: { transaction_id: 't2', transaction_amount: { value: '-9.00' } } },
|
|
{ transaction_info: { transaction_id: 't3', transaction_amount: { value: '0' } } },
|
|
],
|
|
's1',
|
|
NOW,
|
|
)
|
|
expect(rows).toHaveLength(1)
|
|
expect(rows[0]).toEqual<PayPalPaymentRow>({
|
|
space_id: 's1',
|
|
platform: 'paypal',
|
|
amount: 25,
|
|
from_identifier: '[email protected]',
|
|
from_note: 'Dues',
|
|
transaction_date: new Date('2026-02-03T10:00:00Z').toISOString(),
|
|
link_status: 'unlinked',
|
|
external_id: 't1',
|
|
})
|
|
})
|
|
|
|
it('falls back through name then a literal, and note then subject then null', () => {
|
|
const [a, b] = mapPayPalTransactions(
|
|
[
|
|
{
|
|
transaction_info: { transaction_id: 'a', transaction_amount: { value: '1' }, transaction_note: 'n' },
|
|
payer_info: { payer_name: { alternate_full_name: 'Jane' } },
|
|
},
|
|
{ transaction_info: { transaction_id: 'b', transaction_amount: { value: '2' } } },
|
|
],
|
|
's1',
|
|
NOW,
|
|
)
|
|
expect(a.from_identifier).toBe('Jane')
|
|
expect(a.from_note).toBe('n')
|
|
expect(b.from_identifier).toBe('PayPal User')
|
|
expect(b.from_note).toBeNull()
|
|
})
|
|
|
|
it('uses nowIso when the date is missing or invalid (never throws)', () => {
|
|
const [a, b] = mapPayPalTransactions(
|
|
[
|
|
{ transaction_info: { transaction_id: 'a', transaction_amount: { value: '1' } } },
|
|
{
|
|
transaction_info: {
|
|
transaction_id: 'b',
|
|
transaction_amount: { value: '1' },
|
|
transaction_initiation_date: 'not-a-date',
|
|
},
|
|
},
|
|
],
|
|
's1',
|
|
NOW,
|
|
)
|
|
expect(a.transaction_date).toBe(NOW)
|
|
expect(b.transaction_date).toBe(NOW)
|
|
})
|
|
|
|
it('handles empty / nullish input', () => {
|
|
expect(mapPayPalTransactions([], 's1', NOW)).toEqual([])
|
|
expect(mapPayPalTransactions(undefined as unknown as [], 's1', NOW)).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('filterUnseenByExternalId', () => {
|
|
const rows = [
|
|
{ external_id: 'a' },
|
|
{ external_id: 'b' },
|
|
{ external_id: null },
|
|
] as PayPalPaymentRow[]
|
|
|
|
it('drops already-seen ids, keeps unseen and null-id rows', () => {
|
|
const out = filterUnseenByExternalId(rows, new Set(['a']))
|
|
expect(out.map(r => r.external_id)).toEqual(['b', null])
|
|
})
|
|
it('keeps everything when nothing seen', () => {
|
|
expect(filterUnseenByExternalId(rows, new Set())).toHaveLength(3)
|
|
})
|
|
})
|
|
|
|
describe('externalIdsOf', () => {
|
|
it('returns only non-null external ids', () => {
|
|
expect(
|
|
externalIdsOf([{ external_id: 'a' }, { external_id: null }, { external_id: 'c' }] as PayPalPaymentRow[]),
|
|
).toEqual(['a', 'c'])
|
|
})
|
|
})
|