Add comprehensive async tests - all 37 tests passing

Phase 2, Task 2.1, Step 4 Complete: Async Testing Suite

This commit adds a complete test suite for the async client and
async pages endpoint, achieving 100% pass rate for async functionality.

Test Coverage:
--------------

1. **AsyncWikiJSClient Tests** (test_async_client.py)
   - Initialization tests (5 tests)
     * API key string initialization
     * Auth handler initialization
     * Invalid auth parameter handling
     * Custom settings configuration
     * Pages endpoint availability

   - HTTP Request tests (5 tests)
     * Successful API requests
     * 401 authentication errors
     * API error handling (500 errors)
     * Connection error handling
     * Timeout error handling

   - Connection Testing (4 tests)
     * Successful connection test
     * GraphQL error handling
     * Invalid response format detection
     * Missing configuration detection

   - Context Manager tests (2 tests)
     * Async context manager protocol
     * Manual close handling

   - Session Creation tests (3 tests)
     * Session creation and configuration
     * Lazy session initialization
     * Session reuse

2. **AsyncPagesEndpoint Tests** (test_async_pages.py)
   - Initialization test
   - List operations (3 tests)
     * Basic listing
     * Parameterized filtering
     * Validation errors

   - Get operations (3 tests)
     * Get by ID
     * Validation errors
     * Not found handling

   - Get by path operation
   - Create operations (2 tests)
     * Successful creation
     * Failed creation handling

   - Update operation
   - Delete operations (2 tests)
     * Successful deletion
     * Failed deletion handling

   - Search operation
   - Get by tags operation
   - GraphQL error handling
   - Data normalization tests (2 tests)

Bug Fixes:
----------
- Fixed exception handling order in AsyncWikiJSClient._request()
  * ServerTimeoutError now caught before ClientConnectionError
  * Prevents timeout errors being misclassified as connection errors

- Fixed test mocking for async context managers
  * Properly mock __aenter__ and __aexit__ methods
  * Fixed session creation in async context

Test Results:
-------------
 37/37 tests passing (100% pass rate)
 Async client tests: 19/19 passing
 Async pages tests: 18/18 passing
 53% overall code coverage (includes async code)
 Zero flake8 errors
 All imports successful

Quality Metrics:
----------------
- Test coverage for async module: >85%
- All edge cases covered (errors, validation, not found)
- Proper async/await usage throughout
- Mock objects properly configured
- Clean test structure and organization

Next Steps:
-----------
- Create async usage examples
- Write async documentation
- Performance benchmarks (async vs sync)
- Integration tests with real Wiki.js instance

This establishes a solid foundation for async development
with comprehensive test coverage ensuring reliability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2025-10-22 18:12:29 +00:00
parent 95c86b6600
commit 0fa290d67b
4 changed files with 670 additions and 3 deletions

View File

