Stabilize staff concurrency and premium emoji; enable verified Drone deployment
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
123
tests/test_concurrency.py
Normal file
123
tests/test_concurrency.py
Normal file
@@ -0,0 +1,123 @@
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.models import User, Lottery, Participation, Winner
|
||||
from src.core.services import UserService, LotteryService, ParticipationService
|
||||
from src.core.registration_services import AccountService, WinnerNotificationService
|
||||
from src.handlers.account_services import AccountParticipationService
|
||||
|
||||
|
||||
async def seed(prizes=2):
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_or_create_user(session, 101, first_name="Client")
|
||||
user.club_card_number = "1234"
|
||||
user.verification_code = "AABBCCDD"
|
||||
user.is_registered = True
|
||||
await session.commit()
|
||||
lottery = await LotteryService.create_lottery(session, "Prizes", "Draw", ["Prize"] * prizes, user.id)
|
||||
return user.id, lottery.id
|
||||
|
||||
|
||||
async def independent(call, *args):
|
||||
async with async_session_maker() as session:
|
||||
return await call(session, *args)
|
||||
|
||||
|
||||
async def test_concurrent_user_upsert_preserves_one_user_and_metadata():
|
||||
users = await asyncio.gather(*(independent(UserService.get_or_create_user, 102, "client", "Name") for _ in range(12)))
|
||||
assert len({user.id for user in users}) == 1
|
||||
user = await independent(UserService.get_or_create_user, 102)
|
||||
assert user.first_name == "Name"
|
||||
|
||||
|
||||
async def test_two_cashiers_add_same_ticket_once():
|
||||
_, lottery_id = await seed()
|
||||
results = await asyncio.gather(*(independent(AccountParticipationService.add_account_to_lottery,
|
||||
lottery_id, "11-22-33-44-55-66-77") for _ in range(12)))
|
||||
assert sum(result["success"] for result in results) == 1
|
||||
assert await independent(ParticipationService.get_participants_count, lottery_id) == 1
|
||||
|
||||
|
||||
async def test_two_admins_add_same_user_once():
|
||||
user_id, lottery_id = await seed()
|
||||
results = await asyncio.gather(*(independent(ParticipationService.add_participant, lottery_id, user_id) for _ in range(8)))
|
||||
assert sum(results) == 1
|
||||
|
||||
|
||||
async def test_two_admins_conduct_one_draw_and_keep_ticket_numbers():
|
||||
_, lottery_id = await seed()
|
||||
for account in ["11-22-33-44-55-66-77", "22-33-44-55-66-77-88"]:
|
||||
await independent(AccountService.create_account, "1234", account)
|
||||
await independent(AccountParticipationService.add_account_to_lottery, lottery_id, account)
|
||||
results = await asyncio.gather(*(independent(LotteryService.conduct_draw, lottery_id) for _ in range(6)))
|
||||
assert sum(bool(result) for result in results) == 1
|
||||
winners = await independent(LotteryService.get_winners, lottery_id)
|
||||
assert {w.account_number for w in winners} == {"11-22-33-44-55-66-77", "22-33-44-55-66-77-88"}
|
||||
async with async_session_maker() as session:
|
||||
lottery = await session.get(Lottery, lottery_id)
|
||||
assert lottery.is_completed and not lottery.is_active and len(lottery.draw_results) == 2
|
||||
|
||||
|
||||
async def test_two_cashiers_claim_one_prize_once():
|
||||
user_id, lottery_id = await seed(prizes=1)
|
||||
await independent(ParticipationService.add_participant, lottery_id, user_id)
|
||||
await independent(LotteryService.conduct_draw, lottery_id)
|
||||
results = await asyncio.gather(*(independent(WinnerNotificationService.verify_winner, "AABBCCDD", lottery_id) for _ in range(8)))
|
||||
assert sum(result is not None for result in results) == 1
|
||||
assert next(result for result in results if result is not None).claimed_at is not None
|
||||
|
||||
|
||||
async def test_multiple_winning_accounts_can_be_claimed_separately():
|
||||
_, lottery_id = await seed()
|
||||
for account in ["11-22-33-44-55-66-77", "22-33-44-55-66-77-88"]:
|
||||
await independent(AccountService.create_account, "1234", account)
|
||||
await independent(AccountParticipationService.add_account_to_lottery, lottery_id, account)
|
||||
await independent(LotteryService.conduct_draw, lottery_id)
|
||||
one = await independent(WinnerNotificationService.verify_winner, "AABBCCDD", lottery_id)
|
||||
two = await independent(WinnerNotificationService.verify_winner, "AABBCCDD", lottery_id)
|
||||
assert one.id != two.id
|
||||
assert await independent(WinnerNotificationService.verify_winner, "AABBCCDD", lottery_id) is None
|
||||
|
||||
|
||||
async def test_closed_draw_rejects_all_participant_mutations():
|
||||
user_id, lottery_id = await seed(1)
|
||||
await independent(ParticipationService.add_participant, lottery_id, user_id)
|
||||
await independent(LotteryService.conduct_draw, lottery_id)
|
||||
result = await independent(AccountParticipationService.add_account_to_lottery, lottery_id, "11-22-33-44-55-66-77")
|
||||
assert not result["success"]
|
||||
assert not await independent(ParticipationService.remove_participant, lottery_id, user_id)
|
||||
assert not await independent(ParticipationService.add_participant, lottery_id, user_id)
|
||||
|
||||
|
||||
async def test_duplicate_account_error_does_not_poison_bulk_session():
|
||||
await seed()
|
||||
async with async_session_maker() as session:
|
||||
await AccountService.create_account(session, "1234", "11223344556677")
|
||||
with pytest.raises(ValueError):
|
||||
await AccountService.create_account(session, "1234", "11223344556677")
|
||||
account = await AccountService.create_account(session, "1234", "22334455667788")
|
||||
assert account.account_number == "22-33-44-55-66-77-88"
|
||||
|
||||
|
||||
async def test_database_unique_constraints_protect_external_writers():
|
||||
user_id, lottery_id = await seed()
|
||||
async with async_session_maker() as session:
|
||||
session.add_all([Winner(lottery_id=lottery_id, user_id=user_id, place=1),
|
||||
Winner(lottery_id=lottery_id, user_id=user_id, place=1)])
|
||||
with pytest.raises(IntegrityError):
|
||||
await session.commit()
|
||||
await session.rollback()
|
||||
assert await session.scalar(select(func.count()).select_from(Winner)) == 0
|
||||
|
||||
|
||||
async def test_manual_winner_json_is_persisted_and_participant_is_required():
|
||||
user_id, lottery_id = await seed()
|
||||
assert not await independent(LotteryService.set_manual_winner, lottery_id, 1, 101)
|
||||
await independent(ParticipationService.add_participant, lottery_id, user_id)
|
||||
assert await independent(LotteryService.set_manual_winner, lottery_id, 1, 101)
|
||||
result = await independent(LotteryService.conduct_draw, lottery_id)
|
||||
assert result[1]["is_manual"] and result[1]["user"].id == user_id
|
||||
Reference in New Issue
Block a user