Batch Transfer UAT Test Cases¶
This document describes the batch transfer UAT test cases using the hb transfer batch-import command.
Overview¶
Two comprehensive batch transfer test cases have been added to test the batch import functionality with JSON input:
uat_batch_transfer_valid- Batch import with 11 items: 10 valid transfers covering all scenarios (inferred, explicit from-currency, explicit to-currency) + 1 with parsing error to verify error reportinguat_batch_transfer_invalid- Batch import with mixed valid/invalid transfers to test error handling (including over-specified case)
Currency Normalization Layer¶
The implementation includes a normalization layer that accepts user currency specifications for either the from_account OR to_account, and converts them to the backend standard format (currency = from_account, currency_amount = from_amount).
User Input Modes:
- Amount only: System infers currency based on account types (base currency if present)
- From-currency explicit:
currency+currency_amountmatching from_account (pass through) - To-currency explicit:
currency+currency_amountmatching to_account (normalized to from-currency format)
Backend Storage Format:
currency: Always equals from_account currencycurrency_amount: Amount in from_account currencyamount: Amount in to_account currency
Test Case 1: uat_batch_transfer_valid¶
Purpose¶
Validate that batch import correctly creates multiple transfers with different currency scenarios and properly syncs to mobile app. Tests:
- Inferred currency (amount only)
- Explicit from-currency specification (currency matches from_account)
- Explicit to-currency specification (currency matches to_account) - tests normalization layer
- Parsing error reporting (not silently dropped)
Input: transfers_valid_batch.json¶
11 items in single batch (10 valid + 1 parsing error):
Items 1-4: Amount Only (Currency Inferred)
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "200.00",
"notes": "UAT Batch: SGD->USD Amount Only"
},
{
"date": "2026-02-22",
"from_account": "TWH IB USD",
"to_account": "TWH - Personal",
"amount": "400.00",
"notes": "UAT Batch: USD->SGD Amount Only"
},
{
"date": "2026-02-22",
"from_account": "TWH IB USD",
"to_account": "Cash TWH EUR",
"amount": "180.00",
"notes": "UAT Batch: USD->EUR Amount Only"
},
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "30 CC Hashemis",
"amount": "600.00",
"notes": "UAT Batch: SGD->SGD Same Currency"
}
Items 5-7: Explicit From-Currency (Pass Through)
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"currency": "SGD",
"currency_amount": "250.00",
"notes": "UAT Batch: SGD->USD Explicit From Currency (Base)"
},
{
"date": "2026-02-22",
"from_account": "TWH IB USD",
"to_account": "TWH - Personal",
"currency": "USD",
"currency_amount": "150.00",
"notes": "UAT Batch: USD->SGD Explicit From Currency (Non-Base)"
},
{
"date": "2026-02-22",
"from_account": "TWH IB USD",
"to_account": "Cash TWH EUR",
"currency": "USD",
"currency_amount": "120.00",
"notes": "UAT Batch: USD->EUR Explicit From Currency (Non-Base)"
}
Items 8-10: Explicit To-Currency (Normalized to From-Currency)
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"currency": "USD",
"currency_amount": "100.00",
"notes": "UAT Batch: SGD->USD Explicit To Currency (Non-Base) [NORMALIZED]"
},
{
"date": "2026-02-22",
"from_account": "TWH IB USD",
"to_account": "TWH - Personal",
"currency": "SGD",
"currency_amount": "300.00",
"notes": "UAT Batch: USD->SGD Explicit To Currency (Base) [NORMALIZED]"
},
{
"date": "2026-02-22",
"from_account": "Cash TWH EUR",
"to_account": "TWH IB USD",
"currency": "USD",
"currency_amount": "90.00",
"notes": "UAT Batch: EUR->USD Explicit To Currency (Non-Base) [NORMALIZED]"
}
Item 11: Parsing Error (Invalid Date)
{
"date": "2026-22-02",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "100.00",
"notes": "UAT Batch: INVALID DATE FORMAT (should cause parsing error)"
}
Expected Results¶
Command Output:
1 parsing error(s) occurred:
Item 11: time data '2026-22-02' does not match format '%Y-%m-%d'
Batch import completed
Successful: 10
Failed: 0
Parsing Errors:
- ❌ Item 8: Invalid date format (2026-22-02) - demonstrates that parsing errors are now reported instead of being silently dropped
Item 1: Base→Foreign (SGD→USD) - Amount Only
- ✅ Created
currency=SGD(from_account)currency_amount=200(in SGD)amount≈148(in USD, 200/1.35)
Item 2: Foreign→Base (USD→SGD) - Amount Only
- ✅ Created
currency=USD(from_account)currency_amount≈296(in USD, 400/1.35)amount=400(in SGD)
Item 3: Foreign→Foreign (USD→EUR) - Amount Only
- ✅ Created
currency=USD(from_account)currency_amount=180(in USD)amountcalculated using cross-rate
Item 4: Same Currency (SGD→SGD)
- ✅ Created
currency=SGDcurrency_amount=600(1:1)amount=600(1:1, no forex)
Item 5: Base→Foreign (SGD→USD) - Explicit From Currency
- ✅ Created
currency=SGD(explicitly specified, matches from_account)currency_amount=250(in SGD, explicitly specified)amount≈185(in USD, calculated: 250/1.35)
Item 6: Foreign→Base (USD→SGD) - Explicit From Currency
- ✅ Created
currency=USD(explicitly specified, matches from_account)currency_amount=150(in USD, explicitly specified)amount≈202.50(in SGD, calculated: 150*1.35)
Item 7: Foreign→Foreign (USD→EUR) - Explicit From Currency
- ✅ Created
currency=USD(explicitly specified, matches from_account)currency_amount=120(in USD, explicitly specified)amountcalculated using cross-rate (USD→SGD→EUR)
Sync Validation¶
- ✅ 10 SyncUpdate records created (1 item failed parsing, doesn't reach processing)
- ✅ All 10 transfers visible in Windows app
- ✅ All 10 transfers visible in mobile app (with correct currencies)
- ✅ Amounts reflect correct currency conversions in UI
- ✅ Inferred (Items 1-4), explicit from-currency (Items 5-7), and explicit to-currency (Items 8-10) specifications all work correctly
- ✅ To-currency specifications (Items 8-10) are normalized to backend format (currency = from_account)
- ✅ Parsing errors are displayed (Item 11 shows error, not silently dropped)
Test Workflow¶
The test follows a streamlined workflow that doesn't enumerate each transfer case individually:
- Import batch of 11 items (10 valid + 1 parsing error)
- Verify parsing error displayed for Item 11 (invalid date format)
- List all transfers (shows keys, amounts, accounts - should show 10 transfers)
- Record transfer keys from the list (comma-separated) - Important: Choose "pass" to trigger key recording prompt
- Verify all 10 transfers appear in apps with correct data
- Verify to-currency items (8-10) were normalized correctly - check database shows currency = from_account
- Verify SyncUpdates created for 10 transfers
- Automated rollback using recorded keys
Note: The test is generic and adapts to whatever transfers are defined in transfers_valid_batch.json. Adding or removing test cases updates the batch automatically without changing the test workflow.
Rollback¶
- Automated rollback using batch delete with recorded transfer keys
- The rollback step uses the
rollback_transfers.jsontemplate which expands{TRANSFER_KEYS}to individual delete operations
Test Case 2: uat_batch_transfer_invalid¶
Purpose¶
Validate that batch import correctly handles mixed valid/invalid transfers with continue_on_error behavior, reporting specific errors for each invalid record while still processing valid ones.
Input: transfers_invalid_batch.json¶
11 transfer items (2 valid, 9 invalid):
[
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "100.00",
"notes": "INVALID BATCH: Item 1 - Valid (for comparison)"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "-50.00",
"notes": "INVALID BATCH: Item 2 - Negative amount"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "0.00",
"notes": "INVALID BATCH: Item 3 - Zero amount"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "75.00",
"currency": "SGD",
"currency_amount": "75.00",
"notes": "INVALID BATCH: Item 4 - Over-specified (no exchange_rate)"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"currency": "USD",
"currency_amount": "60.00",
"notes": "INVALID BATCH: Item 5 - Currency mismatch (USD not from_account)"
},
{
"date": "2026-02-23",
"from_account": "Nonexistent Account",
"to_account": "TWH IB USD",
"amount": "100.00",
"notes": "INVALID BATCH: Item 6 - Nonexistent from_account"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "Nonexistent Account",
"amount": "100.00",
"notes": "INVALID BATCH: Item 7 - Nonexistent to_account"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH - Personal",
"amount": "100.00",
"notes": "INVALID BATCH: Item 8 - Same from and to account"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"notes": "INVALID BATCH: Item 9 NoAmount"
},
{
"date": "2026-02-23",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"currency": "EUR",
"currency_amount": "70.00",
"notes": "INVALID BATCH: Item 10 ThirdCurrency"
},
{
"date": "2026-02-23",
"from_account": "TWH IB USD",
"to_account": "TWH - Personal",
"amount": "250.00",
"notes": "INVALID BATCH: Item 11 Valid"
}
]
Expected Command Output¶
Batch import completed
Successful: 3
Failed: 8
Failed records:
2026-02-23 TWH - Personal -> TWH IB USD: Invalid amount (must not be zero)
2026-02-23 TWH - Personal -> TWH IB USD: Cannot specify both amount and currency_amount without exchange_rate
2026-02-23 TWH - Personal -> TWH IB USD: Transfer currency must match from_account currency
2026-02-23 Nonexistent Account -> TWH IB USD: Account not found: Nonexistent Account
2026-02-23 TWH - Personal -> Nonexistent Account: Account not found: Nonexistent Account
2026-02-23 TWH - Personal -> TWH - Personal: From and to accounts must be different
2026-02-23 TWH - Personal -> TWH IB USD: Amount or currency_amount must be specified
2026-02-23 TWH - Personal -> TWH IB USD: Currency must match from_account or to_account
Expected Results¶
Successful (3 created):
- ✅ Item 1: SGD->USD, 100 (valid)
- ✅ Item 2: SGD->USD, -50 (valid - negative amount allowed)
- ✅ Item 11: USD->SGD, 250 (valid)
Failed (8 rejected):
- ❌ Item 3: Zero amount (0.00)
- ❌ Item 4: Over-specified (amount + currency_amount, no rate)
- ❌ Item 5: Currency mismatch (USD doesn't match from_account SGD)
- ❌ Item 6: Nonexistent from_account
- ❌ Item 7: Nonexistent to_account
- ❌ Item 8: Same from and to account
- ❌ Item 9: Missing amount and currency_amount
- ❌ Item 10: 3rd currency (EUR doesn't match from_account SGD or to_account USD)
Validation¶
- ✅ Only 2 transfers created in database
- ✅ Only 2 SyncUpdate records created
- ✅ Failed items produce clear, specific error messages
- ✅ Only 2 transfers visible in mobile app (Items 1 and 11)
- ✅ No mobile app data for any failed items
- ✅ Processing continued for all items despite errors (no early termination)
Running the Tests¶
Run Valid Batch Test¶
Run Invalid Batch Test¶
View Available Batch Tests¶
Batch Testing Benefits¶
- Efficiency: Test 7+ scenarios in single batch instead of 7+ individual test runs
- Error Handling: Validate error reporting and continue-on-error behavior
- Parsing Error Visibility: Verify parsing errors are displayed (not silently dropped)
- Sync Validation: Verify SyncUpdates are created correctly for batch operations
- Real-world Usage: Users often import transfers from external systems (spreadsheets, other apps)
- Cross-scenario Coverage: Mix of base→foreign, foreign→base, foreign→foreign, same-currency, inferred vs explicit in one test
- Specification Methods: Tests both inference (amount only) and explicit specification (currency + currency_amount)
Cleanup¶
Both Tests Now Use Automated Rollback:
- Test runner records transfer keys from batch output
- Automated rollback step uses
hb batch runwithrollback_transfers.jsontemplate - Template expands
{TRANSFER_KEYS}to individual delete operations
Manual Cleanup (if needed):
python tests/manual/find_and_delete_transfers.py --date 2026-02-22 --from-account "TWH - Personal" --to-account "TWH IB USD" --amount 200.00
JSON Format Reference¶
Required Fields:
date- YYYY-MM-DD formatfrom_account- Account name (must exist)to_account- Account name (must exist)amount- Decimal value (for inference) OR explicitly specifycurrency+currency_amount
Optional Fields:
notes- Transfer notes/descriptioncurrency- Must match from_account currency (inferred if not specified)currency_amount- Amount in from_account currency (inferred if not specified)
Validation Rules:
- Cannot specify both
amountandcurrency_amountwithoutexchange_rate(ambiguous) currencymust matchfrom_accountcurrency (if specified)amountandcurrency_amountmust not be zero- From and to accounts must be different
- From and to accounts must exist in database