teecup/tests/test_concurrency_version_check.py

139 lines
5.5 KiB
Python
Raw Normal View History

"""
Automatiserer det som til (ADR-057) kun er verifisert manuelt i tre lag
i nettleser: den optimistiske versjonssjekken round_hole. Kaller de
faktiske router-funksjonene i rounds.py direkte (samme SQL, samme
feilhåndtering som produksjon) -- IKKE en gjenimplementering av logikken.
"""
import pytest
from fastapi import HTTPException
from app import db as app_db
from app.auth import CurrentUser
from app.routers.rounds import (
HoleUpdate,
SideHoleUpdate,
update_hole,
update_side_hole,
)
from tests.conftest import create_hole, create_participant, create_round
async def _setup_round_with_hole(hole_number: int = 1):
async with app_db.plain_connection() as conn:
from tests.conftest import create_user
owner_id = await create_user()
round_id = await create_round(conn, owner_id)
participant_id = await create_participant(conn, round_id, user_id=owner_id, is_owner=True)
await create_hole(conn, participant_id, hole_number=hole_number, par=4, stroke_index=9)
return owner_id, round_id, participant_id
async def test_update_succeeds_and_increments_version(pool):
owner_id, round_id, participant_id = await _setup_round_with_hole()
user = CurrentUser(user_id=owner_id)
out = await update_hole(
round_id, participant_id, 1,
HoleUpdate(score=5, expected_version=1),
user=user,
)
assert out.score == 5
assert out.version == 2
async def test_stale_version_is_rejected_with_409(pool):
owner_id, round_id, participant_id = await _setup_round_with_hole()
user = CurrentUser(user_id=owner_id)
# "Enhet 1" leser versjon 1, skriver -- lykkes, hullet er nå versjon 2.
await update_hole(round_id, participant_id, 1, HoleUpdate(score=4, expected_version=1), user=user)
# "Enhet 2" leste OGSÅ versjon 1 (før enhet 1 sin skriving nådde den),
# og prøver nå å skrive basert på den utdaterte versjonen.
with pytest.raises(HTTPException) as exc_info:
await update_hole(round_id, participant_id, 1, HoleUpdate(score=6, expected_version=1), user=user)
assert exc_info.value.status_code == 409
assert exc_info.value.detail["code"] == "STALE_VERSION"
async def test_expected_version_none_bypasses_check(pool):
owner_id, round_id, participant_id = await _setup_round_with_hole()
user = CurrentUser(user_id=owner_id)
await update_hole(round_id, participant_id, 1, HoleUpdate(score=4, expected_version=1), user=user)
# Hullet er nå versjon 2 -- men vi sender ingen forventet versjon i det
# hele tatt (klient som ikke sender feltet, f.eks. eldre app-versjon).
out = await update_hole(round_id, participant_id, 1, HoleUpdate(score=7, expected_version=None), user=user)
assert out.score == 7
assert out.version == 3
async def test_nonexistent_hole_is_404_not_409(pool):
owner_id, round_id, participant_id = await _setup_round_with_hole(hole_number=1)
user = CurrentUser(user_id=owner_id)
with pytest.raises(HTTPException) as exc_info:
await update_hole(round_id, participant_id, 18, HoleUpdate(score=4, expected_version=1), user=user)
assert exc_info.value.status_code == 404
assert exc_info.value.detail["code"] == "NOT_FOUND"
# ---------------------------------------------------------------------------
# round_side (delt-ball-format) -- update_side_hole har sin EGEN 404-vs-409-
# logikk (egen eksistenssjekk lagt til i samme runde som versjonskolonnen),
# adskilt kode fra update_hole -- verifiseres derfor separat, ikke antatt
# dekket av testene over.
# ---------------------------------------------------------------------------
async def _setup_round_with_side_hole(hole_number: int = 1):
async with app_db.plain_connection() as conn:
from tests.conftest import create_user, new_id
owner_id = await create_user()
round_id = await create_round(conn, owner_id)
side_id = new_id()
await conn.execute(
"INSERT INTO round_side (id, round_id, label) VALUES ($1, $2, 'Side A')",
side_id, round_id,
)
participant_id = await create_participant(
conn, round_id, user_id=owner_id, is_owner=True,
)
await conn.execute(
"UPDATE round_participant SET round_side_id = $1 WHERE id = $2", side_id, participant_id
)
await conn.execute(
"""
INSERT INTO round_hole (round_side_id, hole_number, par, stroke_index, version)
VALUES ($1, $2, 4, 9, 1)
""",
side_id, hole_number,
)
return owner_id, round_id, side_id
async def test_side_hole_stale_version_is_409(pool):
owner_id, round_id, side_id = await _setup_round_with_side_hole()
user = CurrentUser(user_id=owner_id)
await update_side_hole(round_id, side_id, 1, SideHoleUpdate(score=4, expected_version=1), user=user)
with pytest.raises(HTTPException) as exc_info:
await update_side_hole(round_id, side_id, 1, SideHoleUpdate(score=6, expected_version=1), user=user)
assert exc_info.value.status_code == 409
assert exc_info.value.detail["code"] == "STALE_VERSION"
async def test_side_hole_nonexistent_is_404_not_409(pool):
owner_id, round_id, side_id = await _setup_round_with_side_hole(hole_number=1)
user = CurrentUser(user_id=owner_id)
with pytest.raises(HTTPException) as exc_info:
await update_side_hole(round_id, side_id, 18, SideHoleUpdate(score=4, expected_version=1), user=user)
assert exc_info.value.status_code == 404
assert exc_info.value.detail["code"] == "NOT_FOUND"