fix(integration): asyncio auto-mode, per-test truncation, artist images IN-subquery
This commit is contained in:
@@ -157,12 +157,16 @@ class ArtistService:
|
|||||||
if artist is None:
|
if artist is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
stmt = (
|
# Dedupe via IN-subquery rather than JOIN + DISTINCT: an image can
|
||||||
select(ImageRecord)
|
# have several provenance rows for one artist, and SELECT DISTINCT
|
||||||
.join(ImageProvenance, ImageProvenance.image_record_id == ImageRecord.id)
|
# over ImageRecord fails in Postgres because its json columns have
|
||||||
|
# no equality operator.
|
||||||
|
stmt = select(ImageRecord).where(
|
||||||
|
ImageRecord.id.in_(
|
||||||
|
select(ImageProvenance.image_record_id)
|
||||||
.join(Source, Source.id == ImageProvenance.source_id)
|
.join(Source, Source.id == ImageProvenance.source_id)
|
||||||
.where(Source.artist_id == artist.id)
|
.where(Source.artist_id == artist.id)
|
||||||
.distinct()
|
)
|
||||||
)
|
)
|
||||||
if cursor:
|
if cursor:
|
||||||
cur_ts, cur_id = decode_cursor(cursor)
|
cur_ts, cur_id = decode_cursor(cursor)
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ where = ["."]
|
|||||||
include = ["backend*"]
|
include = ["backend*"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
|
# auto mode: async test fns and async fixtures don't need explicit
|
||||||
|
# @pytest.mark.asyncio / @pytest_asyncio.fixture decoration. Required
|
||||||
|
# because the test_api_* fixtures use plain @pytest.fixture on async
|
||||||
|
# app/client; strict mode errors on those under pytest-asyncio 1.x.
|
||||||
|
asyncio_mode = "auto"
|
||||||
# The fast backend job runs `-m "not integration"`; the dedicated CI
|
# The fast backend job runs `-m "not integration"`; the dedicated CI
|
||||||
# `integration` job (pgvector Postgres + Redis service containers) runs
|
# `integration` job (pgvector Postgres + Redis service containers) runs
|
||||||
# `-m integration`. Both also runnable locally via docker-compose.
|
# `-m integration`. Both also runnable locally via docker-compose.
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ from sqlalchemy.ext.asyncio import (
|
|||||||
)
|
)
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from backend.app.models import Base
|
||||||
|
|
||||||
|
|
||||||
def _async_database_url() -> str:
|
def _async_database_url() -> str:
|
||||||
user = os.environ.get("DB_USER", "fabledcurator")
|
user = os.environ.get("DB_USER", "fabledcurator")
|
||||||
@@ -73,3 +75,27 @@ def db_sync(sync_engine):
|
|||||||
yield session
|
yield session
|
||||||
finally:
|
finally:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _truncate_after_integration(request):
|
||||||
|
"""Integration tests exercise app/celery code that commits on its own
|
||||||
|
connection, which the rollback fixtures above can't undo — so data
|
||||||
|
would leak between tests. Truncate every model table after each
|
||||||
|
integration-marked test. Scoped to the integration marker so the
|
||||||
|
DB-less fast unit job never connects here.
|
||||||
|
"""
|
||||||
|
yield
|
||||||
|
if request.node.get_closest_marker("integration") is None:
|
||||||
|
return
|
||||||
|
tables = ", ".join(t.name for t in Base.metadata.sorted_tables)
|
||||||
|
if not tables:
|
||||||
|
return
|
||||||
|
eng = create_engine(_sync_database_url(), future=True)
|
||||||
|
try:
|
||||||
|
with eng.begin() as conn:
|
||||||
|
conn.exec_driver_sql(
|
||||||
|
f"TRUNCATE {tables} RESTART IDENTITY CASCADE"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
eng.dispose()
|
||||||
|
|||||||
Reference in New Issue
Block a user