Skip to content

Commit 095bb34

Browse files
olivermeyerclaude
andcommitted
test(database): cover CLI helpers and error paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c061b50 commit 095bb34

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

tests/aignostics_foundry_core/database_test.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
"""Tests for database module — async SQLAlchemy session management."""
22

3+
import asyncio
34
from collections.abc import AsyncGenerator
45
from pathlib import Path
56

67
import pytest
78
from sqlalchemy.ext.asyncio import AsyncSession
89

910
from aignostics_foundry_core.database import (
11+
cli_run_with_db,
12+
cli_run_with_engine,
1013
dispose_engine,
1114
execute_with_session,
1215
get_db_session,
1316
init_engine,
1417
with_engine,
1518
)
1619

20+
NON_SQLITE_DB_URL = "postgresql+asyncpg://u:p@localhost/db"
21+
1722
SESSION_KWARG = "session"
1823

1924

@@ -62,6 +67,12 @@ async def noop(**_kwargs: object) -> None:
6267

6368
await execute_with_session(noop) # Session maker still functional
6469

70+
@pytest.mark.unit
71+
async def test_init_engine_non_sqlite_url_accepted(self) -> None:
72+
"""init_engine accepts a non-SQLite URL without raising (engine creation is lazy)."""
73+
init_engine(NON_SQLITE_DB_URL)
74+
await dispose_engine() # Must not raise even though no connection was attempted
75+
6576

6677
class TestExecuteWithSession:
6778
"""Tests for execute_with_session behaviour."""
@@ -80,6 +91,82 @@ async def capture_session(**kwargs: object) -> None: # noqa: RUF029
8091
assert len(received) == 1
8192
assert isinstance(received[0], AsyncSession)
8293

94+
@pytest.mark.unit
95+
async def test_execute_with_session_raises_before_init(self) -> None:
96+
"""RuntimeError raised when execute_with_session is called before init_engine."""
97+
98+
async def noop(**_: object) -> None:
99+
pass
100+
101+
with pytest.raises(RuntimeError, match="not initialized"):
102+
await execute_with_session(noop)
103+
104+
105+
class TestCliRunWithDb:
106+
"""Tests for cli_run_with_db synchronous CLI helper."""
107+
108+
@pytest.mark.unit
109+
async def test_cli_run_with_db_returns_function_result(self, sqlite_url: str) -> None:
110+
"""cli_run_with_db returns the value produced by the async function."""
111+
112+
async def return_42(**_: object) -> int: # noqa: RUF029
113+
return 42
114+
115+
result = await asyncio.to_thread(cli_run_with_db, return_42, db_url=sqlite_url)
116+
117+
assert result == 42
118+
119+
@pytest.mark.unit
120+
async def test_cli_run_with_db_disposes_engine_on_error(self, sqlite_url: str) -> None:
121+
"""cli_run_with_db disposes the engine via finally even when the function raises."""
122+
err_msg = "boom"
123+
124+
async def raise_error(**_: object) -> None: # noqa: RUF029
125+
raise ValueError(err_msg)
126+
127+
with pytest.raises(ValueError, match=err_msg):
128+
await asyncio.to_thread(cli_run_with_db, raise_error, db_url=sqlite_url)
129+
130+
# Engine was cleaned up; init_engine followed by execute_with_session must work.
131+
async def noop(**_: object) -> None:
132+
pass
133+
134+
init_engine(sqlite_url)
135+
await execute_with_session(noop)
136+
137+
138+
class TestCliRunWithEngine:
139+
"""Tests for cli_run_with_engine synchronous CLI helper."""
140+
141+
@pytest.mark.unit
142+
async def test_cli_run_with_engine_executes_function(self, sqlite_url: str) -> None:
143+
"""cli_run_with_engine returns the value produced by the async function."""
144+
145+
async def return_hello() -> str: # noqa: RUF029
146+
return "hello"
147+
148+
result = await asyncio.to_thread(cli_run_with_engine, return_hello, db_url=sqlite_url)
149+
150+
assert result == "hello"
151+
152+
@pytest.mark.unit
153+
async def test_cli_run_with_engine_disposes_engine_on_error(self, sqlite_url: str) -> None:
154+
"""cli_run_with_engine disposes the engine via finally even when the function raises."""
155+
err_msg = "boom"
156+
157+
async def raise_error() -> None: # noqa: RUF029
158+
raise ValueError(err_msg)
159+
160+
with pytest.raises(ValueError, match=err_msg):
161+
await asyncio.to_thread(cli_run_with_engine, raise_error, db_url=sqlite_url)
162+
163+
# Engine was cleaned up; init_engine followed by execute_with_session must work.
164+
async def noop(**_: object) -> None:
165+
pass
166+
167+
init_engine(sqlite_url)
168+
await execute_with_session(noop)
169+
83170

84171
class TestWithEngine:
85172
"""Tests for the with_engine decorator factory."""
@@ -96,3 +183,15 @@ async def my_job() -> None: # noqa: RUF029
96183
await my_job()
97184

98185
assert calls == [True]
186+
187+
@pytest.mark.unit
188+
async def test_with_engine_propagates_exception(self, sqlite_url: str) -> None:
189+
"""Exceptions raised inside a @with_engine-decorated function are re-raised."""
190+
err_msg = "job failed"
191+
192+
@with_engine(db_url=sqlite_url)
193+
async def failing_job() -> None: # noqa: RUF029
194+
raise RuntimeError(err_msg)
195+
196+
with pytest.raises(RuntimeError, match=err_msg):
197+
await failing_job()

0 commit comments

Comments
 (0)