diff --git a/__tests__/BatchInvoiceForm.test.tsx b/__tests__/BatchInvoiceForm.test.tsx index d0b0224..2b7ed66 100644 --- a/__tests__/BatchInvoiceForm.test.tsx +++ b/__tests__/BatchInvoiceForm.test.tsx @@ -182,4 +182,32 @@ GTEST123,1000.00,USDC,3.50,2024-12-31`; // Should show total amount in summary expect(screen.getByText('Total Amount')).toBeInTheDocument(); }); + + it('rejects/truncates 51-row CSV client-side with clear message matching 50-row contract limit', async () => { + render(); + + // Build a CSV with 51 rows + const header = 'payer,amount,token,discount_rate,due_date'; + const rows = Array.from({ length: 51 }, (_, i) => + `GTEST${i}EXAMPLE456DEF789GHI012JKL345MNO678PQR901STU234VWX567YZ,1000.00,USDC,3.50,2024-12-31` + ); + const csvContent = [header, ...rows].join('\n'); + + const file = new File([csvContent], 'test_51.csv', { type: 'text/csv' }); + const input = screen.getByLabelText('Click to upload CSV file').querySelector('input'); + + if (input) { + fireEvent.change(input, { target: { files: [file] } }); + } + + await waitFor(() => { + expect(mockAddToast).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'warning', + title: 'Rows Truncated', + message: 'Only the first 50 rows were loaded.', + }) + ); + }); + }); }); diff --git a/src/components/BatchInvoiceForm.tsx b/src/components/BatchInvoiceForm.tsx index 768701e..39d93ef 100644 --- a/src/components/BatchInvoiceForm.tsx +++ b/src/components/BatchInvoiceForm.tsx @@ -16,6 +16,7 @@ import { import { submitInvoicesBatch } from '@/utils/soroban'; import TokenSelector from './TokenSelector'; +// Enforces maximum 50 rows per batch, matching the contract's submit_invoices_batch() instruction (source of truth) const MAX_BATCH_SIZE = 50; export interface BatchInvoiceRow extends InvoiceFormValues { diff --git a/src/components/__tests__/BatchInvoiceForm.test.tsx b/src/components/__tests__/BatchInvoiceForm.test.tsx index 48c6006..75a900b 100644 --- a/src/components/__tests__/BatchInvoiceForm.test.tsx +++ b/src/components/__tests__/BatchInvoiceForm.test.tsx @@ -113,4 +113,26 @@ describe('BatchInvoiceForm', () => { render(); expect(screen.queryByText(/batch/i)).toBeInTheDocument(); }); + + it('rejects 51-row CSV file exceeding contract batch limit with a clear notification', async () => { + render(); + const header = 'payer,amount,token,discount_rate,due_date'; + const rows = Array.from({ length: 51 }, (_, i) => + `GCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC${i % 10},100.00,USDC,3.00,2025-12-31` + ); + const file = new File([[header, ...rows].join('\n')], 'over_limit.csv', { type: 'text/csv' }); + const input = document.getElementById('csv-upload') as HTMLInputElement | null; + if (input) { + fireEvent.change(input, { target: { files: [file] } }); + await waitFor(() => { + expect(addToast).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'warning', + title: 'Rows Truncated', + message: 'Only the first 50 rows were loaded.', + }) + ); + }); + } + }); });