Fix code formatting and linting issues

- Updated GitHub Actions workflow to use correct flake8 configuration
- Fixed line length issues by using 88 characters as configured
- Removed unused imports and trailing whitespace
- Fixed f-string placeholders and unused variables
- All linting checks now pass with project configuration

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-30 20:49:40 -04:00
parent 16bd151337
commit ade9aacf56
33 changed files with 1099 additions and 1096 deletions

View File

@@ -1 +1 @@
"""Tests for wikijs-python-sdk."""
"""Tests for wikijs-python-sdk."""

View File

@@ -1 +1 @@
"""Authentication tests for wikijs-python-sdk."""
"""Authentication tests for wikijs-python-sdk."""

View File

@@ -36,10 +36,10 @@ class TestAPIKeyAuth:
def test_get_headers_returns_bearer_token(self, api_key_auth, mock_api_key):
"""Test that get_headers returns proper Authorization header."""
headers = api_key_auth.get_headers()
expected_headers = {
"Authorization": f"Bearer {mock_api_key}",
"Content-Type": "application/json"
"Content-Type": "application/json",
}
assert headers == expected_headers
@@ -75,14 +75,16 @@ class TestAPIKeyAuth:
# Test long key (>8 chars) - shows first 4 and last 4
auth = APIKeyAuth("this-is-a-very-long-api-key-for-testing")
expected = "this" + "*" * (len("this-is-a-very-long-api-key-for-testing") - 8) + "ting"
expected = (
"this" + "*" * (len("this-is-a-very-long-api-key-for-testing") - 8) + "ting"
)
assert auth.api_key == expected
def test_repr_shows_masked_key(self, mock_api_key):
"""Test that __repr__ shows masked API key."""
auth = APIKeyAuth(mock_api_key)
repr_str = repr(auth)
assert "APIKeyAuth" in repr_str
assert mock_api_key not in repr_str # Real key should not appear
assert auth.api_key in repr_str # Masked key should appear
@@ -102,11 +104,19 @@ class TestAPIKeyAuth:
("abcd", "****"),
("abcdefgh", "********"),
("abcdefghi", "abcd*fghi"), # 9 chars: first 4 + 1 star + last 4
("abcdefghij", "abcd**ghij"), # 10 chars: first 4 + 2 stars + last 4
("very-long-api-key-here", "very**************here"), # 22 chars: first 4 + 14 stars + last 4
(
"abcdefghij",
"abcd**ghij",
), # 10 chars: first 4 + 2 stars + last 4
(
"very-long-api-key-here",
"very**************here",
), # 22 chars: first 4 + 14 stars + last 4
]
for key, expected_mask in test_cases:
auth = APIKeyAuth(key)
actual = auth.api_key
assert actual == expected_mask, f"Failed for key '{key}': expected '{expected_mask}', got '{actual}'"
assert (
actual == expected_mask
), f"Failed for key '{key}': expected '{expected_mask}', got '{actual}'"

View File

@@ -1,7 +1,6 @@
"""Tests for base authentication functionality."""
import pytest
from unittest.mock import Mock
from wikijs.auth.base import AuthHandler, NoAuth
from wikijs.exceptions import AuthenticationError
@@ -17,18 +16,19 @@ class TestAuthHandler:
def test_validate_credentials_calls_is_valid(self):
"""Test that validate_credentials calls is_valid."""
# Create concrete implementation for testing
class TestAuth(AuthHandler):
def __init__(self, valid=True):
self.valid = valid
self.refresh_called = False
def get_headers(self):
return {"Authorization": "test"}
def is_valid(self):
return self.valid
def refresh(self):
self.refresh_called = True
self.valid = True
@@ -46,18 +46,21 @@ class TestAuthHandler:
def test_validate_credentials_raises_on_invalid_after_refresh(self):
"""Test that validate_credentials raises if still invalid after refresh."""
class TestAuth(AuthHandler):
def get_headers(self):
return {"Authorization": "test"}
def is_valid(self):
return False # Always invalid
def refresh(self):
pass # No-op refresh
auth = TestAuth()
with pytest.raises(AuthenticationError, match="Authentication credentials are invalid"):
with pytest.raises(
AuthenticationError, match="Authentication credentials are invalid"
):
auth.validate_credentials()
@@ -89,4 +92,4 @@ class TestNoAuth:
"""Test that validate_credentials always succeeds."""
# Should not raise any exception
no_auth.validate_credentials()
assert no_auth.is_valid() is True
assert no_auth.is_valid() is True

View File

@@ -1,7 +1,7 @@
"""Tests for JWT authentication."""
import time
from datetime import datetime, timedelta
from datetime import timedelta
from unittest.mock import patch
import pytest
@@ -24,7 +24,7 @@ class TestJWTAuth:
"""Test initialization with all parameters."""
refresh_token = "refresh-token-123"
expires_at = time.time() + 3600
auth = JWTAuth(mock_jwt_token, refresh_token, expires_at)
assert auth._token == mock_jwt_token
assert auth._refresh_token == refresh_token
@@ -53,10 +53,10 @@ class TestJWTAuth:
def test_get_headers_returns_bearer_token(self, jwt_auth, mock_jwt_token):
"""Test that get_headers returns proper Authorization header."""
headers = jwt_auth.get_headers()
expected_headers = {
"Authorization": f"Bearer {mock_jwt_token}",
"Content-Type": "application/json"
"Content-Type": "application/json",
}
assert headers == expected_headers
@@ -65,16 +65,16 @@ class TestJWTAuth:
# Create JWT with expired token
expires_at = time.time() - 3600 # Expired 1 hour ago
refresh_token = "refresh-token-123"
auth = JWTAuth(mock_jwt_token, refresh_token, expires_at)
# Mock the refresh method to avoid actual implementation
with patch.object(auth, 'refresh') as mock_refresh:
with patch.object(auth, "refresh") as mock_refresh:
mock_refresh.side_effect = AuthenticationError("Refresh not implemented")
with pytest.raises(AuthenticationError):
auth.get_headers()
mock_refresh.assert_called_once()
def test_is_valid_returns_true_for_valid_token_no_expiry(self, jwt_auth):
@@ -111,15 +111,20 @@ class TestJWTAuth:
def test_refresh_raises_error_without_refresh_token(self, jwt_auth):
"""Test that refresh raises error when no refresh token available."""
with pytest.raises(AuthenticationError, match="JWT token expired and no refresh token available"):
with pytest.raises(
AuthenticationError,
match="JWT token expired and no refresh token available",
):
jwt_auth.refresh()
def test_refresh_raises_not_implemented_error(self, mock_jwt_token):
"""Test that refresh raises not implemented error."""
refresh_token = "refresh-token-123"
auth = JWTAuth(mock_jwt_token, refresh_token)
with pytest.raises(AuthenticationError, match="JWT token refresh not yet implemented"):
with pytest.raises(
AuthenticationError, match="JWT token refresh not yet implemented"
):
auth.refresh()
def test_is_expired_returns_false_no_expiry(self, jwt_auth):
@@ -146,7 +151,7 @@ class TestJWTAuth:
"""Test that time_until_expiry returns correct timedelta."""
expires_at = time.time() + 3600 # Expires in 1 hour
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
time_left = auth.time_until_expiry()
assert isinstance(time_left, timedelta)
# Should be approximately 1 hour (allowing for small time differences)
@@ -156,7 +161,7 @@ class TestJWTAuth:
"""Test that time_until_expiry returns zero for expired token."""
expires_at = time.time() - 3600 # Expired 1 hour ago
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
time_left = auth.time_until_expiry()
assert time_left.total_seconds() == 0
@@ -164,7 +169,7 @@ class TestJWTAuth:
"""Test that token_preview masks the token for security."""
auth = JWTAuth(mock_jwt_token)
preview = auth.token_preview
assert preview != mock_jwt_token # Should not show full token
assert preview.startswith(mock_jwt_token[:10])
assert preview.endswith(mock_jwt_token[-10:])
@@ -175,14 +180,14 @@ class TestJWTAuth:
short_token = "short"
auth = JWTAuth(short_token)
preview = auth.token_preview
assert preview == "*****" # Should be all asterisks
def test_token_preview_handles_none_token(self, mock_jwt_token):
"""Test that token_preview handles None token."""
auth = JWTAuth(mock_jwt_token)
auth._token = None
assert auth.token_preview == "None"
def test_repr_shows_masked_token(self, mock_jwt_token):
@@ -190,7 +195,7 @@ class TestJWTAuth:
expires_at = time.time() + 3600
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
repr_str = repr(auth)
assert "JWTAuth" in repr_str
assert mock_jwt_token not in repr_str # Real token should not appear
assert auth.token_preview in repr_str # Masked token should appear
@@ -210,4 +215,4 @@ class TestJWTAuth:
# Test None refresh token
auth = JWTAuth(mock_jwt_token, None)
assert auth._refresh_token is None
assert auth._refresh_token is None

View File

@@ -2,7 +2,6 @@
import pytest
import responses
from unittest.mock import Mock
from wikijs.auth import APIKeyAuth, JWTAuth, NoAuth
@@ -60,21 +59,12 @@ def sample_page_data():
"content": "This is a test page content.",
"created_at": "2023-01-01T00:00:00Z",
"updated_at": "2023-01-01T12:00:00Z",
"author": {
"id": 1,
"name": "Test User",
"email": "test@example.com"
},
"tags": ["test", "example"]
"author": {"id": 1, "name": "Test User", "email": "test@example.com"},
"tags": ["test", "example"],
}
@pytest.fixture
def sample_error_response():
"""Fixture providing sample error response."""
return {
"error": {
"message": "Not found",
"code": "PAGE_NOT_FOUND"
}
}
return {"error": {"message": "Not found", "code": "PAGE_NOT_FOUND"}}

