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:
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -23,13 +23,13 @@ jobs:
|
|||||||
pip install -e ".[dev]"
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
- name: Lint with flake8
|
- name: Lint with flake8
|
||||||
run: flake8 wikijs tests
|
run: flake8 wikijs tests --max-line-length=88 --ignore=E203,E501,W503
|
||||||
|
|
||||||
- name: Format check with black
|
- name: Format check with black
|
||||||
run: black --check wikijs tests
|
run: black --check wikijs tests --line-length=88
|
||||||
|
|
||||||
- name: Import sort check
|
- name: Import sort check
|
||||||
run: isort --check-only wikijs tests
|
run: isort --check-only wikijs tests --line-length=88
|
||||||
|
|
||||||
- name: Type check with mypy
|
- name: Type check with mypy
|
||||||
run: mypy wikijs
|
run: mypy wikijs
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class TestAPIKeyAuth:
|
|||||||
|
|
||||||
expected_headers = {
|
expected_headers = {
|
||||||
"Authorization": f"Bearer {mock_api_key}",
|
"Authorization": f"Bearer {mock_api_key}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
assert headers == expected_headers
|
assert headers == expected_headers
|
||||||
|
|
||||||
@@ -75,7 +75,9 @@ class TestAPIKeyAuth:
|
|||||||
|
|
||||||
# Test long key (>8 chars) - shows first 4 and last 4
|
# Test long key (>8 chars) - shows first 4 and last 4
|
||||||
auth = APIKeyAuth("this-is-a-very-long-api-key-for-testing")
|
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
|
assert auth.api_key == expected
|
||||||
|
|
||||||
def test_repr_shows_masked_key(self, mock_api_key):
|
def test_repr_shows_masked_key(self, mock_api_key):
|
||||||
@@ -102,11 +104,19 @@ class TestAPIKeyAuth:
|
|||||||
("abcd", "****"),
|
("abcd", "****"),
|
||||||
("abcdefgh", "********"),
|
("abcdefgh", "********"),
|
||||||
("abcdefghi", "abcd*fghi"), # 9 chars: first 4 + 1 star + last 4
|
("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:
|
for key, expected_mask in test_cases:
|
||||||
auth = APIKeyAuth(key)
|
auth = APIKeyAuth(key)
|
||||||
actual = auth.api_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."""
|
"""Tests for base authentication functionality."""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
from wikijs.auth.base import AuthHandler, NoAuth
|
from wikijs.auth.base import AuthHandler, NoAuth
|
||||||
from wikijs.exceptions import AuthenticationError
|
from wikijs.exceptions import AuthenticationError
|
||||||
@@ -17,6 +16,7 @@ class TestAuthHandler:
|
|||||||
|
|
||||||
def test_validate_credentials_calls_is_valid(self):
|
def test_validate_credentials_calls_is_valid(self):
|
||||||
"""Test that validate_credentials calls is_valid."""
|
"""Test that validate_credentials calls is_valid."""
|
||||||
|
|
||||||
# Create concrete implementation for testing
|
# Create concrete implementation for testing
|
||||||
class TestAuth(AuthHandler):
|
class TestAuth(AuthHandler):
|
||||||
def __init__(self, valid=True):
|
def __init__(self, valid=True):
|
||||||
@@ -46,6 +46,7 @@ class TestAuthHandler:
|
|||||||
|
|
||||||
def test_validate_credentials_raises_on_invalid_after_refresh(self):
|
def test_validate_credentials_raises_on_invalid_after_refresh(self):
|
||||||
"""Test that validate_credentials raises if still invalid after refresh."""
|
"""Test that validate_credentials raises if still invalid after refresh."""
|
||||||
|
|
||||||
class TestAuth(AuthHandler):
|
class TestAuth(AuthHandler):
|
||||||
def get_headers(self):
|
def get_headers(self):
|
||||||
return {"Authorization": "test"}
|
return {"Authorization": "test"}
|
||||||
@@ -57,7 +58,9 @@ class TestAuthHandler:
|
|||||||
pass # No-op refresh
|
pass # No-op refresh
|
||||||
|
|
||||||
auth = TestAuth()
|
auth = TestAuth()
|
||||||
with pytest.raises(AuthenticationError, match="Authentication credentials are invalid"):
|
with pytest.raises(
|
||||||
|
AuthenticationError, match="Authentication credentials are invalid"
|
||||||
|
):
|
||||||
auth.validate_credentials()
|
auth.validate_credentials()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Tests for JWT authentication."""
|
"""Tests for JWT authentication."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -56,7 +56,7 @@ class TestJWTAuth:
|
|||||||
|
|
||||||
expected_headers = {
|
expected_headers = {
|
||||||
"Authorization": f"Bearer {mock_jwt_token}",
|
"Authorization": f"Bearer {mock_jwt_token}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
assert headers == expected_headers
|
assert headers == expected_headers
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class TestJWTAuth:
|
|||||||
auth = JWTAuth(mock_jwt_token, refresh_token, expires_at)
|
auth = JWTAuth(mock_jwt_token, refresh_token, expires_at)
|
||||||
|
|
||||||
# Mock the refresh method to avoid actual implementation
|
# 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")
|
mock_refresh.side_effect = AuthenticationError("Refresh not implemented")
|
||||||
|
|
||||||
with pytest.raises(AuthenticationError):
|
with pytest.raises(AuthenticationError):
|
||||||
@@ -111,7 +111,10 @@ class TestJWTAuth:
|
|||||||
|
|
||||||
def test_refresh_raises_error_without_refresh_token(self, jwt_auth):
|
def test_refresh_raises_error_without_refresh_token(self, jwt_auth):
|
||||||
"""Test that refresh raises error when no refresh token available."""
|
"""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()
|
jwt_auth.refresh()
|
||||||
|
|
||||||
def test_refresh_raises_not_implemented_error(self, mock_jwt_token):
|
def test_refresh_raises_not_implemented_error(self, mock_jwt_token):
|
||||||
@@ -119,7 +122,9 @@ class TestJWTAuth:
|
|||||||
refresh_token = "refresh-token-123"
|
refresh_token = "refresh-token-123"
|
||||||
auth = JWTAuth(mock_jwt_token, refresh_token)
|
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()
|
auth.refresh()
|
||||||
|
|
||||||
def test_is_expired_returns_false_no_expiry(self, jwt_auth):
|
def test_is_expired_returns_false_no_expiry(self, jwt_auth):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import responses
|
import responses
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
from wikijs.auth import APIKeyAuth, JWTAuth, NoAuth
|
from wikijs.auth import APIKeyAuth, JWTAuth, NoAuth
|
||||||
|
|
||||||
@@ -60,21 +59,12 @@ def sample_page_data():
|
|||||||
"content": "This is a test page content.",
|
"content": "This is a test page content.",
|
||||||
"created_at": "2023-01-01T00:00:00Z",
|
"created_at": "2023-01-01T00:00:00Z",
|
||||||
"updated_at": "2023-01-01T12:00:00Z",
|
"updated_at": "2023-01-01T12:00:00Z",
|
||||||
"author": {
|
"author": {"id": 1, "name": "Test User", "email": "test@example.com"},
|
||||||
"id": 1,
|
"tags": ["test", "example"],
|
||||||
"name": "Test User",
|
|
||||||
"email": "test@example.com"
|
|
||||||
},
|
|
||||||
"tags": ["test", "example"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sample_error_response():
|
def sample_error_response():
|
||||||
"""Fixture providing sample error response."""
|
"""Fixture providing sample error response."""
|
||||||
return {
|
return {"error": {"message": "Not found", "code": "PAGE_NOT_FOUND"}}
|
||||||
"error": {
|
|
||||||
"message": "Not found",
|
|
||||||
"code": "PAGE_NOT_FOUND"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""Tests for base endpoint class."""
|
"""Tests for base endpoint class."""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from wikijs.client import WikiJSClient
|
from wikijs.client import WikiJSClient
|
||||||
from wikijs.endpoints.base import BaseEndpoint
|
from wikijs.endpoints.base import BaseEndpoint
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ class TestBaseEndpoint:
|
|||||||
"/test",
|
"/test",
|
||||||
params={"param": "value"},
|
params={"param": "value"},
|
||||||
json_data={"data": "test"},
|
json_data={"data": "test"},
|
||||||
extra_param="extra"
|
extra_param="extra",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify delegation to client
|
# Verify delegation to client
|
||||||
@@ -47,7 +48,7 @@ class TestBaseEndpoint:
|
|||||||
endpoint="/test",
|
endpoint="/test",
|
||||||
params={"param": "value"},
|
params={"param": "value"},
|
||||||
json_data={"data": "test"},
|
json_data={"data": "test"},
|
||||||
extra_param="extra"
|
extra_param="extra",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify response
|
# Verify response
|
||||||
@@ -64,7 +65,7 @@ class TestBaseEndpoint:
|
|||||||
method="GET",
|
method="GET",
|
||||||
endpoint="/test",
|
endpoint="/test",
|
||||||
params={"param": "value"},
|
params={"param": "value"},
|
||||||
json_data=None
|
json_data=None,
|
||||||
)
|
)
|
||||||
assert result == mock_response
|
assert result == mock_response
|
||||||
|
|
||||||
@@ -74,16 +75,14 @@ class TestBaseEndpoint:
|
|||||||
mock_client._request.return_value = mock_response
|
mock_client._request.return_value = mock_response
|
||||||
|
|
||||||
result = base_endpoint._post(
|
result = base_endpoint._post(
|
||||||
"/test",
|
"/test", json_data={"data": "test"}, params={"param": "value"}
|
||||||
json_data={"data": "test"},
|
|
||||||
params={"param": "value"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_client._request.assert_called_once_with(
|
mock_client._request.assert_called_once_with(
|
||||||
method="POST",
|
method="POST",
|
||||||
endpoint="/test",
|
endpoint="/test",
|
||||||
params={"param": "value"},
|
params={"param": "value"},
|
||||||
json_data={"data": "test"}
|
json_data={"data": "test"},
|
||||||
)
|
)
|
||||||
assert result == mock_response
|
assert result == mock_response
|
||||||
|
|
||||||
@@ -93,16 +92,14 @@ class TestBaseEndpoint:
|
|||||||
mock_client._request.return_value = mock_response
|
mock_client._request.return_value = mock_response
|
||||||
|
|
||||||
result = base_endpoint._put(
|
result = base_endpoint._put(
|
||||||
"/test",
|
"/test", json_data={"data": "test"}, params={"param": "value"}
|
||||||
json_data={"data": "test"},
|
|
||||||
params={"param": "value"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_client._request.assert_called_once_with(
|
mock_client._request.assert_called_once_with(
|
||||||
method="PUT",
|
method="PUT",
|
||||||
endpoint="/test",
|
endpoint="/test",
|
||||||
params={"param": "value"},
|
params={"param": "value"},
|
||||||
json_data={"data": "test"}
|
json_data={"data": "test"},
|
||||||
)
|
)
|
||||||
assert result == mock_response
|
assert result == mock_response
|
||||||
|
|
||||||
@@ -117,7 +114,7 @@ class TestBaseEndpoint:
|
|||||||
method="DELETE",
|
method="DELETE",
|
||||||
endpoint="/test",
|
endpoint="/test",
|
||||||
params={"param": "value"},
|
params={"param": "value"},
|
||||||
json_data=None
|
json_data=None,
|
||||||
)
|
)
|
||||||
assert result == mock_response
|
assert result == mock_response
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""Tests for Pages API endpoint."""
|
"""Tests for Pages API endpoint."""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from wikijs.client import WikiJSClient
|
from wikijs.client import WikiJSClient
|
||||||
from wikijs.endpoints.pages import PagesEndpoint
|
from wikijs.endpoints.pages import PagesEndpoint
|
||||||
from wikijs.exceptions import APIError, ValidationError
|
from wikijs.exceptions import APIError, ValidationError
|
||||||
@@ -41,7 +42,7 @@ class TestPagesEndpoint:
|
|||||||
"authorEmail": "test@example.com",
|
"authorEmail": "test@example.com",
|
||||||
"editor": "markdown",
|
"editor": "markdown",
|
||||||
"createdAt": "2023-01-01T00:00:00Z",
|
"createdAt": "2023-01-01T00:00:00Z",
|
||||||
"updatedAt": "2023-01-02T00:00:00Z"
|
"updatedAt": "2023-01-02T00:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -52,7 +53,7 @@ class TestPagesEndpoint:
|
|||||||
path="new-page",
|
path="new-page",
|
||||||
content="# New Page\n\nContent here.",
|
content="# New Page\n\nContent here.",
|
||||||
description="A new page",
|
description="A new page",
|
||||||
tags=["new", "test"]
|
tags=["new", "test"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -61,7 +62,7 @@ class TestPagesEndpoint:
|
|||||||
return PageUpdate(
|
return PageUpdate(
|
||||||
title="Updated Page",
|
title="Updated Page",
|
||||||
content="# Updated Page\n\nUpdated content.",
|
content="# Updated Page\n\nUpdated content.",
|
||||||
tags=["updated", "test"]
|
tags=["updated", "test"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_init(self, mock_client):
|
def test_init(self, mock_client):
|
||||||
@@ -72,11 +73,7 @@ class TestPagesEndpoint:
|
|||||||
def test_list_basic(self, pages_endpoint, sample_page_data):
|
def test_list_basic(self, pages_endpoint, sample_page_data):
|
||||||
"""Test basic page listing."""
|
"""Test basic page listing."""
|
||||||
# Mock the GraphQL response
|
# Mock the GraphQL response
|
||||||
mock_response = {
|
mock_response = {"data": {"pages": [sample_page_data]}}
|
||||||
"data": {
|
|
||||||
"pages": [sample_page_data]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call list method
|
# Call list method
|
||||||
@@ -96,11 +93,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_list_with_parameters(self, pages_endpoint, sample_page_data):
|
def test_list_with_parameters(self, pages_endpoint, sample_page_data):
|
||||||
"""Test page listing with filter parameters."""
|
"""Test page listing with filter parameters."""
|
||||||
mock_response = {
|
mock_response = {"data": {"pages": [sample_page_data]}}
|
||||||
"data": {
|
|
||||||
"pages": [sample_page_data]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call with parameters
|
# Call with parameters
|
||||||
@@ -112,7 +105,7 @@ class TestPagesEndpoint:
|
|||||||
locale="en",
|
locale="en",
|
||||||
author_id=1,
|
author_id=1,
|
||||||
order_by="created_at",
|
order_by="created_at",
|
||||||
order_direction="DESC"
|
order_direction="DESC",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify request
|
# Verify request
|
||||||
@@ -148,15 +141,15 @@ class TestPagesEndpoint:
|
|||||||
pages_endpoint.list(order_by="invalid")
|
pages_endpoint.list(order_by="invalid")
|
||||||
|
|
||||||
# Test invalid order_direction
|
# 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")
|
pages_endpoint.list(order_direction="INVALID")
|
||||||
|
|
||||||
def test_list_api_error(self, pages_endpoint):
|
def test_list_api_error(self, pages_endpoint):
|
||||||
"""Test list method handling API errors."""
|
"""Test list method handling API errors."""
|
||||||
# Mock GraphQL error response
|
# Mock GraphQL error response
|
||||||
mock_response = {
|
mock_response = {"errors": [{"message": "GraphQL error"}]}
|
||||||
"errors": [{"message": "GraphQL error"}]
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
with pytest.raises(APIError, match="GraphQL errors"):
|
with pytest.raises(APIError, match="GraphQL errors"):
|
||||||
@@ -164,11 +157,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_get_success(self, pages_endpoint, sample_page_data):
|
def test_get_success(self, pages_endpoint, sample_page_data):
|
||||||
"""Test getting a page by ID."""
|
"""Test getting a page by ID."""
|
||||||
mock_response = {
|
mock_response = {"data": {"page": sample_page_data}}
|
||||||
"data": {
|
|
||||||
"page": sample_page_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call method
|
# Call method
|
||||||
@@ -197,11 +186,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_get_not_found(self, pages_endpoint):
|
def test_get_not_found(self, pages_endpoint):
|
||||||
"""Test get method when page not found."""
|
"""Test get method when page not found."""
|
||||||
mock_response = {
|
mock_response = {"data": {"page": None}}
|
||||||
"data": {
|
|
||||||
"page": None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
with pytest.raises(APIError, match="Page with ID 123 not found"):
|
with pytest.raises(APIError, match="Page with ID 123 not found"):
|
||||||
@@ -209,11 +194,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_get_by_path_success(self, pages_endpoint, sample_page_data):
|
def test_get_by_path_success(self, pages_endpoint, sample_page_data):
|
||||||
"""Test getting a page by path."""
|
"""Test getting a page by path."""
|
||||||
mock_response = {
|
mock_response = {"data": {"pageByPath": sample_page_data}}
|
||||||
"data": {
|
|
||||||
"pageByPath": sample_page_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call method
|
# Call method
|
||||||
@@ -240,11 +221,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_create_success(self, pages_endpoint, sample_page_create, sample_page_data):
|
def test_create_success(self, pages_endpoint, sample_page_create, sample_page_data):
|
||||||
"""Test creating a new page."""
|
"""Test creating a new page."""
|
||||||
mock_response = {
|
mock_response = {"data": {"createPage": sample_page_data}}
|
||||||
"data": {
|
|
||||||
"createPage": sample_page_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call method
|
# Call method
|
||||||
@@ -269,11 +246,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_create_with_dict(self, pages_endpoint, sample_page_data):
|
def test_create_with_dict(self, pages_endpoint, sample_page_data):
|
||||||
"""Test creating a page with dict data."""
|
"""Test creating a page with dict data."""
|
||||||
mock_response = {
|
mock_response = {"data": {"createPage": sample_page_data}}
|
||||||
"data": {
|
|
||||||
"createPage": sample_page_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
page_dict = {
|
page_dict = {
|
||||||
@@ -291,7 +264,10 @@ class TestPagesEndpoint:
|
|||||||
def test_create_validation_error(self, pages_endpoint):
|
def test_create_validation_error(self, pages_endpoint):
|
||||||
"""Test create method validation errors."""
|
"""Test create method validation errors."""
|
||||||
# Test invalid data type
|
# 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")
|
pages_endpoint.create("invalid")
|
||||||
|
|
||||||
# Test invalid dict data
|
# Test invalid dict data
|
||||||
@@ -300,9 +276,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_create_api_error(self, pages_endpoint, sample_page_create):
|
def test_create_api_error(self, pages_endpoint, sample_page_create):
|
||||||
"""Test create method API errors."""
|
"""Test create method API errors."""
|
||||||
mock_response = {
|
mock_response = {"errors": [{"message": "Creation failed"}]}
|
||||||
"errors": [{"message": "Creation failed"}]
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
with pytest.raises(APIError, match="Failed to create page"):
|
with pytest.raises(APIError, match="Failed to create page"):
|
||||||
@@ -310,11 +284,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_update_success(self, pages_endpoint, sample_page_update, sample_page_data):
|
def test_update_success(self, pages_endpoint, sample_page_update, sample_page_data):
|
||||||
"""Test updating a page."""
|
"""Test updating a page."""
|
||||||
mock_response = {
|
mock_response = {"data": {"updatePage": sample_page_data}}
|
||||||
"data": {
|
|
||||||
"updatePage": sample_page_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call method
|
# Call method
|
||||||
@@ -341,7 +311,10 @@ class TestPagesEndpoint:
|
|||||||
pages_endpoint.update(0, sample_page_update)
|
pages_endpoint.update(0, sample_page_update)
|
||||||
|
|
||||||
# Test invalid page_data type
|
# 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")
|
pages_endpoint.update(123, "invalid")
|
||||||
|
|
||||||
def test_delete_success(self, pages_endpoint):
|
def test_delete_success(self, pages_endpoint):
|
||||||
@@ -350,7 +323,7 @@ class TestPagesEndpoint:
|
|||||||
"data": {
|
"data": {
|
||||||
"deletePage": {
|
"deletePage": {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Page deleted successfully"
|
"message": "Page deleted successfully",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,12 +348,7 @@ class TestPagesEndpoint:
|
|||||||
def test_delete_failure(self, pages_endpoint):
|
def test_delete_failure(self, pages_endpoint):
|
||||||
"""Test delete method when deletion fails."""
|
"""Test delete method when deletion fails."""
|
||||||
mock_response = {
|
mock_response = {
|
||||||
"data": {
|
"data": {"deletePage": {"success": False, "message": "Deletion failed"}}
|
||||||
"deletePage": {
|
|
||||||
"success": False,
|
|
||||||
"message": "Deletion failed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
@@ -389,11 +357,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_search_success(self, pages_endpoint, sample_page_data):
|
def test_search_success(self, pages_endpoint, sample_page_data):
|
||||||
"""Test searching pages."""
|
"""Test searching pages."""
|
||||||
mock_response = {
|
mock_response = {"data": {"pages": [sample_page_data]}}
|
||||||
"data": {
|
|
||||||
"pages": [sample_page_data]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call method
|
# Call method
|
||||||
@@ -421,11 +385,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_get_by_tags_match_all(self, pages_endpoint, sample_page_data):
|
def test_get_by_tags_match_all(self, pages_endpoint, sample_page_data):
|
||||||
"""Test getting pages by tags (match all)."""
|
"""Test getting pages by tags (match all)."""
|
||||||
mock_response = {
|
mock_response = {"data": {"pages": [sample_page_data]}}
|
||||||
"data": {
|
|
||||||
"pages": [sample_page_data]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call method
|
# Call method
|
||||||
@@ -457,7 +417,7 @@ class TestPagesEndpoint:
|
|||||||
"title": "Test",
|
"title": "Test",
|
||||||
"isPublished": True,
|
"isPublished": True,
|
||||||
"authorId": 1,
|
"authorId": 1,
|
||||||
"createdAt": "2023-01-01T00:00:00Z"
|
"createdAt": "2023-01-01T00:00:00Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized = pages_endpoint._normalize_page_data(api_data)
|
normalized = pages_endpoint._normalize_page_data(api_data)
|
||||||
@@ -472,10 +432,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_normalize_page_data_missing_fields(self, pages_endpoint):
|
def test_normalize_page_data_missing_fields(self, pages_endpoint):
|
||||||
"""Test page data normalization with missing fields."""
|
"""Test page data normalization with missing fields."""
|
||||||
api_data = {
|
api_data = {"id": 123, "title": "Test"}
|
||||||
"id": 123,
|
|
||||||
"title": "Test"
|
|
||||||
}
|
|
||||||
|
|
||||||
normalized = pages_endpoint._normalize_page_data(api_data)
|
normalized = pages_endpoint._normalize_page_data(api_data)
|
||||||
|
|
||||||
@@ -485,17 +442,15 @@ class TestPagesEndpoint:
|
|||||||
assert "is_published" not in normalized
|
assert "is_published" not in normalized
|
||||||
assert "tags" in normalized # Should have default value
|
assert "tags" in normalized # Should have default value
|
||||||
|
|
||||||
@patch('wikijs.endpoints.pages.Page')
|
@patch("wikijs.endpoints.pages.Page")
|
||||||
def test_list_page_parsing_error(self, mock_page_class, pages_endpoint, sample_page_data):
|
def test_list_page_parsing_error(
|
||||||
|
self, mock_page_class, pages_endpoint, sample_page_data
|
||||||
|
):
|
||||||
"""Test handling of page parsing errors in list method."""
|
"""Test handling of page parsing errors in list method."""
|
||||||
# Mock Page constructor to raise an exception
|
# Mock Page constructor to raise an exception
|
||||||
mock_page_class.side_effect = ValueError("Parsing error")
|
mock_page_class.side_effect = ValueError("Parsing error")
|
||||||
|
|
||||||
mock_response = {
|
mock_response = {"data": {"pages": [sample_page_data]}}
|
||||||
"data": {
|
|
||||||
"pages": [sample_page_data]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
with pytest.raises(APIError, match="Failed to parse page data"):
|
with pytest.raises(APIError, match="Failed to parse page data"):
|
||||||
@@ -503,11 +458,7 @@ class TestPagesEndpoint:
|
|||||||
|
|
||||||
def test_graphql_query_structure(self, pages_endpoint, sample_page_data):
|
def test_graphql_query_structure(self, pages_endpoint, sample_page_data):
|
||||||
"""Test that GraphQL queries have correct structure."""
|
"""Test that GraphQL queries have correct structure."""
|
||||||
mock_response = {
|
mock_response = {"data": {"pages": [sample_page_data]}}
|
||||||
"data": {
|
|
||||||
"pages": [sample_page_data]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pages_endpoint._post = Mock(return_value=mock_response)
|
pages_endpoint._post = Mock(return_value=mock_response)
|
||||||
|
|
||||||
# Call list method
|
# Call list method
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from wikijs.models.base import BaseModel, TimestampedModel
|
from wikijs.models.base import BaseModel, TimestampedModel
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +49,11 @@ class TestBaseModel:
|
|||||||
|
|
||||||
# Test including None values
|
# Test including None values
|
||||||
result_include = model.to_dict(exclude_none=False)
|
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
|
assert result_include == expected_include
|
||||||
|
|
||||||
def test_to_json_basic(self):
|
def test_to_json_basic(self):
|
||||||
@@ -77,7 +79,11 @@ class TestBaseModel:
|
|||||||
# Test including None values
|
# Test including None values
|
||||||
result_include = model.to_json(exclude_none=False)
|
result_include = model.to_json(exclude_none=False)
|
||||||
parsed_include = json.loads(result_include)
|
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
|
assert parsed_include == expected_include
|
||||||
|
|
||||||
def test_from_dict(self):
|
def test_from_dict(self):
|
||||||
@@ -113,9 +119,7 @@ class TestTimestampedModel:
|
|||||||
"""Test timestamped model with timestamps."""
|
"""Test timestamped model with timestamps."""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
model = TestTimestampedModelForTesting(
|
model = TestTimestampedModelForTesting(
|
||||||
title="Test Title",
|
title="Test Title", created_at=now, updated_at=now
|
||||||
created_at=now,
|
|
||||||
updated_at=now
|
|
||||||
)
|
)
|
||||||
assert model.title == "Test Title"
|
assert model.title == "Test Title"
|
||||||
assert model.created_at == now
|
assert model.created_at == now
|
||||||
@@ -129,19 +133,14 @@ class TestTimestampedModel:
|
|||||||
def test_is_new_property_false(self):
|
def test_is_new_property_false(self):
|
||||||
"""Test is_new property returns False for existing models."""
|
"""Test is_new property returns False for existing models."""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
model = TestTimestampedModelForTesting(
|
model = TestTimestampedModelForTesting(title="Test Title", created_at=now)
|
||||||
title="Test Title",
|
|
||||||
created_at=now
|
|
||||||
)
|
|
||||||
assert model.is_new is False
|
assert model.is_new is False
|
||||||
|
|
||||||
def test_datetime_serialization(self):
|
def test_datetime_serialization(self):
|
||||||
"""Test datetime serialization in JSON."""
|
"""Test datetime serialization in JSON."""
|
||||||
now = datetime(2023, 1, 1, 12, 0, 0)
|
now = datetime(2023, 1, 1, 12, 0, 0)
|
||||||
model = TestTimestampedModelForTesting(
|
model = TestTimestampedModelForTesting(
|
||||||
title="Test Title",
|
title="Test Title", created_at=now, updated_at=now
|
||||||
created_at=now,
|
|
||||||
updated_at=now
|
|
||||||
)
|
)
|
||||||
|
|
||||||
json_str = model.to_json()
|
json_str = model.to_json()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""Tests for Page models."""
|
"""Tests for Page models."""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from wikijs.models.page import Page, PageCreate, PageUpdate
|
from wikijs.models.page import Page, PageCreate, PageUpdate
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ class TestPageModel:
|
|||||||
"author_email": "test@example.com",
|
"author_email": "test@example.com",
|
||||||
"editor": "markdown",
|
"editor": "markdown",
|
||||||
"created_at": "2023-01-01T00:00:00Z",
|
"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):
|
def test_page_creation_valid(self, valid_page_data):
|
||||||
@@ -37,7 +36,10 @@ class TestPageModel:
|
|||||||
assert page.id == 123
|
assert page.id == 123
|
||||||
assert page.title == "Test Page"
|
assert page.title == "Test Page"
|
||||||
assert page.path == "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.is_published is True
|
||||||
assert page.tags == ["test", "example"]
|
assert page.tags == ["test", "example"]
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class TestPageModel:
|
|||||||
path="minimal",
|
path="minimal",
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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.id == 1
|
||||||
@@ -64,7 +66,7 @@ class TestPageModel:
|
|||||||
"path/with/slashes",
|
"path/with/slashes",
|
||||||
"path_with_underscores",
|
"path_with_underscores",
|
||||||
"path123",
|
"path123",
|
||||||
"category/subcategory/page-name"
|
"category/subcategory/page-name",
|
||||||
]
|
]
|
||||||
|
|
||||||
for path in valid_paths:
|
for path in valid_paths:
|
||||||
@@ -74,7 +76,7 @@ class TestPageModel:
|
|||||||
path=path,
|
path=path,
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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
|
assert page.path == path
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ class TestPageModel:
|
|||||||
path=path,
|
path=path,
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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):
|
def test_page_path_normalization(self):
|
||||||
@@ -107,7 +109,7 @@ class TestPageModel:
|
|||||||
path="/path/to/page/",
|
path="/path/to/page/",
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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"
|
assert page.path == "path/to/page"
|
||||||
|
|
||||||
@@ -119,7 +121,7 @@ class TestPageModel:
|
|||||||
path="test",
|
path="test",
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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"
|
assert page.title == "Valid Title with Spaces"
|
||||||
|
|
||||||
@@ -139,7 +141,7 @@ class TestPageModel:
|
|||||||
path="test",
|
path="test",
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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):
|
def test_page_word_count(self, valid_page_data):
|
||||||
@@ -157,7 +159,7 @@ class TestPageModel:
|
|||||||
path="test",
|
path="test",
|
||||||
content="",
|
content="",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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
|
assert page.word_count == 0
|
||||||
|
|
||||||
@@ -176,7 +178,7 @@ class TestPageModel:
|
|||||||
path="test",
|
path="test",
|
||||||
content=long_content,
|
content=long_content,
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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
|
# 500 words / 200 words per minute = 2.5, rounded down to 2
|
||||||
assert page.reading_time == 2
|
assert page.reading_time == 2
|
||||||
@@ -210,11 +212,16 @@ Final content."""
|
|||||||
path="test",
|
path="test",
|
||||||
content=content,
|
content=content,
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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()
|
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
|
assert headings == expected
|
||||||
|
|
||||||
def test_page_extract_headings_empty_content(self):
|
def test_page_extract_headings_empty_content(self):
|
||||||
@@ -225,7 +232,7 @@ Final content."""
|
|||||||
path="test",
|
path="test",
|
||||||
content="",
|
content="",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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() == []
|
assert page.extract_headings() == []
|
||||||
|
|
||||||
@@ -246,7 +253,7 @@ Final content."""
|
|||||||
path="test",
|
path="test",
|
||||||
content="Content",
|
content="Content",
|
||||||
created_at="2023-01-01T00:00:00Z",
|
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
|
assert page.has_tag("any") is False
|
||||||
|
|
||||||
@@ -259,7 +266,7 @@ class TestPageCreateModel:
|
|||||||
page_create = PageCreate(
|
page_create = PageCreate(
|
||||||
title="New Page",
|
title="New Page",
|
||||||
path="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.title == "New Page"
|
||||||
@@ -279,7 +286,7 @@ class TestPageCreateModel:
|
|||||||
is_private=True,
|
is_private=True,
|
||||||
tags=["new", "test"],
|
tags=["new", "test"],
|
||||||
locale="fr",
|
locale="fr",
|
||||||
editor="html"
|
editor="html",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert page_create.description == "A new page"
|
assert page_create.description == "A new page"
|
||||||
@@ -329,10 +336,7 @@ class TestPageUpdateModel:
|
|||||||
|
|
||||||
def test_page_update_partial(self):
|
def test_page_update_partial(self):
|
||||||
"""Test partial PageUpdate."""
|
"""Test partial PageUpdate."""
|
||||||
page_update = PageUpdate(
|
page_update = PageUpdate(title="Updated Title", content="Updated content")
|
||||||
title="Updated Title",
|
|
||||||
content="Updated content"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert page_update.title == "Updated Title"
|
assert page_update.title == "Updated Title"
|
||||||
assert page_update.content == "Updated content"
|
assert page_update.content == "Updated content"
|
||||||
@@ -346,7 +350,7 @@ class TestPageUpdateModel:
|
|||||||
description="Updated description",
|
description="Updated description",
|
||||||
is_published=False,
|
is_published=False,
|
||||||
is_private=True,
|
is_private=True,
|
||||||
tags=["updated", "test"]
|
tags=["updated", "test"],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert page_update.title == "Updated Title"
|
assert page_update.title == "Updated Title"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"""Tests for WikiJS client."""
|
"""Tests for WikiJS client."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import pytest
|
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from wikijs.auth import APIKeyAuth
|
from wikijs.auth import APIKeyAuth
|
||||||
from wikijs.client import WikiJSClient
|
from wikijs.client import WikiJSClient
|
||||||
from wikijs.exceptions import (
|
from wikijs.exceptions import (
|
||||||
@@ -20,7 +21,7 @@ class TestWikiJSClientInit:
|
|||||||
|
|
||||||
def test_init_with_api_key_string(self):
|
def test_init_with_api_key_string(self):
|
||||||
"""Test initialization with API key string."""
|
"""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")
|
client = WikiJSClient("https://wiki.example.com", auth="test-key")
|
||||||
|
|
||||||
assert client.base_url == "https://wiki.example.com"
|
assert client.base_url == "https://wiki.example.com"
|
||||||
@@ -33,7 +34,7 @@ class TestWikiJSClientInit:
|
|||||||
"""Test initialization with auth handler."""
|
"""Test initialization with auth handler."""
|
||||||
auth_handler = APIKeyAuth("test-key")
|
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)
|
client = WikiJSClient("https://wiki.example.com", auth=auth_handler)
|
||||||
|
|
||||||
assert client._auth_handler is auth_handler
|
assert client._auth_handler is auth_handler
|
||||||
@@ -45,13 +46,13 @@ class TestWikiJSClientInit:
|
|||||||
|
|
||||||
def test_init_with_custom_settings(self):
|
def test_init_with_custom_settings(self):
|
||||||
"""Test initialization with custom settings."""
|
"""Test initialization with custom settings."""
|
||||||
with patch('wikijs.client.requests.Session'):
|
with patch("wikijs.client.requests.Session"):
|
||||||
client = WikiJSClient(
|
client = WikiJSClient(
|
||||||
"https://wiki.example.com",
|
"https://wiki.example.com",
|
||||||
auth="test-key",
|
auth="test-key",
|
||||||
timeout=60,
|
timeout=60,
|
||||||
verify_ssl=False,
|
verify_ssl=False,
|
||||||
user_agent="Custom Agent"
|
user_agent="Custom Agent",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert client.timeout == 60
|
assert client.timeout == 60
|
||||||
@@ -60,10 +61,10 @@ class TestWikiJSClientInit:
|
|||||||
|
|
||||||
def test_has_pages_endpoint(self):
|
def test_has_pages_endpoint(self):
|
||||||
"""Test that client has pages endpoint."""
|
"""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")
|
client = WikiJSClient("https://wiki.example.com", auth="test-key")
|
||||||
|
|
||||||
assert hasattr(client, 'pages')
|
assert hasattr(client, "pages")
|
||||||
assert client.pages._client is client
|
assert client.pages._client is client
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ class TestWikiJSClientTestConnection:
|
|||||||
"""Mock API key."""
|
"""Mock API key."""
|
||||||
return "test-api-key-12345"
|
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):
|
def test_test_connection_success(self, mock_get, mock_wiki_base_url, mock_api_key):
|
||||||
"""Test successful connection test."""
|
"""Test successful connection test."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -92,10 +93,11 @@ class TestWikiJSClientTestConnection:
|
|||||||
|
|
||||||
assert result is True
|
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):
|
def test_test_connection_timeout(self, mock_get, mock_wiki_base_url, mock_api_key):
|
||||||
"""Test connection test timeout."""
|
"""Test connection test timeout."""
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
mock_get.side_effect = requests.exceptions.Timeout("Request timed out")
|
mock_get.side_effect = requests.exceptions.Timeout("Request timed out")
|
||||||
|
|
||||||
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
||||||
@@ -103,10 +105,11 @@ class TestWikiJSClientTestConnection:
|
|||||||
with pytest.raises(TimeoutError, match="Connection test timed out"):
|
with pytest.raises(TimeoutError, match="Connection test timed out"):
|
||||||
client.test_connection()
|
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):
|
def test_test_connection_error(self, mock_get, mock_wiki_base_url, mock_api_key):
|
||||||
"""Test connection test with connection error."""
|
"""Test connection test with connection error."""
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
mock_get.side_effect = requests.exceptions.ConnectionError("Connection failed")
|
mock_get.side_effect = requests.exceptions.ConnectionError("Connection failed")
|
||||||
|
|
||||||
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
||||||
@@ -116,7 +119,7 @@ class TestWikiJSClientTestConnection:
|
|||||||
|
|
||||||
def test_test_connection_no_base_url(self):
|
def test_test_connection_no_base_url(self):
|
||||||
"""Test connection test with no base URL."""
|
"""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 = WikiJSClient("https://wiki.example.com", auth="test-key")
|
||||||
client.base_url = "" # Simulate empty base URL after creation
|
client.base_url = "" # Simulate empty base URL after creation
|
||||||
|
|
||||||
@@ -125,11 +128,13 @@ class TestWikiJSClientTestConnection:
|
|||||||
|
|
||||||
def test_test_connection_no_auth(self):
|
def test_test_connection_no_auth(self):
|
||||||
"""Test connection test with no auth."""
|
"""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 = WikiJSClient("https://wiki.example.com", auth="test-key")
|
||||||
client._auth_handler = None # Simulate no auth
|
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()
|
client.test_connection()
|
||||||
|
|
||||||
|
|
||||||
@@ -146,7 +151,7 @@ class TestWikiJSClientRequests:
|
|||||||
"""Mock API key."""
|
"""Mock API key."""
|
||||||
return "test-api-key-12345"
|
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):
|
def test_request_success(self, mock_request, mock_wiki_base_url, mock_api_key):
|
||||||
"""Test successful API request."""
|
"""Test successful API request."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -160,8 +165,10 @@ class TestWikiJSClientRequests:
|
|||||||
assert result == {"data": "test"}
|
assert result == {"data": "test"}
|
||||||
mock_request.assert_called_once()
|
mock_request.assert_called_once()
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session.request')
|
@patch("wikijs.client.requests.Session.request")
|
||||||
def test_request_with_json_data(self, mock_request, mock_wiki_base_url, mock_api_key):
|
def test_request_with_json_data(
|
||||||
|
self, mock_request, mock_wiki_base_url, mock_api_key
|
||||||
|
):
|
||||||
"""Test API request with JSON data."""
|
"""Test API request with JSON data."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
mock_response.ok = True
|
mock_response.ok = True
|
||||||
@@ -174,8 +181,10 @@ class TestWikiJSClientRequests:
|
|||||||
assert result == {"success": True}
|
assert result == {"success": True}
|
||||||
mock_request.assert_called_once()
|
mock_request.assert_called_once()
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session.request')
|
@patch("wikijs.client.requests.Session.request")
|
||||||
def test_request_authentication_error(self, mock_request, mock_wiki_base_url, mock_api_key):
|
def test_request_authentication_error(
|
||||||
|
self, mock_request, mock_wiki_base_url, mock_api_key
|
||||||
|
):
|
||||||
"""Test request with authentication error."""
|
"""Test request with authentication error."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
mock_response.ok = False
|
mock_response.ok = False
|
||||||
@@ -187,7 +196,7 @@ class TestWikiJSClientRequests:
|
|||||||
with pytest.raises(AuthenticationError, match="Authentication failed"):
|
with pytest.raises(AuthenticationError, match="Authentication failed"):
|
||||||
client._request("GET", "/test")
|
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):
|
def test_request_api_error(self, mock_request, mock_wiki_base_url, mock_api_key):
|
||||||
"""Test request with API error."""
|
"""Test request with API error."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -201,8 +210,10 @@ class TestWikiJSClientRequests:
|
|||||||
with pytest.raises(APIError):
|
with pytest.raises(APIError):
|
||||||
client._request("GET", "/test")
|
client._request("GET", "/test")
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session.request')
|
@patch("wikijs.client.requests.Session.request")
|
||||||
def test_request_invalid_json_response(self, mock_request, mock_wiki_base_url, mock_api_key):
|
def test_request_invalid_json_response(
|
||||||
|
self, mock_request, mock_wiki_base_url, mock_api_key
|
||||||
|
):
|
||||||
"""Test request with invalid JSON response."""
|
"""Test request with invalid JSON response."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
mock_response.ok = True
|
mock_response.ok = True
|
||||||
@@ -214,10 +225,11 @@ class TestWikiJSClientRequests:
|
|||||||
with pytest.raises(APIError, match="Invalid JSON response"):
|
with pytest.raises(APIError, match="Invalid JSON response"):
|
||||||
client._request("GET", "/test")
|
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):
|
def test_request_timeout(self, mock_request, mock_wiki_base_url, mock_api_key):
|
||||||
"""Test request timeout handling."""
|
"""Test request timeout handling."""
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
mock_request.side_effect = requests.exceptions.Timeout("Request timed out")
|
mock_request.side_effect = requests.exceptions.Timeout("Request timed out")
|
||||||
|
|
||||||
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
||||||
@@ -225,21 +237,29 @@ class TestWikiJSClientRequests:
|
|||||||
with pytest.raises(TimeoutError, match="Request timed out"):
|
with pytest.raises(TimeoutError, match="Request timed out"):
|
||||||
client._request("GET", "/test")
|
client._request("GET", "/test")
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session.request')
|
@patch("wikijs.client.requests.Session.request")
|
||||||
def test_request_connection_error(self, mock_request, mock_wiki_base_url, mock_api_key):
|
def test_request_connection_error(
|
||||||
|
self, mock_request, mock_wiki_base_url, mock_api_key
|
||||||
|
):
|
||||||
"""Test request connection error handling."""
|
"""Test request connection error handling."""
|
||||||
import requests
|
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)
|
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError, match="Failed to connect"):
|
with pytest.raises(ConnectionError, match="Failed to connect"):
|
||||||
client._request("GET", "/test")
|
client._request("GET", "/test")
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session.request')
|
@patch("wikijs.client.requests.Session.request")
|
||||||
def test_request_general_exception(self, mock_request, mock_wiki_base_url, mock_api_key):
|
def test_request_general_exception(
|
||||||
|
self, mock_request, mock_wiki_base_url, mock_api_key
|
||||||
|
):
|
||||||
"""Test request general exception handling."""
|
"""Test request general exception handling."""
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
mock_request.side_effect = requests.exceptions.RequestException("General error")
|
mock_request.side_effect = requests.exceptions.RequestException("General error")
|
||||||
|
|
||||||
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
client = WikiJSClient(mock_wiki_base_url, auth=mock_api_key)
|
||||||
@@ -251,7 +271,7 @@ class TestWikiJSClientRequests:
|
|||||||
class TestWikiJSClientWithDifferentAuth:
|
class TestWikiJSClientWithDifferentAuth:
|
||||||
"""Test WikiJSClient with different auth types."""
|
"""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):
|
def test_auth_validation_during_session_creation(self, mock_session_class):
|
||||||
"""Test that auth validation happens during session creation."""
|
"""Test that auth validation happens during session creation."""
|
||||||
mock_session = Mock()
|
mock_session = Mock()
|
||||||
@@ -262,7 +282,9 @@ class TestWikiJSClientWithDifferentAuth:
|
|||||||
from wikijs.exceptions import AuthenticationError
|
from wikijs.exceptions import AuthenticationError
|
||||||
|
|
||||||
mock_auth = Mock(spec=AuthHandler)
|
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 = {}
|
mock_auth.get_headers.return_value = {}
|
||||||
|
|
||||||
with pytest.raises(AuthenticationError, match="Invalid credentials"):
|
with pytest.raises(AuthenticationError, match="Invalid credentials"):
|
||||||
@@ -272,7 +294,7 @@ class TestWikiJSClientWithDifferentAuth:
|
|||||||
class TestWikiJSClientContextManager:
|
class TestWikiJSClientContextManager:
|
||||||
"""Test WikiJSClient context manager functionality."""
|
"""Test WikiJSClient context manager functionality."""
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session')
|
@patch("wikijs.client.requests.Session")
|
||||||
def test_context_manager(self, mock_session_class):
|
def test_context_manager(self, mock_session_class):
|
||||||
"""Test client as context manager."""
|
"""Test client as context manager."""
|
||||||
mock_session = Mock()
|
mock_session = Mock()
|
||||||
@@ -284,7 +306,7 @@ class TestWikiJSClientContextManager:
|
|||||||
# Verify session was closed
|
# Verify session was closed
|
||||||
mock_session.close.assert_called_once()
|
mock_session.close.assert_called_once()
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session')
|
@patch("wikijs.client.requests.Session")
|
||||||
def test_close_method(self, mock_session_class):
|
def test_close_method(self, mock_session_class):
|
||||||
"""Test explicit close method."""
|
"""Test explicit close method."""
|
||||||
mock_session = Mock()
|
mock_session = Mock()
|
||||||
@@ -295,7 +317,7 @@ class TestWikiJSClientContextManager:
|
|||||||
|
|
||||||
mock_session.close.assert_called_once()
|
mock_session.close.assert_called_once()
|
||||||
|
|
||||||
@patch('wikijs.client.requests.Session')
|
@patch("wikijs.client.requests.Session")
|
||||||
def test_repr(self, mock_session_class):
|
def test_repr(self, mock_session_class):
|
||||||
"""Test string representation."""
|
"""Test string representation."""
|
||||||
mock_session = Mock()
|
mock_session = Mock()
|
||||||
@@ -307,7 +329,7 @@ class TestWikiJSClientContextManager:
|
|||||||
assert "WikiJSClient" in repr_str
|
assert "WikiJSClient" in repr_str
|
||||||
assert "https://wiki.example.com" 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):
|
def test_connection_test_generic_exception(self, mock_session_class):
|
||||||
"""Test connection test with generic exception."""
|
"""Test connection test with generic exception."""
|
||||||
mock_session = Mock()
|
mock_session = Mock()
|
||||||
@@ -319,5 +341,8 @@ class TestWikiJSClientContextManager:
|
|||||||
client = WikiJSClient("https://wiki.example.com", auth="test-key")
|
client = WikiJSClient("https://wiki.example.com", auth="test-key")
|
||||||
|
|
||||||
from wikijs.exceptions import ConnectionError
|
from wikijs.exceptions import ConnectionError
|
||||||
with pytest.raises(ConnectionError, match="Connection test failed: Unexpected error"):
|
|
||||||
|
with pytest.raises(
|
||||||
|
ConnectionError, match="Connection test failed: Unexpected error"
|
||||||
|
):
|
||||||
client.test_connection()
|
client.test_connection()
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
"""Tests for exception classes."""
|
"""Tests for exception classes."""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from wikijs.exceptions import (
|
from wikijs.exceptions import (
|
||||||
WikiJSException,
|
|
||||||
APIError,
|
APIError,
|
||||||
ClientError,
|
|
||||||
ServerError,
|
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
|
ClientError,
|
||||||
ConfigurationError,
|
ConfigurationError,
|
||||||
ValidationError,
|
ConnectionError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
PermissionError,
|
PermissionError,
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
ConnectionError,
|
ServerError,
|
||||||
TimeoutError,
|
TimeoutError,
|
||||||
|
ValidationError,
|
||||||
|
WikiJSException,
|
||||||
create_api_error,
|
create_api_error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
"""Integration tests for the full WikiJS client with Pages API."""
|
"""Integration tests for the full WikiJS client with Pages API."""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from wikijs import WikiJSClient
|
from wikijs import WikiJSClient
|
||||||
from wikijs.endpoints.pages import PagesEndpoint
|
from wikijs.endpoints.pages import PagesEndpoint
|
||||||
from wikijs.models.page import Page, PageCreate
|
from wikijs.models.page import Page
|
||||||
|
|
||||||
|
|
||||||
class TestWikiJSClientIntegration:
|
class TestWikiJSClientIntegration:
|
||||||
@@ -13,14 +12,14 @@ class TestWikiJSClientIntegration:
|
|||||||
|
|
||||||
def test_client_has_pages_endpoint(self):
|
def test_client_has_pages_endpoint(self):
|
||||||
"""Test that client has pages endpoint initialized."""
|
"""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")
|
client = WikiJSClient("https://test.wiki", auth="test-key")
|
||||||
|
|
||||||
assert hasattr(client, 'pages')
|
assert hasattr(client, "pages")
|
||||||
assert isinstance(client.pages, PagesEndpoint)
|
assert isinstance(client.pages, PagesEndpoint)
|
||||||
assert client.pages._client is client
|
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):
|
def test_client_pages_integration(self, mock_session_class):
|
||||||
"""Test that pages endpoint works through client."""
|
"""Test that pages endpoint works through client."""
|
||||||
# Mock the session and response
|
# Mock the session and response
|
||||||
@@ -31,7 +30,8 @@ class TestWikiJSClientIntegration:
|
|||||||
mock_response.ok = True
|
mock_response.ok = True
|
||||||
mock_response.json.return_value = {
|
mock_response.json.return_value = {
|
||||||
"data": {
|
"data": {
|
||||||
"pages": [{
|
"pages": [
|
||||||
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "Test Page",
|
"title": "Test Page",
|
||||||
"path": "test",
|
"path": "test",
|
||||||
@@ -41,8 +41,9 @@ class TestWikiJSClientIntegration:
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
"createdAt": "2023-01-01T00:00:00Z",
|
"createdAt": "2023-01-01T00:00:00Z",
|
||||||
"updatedAt": "2023-01-01T00:00:00Z"
|
"updatedAt": "2023-01-01T00:00:00Z",
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mock_session.request.return_value = mock_response
|
mock_session.request.return_value = mock_response
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
"""Tests for utility helper functions."""
|
"""Tests for utility helper functions."""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from wikijs.exceptions import ValidationError
|
from wikijs.exceptions import ValidationError
|
||||||
from wikijs.utils.helpers import (
|
from wikijs.utils.helpers import (
|
||||||
normalize_url,
|
|
||||||
validate_url,
|
|
||||||
sanitize_path,
|
|
||||||
build_api_url,
|
build_api_url,
|
||||||
parse_wiki_response,
|
|
||||||
extract_error_message,
|
|
||||||
chunk_list,
|
chunk_list,
|
||||||
|
extract_error_message,
|
||||||
|
normalize_url,
|
||||||
|
parse_wiki_response,
|
||||||
safe_get,
|
safe_get,
|
||||||
|
sanitize_path,
|
||||||
|
validate_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,11 +30,16 @@ class TestNormalizeUrl:
|
|||||||
|
|
||||||
def test_normalize_url_remove_multiple_trailing_slashes(self):
|
def test_normalize_url_remove_multiple_trailing_slashes(self):
|
||||||
"""Test multiple trailing slash removal."""
|
"""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):
|
def test_normalize_url_with_path(self):
|
||||||
"""Test URL with path normalization."""
|
"""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):
|
def test_normalize_url_empty(self):
|
||||||
"""Test empty URL raises error."""
|
"""Test empty URL raises error."""
|
||||||
@@ -63,7 +69,10 @@ class TestNormalizeUrl:
|
|||||||
|
|
||||||
def test_normalize_url_with_port(self):
|
def test_normalize_url_with_port(self):
|
||||||
"""Test URL with port."""
|
"""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:
|
class TestValidateUrl:
|
||||||
@@ -334,10 +343,9 @@ class TestUtilityEdgeCases:
|
|||||||
def test_validate_url_malformed_url(self):
|
def test_validate_url_malformed_url(self):
|
||||||
"""Test validate_url with malformed URL that causes exception."""
|
"""Test validate_url with malformed URL that causes exception."""
|
||||||
# Test with a string that could cause urlparse to raise an exception
|
# Test with a string that could cause urlparse to raise an exception
|
||||||
import sys
|
|
||||||
from unittest.mock import patch
|
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")
|
mock_urlparse.side_effect = Exception("Parse error")
|
||||||
assert validate_url("http://example.com") is False
|
assert validate_url("http://example.com") is False
|
||||||
|
|
||||||
@@ -362,6 +370,7 @@ class TestUtilityEdgeCases:
|
|||||||
response = {"error": {"message": "Not found", "code": "404"}}
|
response = {"error": {"message": "Not found", "code": "404"}}
|
||||||
|
|
||||||
from wikijs.exceptions import APIError
|
from wikijs.exceptions import APIError
|
||||||
|
|
||||||
with pytest.raises(APIError, match="API Error: Not found"):
|
with pytest.raises(APIError, match="API Error: Not found"):
|
||||||
parse_wiki_response(response)
|
parse_wiki_response(response)
|
||||||
|
|
||||||
@@ -370,14 +379,21 @@ class TestUtilityEdgeCases:
|
|||||||
response = {"error": "Simple error message"}
|
response = {"error": "Simple error message"}
|
||||||
|
|
||||||
from wikijs.exceptions import APIError
|
from wikijs.exceptions import APIError
|
||||||
|
|
||||||
with pytest.raises(APIError, match="API Error: Simple error message"):
|
with pytest.raises(APIError, match="API Error: Simple error message"):
|
||||||
parse_wiki_response(response)
|
parse_wiki_response(response)
|
||||||
|
|
||||||
def test_parse_wiki_response_with_errors_array(self):
|
def test_parse_wiki_response_with_errors_array(self):
|
||||||
"""Test parse_wiki_response with errors array."""
|
"""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
|
from wikijs.exceptions import APIError
|
||||||
|
|
||||||
with pytest.raises(APIError, match="GraphQL Error: GraphQL error"):
|
with pytest.raises(APIError, match="GraphQL Error: GraphQL error"):
|
||||||
parse_wiki_response(response)
|
parse_wiki_response(response)
|
||||||
|
|
||||||
@@ -386,6 +402,7 @@ class TestUtilityEdgeCases:
|
|||||||
response = {"errors": "String error"}
|
response = {"errors": "String error"}
|
||||||
|
|
||||||
from wikijs.exceptions import APIError
|
from wikijs.exceptions import APIError
|
||||||
|
|
||||||
with pytest.raises(APIError, match="GraphQL Error: String error"):
|
with pytest.raises(APIError, match="GraphQL Error: String error"):
|
||||||
parse_wiki_response(response)
|
parse_wiki_response(response)
|
||||||
|
|
||||||
|
|||||||
@@ -18,21 +18,21 @@ Features:
|
|||||||
- Context manager support for resource cleanup
|
- Context manager support for resource cleanup
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .auth import AuthHandler, NoAuth, APIKeyAuth, JWTAuth
|
from .auth import APIKeyAuth, AuthHandler, JWTAuth, NoAuth
|
||||||
from .client import WikiJSClient
|
from .client import WikiJSClient
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
WikiJSException,
|
|
||||||
APIError,
|
APIError,
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
ConfigurationError,
|
|
||||||
ValidationError,
|
|
||||||
ClientError,
|
ClientError,
|
||||||
ServerError,
|
ConfigurationError,
|
||||||
|
ConnectionError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
PermissionError,
|
PermissionError,
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
ConnectionError,
|
ServerError,
|
||||||
TimeoutError,
|
TimeoutError,
|
||||||
|
ValidationError,
|
||||||
|
WikiJSException,
|
||||||
)
|
)
|
||||||
from .models import BaseModel, Page, PageCreate, PageUpdate
|
from .models import BaseModel, Page, PageCreate, PageUpdate
|
||||||
from .version import __version__, __version_info__
|
from .version import __version__, __version_info__
|
||||||
@@ -41,19 +41,16 @@ from .version import __version__, __version_info__
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
# Main client
|
# Main client
|
||||||
"WikiJSClient",
|
"WikiJSClient",
|
||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
"AuthHandler",
|
"AuthHandler",
|
||||||
"NoAuth",
|
"NoAuth",
|
||||||
"APIKeyAuth",
|
"APIKeyAuth",
|
||||||
"JWTAuth",
|
"JWTAuth",
|
||||||
|
|
||||||
# Data models
|
# Data models
|
||||||
"BaseModel",
|
"BaseModel",
|
||||||
"Page",
|
"Page",
|
||||||
"PageCreate",
|
"PageCreate",
|
||||||
"PageUpdate",
|
"PageUpdate",
|
||||||
|
|
||||||
# Exceptions
|
# Exceptions
|
||||||
"WikiJSException",
|
"WikiJSException",
|
||||||
"APIError",
|
"APIError",
|
||||||
@@ -67,7 +64,6 @@ __all__ = [
|
|||||||
"RateLimitError",
|
"RateLimitError",
|
||||||
"ConnectionError",
|
"ConnectionError",
|
||||||
"TimeoutError",
|
"TimeoutError",
|
||||||
|
|
||||||
# Version info
|
# Version info
|
||||||
"__version__",
|
"__version__",
|
||||||
"__version_info__",
|
"__version_info__",
|
||||||
@@ -81,4 +77,10 @@ __description__ = "Professional Python SDK for Wiki.js API integration"
|
|||||||
__url__ = "https://github.com/yourusername/wikijs-python-sdk"
|
__url__ = "https://github.com/yourusername/wikijs-python-sdk"
|
||||||
|
|
||||||
# For type checking
|
# For type checking
|
||||||
__all__ += ["__author__", "__email__", "__license__", "__description__", "__url__"]
|
__all__ += [
|
||||||
|
"__author__",
|
||||||
|
"__email__",
|
||||||
|
"__license__",
|
||||||
|
"__description__",
|
||||||
|
"__url__",
|
||||||
|
]
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ Supported authentication methods:
|
|||||||
- No authentication for testing (NoAuth)
|
- No authentication for testing (NoAuth)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .base import AuthHandler, NoAuth
|
|
||||||
from .api_key import APIKeyAuth
|
from .api_key import APIKeyAuth
|
||||||
|
from .base import AuthHandler, NoAuth
|
||||||
from .jwt import JWTAuth
|
from .jwt import JWTAuth
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This module implements API key authentication for Wiki.js instances.
|
|||||||
API keys are typically used for server-to-server authentication.
|
API keys are typically used for server-to-server authentication.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, Optional
|
from typing import Dict
|
||||||
|
|
||||||
from .base import AuthHandler
|
from .base import AuthHandler
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class APIKeyAuth(AuthHandler):
|
|||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"Authorization": f"Bearer {self._api_key}",
|
"Authorization": f"Bearer {self._api_key}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
@@ -66,7 +66,6 @@ class APIKeyAuth(AuthHandler):
|
|||||||
If the API key becomes invalid, a new one must be provided.
|
If the API key becomes invalid, a new one must be provided.
|
||||||
"""
|
"""
|
||||||
# API keys don't refresh - they're static until manually replaced
|
# API keys don't refresh - they're static until manually replaced
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_key(self) -> str:
|
def api_key(self) -> str:
|
||||||
@@ -78,7 +77,9 @@ class APIKeyAuth(AuthHandler):
|
|||||||
if len(self._api_key) <= 8:
|
if len(self._api_key) <= 8:
|
||||||
return "*" * len(self._api_key)
|
return "*" * len(self._api_key)
|
||||||
|
|
||||||
return f"{self._api_key[:4]}{'*' * (len(self._api_key) - 8)}{self._api_key[-4:]}"
|
return (
|
||||||
|
f"{self._api_key[:4]}{'*' * (len(self._api_key) - 8)}{self._api_key[-4:]}"
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""String representation of the auth handler.
|
"""String representation of the auth handler.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ providing a consistent interface for different authentication methods.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Dict, Optional
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
class AuthHandler(ABC):
|
class AuthHandler(ABC):
|
||||||
@@ -25,7 +25,6 @@ class AuthHandler(ABC):
|
|||||||
Raises:
|
Raises:
|
||||||
AuthenticationError: If authentication is invalid or expired.
|
AuthenticationError: If authentication is invalid or expired.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
@@ -34,7 +33,6 @@ class AuthHandler(ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if authentication is valid, False otherwise.
|
bool: True if authentication is valid, False otherwise.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
@@ -46,7 +44,6 @@ class AuthHandler(ABC):
|
|||||||
Raises:
|
Raises:
|
||||||
AuthenticationError: If refresh fails.
|
AuthenticationError: If refresh fails.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
def validate_credentials(self) -> None:
|
def validate_credentials(self) -> None:
|
||||||
"""Validate credentials and refresh if necessary.
|
"""Validate credentials and refresh if necessary.
|
||||||
@@ -62,6 +59,7 @@ class AuthHandler(ABC):
|
|||||||
|
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
from ..exceptions import AuthenticationError
|
from ..exceptions import AuthenticationError
|
||||||
|
|
||||||
raise AuthenticationError("Authentication credentials are invalid")
|
raise AuthenticationError("Authentication credentials are invalid")
|
||||||
|
|
||||||
|
|
||||||
@@ -94,4 +92,3 @@ class NoAuth(AuthHandler):
|
|||||||
|
|
||||||
This method does nothing since there's no authentication to refresh.
|
This method does nothing since there's no authentication to refresh.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
@@ -5,7 +5,7 @@ JWT tokens are typically used for user-based authentication and have expiration
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from .base import AuthHandler
|
from .base import AuthHandler
|
||||||
@@ -31,7 +31,7 @@ class JWTAuth(AuthHandler):
|
|||||||
self,
|
self,
|
||||||
token: str,
|
token: str,
|
||||||
refresh_token: Optional[str] = None,
|
refresh_token: Optional[str] = None,
|
||||||
expires_at: Optional[float] = None
|
expires_at: Optional[float] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize JWT authentication.
|
"""Initialize JWT authentication.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class JWTAuth(AuthHandler):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"Authorization": f"Bearer {self._token}",
|
"Authorization": f"Bearer {self._token}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import requests
|
|||||||
from requests.adapters import HTTPAdapter
|
from requests.adapters import HTTPAdapter
|
||||||
from urllib3.util.retry import Retry
|
from urllib3.util.retry import Retry
|
||||||
|
|
||||||
from .auth import AuthHandler, APIKeyAuth
|
from .auth import APIKeyAuth, AuthHandler
|
||||||
from .endpoints import PagesEndpoint
|
from .endpoints import PagesEndpoint
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
APIError,
|
APIError,
|
||||||
@@ -17,7 +17,12 @@ from .exceptions import (
|
|||||||
TimeoutError,
|
TimeoutError,
|
||||||
create_api_error,
|
create_api_error,
|
||||||
)
|
)
|
||||||
from .utils import normalize_url, build_api_url, parse_wiki_response, extract_error_message
|
from .utils import (
|
||||||
|
build_api_url,
|
||||||
|
extract_error_message,
|
||||||
|
normalize_url,
|
||||||
|
parse_wiki_response,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WikiJSClient:
|
class WikiJSClient:
|
||||||
@@ -73,7 +78,7 @@ class WikiJSClient:
|
|||||||
# Request configuration
|
# Request configuration
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.verify_ssl = verify_ssl
|
self.verify_ssl = verify_ssl
|
||||||
self.user_agent = user_agent or f"wikijs-python-sdk/0.1.0"
|
self.user_agent = user_agent or "wikijs-python-sdk/0.1.0"
|
||||||
|
|
||||||
# Initialize HTTP session
|
# Initialize HTTP session
|
||||||
self._session = self._create_session()
|
self._session = self._create_session()
|
||||||
@@ -97,7 +102,14 @@ class WikiJSClient:
|
|||||||
total=3,
|
total=3,
|
||||||
backoff_factor=1,
|
backoff_factor=1,
|
||||||
status_forcelist=[429, 500, 502, 503, 504],
|
status_forcelist=[429, 500, 502, 503, 504],
|
||||||
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE"],
|
allowed_methods=[
|
||||||
|
"HEAD",
|
||||||
|
"GET",
|
||||||
|
"OPTIONS",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"DELETE",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter = HTTPAdapter(max_retries=retry_strategy)
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||||
@@ -105,11 +117,13 @@ class WikiJSClient:
|
|||||||
session.mount("https://", adapter)
|
session.mount("https://", adapter)
|
||||||
|
|
||||||
# Set default headers
|
# Set default headers
|
||||||
session.headers.update({
|
session.headers.update(
|
||||||
|
{
|
||||||
"User-Agent": self.user_agent,
|
"User-Agent": self.user_agent,
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Set authentication headers
|
# Set authentication headers
|
||||||
if self._auth_handler:
|
if self._auth_handler:
|
||||||
@@ -126,7 +140,7 @@ class WikiJSClient:
|
|||||||
endpoint: str,
|
endpoint: str,
|
||||||
params: Optional[Dict[str, Any]] = None,
|
params: Optional[Dict[str, Any]] = None,
|
||||||
json_data: Optional[Dict[str, Any]] = None,
|
json_data: Optional[Dict[str, Any]] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make HTTP request to Wiki.js API.
|
"""Make HTTP request to Wiki.js API.
|
||||||
|
|
||||||
@@ -154,7 +168,7 @@ class WikiJSClient:
|
|||||||
"timeout": self.timeout,
|
"timeout": self.timeout,
|
||||||
"verify": self.verify_ssl,
|
"verify": self.verify_ssl,
|
||||||
"params": params,
|
"params": params,
|
||||||
**kwargs
|
**kwargs,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add JSON data if provided
|
# Add JSON data if provided
|
||||||
@@ -197,11 +211,7 @@ class WikiJSClient:
|
|||||||
# Handle other HTTP errors
|
# Handle other HTTP errors
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
error_message = extract_error_message(response)
|
error_message = extract_error_message(response)
|
||||||
raise create_api_error(
|
raise create_api_error(response.status_code, error_message, response)
|
||||||
response.status_code,
|
|
||||||
error_message,
|
|
||||||
response
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse JSON response
|
# Parse JSON response
|
||||||
try:
|
try:
|
||||||
@@ -232,15 +242,15 @@ class WikiJSClient:
|
|||||||
try:
|
try:
|
||||||
# Try to hit a basic endpoint (will implement with actual endpoints)
|
# Try to hit a basic endpoint (will implement with actual endpoints)
|
||||||
# For now, just test basic connectivity
|
# For now, just test basic connectivity
|
||||||
response = self._session.get(
|
self._session.get(
|
||||||
self.base_url,
|
self.base_url, timeout=self.timeout, verify=self.verify_ssl
|
||||||
timeout=self.timeout,
|
|
||||||
verify=self.verify_ssl
|
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
raise TimeoutError(f"Connection test timed out after {self.timeout} seconds")
|
raise TimeoutError(
|
||||||
|
f"Connection test timed out after {self.timeout} seconds"
|
||||||
|
)
|
||||||
|
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
raise ConnectionError(f"Cannot connect to {self.base_url}: {str(e)}")
|
raise ConnectionError(f"Cannot connect to {self.base_url}: {str(e)}")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Base endpoint class for wikijs-python-sdk."""
|
"""Base endpoint class for wikijs-python-sdk."""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..client import WikiJSClient
|
from ..client import WikiJSClient
|
||||||
@@ -30,7 +30,7 @@ class BaseEndpoint:
|
|||||||
endpoint: str,
|
endpoint: str,
|
||||||
params: Optional[Dict[str, Any]] = None,
|
params: Optional[Dict[str, Any]] = None,
|
||||||
json_data: Optional[Dict[str, Any]] = None,
|
json_data: Optional[Dict[str, Any]] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make HTTP request through the client.
|
"""Make HTTP request through the client.
|
||||||
|
|
||||||
@@ -49,14 +49,11 @@ class BaseEndpoint:
|
|||||||
endpoint=endpoint,
|
endpoint=endpoint,
|
||||||
params=params,
|
params=params,
|
||||||
json_data=json_data,
|
json_data=json_data,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get(
|
def _get(
|
||||||
self,
|
self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs
|
||||||
endpoint: str,
|
|
||||||
params: Optional[Dict[str, Any]] = None,
|
|
||||||
**kwargs
|
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make GET request.
|
"""Make GET request.
|
||||||
|
|
||||||
@@ -75,7 +72,7 @@ class BaseEndpoint:
|
|||||||
endpoint: str,
|
endpoint: str,
|
||||||
json_data: Optional[Dict[str, Any]] = None,
|
json_data: Optional[Dict[str, Any]] = None,
|
||||||
params: Optional[Dict[str, Any]] = None,
|
params: Optional[Dict[str, Any]] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make POST request.
|
"""Make POST request.
|
||||||
|
|
||||||
@@ -88,14 +85,16 @@ class BaseEndpoint:
|
|||||||
Returns:
|
Returns:
|
||||||
Parsed response data
|
Parsed response data
|
||||||
"""
|
"""
|
||||||
return self._request("POST", endpoint, params=params, json_data=json_data, **kwargs)
|
return self._request(
|
||||||
|
"POST", endpoint, params=params, json_data=json_data, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def _put(
|
def _put(
|
||||||
self,
|
self,
|
||||||
endpoint: str,
|
endpoint: str,
|
||||||
json_data: Optional[Dict[str, Any]] = None,
|
json_data: Optional[Dict[str, Any]] = None,
|
||||||
params: Optional[Dict[str, Any]] = None,
|
params: Optional[Dict[str, Any]] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make PUT request.
|
"""Make PUT request.
|
||||||
|
|
||||||
@@ -108,13 +107,12 @@ class BaseEndpoint:
|
|||||||
Returns:
|
Returns:
|
||||||
Parsed response data
|
Parsed response data
|
||||||
"""
|
"""
|
||||||
return self._request("PUT", endpoint, params=params, json_data=json_data, **kwargs)
|
return self._request(
|
||||||
|
"PUT", endpoint, params=params, json_data=json_data, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def _delete(
|
def _delete(
|
||||||
self,
|
self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs
|
||||||
endpoint: str,
|
|
||||||
params: Optional[Dict[str, Any]] = None,
|
|
||||||
**kwargs
|
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make DELETE request.
|
"""Make DELETE request.
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
locale: Optional[str] = None,
|
locale: Optional[str] = None,
|
||||||
author_id: Optional[int] = None,
|
author_id: Optional[int] = None,
|
||||||
order_by: str = "title",
|
order_by: str = "title",
|
||||||
order_direction: str = "ASC"
|
order_direction: str = "ASC",
|
||||||
) -> List[Page]:
|
) -> List[Page]:
|
||||||
"""List pages with optional filtering.
|
"""List pages with optional filtering.
|
||||||
|
|
||||||
@@ -77,7 +77,9 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
raise ValidationError("offset must be non-negative")
|
raise ValidationError("offset must be non-negative")
|
||||||
|
|
||||||
if order_by not in ["title", "created_at", "updated_at", "path"]:
|
if order_by not in ["title", "created_at", "updated_at", "path"]:
|
||||||
raise ValidationError("order_by must be one of: title, created_at, updated_at, path")
|
raise ValidationError(
|
||||||
|
"order_by must be one of: title, created_at, updated_at, path"
|
||||||
|
)
|
||||||
|
|
||||||
if order_direction not in ["ASC", "DESC"]:
|
if order_direction not in ["ASC", "DESC"]:
|
||||||
raise ValidationError("order_direction must be ASC or DESC")
|
raise ValidationError("order_direction must be ASC or DESC")
|
||||||
@@ -99,9 +101,7 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Make request (no variables needed for simple list query)
|
# Make request (no variables needed for simple list query)
|
||||||
response = self._post("/graphql", json_data={
|
response = self._post("/graphql", json_data={"query": query})
|
||||||
"query": query
|
|
||||||
})
|
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
if "errors" in response:
|
if "errors" in response:
|
||||||
@@ -166,10 +166,10 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Make request
|
# Make request
|
||||||
response = self._post("/graphql", json_data={
|
response = self._post(
|
||||||
"query": query,
|
"/graphql",
|
||||||
"variables": {"id": page_id}
|
json_data={"query": query, "variables": {"id": page_id}},
|
||||||
})
|
)
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
if "errors" in response:
|
if "errors" in response:
|
||||||
@@ -230,10 +230,13 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Make request
|
# Make request
|
||||||
response = self._post("/graphql", json_data={
|
response = self._post(
|
||||||
|
"/graphql",
|
||||||
|
json_data={
|
||||||
"query": query,
|
"query": query,
|
||||||
"variables": {"path": path, "locale": locale}
|
"variables": {"path": path, "locale": locale},
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
if "errors" in response:
|
if "errors" in response:
|
||||||
@@ -312,19 +315,19 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
"title": page_data.title,
|
"title": page_data.title,
|
||||||
"path": page_data.path,
|
"path": page_data.path,
|
||||||
"content": page_data.content,
|
"content": page_data.content,
|
||||||
"description": page_data.description or f"Created via SDK: {page_data.title}",
|
"description": page_data.description
|
||||||
|
or f"Created via SDK: {page_data.title}",
|
||||||
"isPublished": page_data.is_published,
|
"isPublished": page_data.is_published,
|
||||||
"isPrivate": page_data.is_private,
|
"isPrivate": page_data.is_private,
|
||||||
"tags": page_data.tags,
|
"tags": page_data.tags,
|
||||||
"locale": page_data.locale,
|
"locale": page_data.locale,
|
||||||
"editor": page_data.editor
|
"editor": page_data.editor,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Make request
|
# Make request
|
||||||
response = self._post("/graphql", json_data={
|
response = self._post(
|
||||||
"query": mutation,
|
"/graphql", json_data={"query": mutation, "variables": variables}
|
||||||
"variables": variables
|
)
|
||||||
})
|
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
if "errors" in response:
|
if "errors" in response:
|
||||||
@@ -349,9 +352,7 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
raise APIError(f"Failed to parse created page data: {str(e)}") from e
|
raise APIError(f"Failed to parse created page data: {str(e)}") from e
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self,
|
self, page_id: int, page_data: Union[PageUpdate, Dict[str, Any]]
|
||||||
page_id: int,
|
|
||||||
page_data: Union[PageUpdate, Dict[str, Any]]
|
|
||||||
) -> Page:
|
) -> Page:
|
||||||
"""Update an existing page.
|
"""Update an existing page.
|
||||||
|
|
||||||
@@ -426,10 +427,9 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
variables["tags"] = page_data.tags
|
variables["tags"] = page_data.tags
|
||||||
|
|
||||||
# Make request
|
# Make request
|
||||||
response = self._post("/graphql", json_data={
|
response = self._post(
|
||||||
"query": mutation,
|
"/graphql", json_data={"query": mutation, "variables": variables}
|
||||||
"variables": variables
|
)
|
||||||
})
|
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
if "errors" in response:
|
if "errors" in response:
|
||||||
@@ -473,10 +473,10 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Make request
|
# Make request
|
||||||
response = self._post("/graphql", json_data={
|
response = self._post(
|
||||||
"query": mutation,
|
"/graphql",
|
||||||
"variables": {"id": page_id}
|
json_data={"query": mutation, "variables": {"id": page_id}},
|
||||||
})
|
)
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
if "errors" in response:
|
if "errors" in response:
|
||||||
@@ -495,7 +495,7 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
self,
|
self,
|
||||||
query: str,
|
query: str,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
locale: Optional[str] = None
|
locale: Optional[str] = None,
|
||||||
) -> List[Page]:
|
) -> List[Page]:
|
||||||
"""Search for pages by content and title.
|
"""Search for pages by content and title.
|
||||||
|
|
||||||
@@ -518,17 +518,13 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
raise ValidationError("limit must be greater than 0")
|
raise ValidationError("limit must be greater than 0")
|
||||||
|
|
||||||
# Use the list method with search parameter
|
# Use the list method with search parameter
|
||||||
return self.list(
|
return self.list(search=query, limit=limit, locale=locale)
|
||||||
search=query,
|
|
||||||
limit=limit,
|
|
||||||
locale=locale
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_by_tags(
|
def get_by_tags(
|
||||||
self,
|
self,
|
||||||
tags: List[str],
|
tags: List[str],
|
||||||
match_all: bool = True,
|
match_all: bool = True,
|
||||||
limit: Optional[int] = None
|
limit: Optional[int] = None,
|
||||||
) -> List[Page]:
|
) -> List[Page]:
|
||||||
"""Get pages by tags.
|
"""Get pages by tags.
|
||||||
|
|
||||||
@@ -557,7 +553,9 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
# For match_all=False, we need a more complex query
|
# For match_all=False, we need a more complex query
|
||||||
# This would require a custom GraphQL query or multiple requests
|
# This would require a custom GraphQL query or multiple requests
|
||||||
# For now, implement a simple approach
|
# For now, implement a simple approach
|
||||||
all_pages = self.list(limit=limit * 2 if limit else None) # Get more pages to filter
|
all_pages = self.list(
|
||||||
|
limit=limit * 2 if limit else None
|
||||||
|
) # Get more pages to filter
|
||||||
|
|
||||||
matching_pages = []
|
matching_pages = []
|
||||||
for page in all_pages:
|
for page in all_pages:
|
||||||
@@ -594,7 +592,7 @@ class PagesEndpoint(BaseEndpoint):
|
|||||||
"authorEmail": "author_email",
|
"authorEmail": "author_email",
|
||||||
"editor": "editor",
|
"editor": "editor",
|
||||||
"createdAt": "created_at",
|
"createdAt": "created_at",
|
||||||
"updatedAt": "updated_at"
|
"updatedAt": "updated_at",
|
||||||
}
|
}
|
||||||
|
|
||||||
for api_field, model_field in field_mapping.items():
|
for api_field, model_field in field_mapping.items():
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ class WikiJSException(Exception):
|
|||||||
|
|
||||||
class ConfigurationError(WikiJSException):
|
class ConfigurationError(WikiJSException):
|
||||||
"""Raised when there's an issue with SDK configuration."""
|
"""Raised when there's an issue with SDK configuration."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationError(WikiJSException):
|
class AuthenticationError(WikiJSException):
|
||||||
"""Raised when authentication fails."""
|
"""Raised when authentication fails."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(WikiJSException):
|
class ValidationError(WikiJSException):
|
||||||
@@ -39,7 +37,7 @@ class APIError(WikiJSException):
|
|||||||
message: str,
|
message: str,
|
||||||
status_code: Optional[int] = None,
|
status_code: Optional[int] = None,
|
||||||
response: Optional[Any] = None,
|
response: Optional[Any] = None,
|
||||||
details: Optional[Dict[str, Any]] = None
|
details: Optional[Dict[str, Any]] = None,
|
||||||
):
|
):
|
||||||
super().__init__(message, details)
|
super().__init__(message, details)
|
||||||
self.status_code = status_code
|
self.status_code = status_code
|
||||||
@@ -48,22 +46,18 @@ class APIError(WikiJSException):
|
|||||||
|
|
||||||
class ClientError(APIError):
|
class ClientError(APIError):
|
||||||
"""Raised for 4xx HTTP status codes (client errors)."""
|
"""Raised for 4xx HTTP status codes (client errors)."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ServerError(APIError):
|
class ServerError(APIError):
|
||||||
"""Raised for 5xx HTTP status codes (server errors)."""
|
"""Raised for 5xx HTTP status codes (server errors)."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotFoundError(ClientError):
|
class NotFoundError(ClientError):
|
||||||
"""Raised when a requested resource is not found (404)."""
|
"""Raised when a requested resource is not found (404)."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionError(ClientError):
|
class PermissionError(ClientError):
|
||||||
"""Raised when access is forbidden (403)."""
|
"""Raised when access is forbidden (403)."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RateLimitError(ClientError):
|
class RateLimitError(ClientError):
|
||||||
@@ -71,19 +65,17 @@ class RateLimitError(ClientError):
|
|||||||
|
|
||||||
def __init__(self, message: str, retry_after: Optional[int] = None, **kwargs):
|
def __init__(self, message: str, retry_after: Optional[int] = None, **kwargs):
|
||||||
# Remove status_code from kwargs if present to avoid duplicate argument
|
# Remove status_code from kwargs if present to avoid duplicate argument
|
||||||
kwargs.pop('status_code', None)
|
kwargs.pop("status_code", None)
|
||||||
super().__init__(message, status_code=429, **kwargs)
|
super().__init__(message, status_code=429, **kwargs)
|
||||||
self.retry_after = retry_after
|
self.retry_after = retry_after
|
||||||
|
|
||||||
|
|
||||||
class ConnectionError(WikiJSException):
|
class ConnectionError(WikiJSException):
|
||||||
"""Raised when there's a connection issue."""
|
"""Raised when there's a connection issue."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TimeoutError(WikiJSException):
|
class TimeoutError(WikiJSException):
|
||||||
"""Raised when a request times out."""
|
"""Raised when a request times out."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def create_api_error(status_code: int, message: str, response: Any = None) -> APIError:
|
def create_api_error(status_code: int, message: str, response: Any = None) -> APIError:
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel as PydanticBaseModel, ConfigDict
|
from pydantic import BaseModel as PydanticBaseModel
|
||||||
|
from pydantic import ConfigDict
|
||||||
|
|
||||||
|
|
||||||
class BaseModel(PydanticBaseModel):
|
class BaseModel(PydanticBaseModel):
|
||||||
@@ -26,9 +27,7 @@ class BaseModel(PydanticBaseModel):
|
|||||||
# Allow extra fields for forward compatibility
|
# Allow extra fields for forward compatibility
|
||||||
extra="ignore",
|
extra="ignore",
|
||||||
# Serialize datetime as ISO format
|
# Serialize datetime as ISO format
|
||||||
json_encoders={
|
json_encoders={datetime: lambda v: v.isoformat() if v else None},
|
||||||
datetime: lambda v: v.isoformat() if v else None
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_dict(self, exclude_none: bool = True) -> Dict[str, Any]:
|
def to_dict(self, exclude_none: bool = True) -> Dict[str, Any]:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""Page-related data models for wikijs-python-sdk."""
|
"""Page-related data models for wikijs-python-sdk."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import Field, validator
|
from pydantic import Field, validator
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"""Utility functions for wikijs-python-sdk."""
|
"""Utility functions for wikijs-python-sdk."""
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
build_api_url,
|
||||||
|
chunk_list,
|
||||||
|
extract_error_message,
|
||||||
normalize_url,
|
normalize_url,
|
||||||
|
parse_wiki_response,
|
||||||
|
safe_get,
|
||||||
sanitize_path,
|
sanitize_path,
|
||||||
validate_url,
|
validate_url,
|
||||||
build_api_url,
|
|
||||||
parse_wiki_response,
|
|
||||||
extract_error_message,
|
|
||||||
chunk_list,
|
|
||||||
safe_get,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Helper utilities for wikijs-python-sdk."""
|
"""Helper utilities for wikijs-python-sdk."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict
|
||||||
from urllib.parse import urljoin, urlparse
|
from urllib.parse import urljoin, urlparse
|
||||||
|
|
||||||
from ..exceptions import APIError, ValidationError
|
from ..exceptions import APIError, ValidationError
|
||||||
@@ -135,7 +135,11 @@ def parse_wiki_response(response_data: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
errors = response_data["errors"]
|
errors = response_data["errors"]
|
||||||
if errors:
|
if errors:
|
||||||
first_error = errors[0] if isinstance(errors, list) else errors
|
first_error = errors[0] if isinstance(errors, list) else errors
|
||||||
message = first_error.get("message", "GraphQL error") if isinstance(first_error, dict) else str(first_error)
|
message = (
|
||||||
|
first_error.get("message", "GraphQL error")
|
||||||
|
if isinstance(first_error, dict)
|
||||||
|
else str(first_error)
|
||||||
|
)
|
||||||
raise APIError(f"GraphQL Error: {message}", details={"errors": errors})
|
raise APIError(f"GraphQL Error: {message}", details={"errors": errors})
|
||||||
|
|
||||||
return response_data
|
return response_data
|
||||||
@@ -162,7 +166,9 @@ def extract_error_message(response: Any) -> str:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if hasattr(response, "text"):
|
if hasattr(response, "text"):
|
||||||
return response.text[:200] + "..." if len(response.text) > 200 else response.text
|
return (
|
||||||
|
response.text[:200] + "..." if len(response.text) > 200 else response.text
|
||||||
|
)
|
||||||
|
|
||||||
return str(response)
|
return str(response)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user