Files
bvandeusen 7b2a2051e9
CI / lint (push) Failing after 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 35s
CI / integration (push) Successful in 3m19s
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>
2026-06-09 23:46:01 -04:00

35 lines
1020 B
Python

"""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