Day 12: Integration Testing - Full Trade Flow
What I Built
- Comprehensive Integration Test Suite - 7 end-to-end tests covering the complete trade execution workflow from AI decision to order execution
- Mock IBKR Client - Production-ready mock implementation for testing without real API dependencies
- SQLAlchemy 2.0 Migration - Updated deprecated query syntax to modern select() patterns
- Test Infrastructure - Proper fixtures, async testing, and comprehensive error handling validation
Code Highlight
The full trade flow integration test validates end-to-end execution:
@pytest.mark.asyncio
async def test_full_trade_flow_buy(async_session: AsyncSession, account_with_balance, lock_manager):
"""Test complete BUY trade flow: decision → order → execution."""
service = TradeService(async_session, lock_manager)
# 1. Execute trade decision
trade = await service.execute_trade_decision(
account_id=account_with_balance.id,
symbol="AAPL",
action=TradeAction.BUY,
shares=1.0,
entry_price=150.0,
indicators={"rsi": 45, "ema_signal": "bullish"},
ai_rationale="Mean reversion setup",
ai_confidence=0.75,
)
# 2. Verify trade persisted
persisted_trade = await async_session.get(Trade, trade.id)
assert persisted_trade is not None
# 3. Verify decision created
decision_stmt = select(Decision).where(Decision.trade_id == trade.id)
decision_result = await async_session.execute(decision_stmt)
decision = decision_result.scalar_one_or_none()
assert decision is not None
# 4. Verify pending order (WAL)
pending_stmt = select(PendingOrder).where(PendingOrder.trade_id == trade.id)
pending_result = await async_session.execute(pending_stmt)
pending = pending_result.scalar_one_or_none()
assert pending.status == OrderStatus.PENDING
# 5. Simulate IBKR execution
update_stmt = update(PendingOrder).where(PendingOrder.id == pending.id).values({
"status": OrderStatus.SUBMITTED,
"submitted_at": datetime.utcnow(),
"ibkr_order_id": "IBKR123"
})
await async_session.execute(update_stmt)
await async_session.commit()
# 6. Mark as executed
await service.order_manager.mark_executed(pending.id)
# 7. Verify execution
executed_pending = await async_session.get(PendingOrder, pending.id)
assert executed_pending.status == OrderStatus.EXECUTED
Architecture Decision
Integration vs Unit Testing Balance: While unit tests validate individual components, integration tests ensure the entire trade pipeline works correctly. The decision to implement comprehensive end-to-end testing before production deployment provides confidence that all components (state machine, order manager, trade service, database persistence) work together properly. This catches integration bugs that unit tests miss, such as SQLAlchemy query syntax issues and async context management problems.
Testing Results
All 7 integration tests pass, covering critical production scenarios:
- Full Trade Flow (BUY) - Complete decision → order → execution pipeline
- Order Failure Handling - Error scenarios and recovery logic
- State Machine Integration - Account state transitions during trading
- Concurrent Access Control - Distributed locking prevents race conditions
- Decision Validation - Input validation and persistence
- Order Reconciliation - Startup recovery for pending orders
- Audit Trail - Logging verification (framework ready for implementation)
Next Steps
Day 13: E2E Testing & Load Testing
Follow @therealkamba on X for regular updates. View all posts →