-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_config.py
More file actions
181 lines (148 loc) · 7.26 KB
/
Copy pathtest_config.py
File metadata and controls
181 lines (148 loc) · 7.26 KB
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
"""Tests for config.py module loading logic."""
import sys
import os
import types
import importlib
import tempfile
import pytest
from unittest.mock import patch, MagicMock
# Backend root for path manipulation
BACKEND_DIR = os.path.join(os.path.dirname(__file__), "..")
sys.path.insert(0, BACKEND_DIR)
def _reload_config(env_vars=None, secrets_content=None):
"""Reload config.py with controlled environment.
Temporarily removes the config stub from sys.modules, sets up env vars
and optionally a secrets file, then imports config fresh.
"""
# Ensure mistralai is stubbed so `Mistral(api_key=...)` doesn't fail
_mistralai = types.ModuleType("mistralai")
_mistralai.Mistral = MagicMock()
sys.modules["mistralai"] = _mistralai
# Stub dotenv so load_dotenv() is a no-op
_dotenv = types.ModuleType("dotenv")
_dotenv.load_dotenv = lambda: None
sys.modules["dotenv"] = _dotenv
# Remove cached config module so importlib.reload actually re-executes it
if "config" in sys.modules:
del sys.modules["config"]
env = env_vars or {}
with patch.dict(os.environ, env, clear=False):
# Remove MISTRAL_API_KEY from env if not explicitly provided
if "MISTRAL_API_KEY" not in env and "MISTRAL_API_KEY" in os.environ:
del os.environ["MISTRAL_API_KEY"]
if secrets_content is not None:
# Create a temp directory structure with .secrets/mistral_api_key
with tempfile.TemporaryDirectory() as tmpdir:
secrets_dir = os.path.join(tmpdir, ".secrets")
os.makedirs(secrets_dir)
secrets_path = os.path.join(secrets_dir, "mistral_api_key")
with open(secrets_path, "w") as f:
f.write(secrets_content)
# Patch the project_root calculation to point to our temp dir
with patch("os.path.dirname") as mock_dirname:
# config.py does: dirname(dirname(abspath(__file__)))
# We need the outer dirname call to return tmpdir
def dirname_side_effect(path):
# Use real dirname for all calls
return os.path.dirname.__wrapped__(path) if hasattr(os.path.dirname, '__wrapped__') else _real_dirname(path)
# Simpler approach: patch at the point where secrets_path is constructed
import config
return config
else:
import config
return config
# Save real os.path.dirname before any patching
_real_dirname = os.path.dirname
class TestConfigLoadsFromEnvVar:
def test_config_loads_from_env_var(self):
"""When MISTRAL_API_KEY is in env, config picks it up."""
# Stub out dependencies
_mistralai = types.ModuleType("mistralai")
_mistralai.Mistral = MagicMock()
sys.modules["mistralai"] = _mistralai
_dotenv = types.ModuleType("dotenv")
_dotenv.load_dotenv = lambda: None
sys.modules["dotenv"] = _dotenv
saved = sys.modules.pop("config", None)
try:
with patch.dict(os.environ, {"MISTRAL_API_KEY": "test-env-key"}, clear=False):
import config
importlib.reload(config)
assert config.MISTRAL_API_KEY == "test-env-key"
assert config.AUDIO_MODEL == "voxtral-mini-latest"
assert config.TEXT_MODEL == "mistral-large-latest"
finally:
# Restore the stub so other tests aren't affected
sys.modules.pop("config", None)
if saved is not None:
sys.modules["config"] = saved
class TestConfigLoadsFromSecretsFile:
def test_config_loads_from_secrets_file(self):
"""When env var is unset, config reads from .secrets/mistral_api_key."""
_mistralai = types.ModuleType("mistralai")
_mistralai.Mistral = MagicMock()
sys.modules["mistralai"] = _mistralai
_dotenv = types.ModuleType("dotenv")
_dotenv.load_dotenv = lambda: None
sys.modules["dotenv"] = _dotenv
saved = sys.modules.pop("config", None)
try:
# Create a temp secrets file
with tempfile.TemporaryDirectory() as tmpdir:
secrets_dir = os.path.join(tmpdir, ".secrets")
os.makedirs(secrets_dir)
with open(os.path.join(secrets_dir, "mistral_api_key"), "w") as f:
f.write("test-secret-file-key\n")
# Remove env var if present
env = os.environ.copy()
env.pop("MISTRAL_API_KEY", None)
with patch.dict(os.environ, env, clear=True):
# Patch the project_root to point at tmpdir
# config.py line 10: project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# We need dirname(dirname(abspath(config.__file__))) == tmpdir
config_path = os.path.join(BACKEND_DIR, "config.py")
real_abspath = os.path.abspath
def fake_abspath(path):
if path == "__file__" or (isinstance(path, str) and path.endswith("config.py")):
# Return a path such that dirname(dirname(x)) == tmpdir
return os.path.join(tmpdir, "backend", "config.py")
return real_abspath(path)
with patch("os.path.abspath", side_effect=fake_abspath):
import config
importlib.reload(config)
assert config.MISTRAL_API_KEY == "test-secret-file-key"
finally:
sys.modules.pop("config", None)
if saved is not None:
sys.modules["config"] = saved
class TestConfigEntersDemoWhenNoKey:
def test_config_enters_demo_mode_when_no_key(self):
"""When no env var and no secrets file, config activates demo mode."""
_mistralai = types.ModuleType("mistralai")
_mistralai.Mistral = MagicMock()
sys.modules["mistralai"] = _mistralai
_dotenv = types.ModuleType("dotenv")
_dotenv.load_dotenv = lambda: None
sys.modules["dotenv"] = _dotenv
saved = sys.modules.pop("config", None)
try:
env = os.environ.copy()
env.pop("MISTRAL_API_KEY", None)
with patch.dict(os.environ, env, clear=True):
# Point project root at a temp dir with no secrets file
with tempfile.TemporaryDirectory() as tmpdir:
real_abspath = os.path.abspath
def fake_abspath(path):
if isinstance(path, str) and path.endswith("config.py"):
return os.path.join(tmpdir, "backend", "config.py")
return real_abspath(path)
with patch("os.path.abspath", side_effect=fake_abspath):
import config
importlib.reload(config)
assert config.DEMO_MODE is True
assert config.MISTRAL_API_KEY == "demo"
assert config.client is None
finally:
sys.modules.pop("config", None)
if saved is not None:
sys.modules["config"] = saved