refactor(services): shared race-safe get_or_create helper (DRY backend sweep)
The find-or-create dance — SELECT, then a SAVEPOINT INSERT that recovers (not a full rollback) on IntegrityError when a concurrent worker inserted first — was hand-rolled identically in 4 async sites: ArtistService.find_or_create, TagService.find_or_create, ExtensionService._find_or_create_artist and ._find_or_create_source. Divergent copies of exactly this pattern are how the duplicate-row/race bugs in reference_scalar_one_or_none_duplicates crept in, so it now lives once in services/db_helpers.get_or_create (returns (row, created); factory adds+flushes+returns the row; caller owns the outer commit). Over-DRY guard: SourceService's IntegrityError sites RAISE DuplicateSourceError (reject-on-conflict, a different concept) — left alone. Importer._get_or_create is the lone SYNC consumer (already shared by 2 callers) — stays separate, can't cross the sync/async boundary. §8b: no hand-rolled async find-or-create remains. Test: get_or_create creates then returns existing without re-invoking the factory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
"""Race-safe get_or_create helper (shared by the async services)."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from backend.app.models import Artist
|
||||
from backend.app.services.db_helpers import get_or_create
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_or_create_creates_then_returns_existing(db):
|
||||
stmt = select(Artist).where(Artist.slug == "goc-test")
|
||||
|
||||
factory_calls = {"n": 0}
|
||||
|
||||
async def _make():
|
||||
factory_calls["n"] += 1
|
||||
a = Artist(name="GOC Test", slug="goc-test")
|
||||
db.add(a)
|
||||
await db.flush()
|
||||
return a
|
||||
|
||||
row1, created1 = await get_or_create(db, stmt, _make)
|
||||
assert created1 is True
|
||||
assert row1.slug == "goc-test"
|
||||
assert factory_calls["n"] == 1
|
||||
|
||||
# Second call finds the existing row and does NOT invoke the factory.
|
||||
row2, created2 = await get_or_create(db, stmt, _make)
|
||||
assert created2 is False
|
||||
assert row2.id == row1.id
|
||||
assert factory_calls["n"] == 1 # factory not called when the row exists
|
||||
Reference in New Issue
Block a user