124 lines
6.2 KiB
Python
124 lines
6.2 KiB
Python
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
|