|
8 | 8 | """ |
9 | 9 |
|
10 | 10 | import asyncio |
| 11 | +import gc |
11 | 12 | import os |
12 | 13 | import sys |
13 | 14 | from datetime import datetime, timezone |
|
16 | 17 | import pytest |
17 | 18 | from quart import Quart |
18 | 19 |
|
19 | | -# Set environment variables BEFORE any backend imports |
20 | | -# This prevents settings.py from failing during import |
21 | | -os.environ.update({ |
22 | | - # Base settings |
23 | | - "AZURE_OPENAI_ENDPOINT": "https://test-openai.openai.azure.com/", |
24 | | - "AZURE_OPENAI_API_VERSION": "2024-08-01-preview", |
25 | | - "AZURE_OPENAI_CHAT_DEPLOYMENT": "gpt-4o", |
26 | | - "AZURE_OPENAI_EMBEDDING_DEPLOYMENT": "text-embedding-3-large", |
27 | | - "AZURE_OPENAI_DALLE_DEPLOYMENT": "dall-e-3", |
28 | | - "AZURE_CLIENT_ID": "test-client-id", |
29 | | - |
30 | | - # Cosmos DB |
31 | | - "AZURE_COSMOSDB_ENDPOINT": "https://test-cosmos.documents.azure.com:443/", |
32 | | - "AZURE_COSMOSDB_DATABASE_NAME": "test-db", |
33 | | - "AZURE_COSMOSDB_PRODUCTS_CONTAINER": "products", |
34 | | - "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": "conversations", |
35 | | - |
36 | | - # Blob Storage |
37 | | - "AZURE_STORAGE_ACCOUNT_NAME": "teststorage", |
38 | | - "AZURE_STORAGE_CONTAINER": "test-container", |
39 | | - "AZURE_STORAGE_ACCOUNT_URL": "https://teststorage.blob.core.windows.net", |
40 | | - "AZURE_BLOB_PRODUCT_IMAGES_CONTAINER": "product-images", |
41 | | - "AZURE_BLOB_GENERATED_IMAGES_CONTAINER": "generated-images", |
42 | | - |
43 | | - # Content Safety |
44 | | - "AZURE_CONTENT_SAFETY_ENDPOINT": "https://test-safety.cognitiveservices.azure.com/", |
45 | | - "AZURE_CONTENT_SAFETY_API_VERSION": "2024-09-01", |
46 | | - |
47 | | - # Search Service |
48 | | - "AZURE_SEARCH_ENDPOINT": "https://test-search.search.windows.net", |
49 | | - "AZURE_SEARCH_INDEX_NAME": "products-index", |
50 | | - |
51 | | - # Foundry (optional) |
52 | | - "USE_FOUNDRY": "false", |
53 | | - "AZURE_AI_PROJECT_CONNECTION_STRING": "", |
54 | | - |
55 | | - # Admin - Empty for development mode (no authentication required) |
56 | | - "ADMIN_API_KEY": "", |
57 | | - |
58 | | - # App Configuration |
59 | | - "ALLOWED_ORIGIN": "http://localhost:3000", |
60 | | - "LOG_LEVEL": "DEBUG", |
61 | | -}) |
62 | | - |
63 | | -# Add the backend directory to the Python path so we can import backend modules |
64 | | -tests_dir = os.path.dirname(os.path.abspath(__file__)) |
65 | | -backend_dir = os.path.join(os.path.dirname(tests_dir), 'backend') |
66 | | -if backend_dir not in sys.path: |
67 | | - sys.path.insert(0, backend_dir) |
68 | | - |
69 | | -# Set Windows event loop policy at module level (fixes pytest-asyncio auto mode compatibility) |
70 | | -if sys.platform == "win32": |
71 | | - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) |
| 20 | + |
| 21 | +def pytest_configure(config): |
| 22 | + """Set minimal env vars required for backend imports before test collection. |
| 23 | +
|
| 24 | + Only sets variables absolutely required to import settings.py without errors. |
| 25 | + All other test environment configuration is handled by the mock_environment fixture. |
| 26 | + """ |
| 27 | + # AZURE_OPENAI_ENDPOINT is required by _AzureOpenAISettings validator |
| 28 | + os.environ.setdefault("AZURE_OPENAI_ENDPOINT", "https://test.openai.azure.com/") |
| 29 | + |
| 30 | + # Add the backend directory to the Python path |
| 31 | + tests_dir = os.path.dirname(os.path.abspath(__file__)) |
| 32 | + backend_dir = os.path.join(os.path.dirname(tests_dir), 'backend') |
| 33 | + if backend_dir not in sys.path: |
| 34 | + sys.path.insert(0, backend_dir) |
| 35 | + |
| 36 | + # Set Windows event loop policy (fixes pytest-asyncio auto mode compatibility) |
| 37 | + if sys.platform == "win32": |
| 38 | + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) |
| 39 | + |
| 40 | + |
| 41 | +def pytest_sessionfinish(session, exitstatus): # noqa: ARG001 |
| 42 | + """Clean up any remaining async resources after test session. |
| 43 | +
|
| 44 | + This helps prevent 'Unclosed client session' warnings from aiohttp |
| 45 | + that can occur when Azure SDK or other async clients aren't fully closed. |
| 46 | +
|
| 47 | + Args: |
| 48 | + session: pytest Session object (required by hook signature) |
| 49 | + exitstatus: exit status code (required by hook signature) |
| 50 | + """ |
| 51 | + del session, exitstatus # Unused but required by pytest hook signature |
| 52 | + # Force garbage collection to trigger cleanup of any unclosed sessions |
| 53 | + gc.collect() |
| 54 | + |
| 55 | + # Close any remaining event loops |
| 56 | + try: |
| 57 | + loop = asyncio.get_event_loop() |
| 58 | + if loop.is_running(): |
| 59 | + loop.stop() |
| 60 | + if not loop.is_closed(): |
| 61 | + loop.close() |
| 62 | + except Exception: |
| 63 | + pass |
72 | 64 |
|
73 | 65 |
|
74 | 66 | # ==================== Environment Configuration ==================== |
75 | 67 |
|
76 | 68 | @pytest.fixture(scope="function", autouse=True) |
77 | | -def mock_environment(): |
78 | | - """Ensure environment variables are set for each test.""" |
79 | | - # Environment variables are already set at module level |
80 | | - # This fixture exists for potential test-specific overrides |
| 69 | +def mock_environment(monkeypatch): |
| 70 | + """Set test environment variables with correct names matching settings.py. |
| 71 | +
|
| 72 | + Uses monkeypatch for proper test isolation - each test starts with a clean |
| 73 | + environment and changes are automatically reverted after the test. |
| 74 | + """ |
| 75 | + env_vars = { |
| 76 | + # Azure OpenAI (required - _AzureOpenAISettings) |
| 77 | + "AZURE_OPENAI_ENDPOINT": "https://test-openai.openai.azure.com/", |
| 78 | + "AZURE_OPENAI_API_VERSION": "2024-08-01-preview", |
| 79 | + |
| 80 | + # Azure Cosmos DB (_CosmosSettings uses AZURE_COSMOS_ prefix) |
| 81 | + "AZURE_COSMOS_ENDPOINT": "https://test-cosmos.documents.azure.com:443/", |
| 82 | + "AZURE_COSMOS_DATABASE_NAME": "test-db", |
| 83 | + |
| 84 | + # Chat History (_ChatHistorySettings uses AZURE_COSMOSDB_ prefix) |
| 85 | + "AZURE_COSMOSDB_DATABASE": "test-db", |
| 86 | + "AZURE_COSMOSDB_ACCOUNT": "test-cosmos", |
| 87 | + "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": "conversations", |
| 88 | + "AZURE_COSMOSDB_PRODUCTS_CONTAINER": "products", |
| 89 | + |
| 90 | + # Azure Blob Storage (_StorageSettings uses AZURE_BLOB_ prefix) |
| 91 | + "AZURE_BLOB_ACCOUNT_NAME": "teststorage", |
| 92 | + "AZURE_BLOB_PRODUCT_IMAGES_CONTAINER": "product-images", |
| 93 | + "AZURE_BLOB_GENERATED_IMAGES_CONTAINER": "generated-images", |
| 94 | + |
| 95 | + # Azure AI Search (_SearchSettings uses AZURE_AI_SEARCH_ prefix) |
| 96 | + "AZURE_AI_SEARCH_ENDPOINT": "https://test-search.search.windows.net", |
| 97 | + "AZURE_AI_SEARCH_PRODUCTS_INDEX": "products", |
| 98 | + "AZURE_AI_SEARCH_IMAGE_INDEX": "product-images", |
| 99 | + |
| 100 | + # AI Foundry (disabled for tests) |
| 101 | + "USE_FOUNDRY": "false", |
| 102 | + |
| 103 | + # Admin API (empty = development mode, no auth required) |
| 104 | + "ADMIN_API_KEY": "", |
| 105 | + } |
| 106 | + |
| 107 | + for key, value in env_vars.items(): |
| 108 | + monkeypatch.setenv(key, value) |
| 109 | + |
81 | 110 | yield |
82 | 111 |
|
83 | 112 |
|
|
0 commit comments