stock/tests/test_workflow_langgraph.py
ZhangPeng 9aecdd036c Initial commit: OpenClaw Trading - AI多智能体量化交易系统
- 添加项目核心代码和配置
- 添加前端界面 (Next.js)
- 添加单元测试
- 更新 .gitignore 排除缓存和依赖
2026-02-27 03:47:40 +08:00

210 lines
6.3 KiB
Python

"""Tests for LangGraph-based trading workflow.
This module tests the LangGraph workflow implementation including:
- State management
- Node functions
- Graph construction and execution
- End-to-end workflow runs
"""
import asyncio
import pytest
from openclaw.workflow.state import (
TradingWorkflowState,
create_initial_state,
get_state_summary,
)
from openclaw.workflow.trading_workflow import TradingWorkflow, run_trading_workflow
class TestWorkflowState:
"""Test suite for workflow state management."""
def test_create_initial_state(self):
"""Test initial state creation."""
state = create_initial_state("AAPL", 1000.0)
assert state["config"]["symbol"] == "AAPL"
assert state["current_step"] == "START"
assert state["completed_steps"] == []
assert state["errors"] == []
assert state["technical_report"] is None
assert state["sentiment_report"] is None
assert state["fundamental_report"] is None
def test_create_initial_state_different_symbol(self):
"""Test initial state with different symbol."""
state = create_initial_state("TSLA", 2000.0)
assert state["config"]["symbol"] == "TSLA"
assert state["config"]["market_analyst"]["agent_id"] == "market_analyst_TSLA"
def test_get_state_summary_initial(self):
"""Test state summary for initial state."""
state = create_initial_state("AAPL", 1000.0)
summary = get_state_summary(state)
assert summary["symbol"] == "AAPL"
assert summary["current_step"] == "START"
assert summary["has_technical"] is False
assert summary["has_sentiment"] is False
assert summary["has_fundamental"] is False
assert summary["error_count"] == 0
class TestTradingWorkflow:
"""Test suite for TradingWorkflow class."""
def test_workflow_initialization(self):
"""Test workflow initialization."""
workflow = TradingWorkflow("AAPL", 1000.0)
assert workflow.symbol == "AAPL"
assert workflow.initial_capital == 1000.0
assert workflow.enable_parallel is True
def test_graph_build(self):
"""Test graph compilation."""
workflow = TradingWorkflow("AAPL", 1000.0)
# Build graph
graph = workflow._build_graph()
assert graph is not None
def test_graph_property(self):
"""Test graph property access."""
workflow = TradingWorkflow("AAPL", 1000.0)
# Access graph property (should build on first access)
graph = workflow.graph
assert graph is not None
# Second access should return cached graph
assert workflow.graph is graph
@pytest.mark.asyncio
async def test_workflow_run_basic(self):
"""Test basic workflow execution."""
workflow = TradingWorkflow("AAPL", 1000.0)
final_state = await workflow.run(debug=True)
assert final_state is not None
assert "completed_steps" in final_state
@pytest.mark.asyncio
async def test_workflow_run_outputs_generated(self):
"""Test that workflow generates expected outputs."""
workflow = TradingWorkflow("AAPL", 1000.0)
final_state = await workflow.run()
# Check that at least some analyses completed
completed = final_state.get("completed_steps", [])
assert len(completed) > 0
def test_run_sync(self):
"""Test synchronous workflow execution."""
workflow = TradingWorkflow("AAPL", 1000.0)
final_state = workflow.run_sync()
assert final_state is not None
assert "completed_steps" in final_state
def test_get_final_decision(self):
"""Test getting final decision from state."""
workflow = TradingWorkflow("AAPL", 1000.0)
final_state = workflow.run_sync()
decision = workflow.get_final_decision(final_state)
# Decision may be None if workflow didn't complete, but shouldn't error
if decision:
assert "symbol" in decision
assert decision["symbol"] == "AAPL"
def test_visualize(self):
"""Test workflow visualization generation."""
workflow = TradingWorkflow("AAPL", 1000.0)
mermaid = workflow.visualize()
assert "flowchart" in mermaid
assert "MarketAnalysis" in mermaid
assert "SentimentAnalysis" in mermaid
assert "FundamentalAnalysis" in mermaid
assert "BullBearDebate" in mermaid
assert "DecisionFusion" in mermaid
assert "RiskAssessment" in mermaid
class TestWorkflowIntegration:
"""Integration tests for the complete workflow."""
@pytest.mark.asyncio
async def test_full_workflow_streaming(self):
"""Test workflow with streaming."""
workflow = TradingWorkflow("MSFT", 1000.0)
updates = []
async for update in workflow.astream(debug=True):
updates.append(update)
assert len(updates) > 0
def test_convenience_function(self):
"""Test the convenience function run_trading_workflow."""
decision = run_trading_workflow("GOOGL", 1000.0, debug=False)
assert decision is not None
assert "symbol" in decision
assert decision["symbol"] == "GOOGL"
class TestWorkflowEdgeCases:
"""Test edge cases and error handling."""
def test_empty_symbol(self):
"""Test workflow with empty symbol."""
workflow = TradingWorkflow("", 1000.0)
final_state = workflow.run_sync()
# Should still complete without errors
assert final_state is not None
def test_zero_capital(self):
"""Test workflow with zero capital."""
workflow = TradingWorkflow("AAPL", 0.0)
final_state = workflow.run_sync()
# Should still complete
assert final_state is not None
@pytest.mark.asyncio
async def test_multiple_workflows(self):
"""Test running multiple workflows concurrently."""
workflow1 = TradingWorkflow("AAPL", 1000.0)
workflow2 = TradingWorkflow("GOOGL", 1000.0)
workflow3 = TradingWorkflow("MSFT", 1000.0)
# Run all three concurrently
results = await asyncio.gather(
workflow1.run(),
workflow2.run(),
workflow3.run(),
)
assert len(results) == 3
for result in results:
assert result is not None
if __name__ == "__main__":
pytest.main([__file__, "-v"])