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:
@@ -1 +1 @@
|
||||
"""Tests for wikijs-python-sdk."""
|
||||
"""Tests for wikijs-python-sdk."""
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Authentication tests for wikijs-python-sdk."""
|
||||
"""Authentication tests for wikijs-python-sdk."""
|
||||
|
||||
@@ -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}'"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Tests for API endpoints."""
|
||||
"""Tests for API endpoints."""
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Tests for utility functions."""
|
||||
"""Tests for utility functions."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user