353 lines
13 KiB
Python
353 lines
13 KiB
Python
|
|
"""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"]
|