|
| 1 | +import asyncio |
| 2 | +import typing |
| 3 | +import uuid |
| 4 | + |
| 5 | +import sqlalchemy as sa |
| 6 | +from db_retry import Transaction |
| 7 | + |
| 8 | +from app import models, schemas |
| 9 | +from app.repositories import DecksRepository |
| 10 | +from app.resources.db import create_primary_sa_engine, create_session |
| 11 | +from app.use_cases.create_deck import CreateDeckUseCase |
| 12 | + |
| 13 | + |
| 14 | +if typing.TYPE_CHECKING: |
| 15 | + from sqlalchemy.ext.asyncio import AsyncEngine |
| 16 | + |
| 17 | + |
| 18 | +async def _wait_until_blocked_on_a_lock(engine: AsyncEngine, pid: int) -> None: |
| 19 | + wait_event_type = None |
| 20 | + while wait_event_type != "Lock": |
| 21 | + await asyncio.sleep(0.01) |
| 22 | + async with engine.connect() as connection: |
| 23 | + wait_event_type = await connection.scalar( |
| 24 | + sa.text("SELECT wait_event_type FROM pg_stat_activity WHERE pid = :pid"), {"pid": pid} |
| 25 | + ) |
| 26 | + |
| 27 | + |
| 28 | +async def test_create_deck_returns_the_deck_a_concurrent_request_created_with_the_same_key() -> None: |
| 29 | + engine: typing.Final = create_primary_sa_engine() |
| 30 | + idempotency_key: typing.Final = uuid.uuid4() |
| 31 | + try: |
| 32 | + async with engine.connect() as concurrent_request: |
| 33 | + await concurrent_request.execute( |
| 34 | + sa.insert(models.Deck).values(idempotency_key=idempotency_key, name="first") |
| 35 | + ) |
| 36 | + session: typing.Final = create_session(engine) |
| 37 | + pid: typing.Final = await (await session.connection()).scalar(sa.text("SELECT pg_backend_pid()")) |
| 38 | + use_case: typing.Final = CreateDeckUseCase( |
| 39 | + transaction=Transaction(session=session), decks_repository=DecksRepository(session=session) |
| 40 | + ) |
| 41 | + pending: typing.Final = asyncio.create_task( |
| 42 | + use_case(data=schemas.DeckCreate(idempotency_key=idempotency_key, name="second")) |
| 43 | + ) |
| 44 | + await asyncio.wait_for(_wait_until_blocked_on_a_lock(engine, pid), timeout=5) |
| 45 | + await concurrent_request.commit() |
| 46 | + deck: typing.Final = await pending |
| 47 | + assert deck.name == "first" |
| 48 | + finally: |
| 49 | + async with engine.begin() as connection: |
| 50 | + await connection.execute(sa.delete(models.Deck).where(models.Deck.idempotency_key == idempotency_key)) |
| 51 | + await engine.dispose() |
0 commit comments