import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'bun:test' import type { FastifyInstance } from 'fastify' import { createTestApp, cleanDb, seedTestCompany, registerAndLogin, TEST_COMPANY_ID } from '../../src/test/helpers.js' import { AccountService, PaymentMethodService, TaxExemptionService } from '../../src/services/account.service.js' describe('PaymentMethodService', () => { let app: FastifyInstance let accountId: string beforeAll(async () => { app = await createTestApp() }) beforeEach(async () => { await cleanDb(app) await seedTestCompany(app) const auth = await registerAndLogin(app, { email: `pm-svc-${Date.now()}@test.com` }) const account = await AccountService.create(app.db, TEST_COMPANY_ID, { name: 'PM Test' }) accountId = account.id }) afterAll(async () => { await app.close() }) it('creates a payment method', async () => { const pm = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_abc', cardBrand: 'visa', lastFour: '4242', expMonth: 12, expYear: 2027, isDefault: false, }) expect(pm.processorPaymentMethodId).toBe('pm_abc') expect(pm.isDefault).toBe(false) }) it('unsets old default when creating a new default', async () => { const first = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_first', isDefault: true, }) expect(first.isDefault).toBe(true) const second = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_second', isDefault: true, }) expect(second.isDefault).toBe(true) // First should no longer be default const firstRefreshed = await PaymentMethodService.getById(app.db, TEST_COMPANY_ID, first.id) expect(firstRefreshed!.isDefault).toBe(false) }) it('unsets old default when updating to default', async () => { const first = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_1', isDefault: true, }) const second = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_2', isDefault: false, }) await PaymentMethodService.update(app.db, TEST_COMPANY_ID, second.id, { isDefault: true }) const firstRefreshed = await PaymentMethodService.getById(app.db, TEST_COMPANY_ID, first.id) expect(firstRefreshed!.isDefault).toBe(false) const secondRefreshed = await PaymentMethodService.getById(app.db, TEST_COMPANY_ID, second.id) expect(secondRefreshed!.isDefault).toBe(true) }) it('does not affect other accounts when setting default', async () => { const otherAccount = await AccountService.create(app.db, TEST_COMPANY_ID, { name: 'Other' }) const otherPm = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId: otherAccount.id, processor: 'stripe', processorPaymentMethodId: 'pm_other', isDefault: true, }) // Create default on our account — should not touch the other account's default await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_ours', isDefault: true, }) const otherRefreshed = await PaymentMethodService.getById(app.db, TEST_COMPANY_ID, otherPm.id) expect(otherRefreshed!.isDefault).toBe(true) }) it('lists only methods for the requested account', async () => { const otherAccount = await AccountService.create(app.db, TEST_COMPANY_ID, { name: 'Other' }) await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_a', isDefault: false, }) await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId: otherAccount.id, processor: 'stripe', processorPaymentMethodId: 'pm_b', isDefault: false, }) const methods = await PaymentMethodService.listByAccount(app.db, TEST_COMPANY_ID, accountId) expect(methods.length).toBe(1) expect(methods[0].processorPaymentMethodId).toBe('pm_a') }) it('deletes a payment method', async () => { const pm = await PaymentMethodService.create(app.db, TEST_COMPANY_ID, { accountId, processor: 'stripe', processorPaymentMethodId: 'pm_del', isDefault: false, }) const deleted = await PaymentMethodService.delete(app.db, TEST_COMPANY_ID, pm.id) expect(deleted).not.toBeNull() const fetched = await PaymentMethodService.getById(app.db, TEST_COMPANY_ID, pm.id) expect(fetched).toBeNull() }) }) describe('TaxExemptionService', () => { let app: FastifyInstance let accountId: string let userId: string beforeAll(async () => { app = await createTestApp() }) beforeEach(async () => { await cleanDb(app) await seedTestCompany(app) const auth = await registerAndLogin(app, { email: `tax-svc-${Date.now()}@test.com` }) userId = (auth.user as { id: string }).id const account = await AccountService.create(app.db, TEST_COMPANY_ID, { name: 'Tax Test' }) accountId = account.id }) afterAll(async () => { await app.close() }) it('creates an exemption in pending status', async () => { const exemption = await TaxExemptionService.create(app.db, TEST_COMPANY_ID, { accountId, certificateNumber: 'TX-001', certificateType: 'resale', issuingState: 'TX', }) expect(exemption.status).toBe('pending') expect(exemption.certificateNumber).toBe('TX-001') expect(exemption.approvedBy).toBeNull() }) it('approves a pending exemption', async () => { const exemption = await TaxExemptionService.create(app.db, TEST_COMPANY_ID, { accountId, certificateNumber: 'TX-002', }) const approved = await TaxExemptionService.approve(app.db, TEST_COMPANY_ID, exemption.id, userId) expect(approved!.status).toBe('approved') expect(approved!.approvedBy).toBe(userId) expect(approved!.approvedAt).toBeTruthy() }) it('revokes an exemption with reason', async () => { const exemption = await TaxExemptionService.create(app.db, TEST_COMPANY_ID, { accountId, certificateNumber: 'TX-003', }) await TaxExemptionService.approve(app.db, TEST_COMPANY_ID, exemption.id, userId) const revoked = await TaxExemptionService.revoke(app.db, TEST_COMPANY_ID, exemption.id, userId, 'Expired') expect(revoked!.status).toBe('none') expect(revoked!.revokedBy).toBe(userId) expect(revoked!.revokedReason).toBe('Expired') expect(revoked!.revokedAt).toBeTruthy() }) it('lists exemptions for an account', async () => { await TaxExemptionService.create(app.db, TEST_COMPANY_ID, { accountId, certificateNumber: 'CERT-A', }) await TaxExemptionService.create(app.db, TEST_COMPANY_ID, { accountId, certificateNumber: 'CERT-B', }) const list = await TaxExemptionService.listByAccount(app.db, TEST_COMPANY_ID, accountId) expect(list.length).toBe(2) }) it('updates exemption details', async () => { const exemption = await TaxExemptionService.create(app.db, TEST_COMPANY_ID, { accountId, certificateNumber: 'OLD', }) const updated = await TaxExemptionService.update(app.db, TEST_COMPANY_ID, exemption.id, { certificateNumber: 'NEW', issuingState: 'CA', }) expect(updated!.certificateNumber).toBe('NEW') expect(updated!.issuingState).toBe('CA') }) it('returns null for nonexistent exemption', async () => { const result = await TaxExemptionService.getById(app.db, TEST_COMPANY_ID, '00000000-0000-0000-0000-000000000000') expect(result).toBeNull() }) })