Some bugfix

This commit is contained in:
IgorVolochay
2026-08-21 12:52:37 +03:00
parent 40e2cc8193
commit 68fcc89153
5 changed files with 38 additions and 30 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: 3.12
architecture: x64 architecture: x64
- name: Install dependencies - name: Install dependencies
+11 -3
View File
@@ -9,6 +9,8 @@ from dotenv import load_dotenv
from fastapi import FastAPI, Depends, Response, Header, HTTPException, status from fastapi import FastAPI, Depends, Response, Header, HTTPException, status
from guard import SecurityMiddleware, SecurityConfig from guard import SecurityMiddleware, SecurityConfig
from typing import Optional
from schemas.api_schemas import BaseResponse, AddUserBody, AddCardBody, SelectChoice, ReactionCard, AddCommentBody from schemas.api_schemas import BaseResponse, AddUserBody, AddCardBody, SelectChoice, ReactionCard, AddCommentBody
from schemas.base_schemas import Card from schemas.base_schemas import Card
from mongo_worker import MongoWorker from mongo_worker import MongoWorker
@@ -42,7 +44,14 @@ config = SecurityConfig(
app.add_middleware(SecurityMiddleware, config=config) app.add_middleware(SecurityMiddleware, config=config)
mongo_worker = MongoWorker() 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") 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( async def verify_moderation_secret(
x_moderation_secret: str = Header(..., alias="X-Moderation-Secret"), x_moderation_secret: str = Header(..., alias="X-Moderation-Secret"),
) -> str: ) -> str:
"""Проверяет секретный ключ модерации в заголовке запроса."""
if not secrets.compare_digest(x_moderation_secret, MODERATION_SECRET): if not secrets.compare_digest(x_moderation_secret, MODERATION_SECRET):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
@@ -146,7 +154,7 @@ async def add_card(
# Отправляем карточку в RabbitMQ на ручную модерацию админом # Отправляем карточку в RabbitMQ на ручную модерацию админом
try: try:
await rabbit_worker.send_to_moderation(card) await get_rabbit_worker().send_to_moderation(card)
except Exception as exc: except Exception as exc:
logger.error("Failed to send card %s to moderation queue: %s", card.card_id, exc) logger.error("Failed to send card %s to moderation queue: %s", card.card_id, exc)
+14 -14
View File
@@ -14,7 +14,7 @@ NON_EXIST_CARD_ID = 1000
# ---------- /add_card ---------- # ---------- /add_card ----------
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_valid(): async def test_add_card_valid():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
payload = { payload = {
@@ -32,7 +32,7 @@ async def test_add_card_valid():
assert card.choice_B == payload["choice_B"] assert card.choice_B == payload["choice_B"]
assert card.author_id == payload["author_id"] assert card.author_id == payload["author_id"]
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_missing_field(): async def test_add_card_missing_field():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
payload = { 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()}") print(f"\nINPUT: endpoint=/add_card | payload (missing field)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}")
assert response.status_code == 422 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_wrong_type(): async def test_add_card_wrong_type():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
payload = { 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()}") print(f"\nINPUT: endpoint=/add_card | payload (wrong type)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}")
assert response.status_code == 422 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_empty_strings(): async def test_add_card_empty_strings():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
payload = { 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()}") print(f"\nINPUT: endpoint=/add_card | payload (empty strings)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}")
assert response.status_code == 400 assert response.status_code == 400
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_long_strings(): async def test_add_card_long_strings():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
long_str = "A" * 5000 # long string 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()}") 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 assert response.status_code == 400
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_negative_author_id(): async def test_add_card_negative_author_id():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
payload = { 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()}") print(f"\nINPUT: endpoint=/add_card | payload (negative author_id)={payload}\nOUTPUT: status={response.status_code} | json={response.json()}")
assert response.status_code == 422 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_card_malformed_json(): async def test_add_card_malformed_json():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: 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 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'}") 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 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_async_card_creation(): async def test_async_card_creation():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
tasks = [] tasks = []
@@ -136,7 +136,7 @@ async def test_async_card_creation():
# ---------- /get_card ---------- # ---------- /get_card ----------
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_card_valid(): async def test_get_card_valid():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
payload = { payload = {
@@ -157,30 +157,30 @@ async def test_get_card_valid():
card_from_get = Card.model_validate(base_resp.result) card_from_get = Card.model_validate(base_resp.result)
assert card_from_get.card_id == card_id assert card_from_get.card_id == card_id
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_card_nonexistent(): async def test_get_card_nonexistent():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: 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}) 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()}") 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 assert response.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_card_missing_param(): async def test_get_card_missing_param():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get("/get_card") 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'}") 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 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_card_wrong_type(): async def test_get_card_wrong_type():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get("/get_card", params={"card_id": "abc"}) 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()}") print(f"\nINPUT: endpoint=/get_card | params={{'card_id': 'abc'}}\nOUTPUT: status={response.status_code} | json={response.json()}")
assert response.status_code == 422 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_card_negative(): async def test_get_card_negative():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get("/get_card", params={"card_id": -10}) 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()}") print(f"\nINPUT: endpoint=/get_card | params={{'card_id': -10}}\nOUTPUT: status={response.status_code} | json={response.json()}")
assert response.status_code == 422 assert response.status_code == 404
+8 -8
View File
@@ -16,7 +16,7 @@ NON_EXIST_USER = random.randint(100000000, 1000000000)
# TEST ADD USERS UTILS # # TEST ADD USERS UTILS #
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_user_non_full_data(): async def test_add_user_non_full_data():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -30,7 +30,7 @@ async def test_add_user_non_full_data():
assert raw_response.status_code == 422 assert raw_response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_user_negative_int_id(): async def test_add_user_negative_int_id():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -47,7 +47,7 @@ async def test_add_user_negative_int_id():
assert raw_response.status_code == 422 assert raw_response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_new_user(): async def test_add_new_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -67,7 +67,7 @@ async def test_add_new_user():
assert response.error == False assert response.error == False
assert User.model_validate(response.result) assert User.model_validate(response.result)
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_already_exist_user(): async def test_add_already_exist_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -92,7 +92,7 @@ async def test_add_already_exist_user():
# TEST CHECK USERS UTILS # # TEST CHECK USERS UTILS #
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_check_non_exist_user(): async def test_check_non_exist_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -106,7 +106,7 @@ async def test_check_non_exist_user():
assert response.error == False assert response.error == False
assert response.result == False assert response.result == False
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_check_exist_user(): async def test_check_exist_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -125,7 +125,7 @@ async def test_check_exist_user():
# TEST GET USERS UTILS # # TEST GET USERS UTILS #
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_non_exist_user(): async def test_get_non_exist_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -139,7 +139,7 @@ async def test_get_non_exist_user():
assert response.error == True assert response.error == True
assert response.result == "User doesn't exist" assert response.result == "User doesn't exist"
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_exist_user(): async def test_get_exist_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
+4 -4
View File
@@ -14,7 +14,7 @@ ACTIVE_CARDS_LESS_THAN_TEN = False
# ------------- /add_user --------------- # ------------- /add_user ---------------
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_add_new_user(): async def test_add_new_user():
async with AsyncClient(transport=ASGITransport(app=app), async with AsyncClient(transport=ASGITransport(app=app),
base_url='http://test') as client: base_url='http://test') as client:
@@ -36,7 +36,7 @@ async def test_add_new_user():
# ---------- /get_random_cards ---------- # ---------- /get_random_cards ----------
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_random_cards_valid(): async def test_get_random_cards_valid():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
params = {"user_id": EXIST_USER} params = {"user_id": EXIST_USER}
@@ -61,7 +61,7 @@ async def test_get_random_cards_valid():
ACTIVE_CARDS_LESS_THAN_TEN = True ACTIVE_CARDS_LESS_THAN_TEN = True
pytest.skip(reason="The number of active cards is less than 10 in MongoDB") 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 def test_get_random_cards_randomness():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
params = {"user_id": EXIST_USER} params = {"user_id": EXIST_USER}
@@ -73,7 +73,7 @@ async def test_get_random_cards_randomness():
if len(result1) == 10 and len(result2) == 10: if len(result1) == 10 and len(result2) == 10:
assert result1 != result2 assert result1 != result2
@pytest.mark.asyncio @pytest.mark.asyncio(loop_scope="session")
async def test_get_random_cards_parallel_requests(): async def test_get_random_cards_parallel_requests():
if NO_ACTIVE_CARDS_STATUS: if NO_ACTIVE_CARDS_STATUS:
pytest.skip(reason="No active cards in MongoDB") pytest.skip(reason="No active cards in MongoDB")