"""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