fix: update tests for new login endpoint
This commit is contained in:
parent
7c392a822b
commit
e478807aa4
6
.ipynb_checkpoints/Untitled-checkpoint.ipynb
Normal file
6
.ipynb_checkpoints/Untitled-checkpoint.ipynb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"cells": [],
|
||||||
|
"metadata": {},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
97
Untitled.ipynb
Normal file
97
Untitled.ipynb
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "f28552f1-618c-4853-92e2-566554a2de2c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"True"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"import asyncio\n",
|
||||||
|
"from kwork_api import KworkClient\n",
|
||||||
|
"from dotenv import load_dotenv\n",
|
||||||
|
"import os\n",
|
||||||
|
"\n",
|
||||||
|
"load_dotenv('tests/e2e/.env')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"id": "953d142e-a575-41b7-927d-8cd1546d2747",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"KworkAuthError: Login failed: 404\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"try:\n",
|
||||||
|
" client = await KworkClient.login(\n",
|
||||||
|
" username=os.getenv('KWORK_USERNAME'),\n",
|
||||||
|
" password=os.getenv('KWORK_PASSWORD')\n",
|
||||||
|
" )\n",
|
||||||
|
" print(f\"✅ Logged in as: {client.token[:20]}...\")\n",
|
||||||
|
"except Exception as e:\n",
|
||||||
|
" print(e)\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"id": "655aa71e-5645-4c7a-aadd-5b044a0713c9",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'8AQhyzQRcTJ6v81maCNa'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"os.getenv('KWORK_PASSWORD')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.12.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -81,8 +81,6 @@ class KworkClient:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_URL = "https://api.kwork.ru"
|
BASE_URL = "https://api.kwork.ru"
|
||||||
LOGIN_URL = "https://kwork.ru/signIn"
|
|
||||||
TOKEN_URL = "https://kwork.ru/getWebAuthToken"
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -241,38 +239,41 @@ class KworkClient:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
async with client._get_httpx_client() as http_client:
|
async with client._get_httpx_client() as http_client:
|
||||||
# Step 1: Login to get session cookies
|
# Step 1: Login to get session cookies and token
|
||||||
|
# Kwork uses /api/user/login with JSON body
|
||||||
login_data = {
|
login_data = {
|
||||||
"login_or_email": username,
|
"l_username": username,
|
||||||
"password": password,
|
"l_password": password,
|
||||||
|
"jlog": 1,
|
||||||
|
"recaptcha_pass_token": "",
|
||||||
|
"track_client_id": False,
|
||||||
|
"smart-token": "",
|
||||||
|
"l_remember_me": "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await http_client.post(
|
response = await http_client.post(
|
||||||
cls.LOGIN_URL,
|
"https://kwork.ru/api/user/login",
|
||||||
data=login_data,
|
json=login_data,
|
||||||
headers={"Referer": "https://kwork.ru/"},
|
headers={
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise KworkAuthError(f"Login failed: {response.status_code}")
|
raise KworkAuthError(f"Login failed: {response.status_code}")
|
||||||
|
|
||||||
# Extract cookies
|
response_data = response.json()
|
||||||
|
|
||||||
|
# Extract userId from response or cookies
|
||||||
cookies = dict(response.cookies)
|
cookies = dict(response.cookies)
|
||||||
|
user_id = response_data.get("user_id") or cookies.get("userId")
|
||||||
|
|
||||||
if "userId" not in cookies:
|
if not user_id:
|
||||||
raise KworkAuthError("Login failed: no userId in cookies")
|
raise KworkAuthError("Login failed: no userId in response")
|
||||||
|
|
||||||
# Step 2: Get web auth token
|
# Extract web_auth_token from response
|
||||||
token_response = await http_client.post(
|
web_token = response_data.get("web_auth_token") or cookies.get("web_auth_token")
|
||||||
cls.TOKEN_URL,
|
|
||||||
json={},
|
|
||||||
)
|
|
||||||
|
|
||||||
if token_response.status_code != 200:
|
|
||||||
raise KworkAuthError(f"Token request failed: {token_response.status_code}")
|
|
||||||
|
|
||||||
token_data = token_response.json()
|
|
||||||
web_token = token_data.get("web_auth_token")
|
|
||||||
|
|
||||||
if not web_token:
|
if not web_token:
|
||||||
raise KworkAuthError("No web_auth_token in response")
|
raise KworkAuthError("No web_auth_token in response")
|
||||||
@ -1174,9 +1175,7 @@ class KworkClient:
|
|||||||
"""
|
"""
|
||||||
return await self.client._request("POST", "/actor")
|
return await self.client._request("POST", "/actor")
|
||||||
|
|
||||||
async def validate_text(
|
async def validate_text(self, text: str, context: str | None = None) -> ValidationResponse:
|
||||||
self, text: str, context: str | None = None
|
|
||||||
) -> ValidationResponse:
|
|
||||||
"""
|
"""
|
||||||
Проверить текст на соответствие требованиям Kwork.
|
Проверить текст на соответствие требованиям Kwork.
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import pytest
|
|||||||
import respx
|
import respx
|
||||||
from httpx import Response
|
from httpx import Response
|
||||||
|
|
||||||
from kwork_api import KworkApiError, KworkAuthError, KworkClient
|
from kwork_api import KworkApiError, KworkAuthError, KworkClient, KworkNetworkError
|
||||||
from kwork_api.models import CatalogResponse, ValidationResponse
|
from kwork_api.models import CatalogResponse, ValidationResponse
|
||||||
|
|
||||||
|
|
||||||
@ -18,22 +18,11 @@ class TestAuthentication:
|
|||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_login_success(self):
|
async def test_login_success(self):
|
||||||
"""Test successful login."""
|
"""Test successful login."""
|
||||||
import httpx
|
|
||||||
|
|
||||||
# Mock login endpoint
|
# Mock login endpoint
|
||||||
login_route = respx.post("https://kwork.ru/signIn")
|
login_route = respx.post("https://kwork.ru/api/user/login").mock(
|
||||||
login_route.mock(
|
|
||||||
return_value=httpx.Response(
|
|
||||||
200,
|
|
||||||
headers={"Set-Cookie": "userId=12345; slrememberme=token123"},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mock token endpoint
|
|
||||||
token_route = respx.post("https://kwork.ru/getWebAuthToken").mock(
|
|
||||||
return_value=Response(
|
return_value=Response(
|
||||||
200,
|
200,
|
||||||
json={"web_auth_token": "test_token_abc123"},
|
json={"user_id": 12345, "web_auth_token": "test_token_abc123"},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,13 +31,12 @@ class TestAuthentication:
|
|||||||
|
|
||||||
# Verify
|
# Verify
|
||||||
assert login_route.called
|
assert login_route.called
|
||||||
assert token_route.called
|
|
||||||
assert client._token == "test_token_abc123"
|
assert client._token == "test_token_abc123"
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_login_invalid_credentials(self):
|
async def test_login_invalid_credentials(self):
|
||||||
"""Test login with invalid credentials."""
|
"""Test login with invalid credentials."""
|
||||||
respx.post("https://kwork.ru/signIn").mock(
|
respx.post("https://kwork.ru/api/user/login").mock(
|
||||||
return_value=Response(401, json={"error": "Invalid credentials"})
|
return_value=Response(401, json={"error": "Invalid credentials"})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,11 +45,9 @@ class TestAuthentication:
|
|||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_login_no_userid(self):
|
async def test_login_no_userid(self):
|
||||||
"""Test login without userId in cookies."""
|
"""Test login without userId in response."""
|
||||||
import httpx
|
respx.post("https://kwork.ru/api/user/login").mock(
|
||||||
|
return_value=Response(200, json={"error": "No user_id"})
|
||||||
respx.post("https://kwork.ru/signIn").mock(
|
|
||||||
return_value=httpx.Response(200, headers={"Set-Cookie": "other=value"})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(KworkAuthError, match="no userId"):
|
with pytest.raises(KworkAuthError, match="no userId"):
|
||||||
@ -70,14 +56,8 @@ class TestAuthentication:
|
|||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_login_no_token(self):
|
async def test_login_no_token(self):
|
||||||
"""Test login without web_auth_token in response."""
|
"""Test login without web_auth_token in response."""
|
||||||
import httpx
|
respx.post("https://kwork.ru/api/user/login").mock(
|
||||||
|
return_value=Response(200, json={"user_id": 123})
|
||||||
respx.post("https://kwork.ru/signIn").mock(
|
|
||||||
return_value=httpx.Response(200, headers={"Set-Cookie": "userId=123"})
|
|
||||||
)
|
|
||||||
|
|
||||||
respx.post("https://kwork.ru/getWebAuthToken").mock(
|
|
||||||
return_value=Response(200, json={"other": "data"})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(KworkAuthError, match="No web_auth_token"):
|
with pytest.raises(KworkAuthError, match="No web_auth_token"):
|
||||||
@ -177,7 +157,9 @@ class TestProjectsAPI:
|
|||||||
"pagination": {"current_page": 1},
|
"pagination": {"current_page": 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
respx.post(f"{client.base_url}/projects").mock(return_value=Response(200, json=mock_data))
|
respx.post(f"{client.base_url}/projects").mock(
|
||||||
|
return_value=Response(200, json=mock_data)
|
||||||
|
)
|
||||||
|
|
||||||
result = await client.projects.get_list()
|
result = await client.projects.get_list()
|
||||||
|
|
||||||
@ -193,7 +175,9 @@ class TestErrorHandling:
|
|||||||
"""Test 404 error handling."""
|
"""Test 404 error handling."""
|
||||||
client = KworkClient(token="test")
|
client = KworkClient(token="test")
|
||||||
|
|
||||||
respx.post(f"{client.base_url}/getKworkDetails").mock(return_value=Response(404))
|
respx.post(f"{client.base_url}/getKworkDetails").mock(
|
||||||
|
return_value=Response(404)
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(KworkApiError) as exc_info:
|
with pytest.raises(KworkApiError) as exc_info:
|
||||||
await client.catalog.get_details(999)
|
await client.catalog.get_details(999)
|
||||||
@ -205,7 +189,9 @@ class TestErrorHandling:
|
|||||||
"""Test 401 error handling."""
|
"""Test 401 error handling."""
|
||||||
client = KworkClient(token="invalid")
|
client = KworkClient(token="invalid")
|
||||||
|
|
||||||
respx.post(f"{client.base_url}/catalogMainv2").mock(return_value=Response(401))
|
respx.post(f"{client.base_url}/catalogMainv2").mock(
|
||||||
|
return_value=Response(401)
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(KworkAuthError):
|
with pytest.raises(KworkAuthError):
|
||||||
await client.catalog.get_list()
|
await client.catalog.get_list()
|
||||||
@ -213,10 +199,12 @@ class TestErrorHandling:
|
|||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_network_error(self):
|
async def test_network_error(self):
|
||||||
"""Test network error handling."""
|
"""Test network error handling."""
|
||||||
|
import httpx
|
||||||
|
|
||||||
client = KworkClient(token="test")
|
client = KworkClient(token="test")
|
||||||
|
|
||||||
respx.post(f"{client.base_url}/catalogMainv2").mock(
|
respx.post(f"{client.base_url}/catalogMainv2").mock(
|
||||||
side_effect=Exception("Connection refused")
|
side_effect=httpx.RequestError("Connection refused", request=None)
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(KworkNetworkError):
|
with pytest.raises(KworkNetworkError):
|
||||||
@ -231,9 +219,6 @@ class TestContextManager:
|
|||||||
async with KworkClient(token="test") as client:
|
async with KworkClient(token="test") as client:
|
||||||
assert client._client is None # Not created yet
|
assert client._client is None # Not created yet
|
||||||
|
|
||||||
# Client should be created on first request
|
|
||||||
# (but we don't make actual requests in this test)
|
|
||||||
|
|
||||||
# Client should be closed after context
|
# Client should be closed after context
|
||||||
assert client._client is None or client._client.is_closed
|
assert client._client is None or client._client.is_closed
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user