"""Tests for domclick_session — cookie management service (MVP, #2000). Зеркалит test_cian_session.py (MagicMock db, никакого live DB) — без verify_session, т.к. domclick_session MVP её не реализует (см. модуль docstring). """ from __future__ import annotations import json from unittest.mock import MagicMock import pytest from app.services.domclick_session import ( DOMCLICK_REQUIRED_COOKIES, load_session, mark_session_invalid, save_session, ) # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture() def mock_db() -> MagicMock: db = MagicMock() # Default: no row found (load_session → None) db.execute.return_value.mappings.return_value.first.return_value = None return db # --------------------------------------------------------------------------- # DOMCLICK_REQUIRED_COOKIES # --------------------------------------------------------------------------- def test_required_cookies_set_not_empty() -> None: assert isinstance(DOMCLICK_REQUIRED_COOKIES, set) assert len(DOMCLICK_REQUIRED_COOKIES) >= 5 def test_required_cookies_contains_key_names() -> None: assert "CAS_ID" in DOMCLICK_REQUIRED_COOKIES assert "qrator_jsid2" in DOMCLICK_REQUIRED_COOKIES assert "sessionId" in DOMCLICK_REQUIRED_COOKIES assert "_ym_uid" in DOMCLICK_REQUIRED_COOKIES def test_required_cookies_includes_real_sber_id_dump() -> None: """Real Sber-ID authenticated test-account DevTools export (2026-07-04).""" real_cookies_from_browser = { "qrator_jsid2", "CAS_ID", "CAS_ID_SIGNED", "sessionId", "UNIQ_SESSION_ID", "_visitId", "ns_session", } missing = real_cookies_from_browser - DOMCLICK_REQUIRED_COOKIES assert not missing, f"Filter missing real cookies: {missing}" # --------------------------------------------------------------------------- # save_session # --------------------------------------------------------------------------- def test_save_session_calls_pgp_sym_encrypt(mock_db: MagicMock) -> None: save_session(mock_db, account_cas_id=12345, cookies={"CAS_ID": "12345"}) args, _ = mock_db.execute.call_args sql_text = str(args[0]) assert "pgp_sym_encrypt" in sql_text assert "domclick_session_cookies" in sql_text def test_save_session_uses_cast_not_colon_colon(mock_db: MagicMock) -> None: """Verify psycopg v3 compatibility — no ::bigint syntax in SQL.""" save_session(mock_db, account_cas_id=99, cookies={"CAS_ID": "99"}) args, _ = mock_db.execute.call_args sql_text = str(args[0]) assert "CAST(" in sql_text assert "::bigint" not in sql_text def test_save_session_commits(mock_db: MagicMock) -> None: save_session(mock_db, account_cas_id=12345, cookies={"CAS_ID": "12345"}) assert mock_db.commit.called def test_save_session_on_conflict_do_update(mock_db: MagicMock) -> None: save_session(mock_db, account_cas_id=1, cookies={"CAS_ID": "1"}) args, _ = mock_db.execute.call_args sql_text = str(args[0]) assert "ON CONFLICT" in sql_text assert "DO UPDATE" in sql_text def test_save_session_passes_correct_params(mock_db: MagicMock) -> None: save_session(mock_db, account_cas_id=777, cookies={"qrator_jsid2": "x"}, ttl_days=14) call_args = mock_db.execute.call_args params = call_args[0][1] if len(call_args[0]) > 1 else call_args[1].get("params", {}) assert params["cas_id"] == 777 assert params["ttl_days"] == 14 cookies_payload = json.loads(params["cookies_json"]) assert cookies_payload == {"qrator_jsid2": "x"} # --------------------------------------------------------------------------- # load_session # --------------------------------------------------------------------------- def test_load_session_returns_none_when_empty(mock_db: MagicMock) -> None: result = load_session(mock_db) assert result is None def test_load_session_decodes_json(mock_db: MagicMock) -> None: mock_row = { "account_cas_id": 12345, "cookies_json": json.dumps({"CAS_ID": "12345", "qrator_jsid2": "abc"}), "expires_at_estimate": None, } mock_db.execute.return_value.mappings.return_value.first.return_value = mock_row result = load_session(mock_db) assert result == {"CAS_ID": "12345", "qrator_jsid2": "abc"} def test_load_session_updates_last_used_at(mock_db: MagicMock) -> None: mock_row = { "account_cas_id": 42, "cookies_json": json.dumps({"sessionId": "s"}), "expires_at_estimate": None, } mock_db.execute.return_value.mappings.return_value.first.return_value = mock_row load_session(mock_db) # Должно быть минимум 2 вызова execute: SELECT + UPDATE last_used_at assert mock_db.execute.call_count >= 2 # --------------------------------------------------------------------------- # mark_session_invalid # --------------------------------------------------------------------------- def test_mark_session_invalid_updates_table(mock_db: MagicMock) -> None: mark_session_invalid(mock_db, account_cas_id=12345) args, _ = mock_db.execute.call_args sql = str(args[0]) assert "last_invalid_at" in sql assert "domclick_session_cookies" in sql def test_mark_session_invalid_commits(mock_db: MagicMock) -> None: mark_session_invalid(mock_db, account_cas_id=99) assert mock_db.commit.called def test_mark_session_invalid_uses_cast(mock_db: MagicMock) -> None: mark_session_invalid(mock_db, account_cas_id=5) args, _ = mock_db.execute.call_args sql = str(args[0]) assert "CAST(" in sql assert "::bigint" not in sql