|
3 | 3 | from sqlalchemy import Engine |
4 | 4 | from utils.core.db import ( |
5 | 5 | get_connection_url, |
| 6 | + get_engine, |
| 7 | + clear_engine_cache, |
6 | 8 | assign_permissions_to_role, |
7 | 9 | create_default_roles, |
8 | 10 | create_permissions, |
@@ -147,6 +149,84 @@ def test_get_connection_url_missing_pool_vars(monkeypatch): |
147 | 149 | get_connection_url() |
148 | 150 |
|
149 | 151 |
|
| 152 | +# --- Engine cache --- |
| 153 | + |
| 154 | + |
| 155 | +@pytest.fixture |
| 156 | +def engine_cache(): |
| 157 | + """Ensure engine-cache tests start clean and never leak cached engines, |
| 158 | + even when an assertion fails mid-test.""" |
| 159 | + clear_engine_cache() |
| 160 | + yield |
| 161 | + clear_engine_cache() |
| 162 | + |
| 163 | + |
| 164 | +def _direct_db_env(monkeypatch, *, name: str = "testdb", password: str = "testpass"): |
| 165 | + for var in ( |
| 166 | + "USE_POOL", |
| 167 | + "DB_HOST", |
| 168 | + "DB_PORT", |
| 169 | + "DB_NAME", |
| 170 | + "DB_USER", |
| 171 | + "DB_PASSWORD", |
| 172 | + "DB_POOL_PORT", |
| 173 | + "DB_POOL_NAME", |
| 174 | + "DB_APPUSER", |
| 175 | + "DB_APPUSER_PASSWORD", |
| 176 | + "DB_POOL_SIZE", |
| 177 | + "DB_MAX_OVERFLOW", |
| 178 | + ): |
| 179 | + monkeypatch.delenv(var, raising=False) |
| 180 | + monkeypatch.setenv("DB_HOST", "localhost") |
| 181 | + monkeypatch.setenv("DB_PORT", "5432") |
| 182 | + monkeypatch.setenv("DB_NAME", name) |
| 183 | + monkeypatch.setenv("DB_USER", "testuser") |
| 184 | + monkeypatch.setenv("DB_PASSWORD", password) |
| 185 | + |
| 186 | + |
| 187 | +def test_get_engine_reuses_same_instance(engine_cache, monkeypatch): |
| 188 | + _direct_db_env(monkeypatch) |
| 189 | + assert get_engine() is get_engine() |
| 190 | + |
| 191 | + |
| 192 | +def test_get_engine_different_urls_get_different_engines(engine_cache, monkeypatch): |
| 193 | + _direct_db_env(monkeypatch, name="db_a") |
| 194 | + engine_a = get_engine() |
| 195 | + _direct_db_env(monkeypatch, name="db_b") |
| 196 | + engine_b = get_engine() |
| 197 | + assert engine_a is not engine_b |
| 198 | + |
| 199 | + |
| 200 | +def test_get_engine_not_keyed_by_masked_str_password(engine_cache, monkeypatch): |
| 201 | + """str(URL) masks passwords; cache must still separate credentials.""" |
| 202 | + _direct_db_env(monkeypatch, password="secretA") |
| 203 | + engine_a = get_engine() |
| 204 | + assert "***" in str(get_connection_url()) |
| 205 | + _direct_db_env(monkeypatch, password="secretB") |
| 206 | + engine_b = get_engine() |
| 207 | + assert engine_a is not engine_b |
| 208 | + |
| 209 | + |
| 210 | +def test_clear_engine_cache_disposes_and_creates_new(engine_cache, monkeypatch): |
| 211 | + _direct_db_env(monkeypatch) |
| 212 | + first = get_engine() |
| 213 | + clear_engine_cache() |
| 214 | + second = get_engine() |
| 215 | + assert first is not second |
| 216 | + |
| 217 | + |
| 218 | +def test_get_engine_applies_pool_settings(engine_cache, monkeypatch): |
| 219 | + _direct_db_env(monkeypatch) |
| 220 | + monkeypatch.setenv("DB_POOL_SIZE", "3") |
| 221 | + monkeypatch.setenv("DB_MAX_OVERFLOW", "2") |
| 222 | + engine = get_engine() |
| 223 | + assert engine.pool.size() == 3 |
| 224 | + # No public accessors for these; private attrs are stable in practice but |
| 225 | + # may need updating on a SQLAlchemy major upgrade. |
| 226 | + assert engine.pool._max_overflow == 2 |
| 227 | + assert engine.pool._pre_ping is True |
| 228 | + |
| 229 | + |
150 | 230 | # --- Permission and Role Tests --- |
151 | 231 |
|
152 | 232 |
|
|
0 commit comments