test: fix E2E tests to handle empty API responses
This commit is contained in:
parent
e985e03ddb
commit
bf2fa20a9d
@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 2,
|
||||
"id": "f28552f1-618c-4853-92e2-566554a2de2c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@ -12,13 +12,14 @@
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import asyncio\n",
|
||||
"import logging\n",
|
||||
"from kwork_api import KworkClient\n",
|
||||
"from dotenv import load_dotenv\n",
|
||||
"import os\n",
|
||||
@ -30,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 3,
|
||||
"id": "953d142e-a575-41b7-927d-8cd1546d2747",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@ -38,28 +39,40 @@
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"INFO:kwork_api.client:Login request: POST https://kwork.ru/api/user/login (user: JTJagOmega)\n",
|
||||
"DEBUG:kwork_api.client:Login payload: {'l_username': 'JTJagOmega', 'l_password': '8AQhyzQRcTJ6v81maCNa', 'jlog': 1, 'recaptcha_pass_token': '', 'track_client_id': False, 'smart-token': '', 'l_remember_me': '1'}\n",
|
||||
"DEBUG:httpcore.connection:connect_tcp.started host='kwork.ru' port=443 local_address=None timeout=30.0 socket_options=None\n",
|
||||
"DEBUG:httpcore.connection:connect_tcp.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x723917c9fa70>\n",
|
||||
"DEBUG:httpcore.connection:start_tls.started ssl_context=<ssl.SSLContext object at 0x723917df30d0> server_hostname='kwork.ru' timeout=30.0\n",
|
||||
"DEBUG:httpcore.connection:start_tls.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x723924105d30>\n",
|
||||
"INFO:kwork_api.client:Login request: POST https://api.kwork.ru/signIn (user: JTJagOmega)\n",
|
||||
"DEBUG:kwork_api.client:Login payload: {'login': 'JTJagOmega', 'password': '8AQhyzQRcTJ6v81maCNa', 'uad': '', 'device': ''}\n",
|
||||
"DEBUG:httpcore.connection:connect_tcp.started host='api.kwork.ru' port=443 local_address=None timeout=30.0 socket_options=None\n",
|
||||
"DEBUG:httpcore.connection:connect_tcp.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x75bea599dd00>\n",
|
||||
"DEBUG:httpcore.connection:start_tls.started ssl_context=<ssl.SSLContext object at 0x75bea597e0d0> server_hostname='api.kwork.ru' timeout=30.0\n",
|
||||
"DEBUG:httpcore.connection:start_tls.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x75bec0323a10>\n",
|
||||
"DEBUG:httpcore.http11:send_request_headers.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:send_request_headers.complete\n",
|
||||
"DEBUG:httpcore.http11:send_request_body.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:send_request_body.complete\n",
|
||||
"DEBUG:httpcore.http11:receive_response_headers.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Server', b'QRATOR'), (b'Date', b'Sun, 29 Mar 2026 22:22:41 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Keep-Alive', b'timeout=15'), (b'Vary', b'Accept-Encoding, User-Agent'), (b'Content-Security-Policy', b\"frame-ancestors 'self' https://webvisor.com https://awards.ratingruneta.ru\"), (b'Set-Cookie', b'referrer_url=https%3A%2F%2Fkwork.ru%2F; expires=Sun, 05-Apr-2026 22:22:41 GMT; Max-Age=604800; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'uad=1884597369c9a63194ed0624319983; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'RORSSQIHEK=f15239f2927f4fd08e6945c15ed635c2; expires=Wed, 01-Apr-2026 22:22:41 GMT; Max-Age=259200; path=/; secure; HttpOnly; SameSite=None'), (b'Expires', b'Thu, 19 Nov 1981 08:52:00 GMT'), (b'Cache-Control', b'no-store, no-cache, must-revalidate'), (b'Pragma', b'no-cache'), (b'Set-Cookie', b'csrf_user_token=43ed1b44d6a5a480418b39929da62605; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'userId=18845973; expires=Wed, 26-Mar-2036 22:22:41 GMT; Max-Age=315360000; path=/; secure; SameSite=None'), (b'Set-Cookie', b'slrememberme=18845973_%242y%2410%24GEnC83HAU.ejn2CQB3OMTewWzSYxC0NYcSB3n2ck6eNvcz2aStK0W; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'_kmid=7fb7f3a407728e8d0ffa5ab4d19ff2b6; expires=Wed, 26-Mar-2036 22:22:41 GMT; Max-Age=315360000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'_kmfvt=1774822961; expires=Wed, 26-Mar-2036 22:22:41 GMT; Max-Age=315360000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'csrf_user_token=515cb2f621700da0faf4c3da66efbbfb; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Cache-Control', b'no-cache, private'), (b'Strict-Transport-Security', b'max-age=15552000'), (b'X-Content-Type-Options', b'nosniff'), (b'Content-Encoding', b'gzip')])\n",
|
||||
"INFO:httpx:HTTP Request: POST https://kwork.ru/api/user/login \"HTTP/1.1 200 OK\"\n",
|
||||
"DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Server', b'QRATOR'), (b'Date', b'Sun, 29 Mar 2026 23:32:52 GMT'), (b'Content-Type', b'application/json; charset=utf-8'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Keep-Alive', b'timeout=15'), (b'Vary', b'Accept-Encoding, User-Agent'), (b'Content-Security-Policy', b\"frame-ancestors 'self' https://webvisor.com https://awards.ratingruneta.ru\"), (b'Set-Cookie', b'RORSSQIHEK=a4fe3ce9aadf6c71d101f2914ddc4594; expires=Wed, 01-Apr-2026 23:32:52 GMT; Max-Age=259200; path=/; secure; HttpOnly; SameSite=None'), (b'Expires', b'Thu, 19 Nov 1981 08:52:00 GMT'), (b'Cache-Control', b'no-store, no-cache, must-revalidate'), (b'Pragma', b'no-cache'), (b'Set-Cookie', b'csrf_user_token=fd9c64e5301c952c692d6b60e0693d05; expires=Mon, 29-Mar-2027 23:32:52 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'userId=18845973; expires=Wed, 26-Mar-2036 23:32:52 GMT; Max-Age=315360000; path=/; secure; SameSite=None'), (b'Set-Cookie', b'slrememberme=18845973_%242y%2410%24tbgXEdroo8Dg3L6TjfGfDe6tmbPkwd1.aU4lMgEODFpxNTqKlr3v6; expires=Mon, 29-Mar-2027 23:32:52 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Strict-Transport-Security', b'max-age=15552000'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'Content-Encoding', b'gzip')])\n",
|
||||
"INFO:httpx:HTTP Request: POST https://api.kwork.ru/signIn \"HTTP/1.1 200 OK\"\n",
|
||||
"DEBUG:httpcore.http11:receive_response_body.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:receive_response_body.complete\n",
|
||||
"DEBUG:httpcore.http11:response_closed.started\n",
|
||||
"DEBUG:httpcore.http11:response_closed.complete\n",
|
||||
"DEBUG:kwork_api.client:Login response status: 200\n",
|
||||
"DEBUG:kwork_api.client:Login response headers: {'server': 'QRATOR', 'date': 'Sun, 29 Mar 2026 22:22:41 GMT', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'connection': 'keep-alive', 'keep-alive': 'timeout=15', 'vary': 'Accept-Encoding, User-Agent', 'content-security-policy': \"frame-ancestors 'self' https://webvisor.com https://awards.ratingruneta.ru\", 'set-cookie': 'referrer_url=https%3A%2F%2Fkwork.ru%2F; expires=Sun, 05-Apr-2026 22:22:41 GMT; Max-Age=604800; path=/; secure; HttpOnly; SameSite=None, uad=1884597369c9a63194ed0624319983; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None, RORSSQIHEK=f15239f2927f4fd08e6945c15ed635c2; expires=Wed, 01-Apr-2026 22:22:41 GMT; Max-Age=259200; path=/; secure; HttpOnly; SameSite=None, csrf_user_token=43ed1b44d6a5a480418b39929da62605; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None, userId=18845973; expires=Wed, 26-Mar-2036 22:22:41 GMT; Max-Age=315360000; path=/; secure; SameSite=None, slrememberme=18845973_%242y%2410%24GEnC83HAU.ejn2CQB3OMTewWzSYxC0NYcSB3n2ck6eNvcz2aStK0W; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None, _kmid=7fb7f3a407728e8d0ffa5ab4d19ff2b6; expires=Wed, 26-Mar-2036 22:22:41 GMT; Max-Age=315360000; path=/; secure; HttpOnly; SameSite=None, _kmfvt=1774822961; expires=Wed, 26-Mar-2036 22:22:41 GMT; Max-Age=315360000; path=/; secure; HttpOnly; SameSite=None, csrf_user_token=515cb2f621700da0faf4c3da66efbbfb; expires=Mon, 29-Mar-2027 22:22:41 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None', 'expires': 'Thu, 19 Nov 1981 08:52:00 GMT', 'cache-control': 'no-store, no-cache, must-revalidate, no-cache, private', 'pragma': 'no-cache', 'strict-transport-security': 'max-age=15552000', 'x-content-type-options': 'nosniff', 'content-encoding': 'gzip'}\n",
|
||||
"INFO:kwork_api.client:Login successful: user_id=18845973, csrf_token=515cb2f621700da0faf4\n",
|
||||
"DEBUG:kwork_api.client:Login response data: {'success': True, 'error': '', 'redirect': '', 'action_after': '', 'isUserVerified': True, 'need_2fa': False, 'csrftoken': '515cb2f621700da0faf4c3da66efbbfb'}\n",
|
||||
"DEBUG:kwork_api.client:Login cookies: ['referrer_url', 'uad', 'RORSSQIHEK', 'csrf_user_token', 'userId', 'slrememberme', '_kmid', '_kmfvt']\n",
|
||||
"DEBUG:kwork_api.client:Login response headers: {'server': 'QRATOR', 'date': 'Sun, 29 Mar 2026 23:32:52 GMT', 'content-type': 'application/json; charset=utf-8', 'transfer-encoding': 'chunked', 'connection': 'keep-alive', 'keep-alive': 'timeout=15', 'vary': 'Accept-Encoding, User-Agent', 'content-security-policy': \"frame-ancestors 'self' https://webvisor.com https://awards.ratingruneta.ru\", 'set-cookie': 'RORSSQIHEK=a4fe3ce9aadf6c71d101f2914ddc4594; expires=Wed, 01-Apr-2026 23:32:52 GMT; Max-Age=259200; path=/; secure; HttpOnly; SameSite=None, csrf_user_token=fd9c64e5301c952c692d6b60e0693d05; expires=Mon, 29-Mar-2027 23:32:52 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None, userId=18845973; expires=Wed, 26-Mar-2036 23:32:52 GMT; Max-Age=315360000; path=/; secure; SameSite=None, slrememberme=18845973_%242y%2410%24tbgXEdroo8Dg3L6TjfGfDe6tmbPkwd1.aU4lMgEODFpxNTqKlr3v6; expires=Mon, 29-Mar-2027 23:32:52 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None', 'expires': 'Thu, 19 Nov 1981 08:52:00 GMT', 'cache-control': 'no-store, no-cache, must-revalidate', 'pragma': 'no-cache', 'strict-transport-security': 'max-age=15552000', 'x-frame-options': 'DENY', 'x-content-type-options': 'nosniff', 'content-encoding': 'gzip'}\n",
|
||||
"INFO:kwork_api.client:Login successful: user_id=18845973, csrf_token=N/A\n",
|
||||
"DEBUG:kwork_api.client:Login response data: {'success': True, 'response': {'token': '48e75666c8b5c3ffa97f5ae4bfa6ecfa', 'expired': 31536000, 'need_2fa': False}}\n",
|
||||
"DEBUG:kwork_api.client:Login cookies: ['RORSSQIHEK', 'csrf_user_token', 'userId', 'slrememberme']\n",
|
||||
"DEBUG:httpcore.http11:send_request_headers.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:send_request_headers.complete\n",
|
||||
"DEBUG:httpcore.http11:send_request_body.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:send_request_body.complete\n",
|
||||
"DEBUG:httpcore.http11:receive_response_headers.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Server', b'QRATOR'), (b'Date', b'Sun, 29 Mar 2026 23:32:52 GMT'), (b'Content-Type', b'application/json; charset=utf-8'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Keep-Alive', b'timeout=15'), (b'Vary', b'Accept-Encoding, User-Agent'), (b'Content-Security-Policy', b\"frame-ancestors 'self' https://webvisor.com https://awards.ratingruneta.ru\"), (b'Expires', b'Thu, 19 Nov 1981 08:52:00 GMT'), (b'Cache-Control', b'no-store, no-cache, must-revalidate'), (b'Pragma', b'no-cache'), (b'Set-Cookie', b'slrememberme=18845973_%242y%2410%24Kn3Qr%2FBaJ24e5CWBpUvbBuMP38SflcAdTUtmlW1XXVEJFDVbOmO96; expires=Mon, 29-Mar-2027 23:32:52 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'uad=1884597369c9b6a47efb8415191577; expires=Mon, 29-Mar-2027 23:32:52 GMT; Max-Age=31536000; path=/; secure; HttpOnly; SameSite=None'), (b'Set-Cookie', b'mobile_token=48e75666c8b5c3ffa97f5ae4bfa6ecfa; expires=Sun, 29-Mar-2026 23:33:52 GMT; Max-Age=60; path=/; secure; HttpOnly; SameSite=None'), (b'Strict-Transport-Security', b'max-age=15552000'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'Content-Encoding', b'gzip')])\n",
|
||||
"INFO:httpx:HTTP Request: POST https://api.kwork.ru/getWebAuthToken \"HTTP/1.1 200 OK\"\n",
|
||||
"DEBUG:httpcore.http11:receive_response_body.started request=<Request [b'POST']>\n",
|
||||
"DEBUG:httpcore.http11:receive_response_body.complete\n",
|
||||
"DEBUG:httpcore.http11:response_closed.started\n",
|
||||
"DEBUG:httpcore.http11:response_closed.complete\n",
|
||||
"INFO:kwork_api.client:Got web_auth_token: DKZL5BjWmWo75GQmmCus...\n",
|
||||
"DEBUG:httpcore.connection:close.started\n",
|
||||
"DEBUG:httpcore.connection:close.complete\n"
|
||||
]
|
||||
@ -68,7 +81,7 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"✅ Logged in as: 18845973_%242y%2410%...\n"
|
||||
"✅ Logged in as: DKZL5BjWmWo75GQmmCus...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -85,17 +98,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 4,
|
||||
"id": "655aa71e-5645-4c7a-aadd-5b044a0713c9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'18845973_%242y%2410%24GEnC83HAU.ejn2CQB3OMTewWzSYxC0NYcSB3n2ck6eNvcz2aStK0W'"
|
||||
"'DKZL5BjWmWo75GQmmCusW48U3K1ZL9YUWVGs4oGnvVX5xYPYVrdzP1L6b0ko'"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -267,13 +267,17 @@ class KworkClient:
|
||||
logger.debug(f"Login response headers: {dict(response.headers)}")
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Login failed with status {response.status_code}: {response.text[:200]}")
|
||||
logger.error(
|
||||
f"Login failed with status {response.status_code}: {response.text[:200]}"
|
||||
)
|
||||
raise KworkAuthError(f"Login failed: {response.status_code}")
|
||||
|
||||
response_data = response.json()
|
||||
cookies = dict(response.cookies)
|
||||
|
||||
logger.info(f"Login successful: user_id={cookies.get('userId')}, csrf_token={response_data.get('csrftoken', 'N/A')[:20] if response_data.get('csrftoken') else 'N/A'}")
|
||||
logger.info(
|
||||
f"Login successful: user_id={cookies.get('userId')}, csrf_token={response_data.get('csrftoken', 'N/A')[:20] if response_data.get('csrftoken') else 'N/A'}"
|
||||
)
|
||||
logger.debug(f"Login response data: {response_data}")
|
||||
logger.debug(f"Login cookies: {list(cookies.keys())}")
|
||||
|
||||
@ -281,7 +285,9 @@ class KworkClient:
|
||||
user_id = cookies.get("userId")
|
||||
|
||||
if not user_id:
|
||||
raise KworkAuthError(f"Login failed: no userId in cookies. Response: {response_data}")
|
||||
raise KworkAuthError(
|
||||
f"Login failed: no userId in cookies. Response: {response_data}"
|
||||
)
|
||||
|
||||
# HAR: getWebAuthToken endpoint for API token (same headers as signIn)
|
||||
token_response = await http_client.post(
|
||||
@ -294,19 +300,19 @@ class KworkClient:
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
if token_response.status_code != 200:
|
||||
raise KworkAuthError(f"Token request failed: {token_response.status_code}")
|
||||
|
||||
|
||||
token_data = token_response.json()
|
||||
# HAR shows: {"success":true,"response":{"token":"...", "expires_at":..., "url":...}}
|
||||
web_token = token_data.get("response", {}).get("token")
|
||||
|
||||
|
||||
if not web_token:
|
||||
raise KworkAuthError(f"No token in response: {token_data}")
|
||||
|
||||
|
||||
logger.info(f"Got web_auth_token: {web_token[:20]}...")
|
||||
|
||||
|
||||
# Create new client with token
|
||||
return cls(token=web_token, cookies=cookies, timeout=timeout)
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ async def test_login_invalid_credentials():
|
||||
@pytest.mark.e2e
|
||||
async def test_restore_session(require_credentials):
|
||||
"""E2E: Восстановление сессии из token.
|
||||
|
||||
|
||||
HAR shows: POST /user endpoint works with proper auth token.
|
||||
"""
|
||||
# First login
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
E2E тесты для каталога и проектов.
|
||||
|
||||
Все тесты read-only - ничего не изменяют на сервере.
|
||||
Endpoints основаны на анализе HAR файла.
|
||||
Endpoints основаны на HAR анализе (mitmproxy + har-analyzer skill).
|
||||
"""
|
||||
|
||||
import pytest
|
||||
@ -10,11 +10,69 @@ import pytest
|
||||
from kwork_api import KworkClient
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_get_catalog_list(require_credentials):
|
||||
"""E2E: Получить список кворков из каталога.
|
||||
|
||||
HAR: POST https://api.kwork.ru/catalogMainv2
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
password=require_credentials["password"],
|
||||
)
|
||||
|
||||
try:
|
||||
# Первая страница каталога
|
||||
catalog = await client.catalog.get_list(page=1)
|
||||
|
||||
assert catalog is not None
|
||||
# API может вернуть пустой список (это нормально)
|
||||
if len(catalog.kworks) > 0:
|
||||
# Проверка структуры первого кворка
|
||||
first_kwork = catalog.kworks[0]
|
||||
assert first_kwork.id is not None
|
||||
assert first_kwork.title is not None
|
||||
assert first_kwork.price is not None
|
||||
|
||||
# Пагинация
|
||||
if catalog.pagination:
|
||||
assert catalog.pagination.current_page >= 1
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_get_kwork_details(require_credentials):
|
||||
"""E2E: Получить детали кворка.
|
||||
|
||||
HAR: POST https://api.kwork.ru/getKworkDetails
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
password=require_credentials["password"],
|
||||
)
|
||||
|
||||
try:
|
||||
# HAR: используем известный ID кворка для теста
|
||||
# В реальном использовании можно получить ID из каталога
|
||||
kwork_id = 1 # Тестовый ID
|
||||
|
||||
# Получаем детали
|
||||
details = await client.catalog.get_details(kwork_id)
|
||||
|
||||
assert details is not None
|
||||
assert details.id == kwork_id
|
||||
assert details.title is not None
|
||||
assert details.price is not None
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_get_projects_list(require_credentials):
|
||||
"""E2E: Получить список проектов с биржи.
|
||||
|
||||
Endpoint: GET https://kwork.ru/projects
|
||||
HAR: POST https://api.kwork.ru/projects
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
@ -22,19 +80,23 @@ async def test_get_projects_list(require_credentials):
|
||||
)
|
||||
|
||||
try:
|
||||
# Note: Это может возвращать HTML страницу, не JSON API
|
||||
# Пока просто проверяем что запрос работает
|
||||
# В будущем нужно реализовать парсинг HTML или найти JSON API endpoint
|
||||
assert client is not None
|
||||
projects = await client.projects.get_list(page=1)
|
||||
|
||||
assert projects is not None
|
||||
# Проекты могут быть пустыми
|
||||
if len(projects.projects) > 0:
|
||||
first_project = projects.projects[0]
|
||||
assert first_project.id is not None
|
||||
assert first_project.title is not None
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_get_categories(require_credentials):
|
||||
"""E2E: Получить категорию.
|
||||
async def test_get_user_info(require_credentials):
|
||||
"""E2E: Получить информацию о текущем пользователе.
|
||||
|
||||
Endpoint: GET https://kwork.ru/categories/{slug}
|
||||
HAR: POST https://api.kwork.ru/user
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
@ -42,18 +104,23 @@ async def test_get_categories(require_credentials):
|
||||
)
|
||||
|
||||
try:
|
||||
# Note: Это возвращает HTML страницу категории
|
||||
# Пока просто проверяем что запрос работает
|
||||
assert client is not None
|
||||
user = await client.user.get_info()
|
||||
assert user is not None
|
||||
# API возвращает dict с данными пользователя
|
||||
assert isinstance(user, dict)
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_get_user_profile(require_credentials):
|
||||
"""E2E: Получить профиль пользователя.
|
||||
async def test_get_reference_data(require_credentials):
|
||||
"""E2E: Получить справочные данные (города, страны, фичи).
|
||||
|
||||
Endpoint: GET https://kwork.ru/user/{username}
|
||||
HAR endpoints:
|
||||
- POST https://api.kwork.ru/cities
|
||||
- POST https://api.kwork.ru/countries
|
||||
- POST https://api.kwork.ru/getAvailableFeatures
|
||||
- POST https://api.kwork.ru/getBadgesInfo
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
@ -61,18 +128,30 @@ async def test_get_user_profile(require_credentials):
|
||||
)
|
||||
|
||||
try:
|
||||
# Note: Это возвращает HTML страницу профиля
|
||||
# Пока просто проверяем что запрос работает
|
||||
assert client is not None
|
||||
# Города
|
||||
cities = await client.reference.get_cities()
|
||||
assert isinstance(cities, list)
|
||||
|
||||
# Страны (может вернуть пустой список)
|
||||
countries = await client.reference.get_countries()
|
||||
assert isinstance(countries, list)
|
||||
|
||||
# Фичи
|
||||
features = await client.reference.get_features()
|
||||
assert isinstance(features, list)
|
||||
|
||||
# Бейджи
|
||||
badges = await client.reference.get_badges_info()
|
||||
assert isinstance(badges, list)
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_api_checknotify(require_credentials):
|
||||
"""E2E: Проверить уведомления.
|
||||
async def test_get_notifications(require_credentials):
|
||||
"""E2E: Получить уведомления.
|
||||
|
||||
Endpoint: POST https://kwork.ru/api/user/checknotify
|
||||
HAR: POST https://api.kwork.ru/notifications
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
@ -80,18 +159,21 @@ async def test_api_checknotify(require_credentials):
|
||||
)
|
||||
|
||||
try:
|
||||
# Note: Нужно реализовать endpoint в client.py
|
||||
# Пока просто проверяем что логин работает
|
||||
assert client.token is not None
|
||||
notifications = await client.notifications.get_list()
|
||||
assert notifications is not None
|
||||
# Уведомления могут быть пустыми
|
||||
assert hasattr(notifications, 'notifications')
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_api_addview(require_credentials):
|
||||
"""E2E: Добавить просмотр (read-only операция).
|
||||
async def test_get_user_orders(require_credentials):
|
||||
"""E2E: Получить заказы пользователя.
|
||||
|
||||
Endpoint: POST https://kwork.ru/api/offer/addview
|
||||
HAR endpoints:
|
||||
- POST https://api.kwork.ru/payerOrders
|
||||
- POST https://api.kwork.ru/workerOrders
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
@ -99,31 +181,12 @@ async def test_api_addview(require_credentials):
|
||||
)
|
||||
|
||||
try:
|
||||
# Note: Нужно реализовать endpoint в client.py
|
||||
# Пока просто проверяем что логин работает
|
||||
assert client.token is not None
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
async def test_get_reviews(require_credentials):
|
||||
"""E2E: Получить отзывы пользователя.
|
||||
|
||||
Endpoint: POST https://kwork.ru/user/get_reviews
|
||||
|
||||
HAR shows:
|
||||
POST https://kwork.ru/user/get_reviews
|
||||
{"userId":126921,"type":"positive"}
|
||||
"""
|
||||
client = await KworkClient.login(
|
||||
username=require_credentials["username"],
|
||||
password=require_credentials["password"],
|
||||
)
|
||||
|
||||
try:
|
||||
# Note: Нужно реализовать endpoint в client.py с правильным путём
|
||||
# Пока просто проверяем что логин работает
|
||||
assert client.token is not None
|
||||
# Заказы как заказчик
|
||||
payer_orders = await client.projects.get_payer_orders()
|
||||
assert isinstance(payer_orders, list)
|
||||
|
||||
# Заказы как исполнитель
|
||||
worker_orders = await client.projects.get_worker_orders()
|
||||
assert isinstance(worker_orders, list)
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
@ -157,9 +157,7 @@ class TestProjectsAPI:
|
||||
"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()
|
||||
|
||||
@ -175,9 +173,7 @@ class TestErrorHandling:
|
||||
"""Test 404 error handling."""
|
||||
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:
|
||||
await client.catalog.get_details(999)
|
||||
@ -189,9 +185,7 @@ class TestErrorHandling:
|
||||
"""Test 401 error handling."""
|
||||
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):
|
||||
await client.catalog.get_list()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user