1818
1919import pytest
2020import pytest_asyncio
21+ from sqlalchemy import text
2122from sqlalchemy .ext .asyncio import AsyncSession , async_sessionmaker , create_async_engine
2223
2324from core .database .models import Base
@@ -74,8 +75,10 @@ async def pg_engine():
7475 try :
7576 async with asyncio .timeout (CONNECTION_TIMEOUT + 2 ):
7677 async with engine .begin () as conn :
77- # Drop all tables for clean state
78- await conn .run_sync (Base .metadata .drop_all )
78+ # Drop all tables in FK order (children before parents)
79+ await conn .execute (text ("DROP TABLE IF EXISTS impls CASCADE" ))
80+ await conn .execute (text ("DROP TABLE IF EXISTS specs CASCADE" ))
81+ await conn .execute (text ("DROP TABLE IF EXISTS libraries CASCADE" ))
7982 # Create fresh tables
8083 await conn .run_sync (Base .metadata .create_all )
8184 except (TimeoutError , asyncio .TimeoutError , OSError ) as e :
@@ -87,13 +90,15 @@ async def pg_engine():
8790
8891 yield engine
8992
90- # Cleanup: Drop all tables
93+ # Cleanup: Drop all tables in FK order (children before parents)
9194 async with engine .begin () as conn :
92- await conn .run_sync (Base .metadata .drop_all )
95+ await conn .execute (text ("DROP TABLE IF EXISTS impls CASCADE" ))
96+ await conn .execute (text ("DROP TABLE IF EXISTS specs CASCADE" ))
97+ await conn .execute (text ("DROP TABLE IF EXISTS libraries CASCADE" ))
9398 await engine .dispose ()
9499
95100
96- @pytest_asyncio .fixture
101+ @pytest_asyncio .fixture ( scope = "function" )
97102async def pg_session (pg_engine ):
98103 """Create session for test database."""
99104 async_session = async_sessionmaker (pg_engine , class_ = AsyncSession , expire_on_commit = False )
@@ -102,13 +107,16 @@ async def pg_session(pg_engine):
102107 await session .rollback ()
103108
104109
105- @pytest_asyncio .fixture
110+ @pytest_asyncio .fixture ( scope = "function" )
106111async def pg_db_with_data (pg_session ):
107112 """
108113 Seed test database with sample data.
109114
110115 Creates the same test data as tests/conftest.py:test_db_with_data
111116 but in the PostgreSQL test database.
117+
118+ Uses atomic commit: libraries + specs flushed first (FK targets),
119+ then impls added and everything committed together.
112120 """
113121 from core .database .models import Impl , Library , Spec
114122
@@ -127,7 +135,6 @@ async def pg_db_with_data(pg_session):
127135 documentation_url = "https://seaborn.pydata.org" ,
128136 description = "Statistical data visualization" ,
129137 )
130- pg_session .add_all ([matplotlib_lib , seaborn_lib ])
131138
132139 # Create specs
133140 scatter_spec = Spec (
@@ -152,8 +159,6 @@ async def pg_db_with_data(pg_session):
152159 issue = 43 ,
153160 suggested = "contributor2" ,
154161 )
155- pg_session .add_all ([scatter_spec , bar_spec ])
156- await pg_session .commit ()
157162
158163 # Create implementations
159164 scatter_matplotlib = Impl (
@@ -188,8 +193,15 @@ async def pg_db_with_data(pg_session):
188193 python_version = "3.13" ,
189194 library_version = "3.10.0" ,
190195 )
196+
197+ # Add FK targets first (libraries and specs)
198+ pg_session .add_all ([matplotlib_lib , seaborn_lib ])
199+ pg_session .add_all ([scatter_spec , bar_spec ])
200+ await pg_session .flush () # Ensure FK targets exist before adding children
201+
202+ # Add FK children (implementations)
191203 pg_session .add_all ([scatter_matplotlib , scatter_seaborn , bar_matplotlib ])
192- await pg_session .commit ()
204+ await pg_session .commit () # Single atomic commit
193205
194206 # Expire all cached objects to ensure fresh loading with relationships
195207 pg_session .expire_all ()
0 commit comments