"""Integration tests for /connections routes.""" import secrets from datetime import datetime from unittest.mock import MagicMock, patch import pytest from web.models.connections import EvotorConnection, VkConnection def _login(client, user): client.cookies.clear() return client.post("/login", data={"email": user.email, "password": "testpass123"}, follow_redirects=False) # ── auth guard ──────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_connections_requires_login(client): resp = await client.get("/connections", follow_redirects=False) assert resp.status_code == 303 assert "/login" in resp.headers["location"] # ── GET /connections ────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_connections_get_no_connections(client, active_user): await _login(client, active_user) resp = await client.get("/connections") assert resp.status_code == 200 assert "Эвотор" in resp.text assert "ВКонтакте" in resp.text assert "Не подключено" in resp.text @pytest.mark.asyncio async def test_connections_get_shows_connected(client, active_user, override_db): await _login(client, active_user) conn = EvotorConnection( user_id=active_user.id, evotor_user_id="evo-123", access_token="tok-abc", api_token="api-tok", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() resp = await client.get("/connections") assert resp.status_code == 200 assert "Подключено" in resp.text assert "tok-abc"[:8] in resp.text # ── POST /connections/evotor ────────────────────────────────────────────────── @pytest.mark.asyncio async def test_connections_evotor_post_creates(client, active_user, override_db): await _login(client, active_user) resp = await client.post("/connections/evotor", data={ "access_token": "new-evotor-token", "evotor_user_id": "", }, follow_redirects=False) assert resp.status_code == 303 assert "success=1" in resp.headers["location"] conn = override_db.query(EvotorConnection).filter_by(user_id=active_user.id).first() assert conn is not None assert conn.access_token == "new-evotor-token" assert conn.api_token is not None @pytest.mark.asyncio async def test_connections_evotor_post_updates(client, active_user, override_db): await _login(client, active_user) conn = EvotorConnection( user_id=active_user.id, evotor_user_id="evo-upd", access_token="old-token", api_token="api", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() await client.post("/connections/evotor", data={"access_token": "updated-token"}) override_db.refresh(conn) assert conn.access_token == "updated-token" @pytest.mark.asyncio async def test_connections_evotor_post_empty_token(client, active_user): await _login(client, active_user) resp = await client.post("/connections/evotor", data={"access_token": ""}) assert resp.status_code == 200 assert "обязателен" in resp.text.lower() # ── POST /connections/evotor/disconnect ─────────────────────────────────────── @pytest.mark.asyncio async def test_connections_evotor_disconnect(client, active_user, override_db): await _login(client, active_user) conn = EvotorConnection( user_id=active_user.id, evotor_user_id="evo-del", access_token="tok", api_token="api", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() resp = await client.post("/connections/evotor/disconnect", follow_redirects=False) assert resp.status_code == 303 assert override_db.query(EvotorConnection).filter_by(user_id=active_user.id).first() is None # ── POST /connections/vk (manual token) ────────────────────────────────────── @pytest.mark.asyncio async def test_connections_vk_post_creates(client, active_user, override_db): await _login(client, active_user) resp = await client.post("/connections/vk", data={ "access_token": "vk1.a.testtoken", "vk_group_id": "123456789", }, follow_redirects=False) assert resp.status_code == 303 assert "success=1" in resp.headers["location"] conn = override_db.query(VkConnection).filter_by(user_id=active_user.id).first() assert conn is not None assert conn.access_token == "vk1.a.testtoken" assert conn.vk_user_id == "123456789" @pytest.mark.asyncio async def test_connections_vk_post_empty_token(client, active_user): await _login(client, active_user) resp = await client.post("/connections/vk", data={"access_token": "", "vk_group_id": ""}) assert resp.status_code == 200 assert "обязателен" in resp.text.lower() # ── POST /connections/vk/disconnect ────────────────────────────────────────── @pytest.mark.asyncio async def test_connections_vk_disconnect(client, active_user, override_db): await _login(client, active_user) conn = VkConnection( user_id=active_user.id, access_token="vk-tok", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() resp = await client.post("/connections/vk/disconnect", follow_redirects=False) assert resp.status_code == 303 assert override_db.query(VkConnection).filter_by(user_id=active_user.id).first() is None # ── GET /vk-auth ────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_vk_auth_redirects_to_vk(client, active_user, monkeypatch): monkeypatch.setattr("web.routes.connections.settings.VK_CLIENT_ID", "53265827") monkeypatch.setattr("web.routes.connections.settings.BASE_URL", "http://test") await _login(client, active_user) resp = await client.get("/vk-auth", follow_redirects=False) assert resp.status_code == 302 assert "oauth.vk.com/authorize" in resp.headers["location"] assert "client_id=53265827" in resp.headers["location"] assert "response_type=token" in resp.headers["location"] @pytest.mark.asyncio async def test_vk_auth_no_client_id(client, active_user, monkeypatch): monkeypatch.setattr("web.routes.connections.settings.VK_CLIENT_ID", "") await _login(client, active_user) resp = await client.get("/vk-auth", follow_redirects=False) assert resp.status_code == 303 assert "error=vk_not_configured" in resp.headers["location"] # ── GET /vk-callback ────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_vk_callback_page_returns_html(client, active_user): await _login(client, active_user) resp = await client.get("/vk-callback") assert resp.status_code == 200 assert "access_token" in resp.text assert "fetch" in resp.text # ── POST /vk-callback/save ──────────────────────────────────────────────────── @pytest.mark.asyncio async def test_vk_callback_save_valid(client, active_user, override_db): await _login(client, active_user) # Seed state into session via /vk-auth call monkeypatch_state = "test-state-xyz" # Manually set expected state in session by calling the save endpoint # with a pre-seeded state — we bypass the session by mocking get_current_user # Instead: call /vk-auth to seed the session state, then intercept # Since we can't easily inspect session, test save with wrong state first resp = await client.post("/vk-callback/save", json={ "access_token": "vk1.a.token", "state": "wrong-state", "user_id": "12345", "expires_in": "86400", }) assert resp.status_code == 200 data = resp.json() assert data["ok"] is False assert "state" in data["message"].lower() @pytest.mark.asyncio async def test_vk_callback_save_no_token(client, active_user): await _login(client, active_user) resp = await client.post("/vk-callback/save", json={ "access_token": "", "state": "", }) assert resp.status_code == 200 assert resp.json()["ok"] is False @pytest.mark.asyncio async def test_vk_callback_save_unauthenticated(client): resp = await client.post("/vk-callback/save", json={ "access_token": "tok", "state": "s", }) assert resp.status_code == 401 # ── POST /connections/evotor/test ───────────────────────────────────────────── @pytest.mark.asyncio async def test_evotor_test_no_connection(client, active_user): await _login(client, active_user) resp = await client.post("/connections/evotor/test") assert resp.status_code == 200 assert resp.json()["ok"] is False assert "не настроено" in resp.json()["message"] @pytest.mark.asyncio async def test_evotor_test_success(client, active_user, override_db): await _login(client, active_user) conn = EvotorConnection( user_id=active_user.id, evotor_user_id="evo-t", access_token="tok", api_token="api", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"items": [{"id": "s1"}, {"id": "s2"}]} with patch("web.routes.connections.httpx.get", return_value=mock_resp): resp = await client.post("/connections/evotor/test") assert resp.status_code == 200 data = resp.json() assert data["ok"] is True assert "2" in data["message"] @pytest.mark.asyncio async def test_evotor_test_invalid_token(client, active_user, override_db): await _login(client, active_user) conn = EvotorConnection( user_id=active_user.id, evotor_user_id="evo-inv", access_token="bad-tok", api_token="api", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() mock_resp = MagicMock() mock_resp.status_code = 401 with patch("web.routes.connections.httpx.get", return_value=mock_resp): resp = await client.post("/connections/evotor/test") data = resp.json() assert data["ok"] is False assert "401" in data["message"] # ── POST /connections/vk/test ───────────────────────────────────────────────── @pytest.mark.asyncio async def test_vk_test_no_group_id(client, active_user, override_db): await _login(client, active_user) conn = VkConnection( user_id=active_user.id, access_token="vk-tok", vk_user_id=None, connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() resp = await client.post("/connections/vk/test") assert resp.json()["ok"] is False assert "сообщества" in resp.json()["message"].lower() @pytest.mark.asyncio async def test_vk_test_success(client, active_user, override_db): await _login(client, active_user) conn = VkConnection( user_id=active_user.id, access_token="vk-tok", vk_user_id="229744980", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() mock_resp = MagicMock() mock_resp.json.return_value = {"response": {"groups": [ {"name": "Тестовая чайная", "market": {"enabled": True}} ]}} with patch("web.routes.connections.httpx.get", return_value=mock_resp): resp = await client.post("/connections/vk/test") data = resp.json() assert data["ok"] is True assert "Тестовая чайная" in data["message"] assert "включён" in data["message"] @pytest.mark.asyncio async def test_vk_test_api_error(client, active_user, override_db): await _login(client, active_user) conn = VkConnection( user_id=active_user.id, access_token="vk-tok", vk_user_id="229744980", connected_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) override_db.add(conn) override_db.commit() mock_resp = MagicMock() mock_resp.json.return_value = {"error": {"error_code": 5, "error_msg": "User authorization failed"}} with patch("web.routes.connections.httpx.get", return_value=mock_resp): resp = await client.post("/connections/vk/test") data = resp.json() assert data["ok"] is False assert "5" in data["message"]