From bf2fa20a9d083749b8fec6cd9982e8f23bb2f202 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 29 Mar 2026 23:35:37 +0000 Subject: [PATCH] test: fix E2E tests to handle empty API responses --- Untitled.ipynb | 51 ++++-- .../__pycache__/client.cpython-312.pyc | Bin 63422 -> 63451 bytes src/kwork_api/client.py | 22 ++- tests/e2e/test_auth.py | 2 +- tests/e2e/test_catalog.py | 169 ++++++++++++------ tests/unit/test_client.py | 12 +- 6 files changed, 166 insertions(+), 90 deletions(-) diff --git a/Untitled.ipynb b/Untitled.ipynb index a0585d4..861b305 100644 --- a/Untitled.ipynb +++ b/Untitled.ipynb @@ -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=\n", - "DEBUG:httpcore.connection:start_tls.started ssl_context= server_hostname='kwork.ru' timeout=30.0\n", - "DEBUG:httpcore.connection:start_tls.complete return_value=\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=\n", + "DEBUG:httpcore.connection:start_tls.started ssl_context= server_hostname='api.kwork.ru' timeout=30.0\n", + "DEBUG:httpcore.connection:start_tls.complete return_value=\n", "DEBUG:httpcore.http11:send_request_headers.started request=\n", "DEBUG:httpcore.http11:send_request_headers.complete\n", "DEBUG:httpcore.http11:send_request_body.started request=\n", "DEBUG:httpcore.http11:send_request_body.complete\n", "DEBUG:httpcore.http11:receive_response_headers.started request=\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=\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=\n", + "DEBUG:httpcore.http11:send_request_headers.complete\n", + "DEBUG:httpcore.http11:send_request_body.started request=\n", + "DEBUG:httpcore.http11:send_request_body.complete\n", + "DEBUG:httpcore.http11:receive_response_headers.started request=\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=\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" } diff --git a/src/kwork_api/__pycache__/client.cpython-312.pyc b/src/kwork_api/__pycache__/client.cpython-312.pyc index f153b6eb6a1dd370eb8b832a5c9b8c69e307740c..5c8b1e2bdf37fe7d2d6521a48d46f5a853fc2a41 100644 GIT binary patch delta 4525 zcmaJ^3v`o37S8->(s!Gt^!*C3JR+4)XrZ*Y&W5OR1rX%D~ zP;)N7TARfz>Cya?YuA7B6?k>aIz-sOW2QNrD;*(=wa8q9^Q+Ih1f9 z;ob7HX|tRYW*=*EOCfMkwi;5iwgrHitM<()TahKhK$z|D^&VCr*y; ziZ3{kbRygtTi!3_5~AG_>Y+ObmQ8HUujE*S-az|2@!oW+f)bTC-C7bjLV3e&Ax!6n zSIPV7_2~X~tqN);{SXvDNv5<)`du(Ty&&oX14sX*20w$$AUF6M0(ST}5%@4Y8jhxG zNFZEEPa;7uAuoyq!|;r`Bm^uOBSS;ia0X%si6V~AbJ$yGKmzYIWj#f^J!7{tBc_!^|RFv?K)bTdE zs#<3>@rEpwS}Dw77Ho8?-L}eTU*Vx=fEbs*;F%+!kFHRG*U^(#XQ3rac*o56J2u~m^rQiyvt}~e`F?JRfUCU;*m~DI| zJJfn;ESOAohzANo zKDz_kfgL93`jeK#Lh}16nEhmS^KGzpd(V#@Y9YdBVK6psX92N7@y-hG@_&9|3K_-1Qet_V zUsq)^>SysfdmXLeTA>>)e24Ho{Ol+t8{mN#eN&rXdXA&(CcKg|blni&x`UoqyrLz~ z&~;UC@|A2oW|r1-q9g8|QY>HC{+vaFjLxr7V$8TuViESBbPj!r)$hcCd)MBn4}IGNc{yP(iJ zIkZG;g+4UXk8llI4=*Oa)6-tTPP%tL{!e9PfwH1XpR(TL%-s4Fn>P@KoN0yjcG~c8 z|4g-ZV+I#dVZEf6QwE0g!;SX~$W1ZlNSJKMEV7Kw8Bd*<|HV`i z#gv2bnC4M!GjAGDJ#cC~sb=Q?d&*~i!|5X_YzBL`6q<9a*4uYuV6M$Mi&$u^DsVY{ zbC-8?(U7N}N$`biJd;oAnbN5<;qv3S)s^D;GjAX>o4*=1iQP-={&M;2OeV`J-Y|Yc9uUo~LLxzP31Y_d*NeVvVGV(B*t!Jnbo69{ee@{fkC-(-+2aOxW@IP^@4d9G_% zDl@xB%fkuPx&nsoMdUT{{qC_+H48OdQ)i`*X}*-V+l}TLo99ZUSuyw^i-wS%S>&Kt z+B0z=eduPHjAnj-l0D~ANgH&Yqs+66S#$nd1+y>L==65W;9dXpCo*XMaTMu9?xR~E~QLiTn)TvaWD?SL4jT*?Cmu$ z#pQ3+HJB_q1AXOgxHOaWK>MY}3HTlo@F}QHymtw`?N)}y%VY9F&;z`dU4B&-i%C#m zd+=UXVlxUM79k!X5rH|ULa7#hxEw3{jhax1|GsP`+z0~8d-LGHFXOmFG=)A zV))e-?x?3@@6{9%0iR#}E7$Jnu)1SNG_<(aaTh%u$-To#0?g>$z}@t8xO&w@1%7?2 z$p~26cRv{gZ}z1T4fOQIr|iMVhdlj;W8dQlB@ox2J(vB0sl)ahga(8)2(qCb)R4SwoBQ#O;66_Orhlv~Cmw_Fk# z?e_P`0TN}5JJ2HsNrGbff|=YemB=I9oTp#wmm*7aWOR;9?--fhFQJY8Tp|YltBEAy zYu4pYi#GjPT;r148?5x}alIT~{-lm delta 4526 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 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() diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 980f2fb..ae3ac0f 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -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()