import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { listHelcimCustomerTransactions, updateHelcimCustomerDefaultPaymentMethod, updateHelcimSubscriptionPaymentMethod } from '../../../server/utils/helcim.js' const mockFetch = vi.fn() vi.stubGlobal('fetch', mockFetch) function okResponse(payload) { return { ok: true, status: 200, text: async () => (payload === null || payload === undefined ? '' : JSON.stringify(payload)) } } function errResponse(status = 500, body = 'boom') { return { ok: false, status, text: async () => body } } describe('listHelcimCustomerTransactions', () => { beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { mockFetch.mockReset() }) it('normalizes, sorts newest-first, and maps statuses', async () => { mockFetch.mockResolvedValue(okResponse([ { transactionId: 1, dateCreated: '2024-01-10 10:00:00', amount: 10, currency: 'CAD', status: 'APPROVED', type: 'purchase' }, { transactionId: 2, dateCreated: '2024-03-15 10:00:00', amount: 25, currency: 'CAD', status: 'APPROVED', type: 'refund' }, { transactionId: 3, dateCreated: '2024-02-01 10:00:00', amount: 5, currency: 'CAD', status: 'DECLINED', type: 'purchase' }, { transactionId: 4, dateCreated: '2024-02-15 10:00:00', amount: 1, currency: 'CAD', status: 'UNKNOWN', type: 'purchase' } ])) const result = await listHelcimCustomerTransactions('CST1044') expect(mockFetch).toHaveBeenCalledTimes(1) const url = mockFetch.mock.calls[0][0] expect(url).toContain('/card-transactions/') expect(url).toContain('customerCode=CST1044') expect(url).toContain('limit=50') expect(result).toEqual([ { id: '2', date: '2024-03-15 10:00:00', amount: 25, status: 'refunded', currency: 'CAD' }, { id: '4', date: '2024-02-15 10:00:00', amount: 1, status: 'other', currency: 'CAD' }, { id: '3', date: '2024-02-01 10:00:00', amount: 5, status: 'failed', currency: 'CAD' }, { id: '1', date: '2024-01-10 10:00:00', amount: 10, status: 'paid', currency: 'CAD' } ]) }) it('throws when Helcim returns non-2xx', async () => { mockFetch.mockResolvedValue(errResponse(500)) await expect(listHelcimCustomerTransactions('CST1044')).rejects.toMatchObject({ statusCode: 500, statusMessage: 'Transaction lookup failed' }) }) }) describe('updateHelcimCustomerDefaultPaymentMethod', () => { beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { mockFetch.mockReset() }) it('resolves cardToken to cardId then PATCHes default endpoint', async () => { // First call: list cards filtered by token mockFetch.mockResolvedValueOnce(okResponse([ { id: 161462, cardToken: 'tok-abc', cardHolderName: 'Jane' } ])) // Second call: PATCH default mockFetch.mockResolvedValueOnce(okResponse({ id: 2488717, customerCode: 'CST1200' })) const result = await updateHelcimCustomerDefaultPaymentMethod('2488717', 'tok-abc') expect(mockFetch).toHaveBeenCalledTimes(2) const firstUrl = mockFetch.mock.calls[0][0] expect(firstUrl).toContain('/customers/2488717/cards') expect(firstUrl).toContain('cardToken=tok-abc') const secondUrl = mockFetch.mock.calls[1][0] const secondOpts = mockFetch.mock.calls[1][1] expect(secondUrl).toContain('/customers/2488717/cards/161462/default') expect(secondOpts.method).toBe('PATCH') expect(result).toEqual({ id: 2488717, customerCode: 'CST1200' }) }) it('throws 404 when cardToken is not attached to the customer', async () => { mockFetch.mockResolvedValueOnce(okResponse([])) await expect( updateHelcimCustomerDefaultPaymentMethod('2488717', 'nope') ).rejects.toMatchObject({ statusCode: 404, statusMessage: 'Card token not found on customer' }) // Only the lookup should have happened — no PATCH expect(mockFetch).toHaveBeenCalledTimes(1) }) it('propagates Helcim errors from the PATCH call', async () => { mockFetch.mockResolvedValueOnce(okResponse([ { id: 161462, cardToken: 'tok-abc' } ])) mockFetch.mockResolvedValueOnce(errResponse(500)) await expect( updateHelcimCustomerDefaultPaymentMethod('2488717', 'tok-abc') ).rejects.toMatchObject({ statusCode: 500, statusMessage: 'Default card update failed' }) }) }) describe('updateHelcimSubscriptionPaymentMethod', () => { beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { mockFetch.mockReset() }) it('PATCHes /subscriptions with cardToken wrapped in subscriptions array', async () => { mockFetch.mockResolvedValue(okResponse({ data: [{ id: 123456 }] })) const result = await updateHelcimSubscriptionPaymentMethod('123456', 'tok-abc') expect(mockFetch).toHaveBeenCalledTimes(1) const [url, opts] = mockFetch.mock.calls[0] expect(url).toContain('/subscriptions') expect(opts.method).toBe('PATCH') const body = JSON.parse(opts.body) expect(body).toEqual({ subscriptions: [{ id: 123456, cardToken: 'tok-abc' }] }) expect(result).toEqual({ data: [{ id: 123456 }] }) }) it('throws when Helcim returns non-2xx', async () => { mockFetch.mockResolvedValue(errResponse(400)) await expect( updateHelcimSubscriptionPaymentMethod('123456', 'tok-abc') ).rejects.toMatchObject({ statusCode: 400, statusMessage: 'Subscription update failed' }) }) })