View File

@@ -1 +1 @@
"""Tests for API endpoints."""
"""Tests for API endpoints."""

View File

@@ -1,147 +1,144 @@
"""Tests for base endpoint class."""
import pytest
from unittest.mock import Mock
import pytest
from wikijs.client import WikiJSClient
from wikijs.endpoints.base import BaseEndpoint
class TestBaseEndpoint:
"""Test suite for BaseEndpoint."""
@pytest.fixture
def mock_client(self):
"""Create a mock WikiJS client."""
client = Mock(spec=WikiJSClient)
return client
@pytest.fixture
def base_endpoint(self, mock_client):
"""Create a BaseEndpoint instance with mock client."""
return BaseEndpoint(mock_client)
def test_init(self, mock_client):
"""Test BaseEndpoint initialization."""
endpoint = BaseEndpoint(mock_client)
assert endpoint._client is mock_client
def test_request(self, base_endpoint, mock_client):
"""Test _request method delegates to client."""
# Setup mock response
mock_response = {"data": "test"}
mock_client._request.return_value = mock_response
# Call _request
result = base_endpoint._request(
"GET",
"/test",
params={"param": "value"},
json_data={"data": "test"},
extra_param="extra"
extra_param="extra",
)
# Verify delegation to client
mock_client._request.assert_called_once_with(
method="GET",
endpoint="/test",
params={"param": "value"},
json_data={"data": "test"},
extra_param="extra"
extra_param="extra",
)
# Verify response
assert result == mock_response
def test_get(self, base_endpoint, mock_client):
"""Test _get method."""
mock_response = {"data": "test"}
mock_client._request.return_value = mock_response
result = base_endpoint._get("/test", params={"param": "value"})
mock_client._request.assert_called_once_with(
method="GET",
endpoint="/test",
params={"param": "value"},
json_data=None
json_data=None,
)
assert result == mock_response
def test_post(self, base_endpoint, mock_client):
"""Test _post method."""
mock_response = {"data": "test"}
mock_client._request.return_value = mock_response
result = base_endpoint._post(
"/test",
json_data={"data": "test"},
params={"param": "value"}
"/test", json_data={"data": "test"}, params={"param": "value"}
)
mock_client._request.assert_called_once_with(
method="POST",
endpoint="/test",
params={"param": "value"},
json_data={"data": "test"}
json_data={"data": "test"},
)
assert result == mock_response
def test_put(self, base_endpoint, mock_client):
"""Test _put method."""
mock_response = {"data": "test"}
mock_client._request.return_value = mock_response
result = base_endpoint._put(
"/test",
json_data={"data": "test"},
params={"param": "value"}
"/test", json_data={"data": "test"}, params={"param": "value"}
)
mock_client._request.assert_called_once_with(
method="PUT",
endpoint="/test",
params={"param": "value"},
json_data={"data": "test"}
json_data={"data": "test"},
)
assert result == mock_response
def test_delete(self, base_endpoint, mock_client):
"""Test _delete method."""
mock_response = {"data": "test"}
mock_client._request.return_value = mock_response
result = base_endpoint._delete("/test", params={"param": "value"})
mock_client._request.assert_called_once_with(
method="DELETE",
endpoint="/test",
params={"param": "value"},
json_data=None
json_data=None,
)
assert result == mock_response
def test_build_endpoint_single_part(self, base_endpoint):
"""Test _build_endpoint with single part."""
result = base_endpoint._build_endpoint("test")
assert result == "/test"
def test_build_endpoint_multiple_parts(self, base_endpoint):
"""Test _build_endpoint with multiple parts."""
result = base_endpoint._build_endpoint("api", "v1", "pages")
assert result == "/api/v1/pages"
def test_build_endpoint_with_slashes(self, base_endpoint):
"""Test _build_endpoint handles leading/trailing slashes."""
result = base_endpoint._build_endpoint("/api/", "/v1/", "/pages/")
assert result == "/api/v1/pages"
def test_build_endpoint_empty_parts(self, base_endpoint):
"""Test _build_endpoint filters out empty parts."""
result = base_endpoint._build_endpoint("api", "", "pages", None)
assert result == "/api/pages"
def test_build_endpoint_numeric_parts(self, base_endpoint):
"""Test _build_endpoint handles numeric parts."""
result = base_endpoint._build_endpoint("pages", 123, "edit")
assert result == "/pages/123/edit"
assert result == "/pages/123/edit"

View File

