# ABC Core Phase 2 Testing Checklist

## Unit Tests (tests/Unit/)

### Authentication
- [ ] PasswordPolicy validation (valid/invalid passwords)
- [ ] UuidGenerator generates valid UUID v4
- [ ] UuidGenerator validates UUID format
- [ ] Auth::hashPassword returns BCRYPT hash
- [ ] Auth::verifyPassword matches correct password
- [ ] Auth::verifyPassword rejects incorrect password
- [ ] Auth::needsRehash detects weak cost
- [ ] Auth::generateToken creates 64-char hex string

### Models
- [ ] UserModel fills allowed attributes
- [ ] UserModel hides password_hash
- [ ] UserModel casts types correctly
- [ ] AccountModel fills allowed attributes
- [ ] AccountModel generates UUID on construct

### Constants
- [ ] AccountTypes::isValid accepts valid types
- [ ] AccountTypes::isValid rejects invalid types
- [ ] Currencies::isValid accepts valid currencies
- [ ] UserStatus::canLogin allows ACTIVE
- [ ] UserStatus::canLogin rejects SUSPENDED
- [ ] Roles::isValid accepts valid roles
- [ ] TransactionTypes::direction returns correct direction

### AuditService
- [ ] AuditService::log creates audit record
- [ ] AuditService::log handles DB failures gracefully
- [ ] AuditService::logCreate formats old/new values correctly

## Integration Tests (tests/Integration/)

### API - Authentication
- [ ] POST /api/v1/auth/register creates user with valid data
- [ ] POST /api/v1/auth/register rejects duplicate email
- [ ] POST /api/v1/auth/register enforces password policy
- [ ] POST /api/v1/auth/login returns token with valid credentials
- [ ] POST /api/v1/auth/login rejects invalid credentials
- [ ] POST /api/v1/auth/login locks account after 5 failures
- [ ] POST /api/v1/auth/login rejects locked account
- [ ] POST /api/v1/auth/logout invalidates token
- [ ] POST /api/v1/auth/logout rejects missing token
- [ ] POST /api/v1/auth/forgot-password returns success for any email
- [ ] POST /api/v1/auth/reset-password resets with valid token
- [ ] POST /api/v1/auth/reset-password rejects expired token
- [ ] POST /api/v1/auth/reset-password rejects reused password
- [ ] POST /api/v1/auth/change-password works with valid old password
- [ ] POST /api/v1/auth/change-password rejects wrong old password

### API - Users
- [ ] GET /api/v1/users returns paginated list
- [ ] GET /api/v1/users filters by status
- [ ] GET /api/v1/users filters by email
- [ ] POST /api/v1/users creates user with roles
- [ ] POST /api/v1/users rejects invalid status
- [ ] GET /api/v1/users/{uuid} returns user
- [ ] GET /api/v1/users/{uuid} returns 404 for missing user
- [ ] PUT /api/v1/users/{uuid} updates user
- [ ] PUT /api/v1/users/{uuid} rejects duplicate email
- [ ] PATCH /api/v1/users/{uuid}/activate activates suspended user
- [ ] PATCH /api/v1/users/{uuid}/suspend suspends active user
- [ ] GET /api/v1/users/{uuid}/roles returns roles
- [ ] POST /api/v1/users/{uuid}/roles assigns new role
- [ ] POST /api/v1/users/{uuid}/roles rejects invalid role

### API - Accounts
- [ ] GET /api/v1/accounts returns paginated list
- [ ] GET /api/v1/accounts filters by status
- [ ] GET /api/v1/accounts filters by account_type
- [ ] POST /api/v1/accounts creates account for valid user
- [ ] POST /api/v1/accounts rejects invalid account_type
- [ ] POST /api/v1/accounts rejects invalid currency
- [ ] POST /api/v1/accounts generates unique account_number
- [ ] GET /api/v1/accounts/{uuid} returns account
- [ ] PATCH /api/v1/accounts/{uuid}/freeze freezes active account
- [ ] PATCH /api/v1/accounts/{uuid}/freeze rejects closed account
- [ ] PATCH /api/v1/accounts/{uuid}/unfreeze unfreezes frozen account
- [ ] PATCH /api/v1/accounts/{uuid}/unfreeze rejects non-frozen account
- [ ] PATCH /api/v1/accounts/{uuid}/close closes account
- [ ] PATCH /api/v1/accounts/{uuid}/close stores reason

### Security
- [ ] All protected endpoints reject requests without token
- [ ] All protected endpoints reject invalid token
- [ ] Rate limiting triggers after max requests
- [ ] CSRF middleware rejects requests without token
- [ ] Security headers present in all responses
- [ ] Password hashes never returned in responses
- [ ] UUIDs exposed, internal IDs hidden in responses

### Audit
- [ ] User registration creates audit log
- [ ] Login success creates audit log
- [ ] Login failure creates audit log
- [ ] Password change creates audit log
- [ ] User creation creates audit log
- [ ] User update creates audit log
- [ ] User activation creates audit log
- [ ] User suspension creates audit log
- [ ] Account creation creates audit log
- [ ] Account freeze creates audit log
- [ ] Account close creates audit log

### Database
- [ ] Migration up creates all tables
- [ ] Migration down drops all tables
- [ ] Foreign keys enforce referential integrity
- [ ] Soft delete marks deleted_at without removing row
- [ ] Unique constraints prevent duplicate emails
- [ ] Unique constraints prevent duplicate account numbers
- [ ] Index queries perform efficiently


### Phase 3 – Financial Core

#### Reference Generator
- [ ] generateTransactionReference() returns unique TXN- prefix
- [ ] generateLedgerReference() returns unique LED- prefix
- [ ] generateReceiptReference() returns unique RCP- prefix
- [ ] Reference collision is detected and retried
- [ ] References stored in ref_registry table
- [ ] isValid() accepts valid format
- [ ] isValid() rejects invalid format

