|
| 1 | +""" |
| 2 | +E2E test fixtures with real PostgreSQL database. |
| 3 | +
|
| 4 | +Uses a separate 'test_e2e' schema to isolate test data from production. |
| 5 | +Tests are skipped if DATABASE_URL is not set. |
| 6 | +""" |
| 7 | + |
| 8 | +import os |
| 9 | + |
| 10 | +import pytest |
| 11 | +from sqlalchemy import text |
| 12 | +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine |
| 13 | + |
| 14 | +from core.database.models import Base |
| 15 | + |
| 16 | + |
| 17 | +TEST_SCHEMA = "test_e2e" |
| 18 | + |
| 19 | +# Test data constants |
| 20 | +TEST_IMAGE_URL = "https://storage.googleapis.com/pyplots-images/test/plot.png" |
| 21 | +TEST_THUMB_URL = "https://storage.googleapis.com/pyplots-images/test/thumb.png" |
| 22 | + |
| 23 | + |
| 24 | +@pytest.fixture(scope="session") |
| 25 | +def event_loop(): |
| 26 | + """Create event loop for session-scoped async fixtures.""" |
| 27 | + import asyncio |
| 28 | + |
| 29 | + policy = asyncio.get_event_loop_policy() |
| 30 | + loop = policy.new_event_loop() |
| 31 | + yield loop |
| 32 | + loop.close() |
| 33 | + |
| 34 | + |
| 35 | +@pytest.fixture(scope="session") |
| 36 | +async def pg_engine(): |
| 37 | + """ |
| 38 | + Create PostgreSQL engine and setup test schema. |
| 39 | +
|
| 40 | + Creates a separate 'test_e2e' schema to isolate tests from production data. |
| 41 | + The schema is dropped and recreated at the start of the test session. |
| 42 | + """ |
| 43 | + database_url = os.environ.get("DATABASE_URL") |
| 44 | + if not database_url: |
| 45 | + pytest.skip("DATABASE_URL not set - skipping PostgreSQL E2E tests") |
| 46 | + |
| 47 | + engine = create_async_engine(database_url, echo=False) |
| 48 | + |
| 49 | + # Create test schema and tables |
| 50 | + async with engine.begin() as conn: |
| 51 | + await conn.execute(text(f"DROP SCHEMA IF EXISTS {TEST_SCHEMA} CASCADE")) |
| 52 | + await conn.execute(text(f"CREATE SCHEMA {TEST_SCHEMA}")) |
| 53 | + await conn.execute(text(f"SET search_path TO {TEST_SCHEMA}")) |
| 54 | + await conn.run_sync(Base.metadata.create_all) |
| 55 | + |
| 56 | + yield engine |
| 57 | + |
| 58 | + # Cleanup: Drop entire test schema |
| 59 | + async with engine.begin() as conn: |
| 60 | + await conn.execute(text(f"DROP SCHEMA IF EXISTS {TEST_SCHEMA} CASCADE")) |
| 61 | + await engine.dispose() |
| 62 | + |
| 63 | + |
| 64 | +@pytest.fixture |
| 65 | +async def pg_session(pg_engine): |
| 66 | + """Create session with test schema.""" |
| 67 | + async_session = async_sessionmaker(pg_engine, class_=AsyncSession, expire_on_commit=False) |
| 68 | + async with async_session() as session: |
| 69 | + # Ensure we're in test schema |
| 70 | + await session.execute(text(f"SET search_path TO {TEST_SCHEMA}")) |
| 71 | + yield session |
| 72 | + await session.rollback() |
| 73 | + |
| 74 | + |
| 75 | +@pytest.fixture |
| 76 | +async def pg_db_with_data(pg_session): |
| 77 | + """ |
| 78 | + Seed test schema with sample data. |
| 79 | +
|
| 80 | + Creates the same test data as tests/conftest.py:test_db_with_data |
| 81 | + but in the PostgreSQL test schema. |
| 82 | + """ |
| 83 | + from core.database.models import Impl, Library, Spec |
| 84 | + |
| 85 | + # Create libraries |
| 86 | + matplotlib_lib = Library( |
| 87 | + id="matplotlib", |
| 88 | + name="Matplotlib", |
| 89 | + version="3.10.0", |
| 90 | + documentation_url="https://matplotlib.org", |
| 91 | + description="Comprehensive library for visualizations", |
| 92 | + ) |
| 93 | + seaborn_lib = Library( |
| 94 | + id="seaborn", |
| 95 | + name="Seaborn", |
| 96 | + version="0.13.0", |
| 97 | + documentation_url="https://seaborn.pydata.org", |
| 98 | + description="Statistical data visualization", |
| 99 | + ) |
| 100 | + pg_session.add_all([matplotlib_lib, seaborn_lib]) |
| 101 | + |
| 102 | + # Create specs |
| 103 | + scatter_spec = Spec( |
| 104 | + id="scatter-basic", |
| 105 | + title="Basic Scatter Plot", |
| 106 | + description="A basic scatter plot", |
| 107 | + applications=["data visualization", "correlation analysis"], |
| 108 | + data=["numeric"], |
| 109 | + notes=["Use for 2D data"], |
| 110 | + tags={"plot_type": ["scatter"], "domain": ["statistics"], "data_type": ["numeric"], "features": ["basic"]}, |
| 111 | + issue=42, |
| 112 | + suggested="contributor", |
| 113 | + ) |
| 114 | + bar_spec = Spec( |
| 115 | + id="bar-grouped", |
| 116 | + title="Grouped Bar Chart", |
| 117 | + description="A grouped bar chart", |
| 118 | + applications=["comparisons"], |
| 119 | + data=["categorical", "numeric"], |
| 120 | + notes=["Good for comparing categories"], |
| 121 | + tags={"plot_type": ["bar"], "domain": ["statistics"], "data_type": ["categorical"], "features": ["grouped"]}, |
| 122 | + issue=43, |
| 123 | + suggested="contributor2", |
| 124 | + ) |
| 125 | + pg_session.add_all([scatter_spec, bar_spec]) |
| 126 | + await pg_session.commit() |
| 127 | + |
| 128 | + # Create implementations |
| 129 | + scatter_matplotlib = Impl( |
| 130 | + spec_id="scatter-basic", |
| 131 | + library_id="matplotlib", |
| 132 | + code="import matplotlib.pyplot as plt\n# scatter plot code", |
| 133 | + preview_url=TEST_IMAGE_URL.replace("plot", "scatter-matplotlib"), |
| 134 | + preview_thumb=TEST_THUMB_URL.replace("thumb", "scatter-matplotlib-thumb"), |
| 135 | + quality_score=92.5, |
| 136 | + generated_by="claude", |
| 137 | + python_version="3.13", |
| 138 | + library_version="3.10.0", |
| 139 | + ) |
| 140 | + scatter_seaborn = Impl( |
| 141 | + spec_id="scatter-basic", |
| 142 | + library_id="seaborn", |
| 143 | + code="import seaborn as sns\n# scatter plot code", |
| 144 | + preview_url=TEST_IMAGE_URL.replace("plot", "scatter-seaborn"), |
| 145 | + preview_thumb=TEST_THUMB_URL.replace("thumb", "scatter-seaborn-thumb"), |
| 146 | + quality_score=95.0, |
| 147 | + generated_by="claude", |
| 148 | + python_version="3.13", |
| 149 | + library_version="0.13.0", |
| 150 | + ) |
| 151 | + bar_matplotlib = Impl( |
| 152 | + spec_id="bar-grouped", |
| 153 | + library_id="matplotlib", |
| 154 | + code="import matplotlib.pyplot as plt\n# bar chart code", |
| 155 | + preview_url=TEST_IMAGE_URL.replace("plot", "bar-matplotlib"), |
| 156 | + quality_score=88.0, |
| 157 | + generated_by="claude", |
| 158 | + python_version="3.13", |
| 159 | + library_version="3.10.0", |
| 160 | + ) |
| 161 | + pg_session.add_all([scatter_matplotlib, scatter_seaborn, bar_matplotlib]) |
| 162 | + await pg_session.commit() |
| 163 | + |
| 164 | + # Expire all cached objects to ensure fresh loading with relationships |
| 165 | + pg_session.expire_all() |
| 166 | + |
| 167 | + yield pg_session |
0 commit comments