@@ -1,8 +1,9 @@
"""Tests for Pages API endpoint."""
import pytest
from unittest.mock import Mock, patch
import pytest
from wikijs.client import WikiJSClient
from wikijs.endpoints.pages import PagesEndpoint
from wikijs.exceptions import APIError, ValidationError
@@ -11,18 +12,18 @@ from wikijs.models.page import Page, PageCreate, PageUpdate
class TestPagesEndpoint:
"""Test suite for PagesEndpoint."""
@pytest.fixture
def mock_client(self):
"""Create a mock WikiJS client."""
client = Mock(spec=WikiJSClient)
return client
@pytest.fixture
def pages_endpoint(self, mock_client):
"""Create a PagesEndpoint instance with mock client."""
return PagesEndpoint(mock_client)
@pytest.fixture
def sample_page_data(self):
"""Sample page data from API."""
@@ -41,9 +42,9 @@ class TestPagesEndpoint:
"authorEmail": "test@example.com",
"editor": "markdown",
"createdAt": "2023-01-01T00:00:00Z",
"updatedAt": "2023-01-02T00:00:00Z"
"updatedAt": "2023-01-02T00:00:00Z",
}
@pytest.fixture
def sample_page_create(self):
"""Sample PageCreate object."""
@@ -52,57 +53,49 @@ class TestPagesEndpoint:
path="new-page",
content="# New Page\n\nContent here.",
description="A new page",
tags=["new", "test"]
tags=["new", "test"],
)
@pytest.fixture
def sample_page_update(self):
"""Sample PageUpdate object."""
return PageUpdate(
title="Updated Page",
content="# Updated Page\n\nUpdated content.",
tags=["updated", "test"]
tags=["updated", "test"],
)
def test_init(self, mock_client):
"""Test PagesEndpoint initialization."""
endpoint = PagesEndpoint(mock_client)
assert endpoint._client is mock_client
def test_list_basic(self, pages_endpoint, sample_page_data):
"""Test basic page listing."""
# Mock the GraphQL response
mock_response = {
"data": {
"pages": [sample_page_data]
}
}
mock_response = {"data": {"pages": [sample_page_data]}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call list method
pages = pages_endpoint.list()
# Verify request
pages_endpoint._post.assert_called_once()
call_args = pages_endpoint._post.call_args
assert call_args[0][0] == "/graphql"
# Verify response
assert len(pages) == 1
assert isinstance(pages[0], Page)
assert pages[0].id == 123
assert pages[0].title == "Test Page"
assert pages[0].path == "test-page"
def test_list_with_parameters(self, pages_endpoint, sample_page_data):
"""Test page listing with filter parameters."""
mock_response = {
"data": {
"pages": [sample_page_data]
}
}
mock_response = {"data": {"pages": [sample_page_data]}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call with parameters
pages = pages_endpoint.list(
limit=10,
@@ -112,14 +105,14 @@ class TestPagesEndpoint:
locale="en",
author_id=1,
order_by="created_at",
order_direction="DESC"
order_direction="DESC",
)
# Verify request
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
variables = query_data["variables"]
assert variables["limit"] == 10
assert variables["offset"] == 5
assert variables["search"] == "test"
@@ -128,133 +121,117 @@ class TestPagesEndpoint:
assert variables["authorId"] == 1
assert variables["orderBy"] == "created_at"
assert variables["orderDirection"] == "DESC"
# Verify response
assert len(pages) == 1
assert isinstance(pages[0], Page)
def test_list_validation_errors(self, pages_endpoint):
"""Test list method parameter validation."""
# Test invalid limit
with pytest.raises(ValidationError, match="limit must be greater than 0"):
pages_endpoint.list(limit=0)
# Test invalid offset
with pytest.raises(ValidationError, match="offset must be non-negative"):
pages_endpoint.list(offset=-1)
# Test invalid order_by
with pytest.raises(ValidationError, match="order_by must be one of"):
pages_endpoint.list(order_by="invalid")
# Test invalid order_direction
with pytest.raises(ValidationError, match="order_direction must be ASC or DESC"):
with pytest.raises(
ValidationError, match="order_direction must be ASC or DESC"
):
pages_endpoint.list(order_direction="INVALID")
def test_list_api_error(self, pages_endpoint):
"""Test list method handling API errors."""
# Mock GraphQL error response
mock_response = {
"errors": [{"message": "GraphQL error"}]
}
mock_response = {"errors": [{"message": "GraphQL error"}]}
pages_endpoint._post = Mock(return_value=mock_response)
with pytest.raises(APIError, match="GraphQL errors"):
pages_endpoint.list()
def test_get_success(self, pages_endpoint, sample_page_data):
"""Test getting a page by ID."""
mock_response = {
"data": {
"page": sample_page_data
}
}
mock_response = {"data": {"page": sample_page_data}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
page = pages_endpoint.get(123)
# Verify request
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
assert query_data["variables"]["id"] == 123
# Verify response
assert isinstance(page, Page)
assert page.id == 123
assert page.title == "Test Page"
def test_get_validation_error(self, pages_endpoint):
"""Test get method parameter validation."""
with pytest.raises(ValidationError, match="page_id must be a positive integer"):
pages_endpoint.get(0)
with pytest.raises(ValidationError, match="page_id must be a positive integer"):
pages_endpoint.get(-1)
with pytest.raises(ValidationError, match="page_id must be a positive integer"):
pages_endpoint.get("invalid")
def test_get_not_found(self, pages_endpoint):
"""Test get method when page not found."""
mock_response = {
"data": {
"page": None
}
}
mock_response = {"data": {"page": None}}
pages_endpoint._post = Mock(return_value=mock_response)
with pytest.raises(APIError, match="Page with ID 123 not found"):
pages_endpoint.get(123)
def test_get_by_path_success(self, pages_endpoint, sample_page_data):
"""Test getting a page by path."""
mock_response = {
"data": {
"pageByPath": sample_page_data
}
}
mock_response = {"data": {"pageByPath": sample_page_data}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
page = pages_endpoint.get_by_path("test-page")
# Verify request
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
variables = query_data["variables"]
assert variables["path"] == "test-page"
assert variables["locale"] == "en"
# Verify response
assert isinstance(page, Page)
assert page.path == "test-page"
def test_get_by_path_validation_error(self, pages_endpoint):
"""Test get_by_path method parameter validation."""
with pytest.raises(ValidationError, match="path must be a non-empty string"):
pages_endpoint.get_by_path("")
with pytest.raises(ValidationError, match="path must be a non-empty string"):
pages_endpoint.get_by_path(None)
def test_create_success(self, pages_endpoint, sample_page_create, sample_page_data):
"""Test creating a new page."""
mock_response = {
"data": {
"createPage": sample_page_data
}
}
mock_response = {"data": {"createPage": sample_page_data}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
created_page = pages_endpoint.create(sample_page_create)
# Verify request
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
variables = query_data["variables"]
assert variables["title"] == "New Page"
assert variables["path"] == "new-page"
assert variables["content"] == "# New Page\n\nContent here."
@@ -262,194 +239,177 @@ class TestPagesEndpoint:
assert variables["tags"] == ["new", "test"]
assert variables["isPublished"] is True
assert variables["isPrivate"] is False
# Verify response
assert isinstance(created_page, Page)
assert created_page.id == 123
def test_create_with_dict(self, pages_endpoint, sample_page_data):
"""Test creating a page with dict data."""
mock_response = {
"data": {
"createPage": sample_page_data
}
}
mock_response = {"data": {"createPage": sample_page_data}}
pages_endpoint._post = Mock(return_value=mock_response)
page_dict = {
"title": "Dict Page",
"path": "dict-page",
"content": "Content from dict",
}
# Call method
created_page = pages_endpoint.create(page_dict)
# Verify response
assert isinstance(created_page, Page)
def test_create_validation_error(self, pages_endpoint):
"""Test create method validation errors."""
# Test invalid data type
with pytest.raises(ValidationError, match="page_data must be PageCreate object or dict"):
with pytest.raises(
ValidationError,
match="page_data must be PageCreate object or dict",
):
pages_endpoint.create("invalid")
# Test invalid dict data
with pytest.raises(ValidationError, match="Invalid page data"):
pages_endpoint.create({"invalid": "data"})
def test_create_api_error(self, pages_endpoint, sample_page_create):
"""Test create method API errors."""
mock_response = {
"errors": [{"message": "Creation failed"}]
}
mock_response = {"errors": [{"message": "Creation failed"}]}
pages_endpoint._post = Mock(return_value=mock_response)
with pytest.raises(APIError, match="Failed to create page"):
pages_endpoint.create(sample_page_create)
def test_update_success(self, pages_endpoint, sample_page_update, sample_page_data):
"""Test updating a page."""
mock_response = {
"data": {
"updatePage": sample_page_data
}
}
mock_response = {"data": {"updatePage": sample_page_data}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
updated_page = pages_endpoint.update(123, sample_page_update)
# Verify request
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
variables = query_data["variables"]
assert variables["id"] == 123
assert variables["title"] == "Updated Page"
assert variables["content"] == "# Updated Page\n\nUpdated content."
assert variables["tags"] == ["updated", "test"]
assert "description" not in variables # Should not include None values
# Verify response
assert isinstance(updated_page, Page)
def test_update_validation_errors(self, pages_endpoint, sample_page_update):
"""Test update method validation errors."""
# Test invalid page_id
with pytest.raises(ValidationError, match="page_id must be a positive integer"):
pages_endpoint.update(0, sample_page_update)
# Test invalid page_data type
with pytest.raises(ValidationError, match="page_data must be PageUpdate object or dict"):
with pytest.raises(
ValidationError,
match="page_data must be PageUpdate object or dict",
):
pages_endpoint.update(123, "invalid")
def test_delete_success(self, pages_endpoint):
"""Test deleting a page."""
mock_response = {
"data": {
"deletePage": {
"success": True,
"message": "Page deleted successfully"
"message": "Page deleted successfully",
}
}
}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
result = pages_endpoint.delete(123)
# Verify request
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
assert query_data["variables"]["id"] == 123
# Verify response
assert result is True
def test_delete_validation_error(self, pages_endpoint):
"""Test delete method validation errors."""
with pytest.raises(ValidationError, match="page_id must be a positive integer"):
pages_endpoint.delete(0)
def test_delete_failure(self, pages_endpoint):
"""Test delete method when deletion fails."""
mock_response = {
"data": {
"deletePage": {
"success": False,
"message": "Deletion failed"
}
}
"data": {"deletePage": {"success": False, "message": "Deletion failed"}}
}
pages_endpoint._post = Mock(return_value=mock_response)
with pytest.raises(APIError, match="Page deletion failed: Deletion failed"):
pages_endpoint.delete(123)
def test_search_success(self, pages_endpoint, sample_page_data):
"""Test searching pages."""
mock_response = {
"data": {
"pages": [sample_page_data]
}
}
mock_response = {"data": {"pages": [sample_page_data]}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
results = pages_endpoint.search("test query", limit=5)
# Verify request (should call list with search parameter)
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
variables = query_data["variables"]
assert variables["search"] == "test query"
assert variables["limit"] == 5
# Verify response
assert len(results) == 1
assert isinstance(results[0], Page)
def test_search_validation_error(self, pages_endpoint):
"""Test search method validation errors."""
with pytest.raises(ValidationError, match="query must be a non-empty string"):
pages_endpoint.search("")
with pytest.raises(ValidationError, match="limit must be greater than 0"):
pages_endpoint.search("test", limit=0)
def test_get_by_tags_match_all(self, pages_endpoint, sample_page_data):
"""Test getting pages by tags (match all)."""
mock_response = {
"data": {
"pages": [sample_page_data]
}
}
mock_response = {"data": {"pages": [sample_page_data]}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call method
results = pages_endpoint.get_by_tags(["test", "example"], match_all=True)
# Verify request (should call list with tags parameter)
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
variables = query_data["variables"]
assert variables["tags"] == ["test", "example"]
# Verify response
assert len(results) == 1
assert isinstance(results[0], Page)
def test_get_by_tags_validation_error(self, pages_endpoint):
"""Test get_by_tags method validation errors."""
with pytest.raises(ValidationError, match="tags must be a non-empty list"):
pages_endpoint.get_by_tags([])
with pytest.raises(ValidationError, match="limit must be greater than 0"):
pages_endpoint.get_by_tags(["test"], limit=0)
def test_normalize_page_data(self, pages_endpoint):
"""Test page data normalization."""
api_data = {
@@ -457,11 +417,11 @@ class TestPagesEndpoint:
"title": "Test",
"isPublished": True,
"authorId": 1,
"createdAt": "2023-01-01T00:00:00Z"
"createdAt": "2023-01-01T00:00:00Z",
}
normalized = pages_endpoint._normalize_page_data(api_data)
# Check field mapping
assert normalized["id"] == 123
assert normalized["title"] == "Test"
@@ -469,57 +429,48 @@ class TestPagesEndpoint:
assert normalized["author_id"] == 1
assert normalized["created_at"] == "2023-01-01T00:00:00Z"
assert normalized["tags"] == [] # Default value
def test_normalize_page_data_missing_fields(self, pages_endpoint):
"""Test page data normalization with missing fields."""
api_data = {
"id": 123,
"title": "Test"
}
api_data = {"id": 123, "title": "Test"}
normalized = pages_endpoint._normalize_page_data(api_data)
# Check that only present fields are included
assert "id" in normalized
assert "title" in normalized
assert "is_published" not in normalized
assert "tags" in normalized # Should have default value
@patch('wikijs.endpoints.pages.Page')
def test_list_page_parsing_error(self, mock_page_class, pages_endpoint, sample_page_data):
@patch("wikijs.endpoints.pages.Page")
def test_list_page_parsing_error(
self, mock_page_class, pages_endpoint, sample_page_data
):
"""Test handling of page parsing errors in list method."""
# Mock Page constructor to raise an exception
mock_page_class.side_effect = ValueError("Parsing error")
mock_response = {
"data": {
"pages": [sample_page_data]
}
}
mock_response = {"data": {"pages": [sample_page_data]}}
pages_endpoint._post = Mock(return_value=mock_response)
with pytest.raises(APIError, match="Failed to parse page data"):
pages_endpoint.list()
def test_graphql_query_structure(self, pages_endpoint, sample_page_data):
"""Test that GraphQL queries have correct structure."""
mock_response = {
"data": {
"pages": [sample_page_data]
}
}
mock_response = {"data": {"pages": [sample_page_data]}}
pages_endpoint._post = Mock(return_value=mock_response)
# Call list method
pages_endpoint.list()
# Verify the GraphQL query structure
call_args = pages_endpoint._post.call_args
query_data = call_args[1]["json_data"]
assert "query" in query_data
assert "variables" in query_data
assert "pages(" in query_data["query"]
assert "id" in query_data["query"]
assert "title" in query_data["query"]
assert "content" in query_data["query"]
assert "content" in query_data["query"]

View File

@@ -3,14 +3,12 @@
import json
from datetime import datetime
import pytest
from wikijs.models.base import BaseModel, TimestampedModel
class TestModelForTesting(BaseModel):
"""Test model for testing base functionality."""
name: str
value: int = 42
optional_field: str = None
@@ -18,82 +16,90 @@ class TestModelForTesting(BaseModel):
class TestTimestampedModelForTesting(TimestampedModel):
"""Test model with timestamps."""
title: str
class TestBaseModel:
"""Test base model functionality."""
def test_basic_model_creation(self):
"""Test basic model creation."""
model = TestModelForTesting(name="test", value=100)
assert model.name == "test"
assert model.value == 100
assert model.optional_field is None
def test_to_dict_basic(self):
"""Test to_dict method."""
model = TestModelForTesting(name="test", value=100)
result = model.to_dict()
expected = {"name": "test", "value": 100}
assert result == expected
def test_to_dict_with_none_values(self):
"""Test to_dict with None values."""
model = TestModelForTesting(name="test", value=100)
# Test excluding None values (default)
result_exclude = model.to_dict(exclude_none=True)
expected_exclude = {"name": "test", "value": 100}
assert result_exclude == expected_exclude
# Test including None values
result_include = model.to_dict(exclude_none=False)
expected_include = {"name": "test", "value": 100, "optional_field": None}
expected_include = {
"name": "test",
"value": 100,
"optional_field": None,
}
assert result_include == expected_include
def test_to_json_basic(self):
"""Test to_json method."""
model = TestModelForTesting(name="test", value=100)
result = model.to_json()
# Parse the JSON to verify structure
parsed = json.loads(result)
expected = {"name": "test", "value": 100}
assert parsed == expected
def test_to_json_with_none_values(self):
"""Test to_json with None values."""
model = TestModelForTesting(name="test", value=100)
# Test excluding None values (default)
result_exclude = model.to_json(exclude_none=True)
parsed_exclude = json.loads(result_exclude)
expected_exclude = {"name": "test", "value": 100}
assert parsed_exclude == expected_exclude
# Test including None values
result_include = model.to_json(exclude_none=False)
parsed_include = json.loads(result_include)
expected_include = {"name": "test", "value": 100, "optional_field": None}
expected_include = {
"name": "test",
"value": 100,
"optional_field": None,
}
assert parsed_include == expected_include
def test_from_dict(self):
"""Test from_dict class method."""
data = {"name": "test", "value": 200}
model = TestModelForTesting.from_dict(data)
assert isinstance(model, TestModelForTesting)
assert model.name == "test"
assert model.value == 200
def test_from_json(self):
"""Test from_json class method."""
json_str = '{"name": "test", "value": 300}'
model = TestModelForTesting.from_json(json_str)
assert isinstance(model, TestModelForTesting)
assert model.name == "test"
assert model.value == 300
@@ -101,51 +107,44 @@ class TestBaseModel:
class TestTimestampedModel:
"""Test timestamped model functionality."""
def test_timestamped_model_creation(self):
"""Test timestamped model creation."""
model = TestTimestampedModelForTesting(title="Test Title")
assert model.title == "Test Title"
assert model.created_at is None
assert model.updated_at is None
def test_timestamped_model_with_timestamps(self):
"""Test timestamped model with timestamps."""
now = datetime.now()
model = TestTimestampedModelForTesting(
title="Test Title",
created_at=now,
updated_at=now
title="Test Title", created_at=now, updated_at=now
)
assert model.title == "Test Title"
assert model.created_at == now
assert model.updated_at == now
def test_is_new_property_true(self):
"""Test is_new property returns True for new models."""
model = TestTimestampedModelForTesting(title="Test Title")
assert model.is_new is True
def test_is_new_property_false(self):
"""Test is_new property returns False for existing models."""
now = datetime.now()
model = TestTimestampedModelForTesting(
title="Test Title",
created_at=now
)
model = TestTimestampedModelForTesting(title="Test Title", created_at=now)
assert model.is_new is False
def test_datetime_serialization(self):
"""Test datetime serialization in JSON."""
now = datetime(2023, 1, 1, 12, 0, 0)
model = TestTimestampedModelForTesting(
title="Test Title",
created_at=now,
updated_at=now
title="Test Title", created_at=now, updated_at=now
)
json_str = model.to_json()
parsed = json.loads(json_str)
assert parsed["created_at"] == "2023-01-01T12:00:00"
assert parsed["updated_at"] == "2023-01-01T12:00:00"
assert parsed["updated_at"] == "2023-01-01T12:00:00"

View File

@@ -1,14 +1,13 @@
"""Tests for Page models."""
import pytest
from datetime import datetime
from wikijs.models.page import Page, PageCreate, PageUpdate
class TestPageModel:
"""Test Page model functionality."""
@pytest.fixture
def valid_page_data(self):
"""Valid page data for testing."""
@@ -27,20 +26,23 @@ class TestPageModel:
"author_email": "test@example.com",
"editor": "markdown",
"created_at": "2023-01-01T00:00:00Z",
"updated_at": "2023-01-02T00:00:00Z"
"updated_at": "2023-01-02T00:00:00Z",
}
def test_page_creation_valid(self, valid_page_data):
"""Test creating a valid page."""
page = Page(**valid_page_data)
assert page.id == 123
assert page.title == "Test Page"
assert page.path == "test-page"
assert page.content == "# Test Page\n\nThis is test content with **bold** and *italic* text."
assert (
page.content
== "# Test Page\n\nThis is test content with **bold** and *italic* text."
)
assert page.is_published is True
assert page.tags == ["test", "example"]
def test_page_creation_minimal(self):
"""Test creating page with minimal required fields."""
page = Page(
@@ -49,14 +51,14 @@ class TestPageModel:
path="minimal",
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.id == 1
assert page.title == "Minimal Page"
assert page.is_published is True # Default value
assert page.tags == [] # Default value
def test_page_path_validation_valid(self):
"""Test valid path validation."""
valid_paths = [
@@ -64,9 +66,9 @@ class TestPageModel:
"path/with/slashes",
"path_with_underscores",
"path123",
"category/subcategory/page-name"
"category/subcategory/page-name",
]
for path in valid_paths:
page = Page(
id=1,
@@ -74,10 +76,10 @@ class TestPageModel:
path=path,
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.path == path
def test_page_path_validation_invalid(self):
"""Test invalid path validation."""
invalid_paths = [
@@ -86,7 +88,7 @@ class TestPageModel:
"path@with@symbols", # Special characters
"path.with.dots", # Dots
]
for path in invalid_paths:
with pytest.raises(ValueError):
Page(
@@ -95,9 +97,9 @@ class TestPageModel:
path=path,
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
def test_page_path_normalization(self):
"""Test path normalization."""
# Leading/trailing slashes should be removed
@@ -107,10 +109,10 @@ class TestPageModel:
path="/path/to/page/",
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.path == "path/to/page"
def test_page_title_validation_valid(self):
"""Test valid title validation."""
page = Page(
@@ -119,10 +121,10 @@ class TestPageModel:
path="test",
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.title == "Valid Title with Spaces"
def test_page_title_validation_invalid(self):
"""Test invalid title validation."""
invalid_titles = [
@@ -130,7 +132,7 @@ class TestPageModel:
" ", # Only whitespace
"x" * 256, # Too long
]
for title in invalid_titles:
with pytest.raises(ValueError):
Page(
@@ -139,16 +141,16 @@ class TestPageModel:
path="test",
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
def test_page_word_count(self, valid_page_data):
"""Test word count calculation."""
page = Page(**valid_page_data)
# "# Test Page\n\nThis is test content with **bold** and *italic* text."
# Words: Test, Page, This, is, test, content, with, bold, and, italic, text
assert page.word_count == 12
def test_page_word_count_empty_content(self):
"""Test word count with empty content."""
page = Page(
@@ -157,16 +159,16 @@ class TestPageModel:
path="test",
content="",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.word_count == 0
def test_page_reading_time(self, valid_page_data):
"""Test reading time calculation."""
page = Page(**valid_page_data)
# 11 words, assuming 200 words per minute, should be 1 minute (minimum)
assert page.reading_time == 1
def test_page_reading_time_long_content(self):
"""Test reading time with long content."""
long_content = " ".join(["word"] * 500) # 500 words
@@ -176,20 +178,20 @@ class TestPageModel:
path="test",
content=long_content,
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
# 500 words / 200 words per minute = 2.5, rounded down to 2
assert page.reading_time == 2
def test_page_url_path(self, valid_page_data):
"""Test URL path generation."""
page = Page(**valid_page_data)
assert page.url_path == "/test-page"
def test_page_extract_headings(self):
"""Test heading extraction from markdown content."""
content = """# Main Title
Some content here.
## Secondary Heading
@@ -197,26 +199,31 @@ Some content here.
More content.
### Third Level
And more content.
## Another Secondary
Final content."""
page = Page(
id=1,
title="Test",
path="test",
content=content,
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
headings = page.extract_headings()
expected = ["Main Title", "Secondary Heading", "Third Level", "Another Secondary"]
expected = [
"Main Title",
"Secondary Heading",
"Third Level",
"Another Secondary",
]
assert headings == expected
def test_page_extract_headings_empty_content(self):
"""Test heading extraction with no content."""
page = Page(
@@ -225,19 +232,19 @@ Final content."""
path="test",
content="",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.extract_headings() == []
def test_page_has_tag(self, valid_page_data):
"""Test tag checking."""
page = Page(**valid_page_data)
assert page.has_tag("test") is True
assert page.has_tag("example") is True
assert page.has_tag("TEST") is True # Case insensitive
assert page.has_tag("nonexistent") is False
def test_page_has_tag_no_tags(self):
"""Test tag checking with no tags."""
page = Page(
@@ -246,28 +253,28 @@ Final content."""
path="test",
content="Content",
created_at="2023-01-01T00:00:00Z",
updated_at="2023-01-01T00:00:00Z"
updated_at="2023-01-01T00:00:00Z",
)
assert page.has_tag("any") is False
class TestPageCreateModel:
"""Test PageCreate model functionality."""
def test_page_create_valid(self):
"""Test creating valid PageCreate."""
page_create = PageCreate(
title="New Page",
path="new-page",
content="# New Page\n\nContent here."
content="# New Page\n\nContent here.",
)
assert page_create.title == "New Page"
assert page_create.path == "new-page"
assert page_create.content == "# New Page\n\nContent here."
assert page_create.is_published is True # Default
assert page_create.editor == "markdown" # Default
def test_page_create_with_optional_fields(self):
"""Test PageCreate with optional fields."""
page_create = PageCreate(
@@ -279,65 +286,62 @@ class TestPageCreateModel:
is_private=True,
tags=["new", "test"],
locale="fr",
editor="html"
editor="html",
)
assert page_create.description == "A new page"
assert page_create.is_published is False
assert page_create.is_private is True
assert page_create.tags == ["new", "test"]
assert page_create.locale == "fr"
assert page_create.editor == "html"
def test_page_create_path_validation(self):
"""Test path validation in PageCreate."""
# Valid path
PageCreate(title="Test", path="valid-path", content="Content")
# Invalid paths should raise errors
with pytest.raises(ValueError):
PageCreate(title="Test", path="", content="Content")
with pytest.raises(ValueError):
PageCreate(title="Test", path="invalid path", content="Content")
def test_page_create_title_validation(self):
"""Test title validation in PageCreate."""
# Valid title
PageCreate(title="Valid Title", path="test", content="Content")
# Invalid titles should raise errors
with pytest.raises(ValueError):
PageCreate(title="", path="test", content="Content")
with pytest.raises(ValueError):
PageCreate(title="x" * 256, path="test", content="Content")
class TestPageUpdateModel:
"""Test PageUpdate model functionality."""
def test_page_update_empty(self):
"""Test creating empty PageUpdate."""
page_update = PageUpdate()
assert page_update.title is None
assert page_update.content is None
assert page_update.description is None
assert page_update.is_published is None
assert page_update.tags is None
def test_page_update_partial(self):
"""Test partial PageUpdate."""
page_update = PageUpdate(
title="Updated Title",
content="Updated content"
)
page_update = PageUpdate(title="Updated Title", content="Updated content")
assert page_update.title == "Updated Title"
assert page_update.content == "Updated content"
assert page_update.description is None # Not updated
def test_page_update_full(self):
"""Test full PageUpdate."""
page_update = PageUpdate(
@@ -346,27 +350,27 @@ class TestPageUpdateModel:
description="Updated description",
is_published=False,
is_private=True,
tags=["updated", "test"]
tags=["updated", "test"],
)
assert page_update.title == "Updated Title"
assert page_update.content == "Updated content"
assert page_update.description == "Updated description"
assert page_update.is_published is False
assert page_update.is_private is True
assert page_update.tags == ["updated", "test"]
def test_page_update_title_validation(self):
"""Test title validation in PageUpdate."""
# Valid title
PageUpdate(title="Valid Title")
# None should be allowed (no update)
PageUpdate(title=None)
# Invalid titles should raise errors
with pytest.raises(ValueError):
PageUpdate(title="")
with pytest.raises(ValueError):
PageUpdate(title="x" * 256)
PageUpdate(title="x" * 256)

View File

@@ -1,9 +1,10 @@
"""Tests for WikiJS client."""
import json
import pytest
from unittest.mock import Mock, patch
import pytest
from wikijs.auth import APIKeyAuth
from wikijs.client import WikiJSClient
from wikijs.exceptions import (
@@ -17,177 +18,185 @@ from wikijs.exceptions import (
class TestWikiJSClientInit:
"""Test WikiJSClient initialization."""
def test_init_with_api_key_string(self):
"""Test initialization with API key string."""
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient("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")
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient("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"):
WikiJSClient("https://wiki.example.com", auth=123)
def test_init_with_custom_settings(self):
"""Test initialization with custom settings."""
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient(
"https://wiki.example.com",
auth="test-key",
timeout=60,
verify_ssl=False,
user_agent="Custom Agent"
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."""
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient("https://wiki.example.com", auth="test-key")
assert hasattr(client, 'pages')
assert hasattr(client, "pages")
assert client.pages._client is client
class TestWikiJSClientTestConnection:
"""Test WikiJSClient connection testing."""
@pytest.fixture
def mock_wiki_base_url(self):
"""Mock wiki base URL."""
return "https://wiki.example.com"
@pytest.fixture
def mock_api_key(self):
"""Mock API key."""
return "test-api-key-12345"
@patch('wikijs.client.requests.Session.get')
@patch("wikijs.client.requests.Session.get")
def test_test_connection_success(self, mock_get, mock_wiki_base_url, mock_api_key):
"""Test successful connection test."""
mock_response = Mock()
mock_response.status_code = 200
mock_get.return_value = mock_response
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
result = client.test_connection()
assert result is True
@patch('wikijs.client.requests.Session.get')
@patch("wikijs.client.requests.Session.get")
def test_test_connection_timeout(self, mock_get, mock_wiki_base_url, mock_api_key):
"""Test connection test timeout."""
import requests
mock_get.side_effect = requests.exceptions.Timeout("Request timed out")
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(TimeoutError, match="Connection test timed out"):
client.test_connection()
@patch('wikijs.client.requests.Session.get')
@patch("wikijs.client.requests.Session.get")
def test_test_connection_error(self, mock_get, mock_wiki_base_url, mock_api_key):
"""Test connection test with connection error."""
import requests
mock_get.side_effect = requests.exceptions.ConnectionError("Connection failed")
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(ConnectionError, match="Cannot connect"):
client.test_connection()
def test_test_connection_no_base_url(self):
"""Test connection test with no base URL."""
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient("https://wiki.example.com", auth="test-key")
client.base_url = "" # Simulate empty base URL after creation
with pytest.raises(ConfigurationError, match="Base URL not configured"):
client.test_connection()
def test_test_connection_no_auth(self):
"""Test connection test with no auth."""
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient("https://wiki.example.com", auth="test-key")
client._auth_handler = None # Simulate no auth
with pytest.raises(ConfigurationError, match="Authentication not configured"):
with pytest.raises(
ConfigurationError, match="Authentication not configured"
):
client.test_connection()
class TestWikiJSClientRequests:
"""Test WikiJSClient HTTP request handling."""
@pytest.fixture
def mock_wiki_base_url(self):
"""Mock wiki base URL."""
return "https://wiki.example.com"
@pytest.fixture
def mock_api_key(self):
"""Mock API key."""
return "test-api-key-12345"
@patch('wikijs.client.requests.Session.request')
@patch("wikijs.client.requests.Session.request")
def test_request_success(self, mock_request, mock_wiki_base_url, mock_api_key):
"""Test successful API request."""
mock_response = Mock()
mock_response.ok = True
mock_response.json.return_value = {"data": "test"}
mock_request.return_value = mock_response
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
result = client._request("GET", "/test")
assert result == {"data": "test"}
mock_request.assert_called_once()
@patch('wikijs.client.requests.Session.request')
def test_request_with_json_data(self, mock_request, mock_wiki_base_url, mock_api_key):
@patch("wikijs.client.requests.Session.request")
def test_request_with_json_data(
self, mock_request, mock_wiki_base_url, mock_api_key
):
"""Test API request with JSON data."""
mock_response = Mock()
mock_response.ok = True
mock_response.json.return_value = {"success": True}
mock_request.return_value = mock_response
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
result = client._request("POST", "/test", json_data={"title": "Test"})
assert result == {"success": True}
mock_request.assert_called_once()
@patch('wikijs.client.requests.Session.request')
def test_request_authentication_error(self, mock_request, mock_wiki_base_url, mock_api_key):
@patch("wikijs.client.requests.Session.request")
def test_request_authentication_error(
self, mock_request, mock_wiki_base_url, mock_api_key
):
"""Test request with authentication error."""
mock_response = Mock()
mock_response.ok = False
mock_response.status_code = 401
mock_request.return_value = mock_response
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(AuthenticationError, match="Authentication failed"):
client._request("GET", "/test")
@patch('wikijs.client.requests.Session.request')
@patch("wikijs.client.requests.Session.request")
def test_request_api_error(self, mock_request, mock_wiki_base_url, mock_api_key):
"""Test request with API error."""
mock_response = Mock()
@@ -195,129 +204,145 @@ class TestWikiJSClientRequests:
mock_response.status_code = 404
mock_response.text = "Not found"
mock_request.return_value = mock_response
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(APIError):
client._request("GET", "/test")
@patch('wikijs.client.requests.Session.request')
def test_request_invalid_json_response(self, mock_request, mock_wiki_base_url, mock_api_key):
@patch("wikijs.client.requests.Session.request")
def test_request_invalid_json_response(
self, mock_request, mock_wiki_base_url, mock_api_key
):
"""Test request with invalid JSON response."""
mock_response = Mock()
mock_response.ok = True
mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
mock_request.return_value = mock_response
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(APIError, match="Invalid JSON response"):
client._request("GET", "/test")
@patch('wikijs.client.requests.Session.request')
@patch("wikijs.client.requests.Session.request")
def test_request_timeout(self, mock_request, mock_wiki_base_url, mock_api_key):
"""Test request timeout handling."""
import requests
mock_request.side_effect = requests.exceptions.Timeout("Request timed out")
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(TimeoutError, match="Request timed out"):
client._request("GET", "/test")
@patch('wikijs.client.requests.Session.request')
def test_request_connection_error(self, mock_request, mock_wiki_base_url, mock_api_key):
@patch("wikijs.client.requests.Session.request")
def test_request_connection_error(
self, mock_request, mock_wiki_base_url, mock_api_key
):
"""Test request connection error handling."""
import requests
mock_request.side_effect = requests.exceptions.ConnectionError("Connection failed")
mock_request.side_effect = requests.exceptions.ConnectionError(
"Connection failed"
)
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(ConnectionError, match="Failed to connect"):
client._request("GET", "/test")
@patch('wikijs.client.requests.Session.request')
def test_request_general_exception(self, mock_request, mock_wiki_base_url, mock_api_key):
@patch("wikijs.client.requests.Session.request")
def test_request_general_exception(
self, mock_request, mock_wiki_base_url, mock_api_key
):
"""Test request general exception handling."""
import requests
mock_request.side_effect = requests.exceptions.RequestException("General error")
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
with pytest.raises(APIError, match="Request failed"):
client._request("GET", "/test")
class TestWikiJSClientWithDifferentAuth:
"""Test WikiJSClient with different auth types."""
@patch('wikijs.client.requests.Session')
@patch("wikijs.client.requests.Session")
def test_auth_validation_during_session_creation(self, mock_session_class):
"""Test that auth validation happens during session creation."""
mock_session = Mock()
mock_session_class.return_value = mock_session
# Mock auth handler that raises validation error during validation
from wikijs.auth.base import AuthHandler
from wikijs.exceptions import AuthenticationError
mock_auth = Mock(spec=AuthHandler)
mock_auth.validate_credentials.side_effect = AuthenticationError("Invalid credentials")
mock_auth.validate_credentials.side_effect = AuthenticationError(
"Invalid credentials"
)
mock_auth.get_headers.return_value = {}
with pytest.raises(AuthenticationError, match="Invalid credentials"):
WikiJSClient("https://wiki.example.com", auth=mock_auth)
class TestWikiJSClientContextManager:
"""Test WikiJSClient context manager functionality."""
@patch('wikijs.client.requests.Session')
@patch("wikijs.client.requests.Session")
def test_context_manager(self, mock_session_class):
"""Test client as context manager."""
mock_session = Mock()
mock_session_class.return_value = mock_session
with WikiJSClient("https://wiki.example.com", auth="test-key") as client:
assert isinstance(client, WikiJSClient)
# Verify session was closed
mock_session.close.assert_called_once()
@patch('wikijs.client.requests.Session')
@patch("wikijs.client.requests.Session")
def test_close_method(self, mock_session_class):
"""Test explicit close method."""
mock_session = Mock()
mock_session_class.return_value = mock_session
client = WikiJSClient("https://wiki.example.com", auth="test-key")
client.close()
mock_session.close.assert_called_once()
@patch('wikijs.client.requests.Session')
@patch("wikijs.client.requests.Session")
def test_repr(self, mock_session_class):
"""Test string representation."""
mock_session = Mock()
mock_session_class.return_value = mock_session
client = WikiJSClient("https://wiki.example.com", auth="test-key")
repr_str = repr(client)
assert "WikiJSClient" in repr_str
assert "https://wiki.example.com" in repr_str
@patch('wikijs.client.requests.Session')
@patch("wikijs.client.requests.Session")
def test_connection_test_generic_exception(self, mock_session_class):
"""Test connection test with generic exception."""
mock_session = Mock()
mock_session_class.return_value = mock_session
# Mock generic exception during connection test
mock_session.get.side_effect = RuntimeError("Unexpected error")
client = WikiJSClient("https://wiki.example.com", auth="test-key")
from wikijs.exceptions import ConnectionError
with pytest.raises(ConnectionError, match="Connection test failed: Unexpected error"):
client.test_connection()
with pytest.raises(
ConnectionError, match="Connection test failed: Unexpected error"
):
client.test_connection()

View File

@@ -1,34 +1,33 @@
"""Tests for exception classes."""
import pytest
from unittest.mock import Mock
from wikijs.exceptions import (
WikiJSException,
APIError,
ClientError,
ServerError,
AuthenticationError,
ClientError,
ConfigurationError,
ValidationError,
ConnectionError,
NotFoundError,
PermissionError,
RateLimitError,
ConnectionError,
ServerError,
TimeoutError,
ValidationError,
WikiJSException,
create_api_error,
)
class TestWikiJSException:
"""Test base exception class."""
def test_basic_exception_creation(self):
"""Test basic exception creation."""
exc = WikiJSException("Test error")
assert str(exc) == "Test error"
assert exc.message == "Test error"
def test_exception_with_details(self):
"""Test exception with details."""
details = {"code": "TEST_ERROR", "field": "title"}
@@ -38,13 +37,13 @@ class TestWikiJSException:
class TestAPIError:
"""Test API error classes."""
def test_api_error_creation(self):
"""Test API error with status code and response."""
response = Mock()
response.status_code = 500
response.text = "Internal server error"
exc = APIError("Server error", status_code=500, response=response)
assert exc.status_code == 500
assert exc.response == response
@@ -53,14 +52,14 @@ class TestAPIError:
class TestRateLimitError:
"""Test rate limit error."""
def test_rate_limit_error_with_retry_after(self):
"""Test rate limit error with retry_after parameter."""
exc = RateLimitError("Rate limit exceeded", retry_after=60)
assert exc.status_code == 429
assert exc.retry_after == 60
assert str(exc) == "Rate limit exceeded"
def test_rate_limit_error_without_retry_after(self):
"""Test rate limit error without retry_after parameter."""
exc = RateLimitError("Rate limit exceeded")
@@ -70,7 +69,7 @@ class TestRateLimitError:
class TestCreateAPIError:
"""Test create_api_error factory function."""
def test_create_404_error(self):
"""Test creating 404 NotFoundError."""
response = Mock()
@@ -78,14 +77,14 @@ class TestCreateAPIError:
assert isinstance(error, NotFoundError)
assert error.status_code == 404
assert error.response == response
def test_create_403_error(self):
"""Test creating 403 PermissionError."""
response = Mock()
error = create_api_error(403, "Forbidden", response)
assert isinstance(error, PermissionError)
assert error.status_code == 403
def test_create_429_error(self):
"""Test creating 429 RateLimitError."""
response = Mock()
@@ -94,21 +93,21 @@ class TestCreateAPIError:
assert error.status_code == 429
# Note: RateLimitError constructor hardcodes status_code=429
# so it doesn't use the passed status_code parameter
def test_create_400_client_error(self):
"""Test creating generic 400-level ClientError."""
response = Mock()
error = create_api_error(400, "Bad request", response)
assert isinstance(error, ClientError)
assert error.status_code == 400
def test_create_500_server_error(self):
"""Test creating generic 500-level ServerError."""
response = Mock()
error = create_api_error(500, "Server error", response)
assert isinstance(error, ServerError)
assert error.status_code == 500
def test_create_unknown_status_error(self):
"""Test creating error with unknown status code."""
response = Mock()
@@ -119,33 +118,33 @@ class TestCreateAPIError:
class TestSimpleExceptions:
"""Test simple exception classes."""
def test_connection_error(self):
"""Test ConnectionError creation."""
exc = ConnectionError("Connection failed")
assert str(exc) == "Connection failed"
assert isinstance(exc, WikiJSException)
def test_timeout_error(self):
"""Test TimeoutError creation."""
exc = TimeoutError("Request timed out")
assert str(exc) == "Request timed out"
assert isinstance(exc, WikiJSException)
def test_authentication_error(self):
"""Test AuthenticationError creation."""
exc = AuthenticationError("Invalid credentials")
assert str(exc) == "Invalid credentials"
assert isinstance(exc, WikiJSException)
def test_configuration_error(self):
"""Test ConfigurationError creation."""
exc = ConfigurationError("Invalid config")
assert str(exc) == "Invalid config"
assert isinstance(exc, WikiJSException)
def test_validation_error(self):
"""Test ValidationError creation."""
exc = ValidationError("Invalid input")
assert str(exc) == "Invalid input"
assert isinstance(exc, WikiJSException)
assert isinstance(exc, WikiJSException)

View File

@@ -1,65 +1,66 @@
"""Integration tests for the full WikiJS client with Pages API."""
import pytest
from unittest.mock import Mock, patch
from wikijs import WikiJSClient
from wikijs.endpoints.pages import PagesEndpoint
from wikijs.models.page import Page, PageCreate
from wikijs.models.page import Page
class TestWikiJSClientIntegration:
"""Integration tests for WikiJS client with Pages API."""
def test_client_has_pages_endpoint(self):
"""Test that client has pages endpoint initialized."""
with patch('wikijs.client.requests.Session'):
with patch("wikijs.client.requests.Session"):
client = WikiJSClient("https://test.wiki", auth="test-key")
assert hasattr(client, 'pages')
assert hasattr(client, "pages")
assert isinstance(client.pages, PagesEndpoint)
assert client.pages._client is client
@patch('wikijs.client.requests.Session')
@patch("wikijs.client.requests.Session")
def test_client_pages_integration(self, mock_session_class):
"""Test that pages endpoint works through client."""
# Mock the session and response
mock_session = Mock()
mock_session_class.return_value = mock_session
mock_response = Mock()
mock_response.ok = True
mock_response.json.return_value = {
"data": {
"pages": [{
"id": 1,
"title": "Test Page",
"path": "test",
"content": "Content",
"isPublished": True,
"isPrivate": False,
"tags": [],
"locale": "en",
"createdAt": "2023-01-01T00:00:00Z",
"updatedAt": "2023-01-01T00:00:00Z"
}]
"pages": [
{
"id": 1,
"title": "Test Page",
"path": "test",
"content": "Content",
"isPublished": True,
"isPrivate": False,
"tags": [],
"locale": "en",
"createdAt": "2023-01-01T00:00:00Z",
"updatedAt": "2023-01-01T00:00:00Z",
}
]
}
}
mock_session.request.return_value = mock_response
# Create client
client = WikiJSClient("https://test.wiki", auth="test-key")
# Call pages.list() through client
pages = client.pages.list()
# Verify it works
assert len(pages) == 1
assert isinstance(pages[0], Page)
assert pages[0].title == "Test Page"
# Verify the request was made
mock_session.request.assert_called_once()
call_args = mock_session.request.call_args
assert call_args[0][0] == "POST" # GraphQL uses POST
assert "/graphql" in call_args[0][1]
assert "/graphql" in call_args[0][1]

View File

@@ -1 +1 @@
"""Tests for utility functions."""
"""Tests for utility functions."""

View File

@@ -1,50 +1,56 @@
"""Tests for utility helper functions."""
import pytest
from unittest.mock import Mock
import pytest
from wikijs.exceptions import ValidationError
from wikijs.utils.helpers import (
normalize_url,
validate_url,
sanitize_path,
build_api_url,
parse_wiki_response,
extract_error_message,
chunk_list,
extract_error_message,
normalize_url,
parse_wiki_response,
safe_get,
sanitize_path,
validate_url,
)
class TestNormalizeUrl:
"""Test URL normalization."""
def test_normalize_url_basic(self):
"""Test basic URL normalization."""
assert normalize_url("https://wiki.example.com") == "https://wiki.example.com"
def test_normalize_url_remove_trailing_slash(self):
"""Test trailing slash removal."""
assert normalize_url("https://wiki.example.com/") == "https://wiki.example.com"
def test_normalize_url_remove_multiple_trailing_slashes(self):
"""Test multiple trailing slash removal."""
assert normalize_url("https://wiki.example.com///") == "https://wiki.example.com"
assert (
normalize_url("https://wiki.example.com///") == "https://wiki.example.com"
)
def test_normalize_url_with_path(self):
"""Test URL with path normalization."""
assert normalize_url("https://wiki.example.com/wiki/") == "https://wiki.example.com/wiki"
assert (
normalize_url("https://wiki.example.com/wiki/")
== "https://wiki.example.com/wiki"
)
def test_normalize_url_empty(self):
"""Test empty URL raises error."""
with pytest.raises(ValidationError, match="Base URL cannot be empty"):
normalize_url("")
def test_normalize_url_none(self):
"""Test None URL raises error."""
with pytest.raises(ValidationError, match="Base URL cannot be empty"):
normalize_url(None)
def test_normalize_url_invalid_scheme(self):
"""Test invalid URL scheme gets https:// prepended."""
# The normalize_url function adds https:// to URLs without checking scheme
@@ -55,49 +61,52 @@ class TestNormalizeUrl:
"""Test invalid URL format raises ValidationError."""
with pytest.raises(ValidationError, match="Invalid URL format"):
normalize_url("not a valid url with spaces")
def test_normalize_url_no_scheme(self):
"""Test URL without scheme gets https:// added."""
result = normalize_url("wiki.example.com")
assert result == "https://wiki.example.com"
def test_normalize_url_with_port(self):
"""Test URL with port."""
assert normalize_url("https://wiki.example.com:8080") == "https://wiki.example.com:8080"
assert (
normalize_url("https://wiki.example.com:8080")
== "https://wiki.example.com:8080"
)
class TestValidateUrl:
"""Test URL validation."""
def test_validate_url_valid_https(self):
"""Test valid HTTPS URL."""
assert validate_url("https://wiki.example.com") is True
def test_validate_url_valid_http(self):
"""Test valid HTTP URL."""
assert validate_url("http://wiki.example.com") is True
def test_validate_url_with_path(self):
"""Test valid URL with path."""
assert validate_url("https://wiki.example.com/wiki") is True
def test_validate_url_with_port(self):
"""Test valid URL with port."""
assert validate_url("https://wiki.example.com:8080") is True
def test_validate_url_invalid_scheme(self):
"""Test invalid URL scheme - validate_url only checks format, not scheme type."""
# validate_url only checks that there's a scheme and netloc, not the scheme type
assert validate_url("ftp://wiki.example.com") is True
def test_validate_url_no_scheme(self):
"""Test URL without scheme."""
assert validate_url("wiki.example.com") is False
def test_validate_url_empty(self):
"""Test empty URL."""
assert validate_url("") is False
def test_validate_url_none(self):
"""Test None URL."""
assert validate_url(None) is False
@@ -105,24 +114,24 @@ class TestValidateUrl:
class TestSanitizePath:
"""Test path sanitization."""
def test_sanitize_path_basic(self):
"""Test basic path sanitization."""
assert sanitize_path("simple-path") == "simple-path"
def test_sanitize_path_with_slashes(self):
"""Test path with slashes."""
assert sanitize_path("/path/to/page/") == "path/to/page"
def test_sanitize_path_multiple_slashes(self):
"""Test path with multiple slashes."""
assert sanitize_path("//path///to//page//") == "path/to/page"
def test_sanitize_path_empty(self):
"""Test empty path raises error."""
with pytest.raises(ValidationError, match="Path cannot be empty"):
sanitize_path("")
def test_sanitize_path_none(self):
"""Test None path raises error."""
with pytest.raises(ValidationError, match="Path cannot be empty"):
@@ -131,27 +140,27 @@ class TestSanitizePath:
class TestBuildApiUrl:
"""Test API URL building."""
def test_build_api_url_basic(self):
"""Test basic API URL building."""
result = build_api_url("https://wiki.example.com", "/test")
assert result == "https://wiki.example.com/test"
def test_build_api_url_with_trailing_slash(self):
"""Test API URL building with trailing slash on base."""
result = build_api_url("https://wiki.example.com/", "/test")
assert result == "https://wiki.example.com/test"
def test_build_api_url_without_leading_slash(self):
"""Test API URL building without leading slash on endpoint."""
result = build_api_url("https://wiki.example.com", "test")
assert result == "https://wiki.example.com/test"
def test_build_api_url_complex_endpoint(self):
"""Test API URL building with complex endpoint."""
result = build_api_url("https://wiki.example.com", "/api/v1/pages")
assert result == "https://wiki.example.com/api/v1/pages"
def test_build_api_url_empty_endpoint(self):
"""Test API URL building with empty endpoint."""
result = build_api_url("https://wiki.example.com", "")
@@ -160,25 +169,25 @@ class TestBuildApiUrl:
class TestParseWikiResponse:
"""Test Wiki.js response parsing."""
def test_parse_wiki_response_with_data(self):
"""Test parsing response with data field."""
response = {"data": {"pages": []}, "meta": {"total": 0}}
result = parse_wiki_response(response)
assert result == {"data": {"pages": []}, "meta": {"total": 0}}
def test_parse_wiki_response_without_data(self):
"""Test parsing response without data field."""
response = {"pages": [], "total": 0}
result = parse_wiki_response(response)
assert result == {"pages": [], "total": 0}
def test_parse_wiki_response_empty(self):
"""Test parsing empty response."""
response = {}
result = parse_wiki_response(response)
assert result == {}
def test_parse_wiki_response_none(self):
"""Test parsing None response."""
result = parse_wiki_response(None)
@@ -187,49 +196,49 @@ class TestParseWikiResponse:
class TestExtractErrorMessage:
"""Test error message extraction."""
def test_extract_error_message_json_with_message(self):
"""Test extracting error from JSON response with message."""
mock_response = Mock()
mock_response.text = '{"message": "Not found"}'
mock_response.json.return_value = {"message": "Not found"}
result = extract_error_message(mock_response)
assert result == "Not found"
def test_extract_error_message_json_with_errors_array(self):
"""Test extracting error from JSON response with error field."""
mock_response = Mock()
mock_response.text = '{"error": "Invalid field"}'
mock_response.json.return_value = {"error": "Invalid field"}
result = extract_error_message(mock_response)
assert result == "Invalid field"
def test_extract_error_message_json_with_error_string(self):
"""Test extracting error from JSON response with error string."""
mock_response = Mock()
mock_response.text = '{"error": "Authentication failed"}'
mock_response.json.return_value = {"error": "Authentication failed"}
result = extract_error_message(mock_response)
assert result == "Authentication failed"
def test_extract_error_message_invalid_json(self):
"""Test extracting error from invalid JSON response."""
mock_response = Mock()
mock_response.text = "Invalid JSON response"
mock_response.json.side_effect = ValueError("Invalid JSON")
result = extract_error_message(mock_response)
assert result == "Invalid JSON response"
def test_extract_error_message_empty_response(self):
"""Test extracting error from empty response."""
mock_response = Mock()
mock_response.text = ""
mock_response.json.side_effect = ValueError("Empty response")
result = extract_error_message(mock_response)
# Should return either empty string or default error message
assert result in ["", "Unknown error"]
@@ -237,30 +246,30 @@ class TestExtractErrorMessage:
class TestChunkList:
"""Test list chunking."""
def test_chunk_list_basic(self):
"""Test basic list chunking."""
items = [1, 2, 3, 4, 5, 6]
result = chunk_list(items, 2)
assert result == [[1, 2], [3, 4], [5, 6]]
def test_chunk_list_uneven(self):
"""Test list chunking with uneven division."""
items = [1, 2, 3, 4, 5]
result = chunk_list(items, 2)
assert result == [[1, 2], [3, 4], [5]]
def test_chunk_list_larger_chunk_size(self):
"""Test list chunking with chunk size larger than list."""
items = [1, 2, 3]
result = chunk_list(items, 5)
assert result == [[1, 2, 3]]
def test_chunk_list_empty(self):
"""Test chunking empty list."""
result = chunk_list([], 2)
assert result == []
def test_chunk_list_chunk_size_one(self):
"""Test chunking with chunk size of 1."""
items = [1, 2, 3]
@@ -270,49 +279,49 @@ class TestChunkList:
class TestSafeGet:
"""Test safe dictionary value retrieval."""
def test_safe_get_existing_key(self):
"""Test getting existing key."""
data = {"key": "value", "nested": {"inner": "data"}}
assert safe_get(data, "key") == "value"
def test_safe_get_missing_key(self):
"""Test getting missing key with default."""
data = {"key": "value"}
assert safe_get(data, "missing") is None
def test_safe_get_missing_key_with_custom_default(self):
"""Test getting missing key with custom default."""
data = {"key": "value"}
assert safe_get(data, "missing", "default") == "default"
def test_safe_get_nested_key(self):
"""Test getting nested key (if supported)."""
data = {"nested": {"inner": "data"}}
# This might not be supported, but test if it works
result = safe_get(data, "nested")
assert result == {"inner": "data"}
def test_safe_get_empty_dict(self):
"""Test getting from empty dictionary."""
assert safe_get({}, "key") is None
def test_safe_get_none_data(self):
"""Test getting from None data."""
with pytest.raises(AttributeError):
safe_get(None, "key")
def test_safe_get_dot_notation(self):
"""Test safe_get with dot notation."""
data = {"user": {"profile": {"name": "John"}}}
assert safe_get(data, "user.profile.name") == "John"
def test_safe_get_dot_notation_missing(self):
"""Test safe_get with dot notation for missing key."""
data = {"user": {"profile": {"name": "John"}}}
assert safe_get(data, "user.missing.name") is None
assert safe_get(data, "user.missing.name", "default") == "default"
def test_safe_get_dot_notation_non_dict(self):
"""Test safe_get with dot notation when intermediate value is not dict."""
data = {"user": "not_a_dict"}
@@ -321,121 +330,129 @@ class TestSafeGet:
class TestUtilityEdgeCases:
"""Test edge cases for utility functions."""
def test_validate_url_with_none(self):
"""Test validate_url with None input."""
assert validate_url(None) is False
def test_validate_url_with_exception(self):
"""Test validate_url when urlparse raises exception."""
# This is hard to trigger, but test the exception path
assert validate_url("") is False
def test_validate_url_malformed_url(self):
"""Test validate_url with malformed URL that causes exception."""
# Test with a string that could cause urlparse to raise an exception
import sys
from unittest.mock import patch
with patch('wikijs.utils.helpers.urlparse') as mock_urlparse:
with patch("wikijs.utils.helpers.urlparse") as mock_urlparse:
mock_urlparse.side_effect = Exception("Parse error")
assert validate_url("http://example.com") is False
def test_sanitize_path_whitespace_only(self):
"""Test sanitize_path with whitespace-only input."""
# Whitespace gets stripped and then triggers the empty path check
with pytest.raises(ValidationError, match="Path contains no valid characters"):
sanitize_path(" ")
def test_sanitize_path_invalid_characters_only(self):
"""Test sanitize_path with only invalid characters."""
with pytest.raises(ValidationError, match="Path contains no valid characters"):
sanitize_path("!@#$%^&*()")
def test_sanitize_path_complex_cleanup(self):
"""Test sanitize_path with complex cleanup needs."""
result = sanitize_path(" //hello world//test// ")
assert result == "hello-world/test"
def test_parse_wiki_response_with_error_dict(self):
"""Test parse_wiki_response with error dict."""
response = {"error": {"message": "Not found", "code": "404"}}
from wikijs.exceptions import APIError
with pytest.raises(APIError, match="API Error: Not found"):
parse_wiki_response(response)
def test_parse_wiki_response_with_error_string(self):
"""Test parse_wiki_response with error string."""
response = {"error": "Simple error message"}
from wikijs.exceptions import APIError
with pytest.raises(APIError, match="API Error: Simple error message"):
parse_wiki_response(response)
def test_parse_wiki_response_with_errors_array(self):
"""Test parse_wiki_response with errors array."""
response = {"errors": [{"message": "GraphQL error"}, {"message": "Another error"}]}
response = {
"errors": [
{"message": "GraphQL error"},
{"message": "Another error"},
]
}
from wikijs.exceptions import APIError
with pytest.raises(APIError, match="GraphQL Error: GraphQL error"):
parse_wiki_response(response)
def test_parse_wiki_response_with_non_dict_errors(self):
"""Test parse_wiki_response with non-dict errors."""
response = {"errors": "String error"}
from wikijs.exceptions import APIError
with pytest.raises(APIError, match="GraphQL Error: String error"):
parse_wiki_response(response)
def test_parse_wiki_response_non_dict_input(self):
"""Test parse_wiki_response with non-dict input."""
assert parse_wiki_response("string") == "string"
assert parse_wiki_response(42) == 42
assert parse_wiki_response([1, 2, 3]) == [1, 2, 3]
def test_extract_error_message_with_nested_error(self):
"""Test extract_error_message with nested error structures."""
mock_response = Mock()
mock_response.text = '{"detail": "Validation failed"}'
mock_response.json.return_value = {"detail": "Validation failed"}
result = extract_error_message(mock_response)
assert result == "Validation failed"
def test_extract_error_message_with_msg_field(self):
"""Test extract_error_message with msg field."""
mock_response = Mock()
mock_response.text = '{"msg": "Short message"}'
mock_response.json.return_value = {"msg": "Short message"}
result = extract_error_message(mock_response)
assert result == "Short message"
def test_extract_error_message_long_text(self):
"""Test extract_error_message with very long response text."""
long_text = "x" * 250 # Longer than 200 chars
mock_response = Mock()
mock_response.text = long_text
mock_response.json.side_effect = ValueError("Invalid JSON")
result = extract_error_message(mock_response)
assert len(result) == 203 # 200 chars + "..."
assert result.endswith("...")
def test_extract_error_message_no_json_no_text(self):
"""Test extract_error_message with object that has neither json nor text."""
obj = "simple string"
result = extract_error_message(obj)
assert result == "simple string"
def test_chunk_list_zero_chunk_size(self):
"""Test chunk_list with zero chunk size."""
with pytest.raises(ValueError, match="Chunk size must be positive"):
chunk_list([1, 2, 3], 0)
def test_chunk_list_negative_chunk_size(self):
"""Test chunk_list with negative chunk size."""
with pytest.raises(ValueError, match="Chunk size must be positive"):
chunk_list([1, 2, 3], -1)
chunk_list([1, 2, 3], -1)