Stabilize staff concurrency and premium emoji; enable verified Drone deployment
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2026-09-13 19:48:41 +09:00
parent 733298bf06
commit cb35bb12e3
86 changed files with 2993 additions and 2455 deletions

View File

@@ -0,0 +1,120 @@
import asyncio
from datetime import datetime, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from aiogram import Bot, Dispatcher, Router, F
from aiogram.fsm.storage.memory import MemoryStorage, SimpleEventIsolation
from aiogram.fsm.context import FSMContext
from aiogram.types import Message, Chat, User as TelegramUser, CallbackQuery, Update
from sqlalchemy import select
from src.core.access import is_admin, is_staff
from src.core.database import async_session_maker
from src.core.models import User, Winner
from src.core.services import UserService
from src.middlewares.access import AccessMiddleware
from src.controllers.bot_controller import BotController
async def test_assigned_admin_and_cashier_permissions_update_immediately():
async with async_session_maker() as session:
admin = await UserService.get_or_create_user(session, 301)
admin.is_admin = True
cashier = await UserService.get_or_create_user(session, 302)
cashier.is_cashier = True
await session.commit()
assert await is_admin(301)
assert await is_staff(302) and not await is_admin(302)
admin.is_admin = False
await session.commit()
assert not await is_admin(301)
@pytest.mark.parametrize("role,allow_cashier,permitted", [("user", False, False), ("user", True, False),
("cashier", False, False), ("cashier", True, True), ("admin", False, True)])
async def test_every_staff_fsm_step_checks_permissions(role, allow_cashier, permitted):
async with async_session_maker() as session:
user = await UserService.get_or_create_user(session, 303)
user.is_admin = role == "admin"
user.is_cashier = role == "cashier"
await session.commit()
handler = AsyncMock()
event = SimpleNamespace(from_user=SimpleNamespace(id=303), answer=AsyncMock())
await AccessMiddleware(allow_cashier=allow_cashier)(handler, event, {})
assert handler.await_count == int(permitted)
async def test_callback_main_menu_uses_clicker_identity():
from main import back_to_main_handler
import main
from contextlib import asynccontextmanager
controller = SimpleNamespace(handle_start=AsyncMock())
@asynccontextmanager
async def get_controller():
yield controller
old = main.get_controller
main.get_controller = get_controller
try:
actor = TelegramUser(id=304, is_bot=False, first_name="Human")
event = SimpleNamespace(from_user=actor, message=object(), answer=AsyncMock())
state = SimpleNamespace(clear=AsyncMock())
await back_to_main_handler(event, state)
controller.handle_start.assert_awaited_once_with(event.message, actor=actor)
state.clear.assert_awaited_once()
finally:
main.get_controller = old
async def test_slow_user_does_not_block_other_user_and_same_user_is_serialized():
storage = MemoryStorage()
dispatcher = Dispatcher(storage=storage, events_isolation=SimpleEventIsolation())
router = Router()
bot = Bot("123456:TEST_TOKEN_FOR_ISOLATED_TESTS")
first_started, release_first, other_finished = asyncio.Event(), asyncio.Event(), asyncio.Event()
seen = []
@router.message(F.text)
async def receive(message: Message, state: FSMContext):
if message.text == "slow":
first_started.set()
await release_first.wait()
data = await state.get_data()
seen.append((message.from_user.id, data.get("count", 0)))
await state.update_data(count=data.get("count", 0) + 1)
if message.from_user.id == 402:
other_finished.set()
dispatcher.include_router(router)
def update(number, user_id, text):
return Update(update_id=number, message=Message(message_id=number, date=datetime.now(timezone.utc),
chat=Chat(id=user_id, type="private"), from_user=TelegramUser(id=user_id, is_bot=False, first_name="Actor"), text=text))
slow = asyncio.create_task(dispatcher.feed_update(bot, update(1, 401, "slow")))
await asyncio.wait_for(first_started.wait(), 1)
same = asyncio.create_task(dispatcher.feed_update(bot, update(2, 401, "next")))
other = asyncio.create_task(dispatcher.feed_update(bot, update(3, 402, "fast")))
try:
await asyncio.wait_for(other_finished.wait(), 1)
assert not same.done()
finally:
release_first.set()
await asyncio.gather(slow, same, other)
await bot.session.close()
await dispatcher.fsm.close()
assert seen == [(402, 0), (401, 0), (401, 1)]
async def test_non_owner_cannot_confirm_user_only_prize():
from src.handlers.redraw_handlers import confirm_winner_callback
from src.core.services import LotteryService
async with async_session_maker() as session:
owner = await UserService.get_or_create_user(session, 601)
draw = await LotteryService.create_lottery(session, "Draw", "", ["Prize"], owner.id)
winner = Winner(lottery_id=draw.id, user_id=owner.id, place=1)
session.add(winner)
await session.commit()
winner_id = winner.id
callback = SimpleNamespace(data=f"confirm_win_{winner_id}", from_user=SimpleNamespace(id=602), answer=AsyncMock())
await confirm_winner_callback(callback)
async with async_session_maker() as session:
assert not (await session.get(Winner, winner_id)).is_claimed
callback.answer.assert_awaited_once()