""" Unit tests for KworkClient with mocks. These tests use respx for HTTP mocking and don't require real API access. """ import pytest import respx from httpx import Response from kwork_api import KworkClient, KworkAuthError, KworkApiError from kwork_api.models import CatalogResponse, Kwork class TestAuthentication: """Test authentication flows.""" @respx.mock async def test_login_success(self): """Test successful login.""" import httpx # Mock login endpoint login_route = respx.post("https://kwork.ru/signIn") 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( 200, json={"web_auth_token": "test_token_abc123"}, ) ) # Login client = await KworkClient.login("testuser", "testpass") # Verify assert login_route.called assert token_route.called assert client._token == "test_token_abc123" @respx.mock async def test_login_invalid_credentials(self): """Test login with invalid credentials.""" respx.post("https://kwork.ru/signIn").mock( return_value=Response(401, json={"error": "Invalid credentials"}) ) with pytest.raises(KworkAuthError): await KworkClient.login("wrong", "wrong") @respx.mock async def test_login_no_userid(self): """Test login without userId in cookies.""" import httpx respx.post("https://kwork.ru/signIn").mock( return_value=httpx.Response(200, headers={"Set-Cookie": "other=value"}) ) with pytest.raises(KworkAuthError, match="no userId"): await KworkClient.login("test", "test") @respx.mock async def test_login_no_token(self): """Test login without web_auth_token in response.""" import httpx 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"): await KworkClient.login("test", "test") def test_init_with_token(self): """Test client initialization with token.""" client = KworkClient(token="test_token") assert client._token == "test_token" class TestCatalogAPI: """Test catalog endpoints.""" @respx.mock async def test_get_catalog(self): """Test getting catalog list.""" client = KworkClient(token="test") mock_data = { "kworks": [ {"id": 1, "title": "Test Kwork", "price": 1000.0}, {"id": 2, "title": "Another Kwork", "price": 2000.0}, ], "pagination": { "current_page": 1, "total_pages": 5, "total_items": 100, }, } respx.post(f"{client.base_url}/catalogMainv2").mock( return_value=Response(200, json=mock_data) ) result = await client.catalog.get_list(page=1) assert isinstance(result, CatalogResponse) assert len(result.kworks) == 2 assert result.kworks[0].id == 1 assert result.pagination.total_pages == 5 @respx.mock async def test_get_kwork_details(self): """Test getting kwork details.""" client = KworkClient(token="test") mock_data = { "id": 123, "title": "Detailed Kwork", "price": 5000.0, "full_description": "Full description here", "delivery_time": 3, } respx.post(f"{client.base_url}/getKworkDetails").mock( return_value=Response(200, json=mock_data) ) result = await client.catalog.get_details(123) assert result.id == 123 assert result.full_description == "Full description here" assert result.delivery_time == 3 @respx.mock async def test_catalog_error(self): """Test catalog API error handling.""" client = KworkClient(token="test") respx.post(f"{client.base_url}/catalogMainv2").mock( return_value=Response(400, json={"message": "Invalid category"}) ) with pytest.raises(KworkApiError): await client.catalog.get_list(category_id=99999) class TestProjectsAPI: """Test projects endpoints.""" @respx.mock async def test_get_projects(self): """Test getting projects list.""" client = KworkClient(token="test") mock_data = { "projects": [ { "id": 1, "title": "Test Project", "description": "Test description", "budget": 10000.0, "status": "open", } ], "pagination": {"current_page": 1}, } respx.post(f"{client.base_url}/projects").mock( return_value=Response(200, json=mock_data) ) result = await client.projects.get_list() assert len(result.projects) == 1 assert result.projects[0].budget == 10000.0 class TestErrorHandling: """Test error handling.""" @respx.mock async def test_404_error(self): """Test 404 error handling.""" client = KworkClient(token="test") 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) assert exc_info.value.status_code == 404 @respx.mock async def test_401_error(self): """Test 401 error handling.""" client = KworkClient(token="invalid") respx.post(f"{client.base_url}/catalogMainv2").mock( return_value=Response(401) ) with pytest.raises(KworkAuthError): await client.catalog.get_list() @respx.mock async def test_network_error(self): """Test network error handling.""" client = KworkClient(token="test") respx.post(f"{client.base_url}/catalogMainv2").mock( side_effect=Exception("Connection refused") ) with pytest.raises(Exception): await client.catalog.get_list() class TestContextManager: """Test async context manager.""" async def test_context_manager(self): """Test using client as context manager.""" async with KworkClient(token="test") as client: 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 assert client._client is None or client._client.is_closed