#### Balance Engine
- [ ] calculateBalance() returns sum of CR minus DR
- [ ] calculateBalance() returns 0.00 for new account
- [ ] getAvailableBalance() equals calculateBalance()
- [ ] rebuildBalance() updates accounts.balance column
- [ ] verifyBalance() returns true when stored matches calculated
- [ ] verifyBalance() returns false when mismatch detected
- [ ] verifyBalance() throws exception for missing account

#### Ledger Engine
- [ ] recordEntry() creates ledger entry with correct opening/closing balance
- [ ] recordEntry() throws exception for zero/negative amount
- [ ] recordEntry() throws exception for invalid DR/CR indicator
- [ ] recordDoubleEntry() creates two entries (debit + credit)
- [ ] verifyDoubleEntry() returns true for balanced entries
- [ ] verifyDoubleEntry() returns false for imbalanced entries
- [ ] reverseEntry() creates reversal entry with opposite DR/CR
- [ ] reverseEntry() marks original as REVERSED
- [ ] reverseEntry() throws exception for non-existent entry
- [ ] getEntriesByTransaction() returns all entries for transaction
- [ ] getEntriesByAccount() returns all entries for account
- [ ] updateBalances() updates all specified account balances
- [ ] LedgerEntryCreated event dispatched after entry creation
- [ ] BalanceUpdated event dispatched after balance update

#### Transaction Engine
- [ ] createDeposit() creates CR ledger entry, updates balance, creates receipt
- [ ] createWithdrawal() creates DR ledger entry, updates balance, creates receipt
- [ ] createTransfer() creates two entries (DR + CR), updates both balances
- [ ] createFee() creates DR fee entry, updates balance
- [ ] createInterest() creates CR interest entry, updates balance
- [ ] createAdjustment() creates entry with specified direction
- [ ] createLoanDisbursement() creates CR entry, updates balance
- [ ] createLoanRepayment() creates DR entry, updates balance
- [ ] createCardPayment() creates DR entry, updates balance
- [ ] createRefund() creates CR entry, updates balance
- [ ] createReversal() reverses all entries of original transaction
- [ ] createReversal() throws exception for non-completed transaction
- [ ] getTransaction() returns transaction with metadata, ledger entries, receipt
- [ ] searchTransactions() returns paginated results with filters
- [ ] TransactionCreated event dispatched after completion
- [ ] TransactionReversed event dispatched after reversal
- [ ] All transactions wrapped in database transaction (rollback on failure)
- [ ] Double-entry verification fails throw exception and rollback
- [ ] Insufficient balance throws ValidationException
- [ ] Daily limit exceeded throws ValidationException
- [ ] Currency mismatch throws ValidationException
- [ ] Frozen account throws ValidationException
- [ ] Closed account throws ValidationException
- [ ] Invalid transaction type throws ValidationException
- [ ] Negative amount throws ValidationException
- [ ] Zero amount throws ValidationException
- [ ] Amount with more than 2 decimals throws ValidationException

#### Financial Validation Service
- [ ] validateAmount() accepts valid positive amount
- [ ] validateAmount() rejects zero amount
- [ ] validateAmount() rejects negative amount
- [ ] validateAmount() rejects non-numeric amount
- [ ] validateAmount() rejects amount exceeding maximum
- [ ] validateAmount() rejects amount with >2 decimals
- [ ] validateCurrency() accepts supported currency
- [ ] validateCurrency() rejects unsupported currency
- [ ] validateAccountForDebit() accepts active account with sufficient balance
- [ ] validateAccountForDebit() rejects frozen account
- [ ] validateAccountForDebit() rejects closed account
- [ ] validateAccountForDebit() rejects currency mismatch
- [ ] validateAccountForDebit() rejects insufficient balance
- [ ] validateAccountForDebit() rejects daily limit exceeded
- [ ] validateAccountForCredit() accepts non-closed account
- [ ] validateAccountForCredit() rejects closed account
- [ ] validateTransaction() combines all validation rules
- [ ] validateTransaction() throws ValidationException with field errors

#### Receipt Foundation
- [ ] create() creates receipt with UUID, reference, receipt number
- [ ] create() returns existing receipt if already created
- [ ] findByTransaction() returns receipt by transaction UUID
- [ ] findByReceiptNumber() returns receipt by receipt number
- [ ] updateStatus() updates receipt status
- [ ] ReceiptCreated event dispatched after creation
- [ ] Receipt failure is logged but does not break transaction

#### Database
- [ ] ledger_entries table has correct schema and indexes
- [ ] transactions table has correct schema and indexes
- [ ] transaction_metadata table has correct schema and indexes
- [ ] receipts table has correct schema and indexes
- [ ] ref_registry table has correct schema and indexes
- [ ] Foreign keys not applicable (no cross-table FKs in Phase 3 tables)
- [ ] UUID columns are unique and indexed
- [ ] Reference columns are unique and indexed
- [ ] Transaction UUID index on ledger_entries
- [ ] Account UUID index on ledger_entries

#### Integration
- [ ] Deposit transaction flow: validate -> transaction -> ledger -> balance -> receipt -> audit
- [ ] Withdrawal transaction flow: validate -> transaction -> ledger -> balance -> receipt -> audit
- [ ] Transfer transaction flow: validate -> transaction -> double-entry -> both balances -> receipt -> audit
- [ ] Reversal transaction flow: original marked REVERSED, new reversal transaction created, balances updated
- [ ] Event listeners failing do not break transaction commit
- [ ] Receipt creation failing do not break transaction commit
- [ ] Audit log failing do not break transaction commit
- [ ] Database transaction rolls back on any exception
