Files
new_lottery_bot/tests/test_concurrency.py
Trevor1985 cb35bb12e3
Some checks failed
continuous-integration/drone/push Build is failing
Stabilize staff concurrency and premium emoji; enable verified Drone deployment
2026-09-13 19:48:41 +09:00

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