Skip to content

Commit 8b7b077

Browse files
Backend UT for app_config.py
1 parent ae91ce5 commit 8b7b077

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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

Comments
 (0)