@@ -0,0 +1,307 @@
"""Tests for AsyncWikiJSClient."""
import json
from unittest.mock import AsyncMock, Mock, patch
import aiohttp
import pytest
from wikijs.aio import AsyncWikiJSClient
from wikijs.auth import APIKeyAuth
from wikijs.exceptions import (
APIError,
AuthenticationError,
ConfigurationError,
ConnectionError,
TimeoutError,
)
class TestAsyncWikiJSClientInit:
"""Test AsyncWikiJSClient initialization."""
def test_init_with_api_key_string(self):
"""Test initialization with API key string."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
assert client.base_url == "https://wiki.example.com"
assert isinstance(client._auth_handler, APIKeyAuth)
assert client.timeout == 30
assert client.verify_ssl is True
assert "wikijs-python-sdk" in client.user_agent
def test_init_with_auth_handler(self):
"""Test initialization with auth handler."""
auth_handler = APIKeyAuth("test-key")
client = AsyncWikiJSClient("https://wiki.example.com", auth=auth_handler)
assert client._auth_handler is auth_handler
def test_init_invalid_auth(self):
"""Test initialization with invalid auth parameter."""
with pytest.raises(ConfigurationError, match="Invalid auth parameter"):
AsyncWikiJSClient("https://wiki.example.com", auth=123)
def test_init_with_custom_settings(self):
"""Test initialization with custom settings."""
client = AsyncWikiJSClient(
"https://wiki.example.com",
auth="test-key",
timeout=60,
verify_ssl=False,
user_agent="Custom Agent",
)
assert client.timeout == 60
assert client.verify_ssl is False
assert client.user_agent == "Custom Agent"
def test_has_pages_endpoint(self):
"""Test that client has pages endpoint."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
assert hasattr(client, "pages")
assert client.pages._client is client
class TestAsyncWikiJSClientRequest:
"""Test AsyncWikiJSClient HTTP request methods."""
@pytest.fixture
def client(self):
"""Create test client."""
return AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
@pytest.mark.asyncio
async def test_successful_request(self, client):
"""Test successful API request."""
mock_response = AsyncMock()
mock_response.status = 200
# Response returns full data structure
mock_response.json = AsyncMock(return_value={"data": {"result": "success"}})
# Create a context manager mock
mock_ctx_manager = AsyncMock()
mock_ctx_manager.__aenter__.return_value = mock_response
mock_ctx_manager.__aexit__.return_value = False
with patch.object(client, "_get_session") as mock_get_session:
mock_session = Mock()
mock_session.request = Mock(return_value=mock_ctx_manager)
mock_get_session.return_value = mock_session
result = await client._request("GET", "/test")
# parse_wiki_response returns full response if no errors
assert result == {"data": {"result": "success"}}
mock_session.request.assert_called_once()
@pytest.mark.asyncio
async def test_authentication_error(self, client):
"""Test 401 authentication error."""
mock_response = AsyncMock()
mock_response.status = 401
# Create a context manager mock
mock_ctx_manager = AsyncMock()
mock_ctx_manager.__aenter__.return_value = mock_response
mock_ctx_manager.__aexit__.return_value = False
with patch.object(client, "_get_session") as mock_get_session:
mock_session = Mock()
mock_session.request = Mock(return_value=mock_ctx_manager)
mock_get_session.return_value = mock_session
with pytest.raises(AuthenticationError, match="Authentication failed"):
await client._request("GET", "/test")
@pytest.mark.asyncio
async def test_api_error(self, client):
"""Test API error handling."""
mock_response = AsyncMock()
mock_response.status = 500
mock_response.text = AsyncMock(return_value="Internal Server Error")
# Create a context manager mock
mock_ctx_manager = AsyncMock()
mock_ctx_manager.__aenter__.return_value = mock_response
mock_ctx_manager.__aexit__.return_value = False
with patch.object(client, "_get_session") as mock_get_session:
mock_session = Mock()
mock_session.request = Mock(return_value=mock_ctx_manager)
mock_get_session.return_value = mock_session
with pytest.raises(APIError):
await client._request("GET", "/test")
@pytest.mark.asyncio
async def test_connection_error(self, client):
"""Test connection error handling."""
with patch.object(client, "_get_session") as mock_get_session:
mock_session = Mock()
mock_session.request = Mock(
side_effect=aiohttp.ClientConnectionError("Connection failed")
)
mock_get_session.return_value = mock_session
with pytest.raises(ConnectionError, match="Failed to connect"):
await client._request("GET", "/test")
@pytest.mark.asyncio
async def test_timeout_error(self, client):
"""Test timeout error handling."""
with patch.object(client, "_get_session") as mock_get_session:
mock_session = Mock()
mock_session.request = Mock(
side_effect=aiohttp.ServerTimeoutError("Timeout")
)
mock_get_session.return_value = mock_session
with pytest.raises(TimeoutError, match="timed out"):
await client._request("GET", "/test")
class TestAsyncWikiJSClientTestConnection:
"""Test AsyncWikiJSClient connection testing."""
@pytest.fixture
def client(self):
"""Create test client."""
return AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
@pytest.mark.asyncio
async def test_successful_connection(self, client):
"""Test successful connection test."""
mock_response = {"data": {"site": {"title": "Test Wiki"}}}
with patch.object(client, "_request", new_callable=AsyncMock) as mock_request:
mock_request.return_value = mock_response
result = await client.test_connection()
assert result is True
mock_request.assert_called_once()
args, kwargs = mock_request.call_args
assert args[0] == "POST"
assert args[1] == "/graphql"
assert "query" in kwargs["json_data"]
@pytest.mark.asyncio
async def test_connection_graphql_error(self, client):
"""Test connection with GraphQL error."""
mock_response = {"errors": [{"message": "Unauthorized"}]}
with patch.object(client, "_request", new_callable=AsyncMock) as mock_request:
mock_request.return_value = mock_response
with pytest.raises(AuthenticationError, match="GraphQL query failed"):
await client.test_connection()
@pytest.mark.asyncio
async def test_connection_invalid_response(self, client):
"""Test connection with invalid response."""
mock_response = {"data": {}} # Missing 'site' key
with patch.object(client, "_request", new_callable=AsyncMock) as mock_request:
mock_request.return_value = mock_response
with pytest.raises(APIError, match="Unexpected response format"):
await client.test_connection()
@pytest.mark.asyncio
async def test_connection_no_base_url(self):
"""Test connection with no base URL."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
client.base_url = None
with pytest.raises(ConfigurationError, match="Base URL not configured"):
await client.test_connection()
class TestAsyncWikiJSClientContextManager:
"""Test AsyncWikiJSClient async context manager."""
@pytest.mark.asyncio
async def test_context_manager(self):
"""Test async context manager."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
# Mock the session
mock_session = AsyncMock()
mock_session.closed = False
with patch.object(client, "_create_session", return_value=mock_session):
async with client as ctx_client:
assert ctx_client is client
assert client._session is mock_session
# Check that close was called
mock_session.close.assert_called_once()
@pytest.mark.asyncio
async def test_manual_close(self):
"""Test manual close."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
# Mock the session
mock_session = AsyncMock()
mock_session.closed = False
client._session = mock_session
await client.close()
mock_session.close.assert_called_once()
class TestAsyncWikiJSClientSessionCreation:
"""Test AsyncWikiJSClient session creation."""
@pytest.mark.asyncio
async def test_create_session(self):
"""Test session creation."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
session = client._create_session()
assert isinstance(session, aiohttp.ClientSession)
assert "wikijs-python-sdk" in session.headers["User-Agent"]
assert session.headers["Accept"] == "application/json"
assert session.headers["Content-Type"] == "application/json"
# Clean up
await session.close()
if client._connector:
await client._connector.close()
@pytest.mark.asyncio
async def test_get_session_creates_if_none(self):
"""Test get_session creates session if none exists."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
assert client._session is None
session = client._get_session()
assert session is not None
assert isinstance(session, aiohttp.ClientSession)
# Clean up
await session.close()
if client._connector:
await client._connector.close()
@pytest.mark.asyncio
async def test_get_session_reuses_existing(self):
"""Test get_session reuses existing session."""
client = AsyncWikiJSClient("https://wiki.example.com", auth="test-key")
session1 = client._get_session()
session2 = client._get_session()
assert session1 is session2
# Clean up
await session1.close()
if client._connector:
await client._connector.close()