From 68fcc89153701f7e7f902a3b20838e3f3903974e Mon Sep 17 00:00:00 2001 From: IgorVolochay Date: Fri, 21 Aug 2026 12:52:37 +0300 Subject: [PATCH] Some bugfix --- .github/workflows/app-actions.yml | 2 +- app/main.py | 14 +++++++++++--- app/tests/test_cards.py | 28 ++++++++++++++-------------- app/tests/test_user_info.py | 16 ++++++++-------- app/tests/test_visited_cards.py | 8 ++++---- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/.github/workflows/app-actions.yml b/.github/workflows/app-actions.yml index 35c2877..7c1efe5 100644 --- a/.github/workflows/app-actions.yml +++ b/.github/workflows/app-actions.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.12 architecture: x64 - name: Install dependencies diff --git a/app/main.py b/app/main.py index 9a03dbd..e252809 100644 --- a/app/main.py +++ b/app/main.py @@ -9,6 +9,8 @@ from dotenv import load_dotenv from fastapi import FastAPI, Depends, Response, Header, HTTPException, status from guard import SecurityMiddleware, SecurityConfig +from typing import Optional + from schemas.api_schemas import BaseResponse, AddUserBody, AddCardBody, SelectChoice, ReactionCard, AddCommentBody from schemas.base_schemas import Card from mongo_worker import MongoWorker @@ -42,7 +44,14 @@ config = SecurityConfig( app.add_middleware(SecurityMiddleware, config=config) mongo_worker = MongoWorker() -rabbit_worker = RabbitWorker() +_rabbit_worker: Optional[RabbitWorker] = None + + +def get_rabbit_worker() -> RabbitWorker: + global _rabbit_worker + if _rabbit_worker is None: + _rabbit_worker = RabbitWorker() + return _rabbit_worker MODERATION_SECRET = os.getenv("MODERATION_SECRET", "change-me-in-production") @@ -50,7 +59,6 @@ MODERATION_SECRET = os.getenv("MODERATION_SECRET", "change-me-in-production") async def verify_moderation_secret( x_moderation_secret: str = Header(..., alias="X-Moderation-Secret"), ) -> str: - """Проверяет секретный ключ модерации в заголовке запроса.""" if not secrets.compare_digest(x_moderation_secret, MODERATION_SECRET): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -146,7 +154,7 @@ async def add_card( # Отправляем карточку в RabbitMQ на ручную модерацию админом try: - await rabbit_worker.send_to_moderation(card) + await get_rabbit_worker().send_to_moderation(card) except Exception as exc: logger.error("Failed to send card %s to moderation queue: %s", card.card_id, exc) diff --git a/app/tests/test_cards.py b/app/tests/test_cards.py index 1e0cbab..12623fa 100644 --- a/app/tests/test_cards.py +++ b/app/tests/test_cards.py @@ -14,7 +14,7 @@ NON_EXIST_CARD_ID = 1000 # ---------- /add_card ---------- -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_valid(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: payload = { @@ -32,7 +32,7 @@ async def test_add_card_valid(): assert card.choice_B == payload["choice_B"] assert card.author_id == payload["author_id"] -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_missing_field(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: payload = { @@ -44,7 +44,7 @@ async def test_add_card_missing_field(): print(f"\nINPUT: endpoint=/add_card | payload (missing field)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_wrong_type(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: payload = { @@ -56,7 +56,7 @@ async def test_add_card_wrong_type(): print(f"\nINPUT: endpoint=/add_card | payload (wrong type)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_empty_strings(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: payload = { @@ -68,7 +68,7 @@ async def test_add_card_empty_strings(): print(f"\nINPUT: endpoint=/add_card | payload (empty strings)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 400 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_long_strings(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: long_str = "A" * 5000 # long string @@ -81,7 +81,7 @@ async def test_add_card_long_strings(): print(f"\nINPUT: endpoint=/add_card | payload with long strings (length={len(long_str)})\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 400 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_negative_author_id(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: payload = { @@ -93,7 +93,7 @@ async def test_add_card_negative_author_id(): print(f"\nINPUT: endpoint=/add_card | payload (negative author_id)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_card_malformed_json(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: malformed_json = '{"choice_A": "Option A", "choice_B": "Option B", "author_id": 123' # broken json @@ -105,7 +105,7 @@ async def test_add_card_malformed_json(): print(f"\nINPUT: endpoint=/add_card | payload (malformed JSON)={malformed_json}\nOUTPUT: status={response.status_code} | json={response.json() if response.content else 'No JSON'}") assert response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_async_card_creation(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: tasks = [] @@ -136,7 +136,7 @@ async def test_async_card_creation(): # ---------- /get_card ---------- -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_card_valid(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: payload = { @@ -157,30 +157,30 @@ async def test_get_card_valid(): card_from_get = Card.model_validate(base_resp.result) assert card_from_get.card_id == card_id -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_card_nonexistent(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/get_card", params={"card_id": NON_EXIST_CARD_ID}) print(f"\nINPUT: endpoint=/get_card | params={{'card_id': {NON_EXIST_CARD_ID}}}\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 404 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_card_missing_param(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/get_card") print(f"\nINPUT: endpoint=/get_card (missing card_id param)\nOUTPUT: status={response.status_code} | json={response.json() if response.content else 'No content'}") assert response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_card_wrong_type(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/get_card", params={"card_id": "abc"}) print(f"\nINPUT: endpoint=/get_card | params={{'card_id': 'abc'}}\nOUTPUT: status={response.status_code} | json={response.json()}") assert response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_card_negative(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/get_card", params={"card_id": -10}) print(f"\nINPUT: endpoint=/get_card | params={{'card_id': -10}}\nOUTPUT: status={response.status_code} | json={response.json()}") - assert response.status_code == 422 \ No newline at end of file + assert response.status_code == 404 \ No newline at end of file diff --git a/app/tests/test_user_info.py b/app/tests/test_user_info.py index 67f52c4..157d145 100644 --- a/app/tests/test_user_info.py +++ b/app/tests/test_user_info.py @@ -16,7 +16,7 @@ NON_EXIST_USER = random.randint(100000000, 1000000000) # TEST ADD USERS UTILS # -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_user_non_full_data(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -30,7 +30,7 @@ async def test_add_user_non_full_data(): assert raw_response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_user_negative_int_id(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -47,7 +47,7 @@ async def test_add_user_negative_int_id(): assert raw_response.status_code == 422 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_new_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -67,7 +67,7 @@ async def test_add_new_user(): assert response.error == False assert User.model_validate(response.result) -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_already_exist_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -92,7 +92,7 @@ async def test_add_already_exist_user(): # TEST CHECK USERS UTILS # -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_check_non_exist_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -106,7 +106,7 @@ async def test_check_non_exist_user(): assert response.error == False assert response.result == False -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_check_exist_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -125,7 +125,7 @@ async def test_check_exist_user(): # TEST GET USERS UTILS # -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_non_exist_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -139,7 +139,7 @@ async def test_get_non_exist_user(): assert response.error == True assert response.result == "User doesn't exist" -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_exist_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: diff --git a/app/tests/test_visited_cards.py b/app/tests/test_visited_cards.py index 62ea676..e1b538e 100644 --- a/app/tests/test_visited_cards.py +++ b/app/tests/test_visited_cards.py @@ -14,7 +14,7 @@ ACTIVE_CARDS_LESS_THAN_TEN = False # ------------- /add_user --------------- -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_add_new_user(): async with AsyncClient(transport=ASGITransport(app=app), base_url='http://test') as client: @@ -36,7 +36,7 @@ async def test_add_new_user(): # ---------- /get_random_cards ---------- -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_random_cards_valid(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: params = {"user_id": EXIST_USER} @@ -61,7 +61,7 @@ async def test_get_random_cards_valid(): ACTIVE_CARDS_LESS_THAN_TEN = True pytest.skip(reason="The number of active cards is less than 10 in MongoDB") -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_random_cards_randomness(): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: params = {"user_id": EXIST_USER} @@ -73,7 +73,7 @@ async def test_get_random_cards_randomness(): if len(result1) == 10 and len(result2) == 10: assert result1 != result2 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_get_random_cards_parallel_requests(): if NO_ACTIVE_CARDS_STATUS: pytest.skip(reason="No active cards in MongoDB")