import Database from 'better-sqlite3'; import { Invoice } from '../src/invoicing'; import { SqliteInvoiceRepo } from '../src/db/invoice-repo'; const invoice = (overrides: Partial = {}): Invoice => ({ id: 'INV-2026-07-0001', customerId: 'cust_2', period: '2026-07', status: 'draft', lines: [{ endpointId: 'storage', calls: 2, cents: 1500 }], totalCents: 1500, creditCents: 1000, billableCents: 500, billingType: 'stripe', ...overrides, }); describe('SqliteInvoiceRepo', () => { it('saves and retrieves an invoice with its lines', () => { const repo = new SqliteInvoiceRepo(new Database(':memory:')); repo.save(invoice()); const got = repo.get('INV-2026-07-0001'); expect(got?.billableCents).toBe(500); expect(got?.lines).toEqual([{ endpointId: 'storage', calls: 2, cents: 1500 }]); }); it('re-saving an invoice replaces its lines (draft regeneration)', () => { const repo = new SqliteInvoiceRepo(new Database(':memory:')); repo.save(invoice()); repo.save(invoice({ lines: [{ endpointId: 'transform', calls: 5, cents: 20 }], totalCents: 20 })); const got = repo.get('INV-2026-07-0001'); expect(got?.lines).toEqual([{ endpointId: 'transform', calls: 5, cents: 20 }]); expect(repo.list({})).toHaveLength(1); }); it('filters by customer, period, and status', () => { const repo = new SqliteInvoiceRepo(new Database(':memory:')); repo.save(invoice()); repo.save(invoice({ id: 'INV-2026-07-0002', customerId: 'cust_3', status: 'issued' })); repo.save(invoice({ id: 'INV-2026-08-0001', period: '2026-08' })); expect(repo.list({ customerId: 'cust_3' })).toHaveLength(1); expect(repo.list({ period: '2026-08' })).toHaveLength(1); expect(repo.list({ status: 'issued' })).toHaveLength(1); expect(repo.list({ period: '2026-07' })).toHaveLength(2); expect(repo.list({})).toHaveLength(3); }); it('increments sequences per period independently', () => { const repo = new SqliteInvoiceRepo(new Database(':memory:')); expect(repo.nextSequence('2026-07')).toBe(1); repo.save(invoice()); expect(repo.nextSequence('2026-07')).toBe(2); expect(repo.nextSequence('2026-08')).toBe(1); }); it('persists lifecycle timestamps and PO numbers', () => { const repo = new SqliteInvoiceRepo(new Database(':memory:')); repo.save( invoice({ billingType: 'purchase_order', poNumber: 'PO-1', status: 'issued', issuedAtMs: 1000, dueAtMs: 2000, }), ); const got = repo.get('INV-2026-07-0001'); expect(got?.poNumber).toBe('PO-1'); expect(got?.issuedAtMs).toBe(1000); expect(got?.dueAtMs).toBe(2000); }); });