1+ import os
2+ import sys
3+ import pytest
4+ import types
5+ import asyncio
6+
7+ # --- Provide minimal env vars so AppConfig() import doesn't fail ---
8+ os .environ .setdefault ('AZURE_OPENAI_ENDPOINT' , 'https://dummy' )
9+ os .environ .setdefault ('AZURE_OPENAI_API_VERSION' , 'v' )
10+ os .environ .setdefault ('AZURE_OPENAI_DEPLOYMENT_NAME' , 'd' )
11+ os .environ .setdefault ('AZURE_AI_SUBSCRIPTION_ID' , 'sub' )
12+ os .environ .setdefault ('AZURE_AI_RESOURCE_GROUP' , 'rg' )
13+ os .environ .setdefault ('AZURE_AI_PROJECT_NAME' , 'pn' )
14+ os .environ .setdefault ('AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' , 'cs' )
15+
16+ # --- Stub external modules before importing app_config ---
17+ # Stub dotenv.load_dotenv
18+ dotenv_mod = types .ModuleType ("dotenv" )
19+ dotenv_mod .load_dotenv = lambda : None
20+ sys .modules ['dotenv' ] = dotenv_mod
21+ sys .modules ['dotenv.load_dotenv' ] = dotenv_mod
22+
23+ # Stub azure.identity
24+ azure_pkg = types .ModuleType ('azure' )
25+ identity_pkg = types .ModuleType ('azure.identity' )
26+ def DummyDefaultAzureCredential ():
27+ class C :
28+ def __init__ (self ): pass
29+ return C ()
30+ identity_pkg .DefaultAzureCredential = DummyDefaultAzureCredential
31+ identity_pkg .ClientSecretCredential = lambda * args , ** kwargs : 'secret'
32+ azure_pkg .identity = identity_pkg
33+ sys .modules ['azure' ] = azure_pkg
34+ sys .modules ['azure.identity' ] = identity_pkg
35+
36+ # Stub azure.cosmos.aio.CosmosClient
37+ cosmos_aio_pkg = types .ModuleType ('azure.cosmos.aio' )
38+ class DummyCosmosClient :
39+ def __init__ (self , endpoint , credential ):
40+ self .endpoint = endpoint
41+ self .credential = credential
42+ def get_database_client (self , name ):
43+ return f"db_client:{ name } "
44+ cosmos_aio_pkg .CosmosClient = DummyCosmosClient
45+ sys .modules ['azure.cosmos.aio' ] = cosmos_aio_pkg
46+
47+ # Stub azure.ai.projects.aio.AIProjectClient
48+ ai_projects_pkg = types .ModuleType ('azure.ai.projects.aio' )
49+ class DummyAgentDefinition : pass
50+ class DummyAgents :
51+ async def create_agent (self , ** kwargs ):
52+ return DummyAgentDefinition ()
53+ class DummyClient :
54+ agents = DummyAgents ()
55+ DummyAIProjectClient = types .SimpleNamespace (
56+ from_connection_string = lambda credential , conn_str : DummyClient ()
57+ )
58+ ai_projects_pkg .AIProjectClient = DummyAIProjectClient
59+ sys .modules ['azure.ai.projects.aio' ] = ai_projects_pkg
60+
61+ # Stub semantic_kernel.kernel.Kernel
62+ sk_kernel_pkg = types .ModuleType ('semantic_kernel.kernel' )
63+ sk_kernel_pkg .Kernel = lambda : 'kernel'
64+ sys .modules ['semantic_kernel.kernel' ] = sk_kernel_pkg
65+
66+ # Stub semantic_kernel.contents.ChatHistory
67+ sk_contents_pkg = types .ModuleType ('semantic_kernel.contents' )
68+ sk_contents_pkg .ChatHistory = lambda * args , ** kwargs : None
69+ sys .modules ['semantic_kernel.contents' ] = sk_contents_pkg
70+
71+ # Stub AzureAIAgent
72+ az_ai_agent_pkg = types .ModuleType ('semantic_kernel.agents.azure_ai.azure_ai_agent' )
73+ class DummyAzureAIAgent :
74+ def __init__ (self , client , definition , plugins ):
75+ self .client = client
76+ self .definition = definition
77+ self .plugins = plugins
78+ az_ai_agent_pkg .AzureAIAgent = DummyAzureAIAgent
79+ sys .modules ['semantic_kernel.agents.azure_ai.azure_ai_agent' ] = az_ai_agent_pkg
80+
81+ # Stub KernelFunction for type
82+ sk_funcs_pkg = types .ModuleType ('semantic_kernel.functions' )
83+ sk_funcs_pkg .KernelFunction = lambda * args , ** kwargs : (lambda f : f )
84+ sys .modules ['semantic_kernel.functions' ] = sk_funcs_pkg
85+
86+ # Now import AppConfig
87+ after_stubs = True
88+ import importlib
89+ AppConfig_mod = importlib .import_module ('backend.app_config' )
90+ AppConfig = AppConfig_mod .AppConfig
91+
92+ @pytest .fixture (autouse = True )
93+ def clear_env (monkeypatch ):
94+ # Clear relevant env vars before each test
95+ for key in list (os .environ ):
96+ if key .startswith (('AZURE_' , 'COSMOSDB_' , 'FRONTEND_' )):
97+ monkeypatch .delenv (key , raising = False )
98+ # Re-set mandatory ones for import
99+ os .environ ['AZURE_OPENAI_ENDPOINT' ] = 'https://dummy'
100+ os .environ ['AZURE_OPENAI_API_VERSION' ] = 'v'
101+ os .environ ['AZURE_OPENAI_DEPLOYMENT_NAME' ] = 'd'
102+ os .environ ['AZURE_AI_SUBSCRIPTION_ID' ] = 'sub'
103+ os .environ ['AZURE_AI_RESOURCE_GROUP' ] = 'rg'
104+ os .environ ['AZURE_AI_PROJECT_NAME' ] = 'pn'
105+ os .environ ['AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' ] = 'cs'
106+ yield
107+
108+ @pytest .fixture
109+ def config ():
110+ return AppConfig ()
111+
112+ # Test required/optional env getters
113+ def test_get_required_with_default (config , monkeypatch ):
114+ monkeypatch .delenv ('AZURE_OPENAI_API_VERSION' , raising = False )
115+ # default provided
116+ assert config ._get_required ('AZURE_OPENAI_API_VERSION' , 'x' ) == 'x'
117+
118+ @pytest .mark .parametrize ('name,default,expected' , [
119+ ('NON_EXISTENT' , None , pytest .raises (ValueError )),
120+ ('AZURE_OPENAI_ENDPOINT' , None , 'https://dummy' ),
121+ ])
122+ def test_get_required_raises_or_returns (config , name , default , expected ):
123+ if default is None and name == 'NON_EXISTENT' :
124+ with expected :
125+ config ._get_required (name )
126+ else :
127+ assert config ._get_required (name ) == expected
128+
129+ # _get_optional
130+
131+ def test_get_optional (config , monkeypatch ):
132+ monkeypatch .delenv ('COSMOSDB_ENDPOINT' , raising = False )
133+ assert config ._get_optional ('COSMOSDB_ENDPOINT' , 'ep' ) == 'ep'
134+ os .environ ['COSMOSDB_ENDPOINT' ] = 'real'
135+ assert config ._get_optional ('COSMOSDB_ENDPOINT' , 'ep' ) == 'real'
136+
137+ # _get_bool
138+
139+ def test_get_bool (config , monkeypatch ):
140+ monkeypatch .setenv ('FEATURE_FLAG' , 'true' )
141+ assert config ._get_bool ('FEATURE_FLAG' )
142+ monkeypatch .setenv ('FEATURE_FLAG' , '0' )
143+ assert not config ._get_bool ('FEATURE_FLAG' )
144+
145+ # credentials
146+
147+ def test_get_azure_credentials_caches (config ):
148+ cred1 = config .get_azure_credentials ()
149+ cred2 = config .get_azure_credentials ()
150+ assert cred1 is cred2
151+
152+ # Cosmos DB client
153+
154+ def test_get_cosmos_database_client (config ):
155+ db = config .get_cosmos_database_client ()
156+ assert db == 'db_client:' + config .COSMOSDB_DATABASE
157+
158+ # Kernel creation
159+
160+ def test_create_kernel (config ):
161+ assert config .create_kernel () == 'kernel'
162+
163+ # AI project client
164+
165+ def test_get_ai_project_client (config ):
166+ client = config .get_ai_project_client ()
167+ assert hasattr (client , 'agents' )
168+
169+ # create_azure_ai_agent
170+
171+ @pytest .mark .asyncio
172+ async def test_create_azure_ai_agent (config ):
173+ client = config .get_ai_project_client ()
174+ agent = await config .create_azure_ai_agent ('agent1' , 'instr' , tools = ['t' ], client = client )
175+ assert isinstance (agent , DummyAzureAIAgent )
176+ assert agent .plugins == ['t' ]
177+
178+
179+ # ensure global config instance exists
180+
181+ def test_global_config_instance ():
182+ from backend .app_config import config as global_config
183+ assert isinstance (global_config , AppConfig )
0 commit comments