fix: auto-format code and fix linter errors
Some checks failed
PR Checks / test (pull_request) Failing after 25s
PR Checks / security (pull_request) Failing after 7s

This commit is contained in:
root 2026-03-29 08:58:35 +00:00
parent b67f0e5031
commit 3995d60b6b
15 changed files with 1914 additions and 809 deletions

View File

@ -17,15 +17,23 @@ Example:
"""
from .client import KworkClient
from .errors import KworkError, KworkAuthError, KworkApiError
from .errors import (
KworkApiError,
KworkAuthError,
KworkError,
KworkNetworkError,
KworkNotFoundError,
KworkRateLimitError,
KworkValidationError,
)
from .models import (
ValidationResponse,
ValidationIssue,
CatalogResponse,
Kwork,
KworkDetails,
Project,
CatalogResponse,
ProjectsResponse,
ValidationIssue,
ValidationResponse,
)
__version__ = "0.1.0" # Updated by semantic-release
@ -34,6 +42,10 @@ __all__ = [
"KworkError",
"KworkAuthError",
"KworkApiError",
"KworkNetworkError",
"KworkNotFoundError",
"KworkRateLimitError",
"KworkValidationError",
"ValidationResponse",
"ValidationIssue",
"Kwork",

View File

@ -5,10 +5,9 @@ Main client class with authentication and all API endpoints.
"""
import logging
from typing import Any, Optional
from typing import Any
import httpx
from pydantic import HttpUrl
from .errors import (
KworkApiError,
@ -20,13 +19,10 @@ from .errors import (
KworkValidationError,
)
from .models import (
APIErrorResponse,
AuthResponse,
Badge,
CatalogResponse,
City,
Country,
DataResponse,
Dialog,
Feature,
Kwork,
@ -34,7 +30,6 @@ from .models import (
NotificationsResponse,
Project,
ProjectsResponse,
Review,
ReviewsResponse,
TimeZone,
ValidationResponse,
@ -91,10 +86,10 @@ class KworkClient:
def __init__(
self,
token: Optional[str] = None,
cookies: Optional[dict[str, str]] = None,
token: str | None = None,
cookies: dict[str, str] | None = None,
timeout: float = 30.0,
base_url: Optional[str] = None,
base_url: str | None = None,
):
"""
Инициализация клиента.
@ -130,10 +125,10 @@ class KworkClient:
self._cookies = cookies or {}
# Initialize HTTP client
self._client: Optional[httpx.AsyncClient] = None
self._client: httpx.AsyncClient | None = None
@property
def token(self) -> Optional[str]:
def token(self) -> str | None:
"""
Web auth token для аутентификации.
@ -169,7 +164,7 @@ class KworkClient:
return self._cookies.copy()
@property
def credentials(self) -> dict[str, Optional[str]]:
def credentials(self) -> dict[str, str | None]:
"""
Учётные данные для восстановления сессии.
@ -286,7 +281,7 @@ class KworkClient:
return cls(token=web_token, cookies=cookies, timeout=timeout)
except httpx.RequestError as e:
raise KworkNetworkError(f"Login request failed: {e}")
raise KworkNetworkError(f"Login request failed: {e}") from e
def _get_httpx_client(self) -> httpx.AsyncClient:
"""Get or create HTTP client with proper headers."""
@ -367,7 +362,7 @@ class KworkClient:
try:
return response.json()
except Exception as e:
raise KworkError(f"Failed to parse response: {e}")
raise KworkError(f"Failed to parse response: {e}") from e
async def _request(
self,
@ -392,7 +387,7 @@ class KworkClient:
response = await http_client.request(method, endpoint, **kwargs)
return self._handle_response(response)
except httpx.RequestError as e:
raise KworkNetworkError(f"Request failed: {e}")
raise KworkNetworkError(f"Request failed: {e}") from e
# ========== Catalog Endpoints ==========
@ -422,7 +417,7 @@ class KworkClient:
async def get_list(
self,
page: int = 1,
category_id: Optional[int] = None,
category_id: int | None = None,
sort: str = "recommend",
) -> CatalogResponse:
"""
@ -564,7 +559,7 @@ class KworkClient:
async def get_list(
self,
page: int = 1,
category_id: Optional[int] = None,
category_id: int | None = None,
) -> ProjectsResponse:
"""
Получить список проектов с биржи.
@ -689,7 +684,7 @@ class KworkClient:
async def get_reviews(
self,
user_id: Optional[int] = None,
user_id: int | None = None,
page: int = 1,
) -> ReviewsResponse:
"""
@ -1179,7 +1174,9 @@ class KworkClient:
"""
return await self.client._request("POST", "/actor")
async def validate_text(self, text: str, context: Optional[str] = None) -> ValidationResponse:
async def validate_text(
self, text: str, context: str | None = None
) -> ValidationResponse:
"""
Проверить текст на соответствие требованиям Kwork.

View File

@ -13,7 +13,7 @@
KworkNetworkError (ошибки сети)
"""
from typing import Any, Optional
from typing import Any
__all__ = [
"KworkError",
@ -43,7 +43,7 @@ class KworkError(Exception):
print(f"Ошибка: {e.message}")
"""
def __init__(self, message: str, response: Optional[Any] = None):
def __init__(self, message: str, response: Any | None = None):
self.message = message
self.response = response
super().__init__(self.message)
@ -68,7 +68,7 @@ class KworkAuthError(KworkError):
print("Неверные учётные данные")
"""
def __init__(self, message: str = "Authentication failed", response: Optional[Any] = None):
def __init__(self, message: str = "Authentication failed", response: Any | None = None):
super().__init__(message, response)
def __str__(self) -> str:
@ -94,8 +94,8 @@ class KworkApiError(KworkError):
def __init__(
self,
message: str,
status_code: Optional[int] = None,
response: Optional[Any] = None,
status_code: int | None = None,
response: Any | None = None,
):
self.status_code = status_code
super().__init__(message, response)
@ -120,7 +120,7 @@ class KworkNotFoundError(KworkApiError):
print("Кворк не найден")
"""
def __init__(self, resource: str, response: Optional[Any] = None):
def __init__(self, resource: str, response: Any | None = None):
super().__init__(f"Resource not found: {resource}", 404, response)
@ -147,8 +147,8 @@ class KworkRateLimitError(KworkApiError):
def __init__(
self,
message: str = "Rate limit exceeded",
response: Optional[Any] = None,
retry_after: Optional[int] = None,
response: Any | None = None,
retry_after: int | None = None,
):
self.retry_after = retry_after
super().__init__(message, 429, response)
@ -175,8 +175,8 @@ class KworkValidationError(KworkApiError):
def __init__(
self,
message: str = "Validation failed",
fields: Optional[dict[str, list[str]]] = None,
response: Optional[Any] = None,
fields: dict[str, list[str]] | None = None,
response: Any | None = None,
):
self.fields = fields or {}
super().__init__(message, 400, response)
@ -205,7 +205,7 @@ class KworkNetworkError(KworkError):
print("Проверьте подключение к интернету")
"""
def __init__(self, message: str = "Network error", response: Optional[Any] = None):
def __init__(self, message: str = "Network error", response: Any | None = None):
super().__init__(message, response)
def __str__(self) -> str:

View File

@ -6,7 +6,7 @@ Pydantic модели для ответов Kwork API.
"""
from datetime import datetime
from typing import Any, Optional
from typing import Any
from pydantic import BaseModel, Field
@ -26,11 +26,12 @@ class KworkUser(BaseModel):
user = KworkUser(id=123, username="seller", rating=4.9)
print(f"{user.username}: {user.rating}")
"""
id: int
username: str
avatar_url: Optional[str] = None
avatar_url: str | None = None
is_online: bool = False
rating: Optional[float] = None
rating: float | None = None
class KworkCategory(BaseModel):
@ -43,10 +44,11 @@ class KworkCategory(BaseModel):
slug: URL-safe идентификатор.
parent_id: ID родительской категории для вложенности.
"""
id: int
name: str
slug: str
parent_id: Optional[int] = None
parent_id: int | None = None
class Kwork(BaseModel):
@ -69,18 +71,19 @@ class Kwork(BaseModel):
created_at: Дата создания.
updated_at: Дата последнего обновления.
"""
id: int
title: str
description: Optional[str] = None
description: str | None = None
price: float
currency: str = "RUB"
category_id: Optional[int] = None
seller: Optional[KworkUser] = None
category_id: int | None = None
seller: KworkUser | None = None
images: list[str] = Field(default_factory=list)
rating: Optional[float] = None
rating: float | None = None
reviews_count: int = 0
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
created_at: datetime | None = None
updated_at: datetime | None = None
class KworkDetails(Kwork):
@ -97,10 +100,11 @@ class KworkDetails(Kwork):
features: Список дополнительных опций.
faq: Список вопросов и ответов.
"""
full_description: Optional[str] = None
requirements: Optional[str] = None
delivery_time: Optional[int] = None
revisions: Optional[int] = None
full_description: str | None = None
requirements: str | None = None
delivery_time: int | None = None
revisions: int | None = None
features: list[str] = Field(default_factory=list)
faq: list[dict[str, str]] = Field(default_factory=list)
@ -117,6 +121,7 @@ class PaginationInfo(BaseModel):
has_next: Есть ли следующая страница.
has_prev: Есть ли предыдущая страница.
"""
current_page: int = 1
total_pages: int = 1
total_items: int = 0
@ -135,9 +140,10 @@ class CatalogResponse(BaseModel):
filters: Доступные фильтры.
sort_options: Доступные опции сортировки.
"""
kworks: list[Kwork] = Field(default_factory=list)
pagination: Optional[PaginationInfo] = None
filters: Optional[dict[str, Any]] = None
pagination: PaginationInfo | None = None
filters: dict[str, Any] | None = None
sort_options: list[str] = Field(default_factory=list)
@ -159,16 +165,17 @@ class Project(BaseModel):
bids_count: Количество откликов.
skills: Требуемые навыки.
"""
id: int
title: str
description: Optional[str] = None
budget: Optional[float] = None
description: str | None = None
budget: float | None = None
budget_type: str = "fixed"
category_id: Optional[int] = None
customer: Optional[KworkUser] = None
category_id: int | None = None
customer: KworkUser | None = None
status: str = "open"
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
created_at: datetime | None = None
updated_at: datetime | None = None
bids_count: int = 0
skills: list[str] = Field(default_factory=list)
@ -181,8 +188,9 @@ class ProjectsResponse(BaseModel):
projects: Список проектов.
pagination: Информация о пагинации.
"""
projects: list[Project] = Field(default_factory=list)
pagination: Optional[PaginationInfo] = None
pagination: PaginationInfo | None = None
class Review(BaseModel):
@ -197,12 +205,13 @@ class Review(BaseModel):
kwork_id: ID кворка (если отзыв о кворке).
created_at: Дата создания.
"""
id: int
rating: int = Field(ge=1, le=5)
comment: Optional[str] = None
author: Optional[KworkUser] = None
kwork_id: Optional[int] = None
created_at: Optional[datetime] = None
comment: str | None = None
author: KworkUser | None = None
kwork_id: int | None = None
created_at: datetime | None = None
class ReviewsResponse(BaseModel):
@ -214,9 +223,10 @@ class ReviewsResponse(BaseModel):
pagination: Информация о пагинации.
average_rating: Средний рейтинг.
"""
reviews: list[Review] = Field(default_factory=list)
pagination: Optional[PaginationInfo] = None
average_rating: Optional[float] = None
pagination: PaginationInfo | None = None
average_rating: float | None = None
class Notification(BaseModel):
@ -232,13 +242,14 @@ class Notification(BaseModel):
created_at: Дата создания.
link: Ссылка для перехода (если есть).
"""
id: int
type: str
title: str
message: str
is_read: bool = False
created_at: Optional[datetime] = None
link: Optional[str] = None
created_at: datetime | None = None
link: str | None = None
class NotificationsResponse(BaseModel):
@ -249,6 +260,7 @@ class NotificationsResponse(BaseModel):
notifications: Список уведомлений.
unread_count: Количество непрочитанных уведомлений.
"""
notifications: list[Notification] = Field(default_factory=list)
unread_count: int = 0
@ -264,11 +276,12 @@ class Dialog(BaseModel):
unread_count: Количество непрочитанных сообщений.
updated_at: Время последнего сообщения.
"""
id: int
participant: Optional[KworkUser] = None
last_message: Optional[str] = None
participant: KworkUser | None = None
last_message: str | None = None
unread_count: int = 0
updated_at: Optional[datetime] = None
updated_at: datetime | None = None
class AuthResponse(BaseModel):
@ -282,11 +295,12 @@ class AuthResponse(BaseModel):
web_auth_token: Токен для последующих запросов.
message: Сообщение (например, об ошибке).
"""
success: bool
user_id: Optional[int] = None
username: Optional[str] = None
web_auth_token: Optional[str] = None
message: Optional[str] = None
user_id: int | None = None
username: str | None = None
web_auth_token: str | None = None
message: str | None = None
class ErrorDetail(BaseModel):
@ -298,9 +312,10 @@ class ErrorDetail(BaseModel):
message: Сообщение об ошибке.
field: Поле, вызвавшее ошибку (если применимо).
"""
code: str
message: str
field: Optional[str] = None
field: str | None = None
class APIErrorResponse(BaseModel):
@ -312,9 +327,10 @@ class APIErrorResponse(BaseModel):
errors: Список деталей ошибок.
message: Общее сообщение об ошибке.
"""
success: bool = False
errors: list[ErrorDetail] = Field(default_factory=list)
message: Optional[str] = None
message: str | None = None
class City(BaseModel):
@ -326,9 +342,10 @@ class City(BaseModel):
name: Название города.
country_id: ID страны.
"""
id: int
name: str
country_id: Optional[int] = None
country_id: int | None = None
class Country(BaseModel):
@ -341,9 +358,10 @@ class Country(BaseModel):
code: Код страны (ISO).
cities: Список городов в стране.
"""
id: int
name: str
code: Optional[str] = None
code: str | None = None
cities: list[City] = Field(default_factory=list)
@ -356,6 +374,7 @@ class TimeZone(BaseModel):
name: Название пояса.
offset: Смещение от UTC (например, "+03:00").
"""
id: int
name: str
offset: str
@ -372,9 +391,10 @@ class Feature(BaseModel):
price: Стоимость в рублях.
type: Тип: "extra", "premium", etc.
"""
id: int
name: str
description: Optional[str] = None
description: str | None = None
price: float
type: str
@ -389,10 +409,11 @@ class Badge(BaseModel):
description: Описание достижения.
icon_url: URL иконки значка.
"""
id: int
name: str
description: Optional[str] = None
icon_url: Optional[str] = None
description: str | None = None
icon_url: str | None = None
# Generic response wrapper
@ -407,9 +428,10 @@ class DataResponse(BaseModel):
data: Полезные данные (словарь).
message: Дополнительное сообщение.
"""
success: bool = True
data: Optional[dict[str, Any]] = None
message: Optional[str] = None
data: dict[str, Any] | None = None
message: str | None = None
class ValidationIssue(BaseModel):
@ -423,11 +445,12 @@ class ValidationIssue(BaseModel):
position: Позиция в тексте (если применимо).
suggestion: Предлагаемое исправление (если есть).
"""
type: str = "error"
code: str
message: str
position: Optional[int] = None
suggestion: Optional[str] = None
position: int | None = None
suggestion: str | None = None
class ValidationResponse(BaseModel):
@ -443,8 +466,9 @@ class ValidationResponse(BaseModel):
score: Оценка качества текста (0-100, если доступна).
message: Дополнительное сообщение.
"""
success: bool = True
is_valid: bool = True
issues: list[ValidationIssue] = Field(default_factory=list)
score: Optional[int] = None
message: Optional[str] = None
score: int | None = None
message: str | None = None

1094
test-results/report.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,9 @@ E2E тесты для Kwork API.
"""
import os
import pytest
from pathlib import Path
import pytest
from dotenv import load_dotenv
# Загружаем .env
@ -39,21 +40,17 @@ def slowmo(request):
slowmo = request.config.getoption("--slowmo", default=0)
if slowmo > 0:
import time
time.sleep(slowmo)
def pytest_configure(config):
"""Регистрация маркера e2e."""
config.addinivalue_line(
"markers", "e2e: mark test as end-to-end (requires credentials)"
)
config.addinivalue_line("markers", "e2e: mark test as end-to-end (requires credentials)")
def pytest_addoption(parser):
"""Добавляет опцию --slowmo."""
parser.addoption(
"--slowmo",
type=float,
default=0,
help="Delay between tests in seconds (for rate limiting)"
"--slowmo", type=float, default=0, help="Delay between tests in seconds (for rate limiting)"
)

View File

@ -3,6 +3,7 @@ E2E тесты аутентификации.
"""
import pytest
from kwork_api import KworkClient
from kwork_api.errors import KworkAuthError
@ -11,8 +12,7 @@ from kwork_api.errors import KworkAuthError
async def test_login_success(require_credentials):
"""E2E: Успешная аутентификация."""
client = await KworkClient.login(
username=require_credentials["username"],
password=require_credentials["password"]
username=require_credentials["username"], password=require_credentials["password"]
)
try:
@ -26,10 +26,7 @@ async def test_login_success(require_credentials):
async def test_login_invalid_credentials():
"""E2E: Неверные credentials."""
with pytest.raises(KworkAuthError):
await KworkClient.login(
username="invalid_user_12345",
password="invalid_pass_12345"
)
await KworkClient.login(username="invalid_user_12345", password="invalid_pass_12345")
@pytest.mark.e2e
@ -37,8 +34,7 @@ async def test_restore_session(require_credentials):
"""E2E: Восстановление сессии из токена."""
# First login
client1 = await KworkClient.login(
username=require_credentials["username"],
password=require_credentials["password"]
username=require_credentials["username"], password=require_credentials["password"]
)
token = client1.token
await client1.aclose()

View File

@ -12,15 +12,14 @@ Usage:
"""
import os
from typing import Optional
import pytest
from kwork_api import KworkClient, KworkAuthError
from kwork_api import KworkAuthError, KworkClient
@pytest.fixture(scope="module")
def client() -> Optional[KworkClient]:
def client() -> KworkClient | None:
"""
Create authenticated client for integration tests.
@ -172,6 +171,7 @@ class TestReferenceAPI:
pytest.skip("No client")
import asyncio
result = asyncio.run(client.reference.get_countries())
assert isinstance(result, list)
@ -183,6 +183,7 @@ class TestReferenceAPI:
pytest.skip("No client")
import asyncio
result = asyncio.run(client.reference.get_timezones())
assert isinstance(result, list)
@ -199,6 +200,7 @@ class TestUserAPI:
pytest.skip("No client")
import asyncio
result = asyncio.run(client.user.get_info())
assert isinstance(result, dict)
@ -224,7 +226,7 @@ class TestErrorHandling:
except Exception:
return True
result = asyncio.run(fetch())
asyncio.run(fetch())
# May or may not raise error depending on API behavior

View File

@ -8,8 +8,8 @@ import pytest
import respx
from httpx import Response
from kwork_api import KworkClient, KworkAuthError, KworkApiError
from kwork_api.models import CatalogResponse, Kwork, ValidationResponse, ValidationIssue
from kwork_api import KworkApiError, KworkAuthError, KworkClient, KworkNetworkError
from kwork_api.models import CatalogResponse, ValidationResponse
class TestAuthentication:
@ -22,10 +22,12 @@ class TestAuthentication:
# Mock login endpoint
login_route = respx.post("https://kwork.ru/signIn")
login_route.mock(return_value=httpx.Response(
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(
@ -175,9 +177,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()
@ -193,9 +193,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)
@ -207,9 +205,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()
@ -217,13 +213,15 @@ class TestErrorHandling:
@respx.mock
async def test_network_error(self):
"""Test network error handling."""
import httpx
client = KworkClient(token="test")
respx.post(f"{client.base_url}/catalogMainv2").mock(
side_effect=Exception("Connection refused")
side_effect=httpx.RequestError("Connection refused", request=None)
)
with pytest.raises(Exception):
with pytest.raises(KworkNetworkError):
await client.catalog.get_list()

View File

@ -8,17 +8,17 @@ import pytest
import respx
from httpx import Response
from kwork_api import KworkClient, KworkAuthError, KworkApiError
from kwork_api import KworkApiError, KworkClient
from kwork_api.models import (
NotificationsResponse,
Kwork,
Dialog,
Badge,
City,
Country,
TimeZone,
Dialog,
Feature,
Badge,
Kwork,
NotificationsResponse,
Project,
TimeZone,
)
BASE_URL = "https://api.kwork.ru"
@ -103,9 +103,7 @@ class TestProjectsAPIExtended:
]
}
respx.post(f"{BASE_URL}/payerOrders").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/payerOrders").mock(return_value=Response(200, json=mock_data))
result = await client.projects.get_payer_orders()
@ -124,9 +122,7 @@ class TestProjectsAPIExtended:
]
}
respx.post(f"{BASE_URL}/workerOrders").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/workerOrders").mock(return_value=Response(200, json=mock_data))
result = await client.projects.get_worker_orders()
@ -149,9 +145,7 @@ class TestUserAPI:
"balance": 50000.0,
}
respx.post(f"{BASE_URL}/user").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/user").mock(return_value=Response(200, json=mock_data))
result = await client.user.get_info()
@ -165,14 +159,17 @@ class TestUserAPI:
mock_data = {
"reviews": [
{"id": 1, "rating": 5, "comment": "Great work!", "author": {"id": 999, "username": "client1"}},
{
"id": 1,
"rating": 5,
"comment": "Great work!",
"author": {"id": 999, "username": "client1"},
},
],
"pagination": {"current_page": 1, "total_pages": 5},
}
respx.post(f"{BASE_URL}/userReviews").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/userReviews").mock(return_value=Response(200, json=mock_data))
result = await client.user.get_reviews(user_id=12345, page=1)
@ -191,9 +188,7 @@ class TestUserAPI:
]
}
respx.post(f"{BASE_URL}/favoriteKworks").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/favoriteKworks").mock(return_value=Response(200, json=mock_data))
result = await client.user.get_favorite_kworks()
@ -217,9 +212,7 @@ class TestReferenceAPI:
]
}
respx.post(f"{BASE_URL}/cities").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/cities").mock(return_value=Response(200, json=mock_data))
result = await client.reference.get_cities()
@ -239,9 +232,7 @@ class TestReferenceAPI:
]
}
respx.post(f"{BASE_URL}/countries").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/countries").mock(return_value=Response(200, json=mock_data))
result = await client.reference.get_countries()
@ -261,9 +252,7 @@ class TestReferenceAPI:
]
}
respx.post(f"{BASE_URL}/timezones").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/timezones").mock(return_value=Response(200, json=mock_data))
result = await client.reference.get_timezones()
@ -300,13 +289,17 @@ class TestReferenceAPI:
mock_data = {
"features": [
{"id": 10, "name": "Public Feature", "is_public": True, "price": 500, "type": "extra"},
{
"id": 10,
"name": "Public Feature",
"is_public": True,
"price": 500,
"type": "extra",
},
]
}
respx.post(f"{BASE_URL}/getPublicFeatures").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/getPublicFeatures").mock(return_value=Response(200, json=mock_data))
result = await client.reference.get_public_features()
@ -325,9 +318,7 @@ class TestReferenceAPI:
]
}
respx.post(f"{BASE_URL}/getBadgesInfo").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/getBadgesInfo").mock(return_value=Response(200, json=mock_data))
result = await client.reference.get_badges_info()
@ -346,15 +337,25 @@ class TestNotificationsAPI:
mock_data = {
"notifications": [
{"id": 1, "type": "order", "title": "New Order", "message": "New order received", "is_read": False},
{"id": 2, "type": "message", "title": "New Message", "message": "You have a new message", "is_read": True},
{
"id": 1,
"type": "order",
"title": "New Order",
"message": "New order received",
"is_read": False,
},
{
"id": 2,
"type": "message",
"title": "New Message",
"message": "You have a new message",
"is_read": True,
},
],
"unread_count": 5,
}
respx.post(f"{BASE_URL}/notifications").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/notifications").mock(return_value=Response(200, json=mock_data))
result = await client.notifications.get_list()
@ -368,7 +369,13 @@ class TestNotificationsAPI:
mock_data = {
"notifications": [
{"id": 3, "type": "system", "title": "System Update", "message": "System update available", "is_read": False},
{
"id": 3,
"type": "system",
"title": "System Update",
"message": "System update available",
"is_read": False,
},
],
"unread_count": 1,
}
@ -394,9 +401,7 @@ class TestNotificationsAPI:
]
}
respx.post(f"{BASE_URL}/dialogs").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/dialogs").mock(return_value=Response(200, json=mock_data))
result = await client.notifications.get_dialogs()
@ -415,9 +420,7 @@ class TestNotificationsAPI:
]
}
respx.post(f"{BASE_URL}/blockedDialogList").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/blockedDialogList").mock(return_value=Response(200, json=mock_data))
result = await client.notifications.get_blocked_dialogs()
@ -438,9 +441,7 @@ class TestOtherAPI:
"count": 1,
}
respx.post(f"{BASE_URL}/myWants").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/myWants").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_wants()
@ -457,9 +458,7 @@ class TestOtherAPI:
"completed_wants": 10,
}
respx.post(f"{BASE_URL}/wantsStatusList").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/wantsStatusList").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_wants_status()
@ -475,9 +474,7 @@ class TestOtherAPI:
"total_sales": 50,
}
respx.post(f"{BASE_URL}/kworksStatusList").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/kworksStatusList").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_kworks_status()
@ -492,9 +489,7 @@ class TestOtherAPI:
"offers": [{"id": 1, "title": "Special offer"}],
}
respx.post(f"{BASE_URL}/offers").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/offers").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_offers()
@ -510,9 +505,7 @@ class TestOtherAPI:
"eur_rate": 98.2,
}
respx.post(f"{BASE_URL}/exchangeInfo").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/exchangeInfo").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_exchange_info()
@ -528,9 +521,7 @@ class TestOtherAPI:
"name": "Main Channel",
}
respx.post(f"{BASE_URL}/getChannel").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/getChannel").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_channel()
@ -647,9 +638,7 @@ class TestOtherAPI:
"updated": {"notifications_enabled": False},
}
respx.post(f"{BASE_URL}/updateSettings").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/updateSettings").mock(return_value=Response(200, json=mock_data))
settings = {"notifications_enabled": False, "theme": "dark"}
result = await client.other.update_settings(settings)
@ -666,9 +655,7 @@ class TestOtherAPI:
"status": "offline",
}
respx.post(f"{BASE_URL}/offline").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/offline").mock(return_value=Response(200, json=mock_data))
result = await client.other.go_offline()
@ -685,9 +672,7 @@ class TestOtherAPI:
"name": "Test Actor",
}
respx.post(f"{BASE_URL}/actor").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/actor").mock(return_value=Response(200, json=mock_data))
result = await client.other.get_actor()
@ -700,6 +685,7 @@ class TestClientInternals:
def test_handle_response_success(self):
"""Test _handle_response with successful response."""
import httpx
client = KworkClient(token="test")
response = httpx.Response(200, json={"success": True, "data": "test"})
@ -711,6 +697,7 @@ class TestClientInternals:
def test_handle_response_error(self):
"""Test _handle_response with error response."""
import httpx
client = KworkClient(token="test")
response = httpx.Response(400, json={"message": "Bad request"})
@ -727,9 +714,7 @@ class TestClientInternals:
mock_data = {"result": "success"}
respx.post(f"{BASE_URL}/test-endpoint").mock(
return_value=Response(200, json=mock_data)
)
respx.post(f"{BASE_URL}/test-endpoint").mock(return_value=Response(200, json=mock_data))
result = await client._request("POST", "/test-endpoint", json={"param": "value"})