Day 12: Integration Testing - Full Trade Flow

~5 min read

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 →