|
| 1 | +""" |
| 2 | +Test configuration for v4 API router tests. |
| 3 | +Sets up mocks before module imports to enable proper test discovery. |
| 4 | +""" |
| 5 | + |
| 6 | +import os |
| 7 | +import sys |
| 8 | +from enum import Enum |
| 9 | +from pathlib import Path |
| 10 | +from unittest.mock import AsyncMock, MagicMock, Mock |
| 11 | + |
| 12 | +import pytest |
| 13 | + |
| 14 | +# Add backend to path FIRST |
| 15 | +# From src/tests/backend/v4/api/conftest.py, go up to src/ then into backend/ |
| 16 | +backend_path = Path(__file__).parent.parent.parent.parent.parent / "backend" |
| 17 | +sys.path.insert(0, str(backend_path)) |
| 18 | + |
| 19 | +# Set up environment variables before any imports |
| 20 | +os.environ.update({ |
| 21 | + 'APPLICATIONINSIGHTS_CONNECTION_STRING': 'InstrumentationKey=test-key', |
| 22 | + 'AZURE_AI_SUBSCRIPTION_ID': 'test-subscription', |
| 23 | + 'AZURE_AI_RESOURCE_GROUP': 'test-rg', |
| 24 | + 'AZURE_AI_PROJECT_NAME': 'test-project', |
| 25 | + 'AZURE_AI_AGENT_ENDPOINT': 'https://test.agent.endpoint.com', |
| 26 | + 'AZURE_OPENAI_ENDPOINT': 'https://test.openai.azure.com/', |
| 27 | + 'AZURE_OPENAI_API_KEY': 'test-key', |
| 28 | + 'AZURE_OPENAI_API_VERSION': '2023-05-15', |
| 29 | + 'COSMOSDB_ENDPOINT': 'https://mock-endpoint', |
| 30 | + 'COSMOSDB_KEY': 'mock-key', |
| 31 | + 'COSMOSDB_DATABASE': 'mock-database', |
| 32 | + 'COSMOSDB_CONTAINER': 'mock-container', |
| 33 | + 'USER_LOCAL_BROWSER_LANGUAGE': 'en-US', |
| 34 | +}) |
| 35 | + |
| 36 | +# Mock Azure dependencies with proper module structure |
| 37 | +azure_monitor_mock = MagicMock() |
| 38 | +sys.modules["azure.monitor"] = azure_monitor_mock |
| 39 | +sys.modules["azure.monitor.events"] = MagicMock() |
| 40 | +sys.modules["azure.monitor.events.extension"] = MagicMock() |
| 41 | +sys.modules["azure.monitor.opentelemetry"] = MagicMock() |
| 42 | +azure_monitor_mock.opentelemetry = sys.modules["azure.monitor.opentelemetry"] |
| 43 | +azure_monitor_mock.opentelemetry.configure_azure_monitor = MagicMock() |
| 44 | + |
| 45 | +azure_ai_mock = type(sys)("azure.ai") |
| 46 | +azure_ai_agents_mock = type(sys)("azure.ai.agents") |
| 47 | +azure_ai_agents_mock.aio = MagicMock() |
| 48 | +azure_ai_mock.agents = azure_ai_agents_mock |
| 49 | +sys.modules["azure.ai"] = azure_ai_mock |
| 50 | +sys.modules["azure.ai.agents"] = azure_ai_agents_mock |
| 51 | +sys.modules["azure.ai.agents.aio"] = azure_ai_agents_mock.aio |
| 52 | + |
| 53 | +azure_ai_projects_mock = type(sys)("azure.ai.projects") |
| 54 | +azure_ai_projects_mock.models = MagicMock() |
| 55 | +azure_ai_projects_mock.aio = MagicMock() |
| 56 | +sys.modules["azure.ai.projects"] = azure_ai_projects_mock |
| 57 | +sys.modules["azure.ai.projects.models"] = azure_ai_projects_mock.models |
| 58 | +sys.modules["azure.ai.projects.aio"] = azure_ai_projects_mock.aio |
| 59 | + |
| 60 | +# Cosmos DB mocks with nested structure |
| 61 | +sys.modules["azure.cosmos"] = MagicMock() |
| 62 | +cosmos_aio_mock = type(sys)("azure.cosmos.aio") # Create a real module object |
| 63 | +cosmos_aio_mock.CosmosClient = MagicMock() # Add CosmosClient |
| 64 | +cosmos_aio_mock._database = MagicMock() |
| 65 | +cosmos_aio_mock._database.DatabaseProxy = MagicMock() |
| 66 | +cosmos_aio_mock._container = MagicMock() |
| 67 | +cosmos_aio_mock._container.ContainerProxy = MagicMock() |
| 68 | +sys.modules["azure.cosmos.aio"] = cosmos_aio_mock |
| 69 | +sys.modules["azure.cosmos.aio._database"] = cosmos_aio_mock._database |
| 70 | +sys.modules["azure.cosmos.aio._container"] = cosmos_aio_mock._container |
| 71 | + |
| 72 | +sys.modules["azure.identity"] = MagicMock() |
| 73 | +sys.modules["azure.identity.aio"] = MagicMock() |
| 74 | + |
| 75 | +# Create proper enum mocks for agent_framework |
| 76 | +class MockRole(str, Enum): |
| 77 | + """Mock Role enum for agent_framework.""" |
| 78 | + USER = "user" |
| 79 | + ASSISTANT = "assistant" |
| 80 | + SYSTEM = "system" |
| 81 | + TOOL = "tool" |
| 82 | + |
| 83 | +# Create proper base classes for agent_framework |
| 84 | +class MockBaseAgent: |
| 85 | + """Mock base agent class.""" |
| 86 | + __name__ = "BaseAgent" |
| 87 | + __module__ = "agent_framework" |
| 88 | + __qualname__ = "BaseAgent" |
| 89 | + |
| 90 | +class MockChatAgent: |
| 91 | + """Mock chat agent class.""" |
| 92 | + __name__ = "ChatAgent" |
| 93 | + __module__ = "agent_framework" |
| 94 | + __qualname__ = "ChatAgent" |
| 95 | + |
| 96 | +# Mock agent framework dependencies |
| 97 | +agent_framework_mock = type(sys)("agent_framework") |
| 98 | +agent_framework_mock.azure = type(sys)("agent_framework.azure") |
| 99 | +agent_framework_mock.azure.AzureOpenAIChatClient = MagicMock() |
| 100 | +agent_framework_mock._workflows = type(sys)("agent_framework._workflows") |
| 101 | +agent_framework_mock._workflows._magentic = type(sys)("agent_framework._workflows._magentic") |
| 102 | +agent_framework_mock._workflows._magentic.MagenticContext = MagicMock() |
| 103 | +agent_framework_mock._workflows._magentic.StandardMagenticManager = MagicMock() |
| 104 | +agent_framework_mock._workflows._magentic.ORCHESTRATOR_FINAL_ANSWER_PROMPT = "mock_prompt" |
| 105 | +agent_framework_mock._workflows._magentic.ORCHESTRATOR_TASK_LEDGER_PLAN_PROMPT = "mock_prompt" |
| 106 | +agent_framework_mock._workflows._magentic.ORCHESTRATOR_TASK_LEDGER_PLAN_UPDATE_PROMPT = "mock_prompt" |
| 107 | +agent_framework_mock._workflows._magentic.ORCHESTRATOR_PROGRESS_LEDGER_PROMPT = "mock_prompt" |
| 108 | +agent_framework_mock.AgentResponse = MagicMock() |
| 109 | +agent_framework_mock.AgentResponseUpdate = MagicMock() |
| 110 | +agent_framework_mock.AgentRunUpdateEvent = MagicMock() |
| 111 | +agent_framework_mock.AgentThread = MagicMock() |
| 112 | +agent_framework_mock.BaseAgent = MockBaseAgent |
| 113 | +agent_framework_mock.ChatAgent = MockChatAgent |
| 114 | +agent_framework_mock.ChatMessage = MagicMock() |
| 115 | +agent_framework_mock.ChatOptions = MagicMock() |
| 116 | +agent_framework_mock.Content = MagicMock() |
| 117 | +agent_framework_mock.ExecutorCompletedEvent = MagicMock() |
| 118 | +agent_framework_mock.GroupChatRequestSentEvent = MagicMock() |
| 119 | +agent_framework_mock.GroupChatResponseReceivedEvent = MagicMock() |
| 120 | +agent_framework_mock.HostedCodeInterpreterTool = MagicMock() |
| 121 | +agent_framework_mock.HostedMCPTool = MagicMock() |
| 122 | +agent_framework_mock.ImageContent = MagicMock() |
| 123 | +agent_framework_mock.ImageDetail = MagicMock() |
| 124 | +agent_framework_mock.ImageUrl = MagicMock() |
| 125 | +agent_framework_mock.InMemoryCheckpointStorage = MagicMock() |
| 126 | +agent_framework_mock.MagenticBuilder = MagicMock() |
| 127 | +agent_framework_mock.MagenticOrchestratorEvent = MagicMock() |
| 128 | +agent_framework_mock.MagenticProgressLedger = MagicMock() |
| 129 | +agent_framework_mock.MCPStreamableHTTPTool = MagicMock() |
| 130 | +agent_framework_mock.Role = MockRole |
| 131 | +agent_framework_mock.TemplatedChatAgent = MagicMock() |
| 132 | +agent_framework_mock.TextContent = MagicMock() |
| 133 | +agent_framework_mock.UsageDetails = MagicMock() |
| 134 | +agent_framework_mock.WorkflowOutputEvent = MagicMock() |
| 135 | +sys.modules["agent_framework"] = agent_framework_mock |
| 136 | +sys.modules["agent_framework.azure"] = agent_framework_mock.azure |
| 137 | +sys.modules["agent_framework._workflows"] = agent_framework_mock._workflows |
| 138 | +sys.modules["agent_framework._workflows._magentic"] = agent_framework_mock._workflows._magentic |
| 139 | +sys.modules["agent_framework_azure_ai"] = MagicMock() |
| 140 | +sys.modules["magentic"] = MagicMock() |
| 141 | + |
| 142 | +# OpenTelemetry mocks |
| 143 | +otel_mock = type(sys)("opentelemetry") |
| 144 | +otel_mock.trace = MagicMock() |
| 145 | +sys.modules["opentelemetry"] = otel_mock |
| 146 | +sys.modules["opentelemetry.trace"] = otel_mock.trace |
| 147 | +sys.modules["opentelemetry.sdk"] = MagicMock() |
| 148 | +sys.modules["opentelemetry.sdk.trace"] = MagicMock() |
| 149 | + |
| 150 | +# --------------------------------------------------------------------------- |
| 151 | +# Shared Fixtures - Simple approach: create test client and don't pre-patch |
| 152 | +# --------------------------------------------------------------------------- |
| 153 | + |
| 154 | +@pytest.fixture |
| 155 | +def create_test_client(): |
| 156 | + """Create FastAPI TestClient with inline mocks.""" |
| 157 | + from fastapi.testclient import TestClient |
| 158 | + from fastapi import FastAPI |
| 159 | + |
| 160 | + # Import router - all dependencies are stubbed in sys.modules |
| 161 | + from v4.api import router as router_module |
| 162 | + |
| 163 | + # Now replace everything in router's namespace with mocks |
| 164 | + # Auth |
| 165 | + router_module.get_authenticated_user_details = MagicMock(return_value={"user_principal_id": "test-user-123"}) |
| 166 | + |
| 167 | + # Database |
| 168 | + mock_db = AsyncMock() |
| 169 | + mock_db.get_current_team = AsyncMock(return_value=None) |
| 170 | + mock_db.get_team_by_id = AsyncMock(return_value=None) |
| 171 | + mock_db.get_plan_by_plan_id = AsyncMock(return_value=None) |
| 172 | + mock_db.get_all_plans_by_team_id_status = AsyncMock(return_value=[]) |
| 173 | + mock_db.add_plan = AsyncMock() |
| 174 | + mock_db_factory = MagicMock() |
| 175 | + mock_db_factory.get_database = AsyncMock(return_value=mock_db) |
| 176 | + router_module.DatabaseFactory = mock_db_factory |
| 177 | + |
| 178 | + # Services |
| 179 | + router_module.PlanService = MagicMock() |
| 180 | + router_module.PlanService.handle_plan_approval = AsyncMock(return_value={"status": "success"}) |
| 181 | + router_module.PlanService.handle_human_clarification = AsyncMock(return_value={"status": "success"}) |
| 182 | + router_module.PlanService.handle_agent_messages = AsyncMock(return_value={"status": "success"}) |
| 183 | + |
| 184 | + team_svc_instance = AsyncMock() |
| 185 | + team_svc_instance.handle_team_selection = AsyncMock(return_value=MagicMock(team_id="team-123")) |
| 186 | + team_svc_instance.get_team_configuration = AsyncMock(return_value=None) |
| 187 | + team_svc_instance.get_all_team_configurations = AsyncMock(return_value=[]) |
| 188 | + team_svc_instance.delete_team_configuration = AsyncMock(return_value=True) |
| 189 | + team_svc_instance.validate_team_models = AsyncMock(return_value=(True, [])) |
| 190 | + team_svc_instance.validate_team_search_indexes = AsyncMock(return_value=(True, [])) |
| 191 | + team_svc_instance.validate_and_parse_team_config = AsyncMock() |
| 192 | + team_svc_instance.save_team_configuration = AsyncMock(return_value="team-123") |
| 193 | + router_module.TeamService = MagicMock(return_value=team_svc_instance) |
| 194 | + |
| 195 | + orch_mgr_instance = AsyncMock() |
| 196 | + orch_mgr_instance.run_orchestration = AsyncMock() |
| 197 | + router_module.OrchestrationManager = MagicMock(return_value=orch_mgr_instance) |
| 198 | + router_module.OrchestrationManager.get_current_or_new_orchestration = AsyncMock(return_value=orch_mgr_instance) |
| 199 | + |
| 200 | + # Utils |
| 201 | + router_module.find_first_available_team = MagicMock(return_value="team-123") |
| 202 | + router_module.rai_success = AsyncMock(return_value=True) |
| 203 | + router_module.rai_validate_team_config = MagicMock(return_value=(True, None)) |
| 204 | + router_module.track_event_if_configured = MagicMock(return_value=None) |
| 205 | + |
| 206 | + # Configs |
| 207 | + conn_cfg = MagicMock() |
| 208 | + conn_cfg.add_connection = AsyncMock() |
| 209 | + conn_cfg.close_connection = AsyncMock() |
| 210 | + conn_cfg.send_status_update_async = AsyncMock() |
| 211 | + router_module.connection_config = conn_cfg |
| 212 | + |
| 213 | + orch_cfg = MagicMock() |
| 214 | + orch_cfg.approvals = {} |
| 215 | + orch_cfg.clarifications = {} |
| 216 | + orch_cfg.set_approval_result = Mock() |
| 217 | + orch_cfg.set_clarification_result = Mock() |
| 218 | + router_module.orchestration_config = orch_cfg |
| 219 | + |
| 220 | + team_cfg = MagicMock() |
| 221 | + team_cfg.set_current_team = Mock() |
| 222 | + router_module.team_config = team_cfg |
| 223 | + |
| 224 | + # Create test app with router |
| 225 | + app = FastAPI() |
| 226 | + app.include_router(router_module.app_v4) |
| 227 | + |
| 228 | + client = TestClient(app) |
| 229 | + client.headers = {"Authorization": "Bearer test-token"} |
| 230 | + |
| 231 | + # Store mocks as client attributes for test access |
| 232 | + client._mock_db = mock_db |
| 233 | + client._mock_team_svc = team_svc_instance |
| 234 | + client._mock_auth = router_module.get_authenticated_user_details |
| 235 | + client._mock_utils = { |
| 236 | + "find_first_available_team": router_module.find_first_available_team, |
| 237 | + "rai_success": router_module.rai_success, |
| 238 | + "rai_validate_team_config": router_module.rai_validate_team_config, |
| 239 | + } |
| 240 | + client._mock_configs = { |
| 241 | + "connection_config": conn_cfg, |
| 242 | + "orchestration_config": orch_cfg, |
| 243 | + "team_config": team_cfg, |
| 244 | + } |
| 245 | + |
| 246 | + yield client |
| 247 | + |
| 248 | + |
| 249 | +# --------------------------------------------------------------------------- |
| 250 | +# Additional Fixtures for Test Access |
| 251 | +# --------------------------------------------------------------------------- |
| 252 | + |
| 253 | +@pytest.fixture |
| 254 | +def mock_database(create_test_client): |
| 255 | + """Provide access to the mock database.""" |
| 256 | + return create_test_client._mock_db |
| 257 | + |
| 258 | + |
| 259 | +@pytest.fixture |
| 260 | +def mock_services(create_test_client): |
| 261 | + """Provide access to mock services.""" |
| 262 | + # Return a callable that always returns the same instance |
| 263 | + class ServiceGetter: |
| 264 | + def __call__(self): |
| 265 | + return create_test_client._mock_team_svc |
| 266 | + |
| 267 | + return { |
| 268 | + "team_service": ServiceGetter() |
| 269 | + } |
| 270 | + |
| 271 | + |
| 272 | +@pytest.fixture |
| 273 | +def mock_auth(create_test_client): |
| 274 | + """Provide access to mock authentication.""" |
| 275 | + return create_test_client._mock_auth |
| 276 | + |
| 277 | + |
| 278 | +@pytest.fixture |
| 279 | +def mock_utils(create_test_client): |
| 280 | + """Provide access to mock utilities.""" |
| 281 | + return create_test_client._mock_utils |
| 282 | + |
| 283 | + |
| 284 | +@pytest.fixture |
| 285 | +def mock_configs(create_test_client): |
| 286 | + """Provide access to mock configurations.""" |
| 287 | + return create_test_client._mock_configs |
0 commit comments