File size: 6,417 Bytes
aa3c874
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
752f5cc
aa3c874
 
 
 
752f5cc
aa3c874
752f5cc
aa3c874
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
 
752f5cc
aa3c874
 
 
 
752f5cc
 
 
 
 
 
 
 
 
 
 
aa3c874
752f5cc
aa3c874
 
 
 
 
 
752f5cc
 
 
 
 
 
 
 
 
 
 
 
 
 
aa3c874
 
 
 
 
 
752f5cc
aa3c874
 
 
 
 
752f5cc
aa3c874
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
"""
Pytest Configuration for Roger Intelligence Platform

Provides fixtures and configuration for testing agentic AI components:
- Agent graph fixtures
- Mock LLM for unit testing
- LangSmith integration
- Golden dataset loading
"""

import os
import sys
import pytest
from pathlib import Path
from typing import Dict, Any, List
from unittest.mock import MagicMock, patch

# Add project root to path
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))


# =============================================================================
# ENVIRONMENT CONFIGURATION
# =============================================================================


@pytest.fixture(scope="session", autouse=True)
def configure_test_environment():
    """Configure environment for testing (runs once per session)."""
    # Ensure we're in test mode
    os.environ["TESTING"] = "true"

    # Optionally disable LangSmith tracing in unit tests for speed
    # Set LANGSMITH_TRACING_TESTS=true to enable tracing in tests
    if os.getenv("LANGSMITH_TRACING_TESTS", "false").lower() != "true":
        os.environ["LANGCHAIN_TRACING_V2"] = "false"

    yield

    # Cleanup
    os.environ.pop("TESTING", None)


# =============================================================================
# MOCK LLM FIXTURES
# =============================================================================


@pytest.fixture
def mock_llm():
    """
    Provides a mock LLM for testing without API calls.
    Returns predictable responses for deterministic testing.
    """
    mock = MagicMock()
    mock.invoke.return_value = MagicMock(
        content='{"decision": "proceed", "reasoning": "Test response"}'
    )
    return mock


@pytest.fixture
def mock_groq_llm():
    """Mock GroqLLM class for testing agent nodes."""
    with patch("src.llms.groqllm.GroqLLM") as mock_class:
        mock_instance = MagicMock()
        mock_instance.get_llm.return_value = MagicMock()
        mock_class.return_value = mock_instance
        yield mock_class


# =============================================================================
# AGENT FIXTURES
# =============================================================================


@pytest.fixture
def sample_agent_state() -> Dict[str, Any]:
    """Returns a sample CombinedAgentState for testing."""
    return {
        "run_count": 1,
        "last_run_ts": "2024-01-01T00:00:00",
        "domain_insights": [],
        "final_ranked_feed": [],
        "risk_dashboard_snapshot": {},
        "route": None,
    }


@pytest.fixture
def sample_domain_insight() -> Dict[str, Any]:
    """Returns a sample domain insight for testing aggregation."""
    return {
        "title": "Test Flood Warning",
        "summary": "Heavy rainfall expected in Colombo district",
        "source": "DMC",
        "domain": "meteorological",
        "timestamp": "2024-01-01T10:00:00",
        "confidence": 0.85,
        "risk_type": "Flood",
        "severity": "High",
    }


# =============================================================================
# GOLDEN DATASET FIXTURES
# =============================================================================


@pytest.fixture
def golden_dataset_path() -> Path:
    """Returns path to golden datasets directory."""
    return PROJECT_ROOT / "tests" / "evaluation" / "golden_datasets"


@pytest.fixture
def expected_responses(golden_dataset_path) -> List[Dict]:
    """Load expected responses for LLM-as-Judge evaluation."""
    import json

    response_file = golden_dataset_path / "expected_responses.json"
    if response_file.exists():
        with open(response_file, "r", encoding="utf-8") as f:
            return json.load(f)
    return []


# =============================================================================
# LANGSMITH FIXTURES
# =============================================================================


@pytest.fixture
def langsmith_client():
    """
    Provides LangSmith client for evaluation tests.
    Returns None if not configured.
    """
    try:
        from src.config.langsmith_config import get_langsmith_client

        return get_langsmith_client()
    except ImportError:
        return None


@pytest.fixture
def traced_test(langsmith_client):
    """
    Context manager for traced test execution.
    Automatically logs test runs to LangSmith.
    """
    from contextlib import contextmanager

    @contextmanager
    def _traced_test(test_name: str):
        if langsmith_client:
            # Start a trace run
            pass  # LangSmith auto-traces when configured
        yield

    return _traced_test


# =============================================================================
# TOOL FIXTURES
# =============================================================================


@pytest.fixture
def weather_tool_response() -> str:
    """Sample response from weather tool for testing."""
    import json

    return json.dumps(
        {
            "status": "success",
            "data": {
                "location": "Colombo",
                "temperature": 28,
                "humidity": 75,
                "condition": "Partly Cloudy",
                "rainfall_probability": 30,
            },
        }
    )


@pytest.fixture
def news_tool_response() -> str:
    """Sample response from news tool for testing."""
    import json

    return json.dumps(
        {
            "status": "success",
            "results": [
                {
                    "title": "Economic growth forecast for 2024",
                    "source": "Daily Mirror",
                    "url": "https://example.com/news/1",
                    "published": "2024-01-01",
                }
            ],
        }
    )


# =============================================================================
# TEST MARKERS
# =============================================================================


def pytest_configure(config):
    """Register custom markers."""
    config.addinivalue_line(
        "markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')"
    )
    config.addinivalue_line("markers", "integration: marks tests as integration tests")
    config.addinivalue_line(
        "markers", "evaluation: marks tests as LLM evaluation tests"
    )
    config.addinivalue_line(
        "markers", "adversarial: marks tests as adversarial/security tests"
    )