All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 2m18s
Deploy Trade-In / test (push) Successful in 4m4s
Deploy Trade-In / build-backend (push) Successful in 1m36s
Deploy Trade-In / deploy (push) Successful in 2m0s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
168 lines
6.6 KiB
Python
168 lines
6.6 KiB
Python
"""Offline tests для Yandex session-cookie admin-эндпоинтов (#3192 MVP).
|
||
|
||
Покрытие 2 эндпоинтов (db мокается, NO live network/DB), зеркалит паттерн
|
||
test_domclick_admin_apis.py / test_admin_cian_session_endpoints.py
|
||
(dependency_overrides[get_db] + TestClient):
|
||
- POST /api/v1/admin/scrape/yandex/upload-cookies
|
||
- GET /api/v1/admin/scrape/yandex/session-status
|
||
|
||
Про app-уровневый 403/401 для не-админа: намеренно НЕ тестируется — как и у
|
||
Cian/DomClick-соседей, admin.py не гейтит auth сам (см. модульный docstring
|
||
"Auth: Caddy basic_auth гейтит /trade-in/api/v1/admin/* — application layer
|
||
открыт"), TestClient здесь Caddy обходит по конструкции. Это не регрессия
|
||
именно этого эндпоинта, а свойство всего роутера.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
|
||
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
|
||
|
||
from datetime import UTC, datetime, timedelta
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
|
||
@pytest.fixture
|
||
def client() -> TestClient:
|
||
from app.api.v1 import admin as admin_module
|
||
from app.core.db import get_db
|
||
|
||
app = FastAPI()
|
||
app.include_router(admin_module.router, prefix="/api/v1/admin")
|
||
|
||
def fake_db():
|
||
yield MagicMock()
|
||
|
||
app.dependency_overrides[get_db] = fake_db
|
||
return TestClient(app)
|
||
|
||
|
||
_UPLOAD_URL = "/api/v1/admin/scrape/yandex/upload-cookies"
|
||
_STATUS_URL = "/api/v1/admin/scrape/yandex/session-status"
|
||
|
||
_SESSION_ID_COOKIE = {"name": "Session_id", "value": "abc", "domain": ".yandex.ru", "path": "/"}
|
||
_YANDEXUID_COOKIE = {"name": "yandexuid", "value": "def", "domain": ".yandex.ru", "path": "/"}
|
||
_YM_COOKIE = {"name": "_ym_uid", "value": "ghi", "domain": ".yandex.ru", "path": "/"}
|
||
|
||
|
||
# ── POST /scrape/yandex/upload-cookies ────────────────────────────────────────
|
||
|
||
|
||
def test_upload_cookies_503_when_no_encryption_key(client: TestClient) -> None:
|
||
with patch("app.api.v1.admin.settings.cookie_encryption_key", ""):
|
||
resp = client.post(
|
||
_UPLOAD_URL,
|
||
json={"account_label": "acc", "cookies": [_SESSION_ID_COOKIE]},
|
||
)
|
||
assert resp.status_code == 503
|
||
|
||
|
||
def test_upload_cookies_400_when_no_session_id_marker(client: TestClient) -> None:
|
||
"""Дамп без Session_id — похоже на анонимную сессию, отказ, ничего не сохраняем."""
|
||
with (
|
||
patch("app.api.v1.admin.settings.cookie_encryption_key", "test-key"),
|
||
patch("app.api.v1.admin.yandex_session_svc.save_session") as mock_save,
|
||
):
|
||
resp = client.post(
|
||
_UPLOAD_URL,
|
||
json={"account_label": "acc", "cookies": [_YANDEXUID_COOKIE, _YM_COOKIE]},
|
||
)
|
||
assert resp.status_code == 400
|
||
assert "Session_id" in resp.json()["detail"]
|
||
mock_save.assert_not_called()
|
||
|
||
|
||
def test_upload_cookies_400_when_account_label_empty(client: TestClient) -> None:
|
||
with patch("app.api.v1.admin.settings.cookie_encryption_key", "test-key"):
|
||
resp = client.post(
|
||
_UPLOAD_URL,
|
||
json={"account_label": " ", "cookies": [_SESSION_ID_COOKIE]},
|
||
)
|
||
assert resp.status_code == 400
|
||
assert "account_label" in resp.json()["detail"]
|
||
|
||
|
||
def test_upload_cookies_success_filters_analytics_and_saves(client: TestClient) -> None:
|
||
with (
|
||
patch("app.api.v1.admin.settings.cookie_encryption_key", "test-key"),
|
||
patch("app.api.v1.admin.yandex_session_svc.save_session") as mock_save,
|
||
):
|
||
resp = client.post(
|
||
_UPLOAD_URL,
|
||
json={
|
||
"account_label": "my-account",
|
||
"cookies": [_SESSION_ID_COOKIE, _YANDEXUID_COOKIE, _YM_COOKIE],
|
||
},
|
||
)
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
assert body == {"ok": True, "accountLabel": "my-account", "cookieCount": 2}
|
||
|
||
mock_save.assert_called_once()
|
||
_, kwargs = mock_save.call_args
|
||
assert kwargs["account_label"] == "my-account"
|
||
saved_names = {c["name"] for c in kwargs["cookies"]}
|
||
assert saved_names == {"Session_id", "yandexuid"} # _ym_uid отфильтрован
|
||
|
||
|
||
# ── GET /scrape/yandex/session-status ─────────────────────────────────────────
|
||
|
||
|
||
def test_session_status_no_key_returns_no_session(client: TestClient) -> None:
|
||
with patch("app.api.v1.admin.settings.cookie_encryption_key", ""):
|
||
resp = client.get(_STATUS_URL)
|
||
assert resp.status_code == 200
|
||
assert resp.json() == {"hasSession": False, "expiresAt": None, "expiresSoon": False}
|
||
|
||
|
||
def test_session_status_no_session_in_db(client: TestClient) -> None:
|
||
with (
|
||
patch("app.api.v1.admin.settings.cookie_encryption_key", "test-key"),
|
||
patch("app.api.v1.admin.yandex_session_svc.load_session", return_value=None),
|
||
patch("app.api.v1.admin.yandex_session_svc.session_expires_at", return_value=None),
|
||
):
|
||
resp = client.get(_STATUS_URL)
|
||
assert resp.status_code == 200
|
||
assert resp.json() == {"hasSession": False, "expiresAt": None, "expiresSoon": False}
|
||
|
||
|
||
def test_session_status_valid_session_far_from_expiry(client: TestClient) -> None:
|
||
far_future = datetime.now(UTC) + timedelta(days=20)
|
||
with (
|
||
patch("app.api.v1.admin.settings.cookie_encryption_key", "test-key"),
|
||
patch(
|
||
"app.api.v1.admin.yandex_session_svc.load_session",
|
||
return_value=[_SESSION_ID_COOKIE],
|
||
),
|
||
patch(
|
||
"app.api.v1.admin.yandex_session_svc.session_expires_at",
|
||
return_value=far_future,
|
||
),
|
||
):
|
||
resp = client.get(_STATUS_URL)
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
assert body["hasSession"] is True
|
||
assert body["expiresSoon"] is False
|
||
|
||
|
||
def test_session_status_expiring_soon(client: TestClient) -> None:
|
||
soon = datetime.now(UTC) + timedelta(days=2)
|
||
with (
|
||
patch("app.api.v1.admin.settings.cookie_encryption_key", "test-key"),
|
||
patch(
|
||
"app.api.v1.admin.yandex_session_svc.load_session",
|
||
return_value=[_SESSION_ID_COOKIE],
|
||
),
|
||
patch("app.api.v1.admin.yandex_session_svc.session_expires_at", return_value=soon),
|
||
):
|
||
resp = client.get(_STATUS_URL)
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
assert body["hasSession"] is True
|
||
assert body["expiresSoon"] is True
|