33from pathlib import Path
44
55import pytest
6+ import sqlalchemy as sa
67from alembic import command
78from alembic .config import Config
89from api .deps import get_db_session
1112from starlette .testclient import TestClient
1213
1314
15+ def pytest_addoption (parser ):
16+ """Add custom command line options."""
17+ parser .addoption (
18+ "--db" ,
19+ action = "store" ,
20+ default = "sqlite" ,
21+ choices = ["sqlite" , "postgres" ],
22+ help = "Database to use for tests: sqlite (default) or postgres" ,
23+ )
24+
25+
1426@pytest .fixture
1527def data_folder_path ():
1628 return Path (__file__ ).parent .parent / "data"
1729
1830
1931@pytest .fixture (name = "session" )
20- def session_fixture (monkeypatch ) -> Session :
32+ def session_fixture (request , monkeypatch ) -> Session :
2133 """Create a new database session for a test."""
2234 # TODO: Use an in-memory SQLite database for faster tests if possible.
2335 # https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/#memory-database
2436
25- # Create a temporary database file
26- fd , path = tempfile .mkstemp (suffix = ".db" )
27- os .close (fd )
28- db_url = f"sqlite:///{ path } "
37+ db_type = request .config .getoption ("--db" )
38+
39+ if db_type == "postgres" :
40+ db_url = "postgresql://develop:develop_secret@localhost:5432/develop"
41+ path = None
42+ else :
43+ # Create a temporary database file for SQLite
44+ fd , path = tempfile .mkstemp (suffix = ".db" )
45+ os .close (fd )
46+ db_url = f"sqlite:///{ path } "
2947
3048 # Use monkeypatch to set DATABASE_URL environment variable
3149 monkeypatch .setattr ("config.settings.DATABASE_URL" , db_url )
@@ -42,12 +60,31 @@ def session_fixture(monkeypatch) -> Session:
4260 engine = create_engine (db_url )
4361 # TODO: Alternatively, you can create tables directly without migrations for simpler setups.
4462 # create_db_and_tables(engine)
45- with Session (engine ) as session :
46- yield session
63+
64+ # Ensure that changes made during tests do not persist and affect other tests using a nested transaction
65+ # This is needed for PostgreSQL since the SQLite is erased after each test by deleting the temp file
66+ connection = engine .connect ()
67+ transaction = connection .begin ()
68+ session = Session (bind = connection )
69+
70+ nested = connection .begin_nested ()
71+
72+ @sa .event .listens_for (session , "after_transaction_end" )
73+ def end_savepoint (session , transaction ):
74+ nonlocal nested
75+ if not nested .is_active :
76+ nested = connection .begin_nested ()
77+
78+ yield session
79+
80+ session .close ()
81+ transaction .rollback ()
82+ connection .close ()
4783
4884 # Clean up
4985 engine .dispose ()
50- os .unlink (path )
86+ if path :
87+ os .unlink (path )
5188
5289
5390@pytest .fixture (name = "client_with_db" )
0 commit comments