Files
py-wikijs/tests/endpoints/test_pages_cache.py
Claude cef6903cbc feat: implement production-ready features from improvement plan phase 2.5 & 2.6
Phase 2.5: Fix Foundation (CRITICAL)
- Fixed 4 failing tests by adding cache attribute to mock_client fixture
- Created comprehensive cache tests for Pages endpoint (test_pages_cache.py)
- Added missing dependencies: pydantic[email] and aiohttp to core requirements
- Updated requirements.txt with proper dependency versions
- Achieved 82.67% test coverage with 454 passing tests

Phase 2.6: Production Essentials
- Implemented structured logging (wikijs/logging.py)
  * JSON and text log formatters
  * Configurable log levels and output destinations
  * Integration with client operations

- Implemented metrics and telemetry (wikijs/metrics.py)
  * Request tracking with duration, status codes, errors
  * Latency percentiles (min, max, avg, p50, p95, p99)
  * Error rate calculation
  * Thread-safe metrics collection

- Implemented rate limiting (wikijs/ratelimit.py)
  * Token bucket algorithm for request throttling
  * Per-endpoint rate limiting support
  * Configurable timeout handling
  * Burst capacity management

- Created SECURITY.md policy
  * Vulnerability reporting procedures
  * Security best practices
  * Response timelines
  * Supported versions

Documentation
- Added comprehensive logging guide (docs/logging.md)
- Added metrics and telemetry guide (docs/metrics.md)
- Added rate limiting guide (docs/rate_limiting.md)
- Updated README.md with production features section
- Updated IMPROVEMENT_PLAN_2.md with completed checkboxes

Testing
- Created test suite for logging (tests/test_logging.py)
- Created test suite for metrics (tests/test_metrics.py)
- Created test suite for rate limiting (tests/test_ratelimit.py)
- All 454 tests passing
- Test coverage: 82.67%

Breaking Changes: None
Dependencies Added: pydantic[email], email-validator, dnspython

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 16:45:02 +00:00

176 lines
5.2 KiB
Python

"""Tests for Pages endpoint caching functionality."""
import pytest
from unittest.mock import MagicMock, Mock
from wikijs.cache import MemoryCache, CacheKey
from wikijs.endpoints.pages import PagesEndpoint
from wikijs.models import Page
class TestPagesCaching:
"""Test caching behavior in Pages endpoint."""
def test_get_with_cache_hit(self):
"""Test page retrieval uses cache when available."""
# Setup
cache = MemoryCache(ttl=300)
client = MagicMock()
client.cache = cache
client._request = MagicMock()
pages = PagesEndpoint(client)
# Pre-populate cache
page_data = {"id": 123, "title": "Test", "path": "test"}
cache_key = CacheKey("page", "123", "get")
cache.set(cache_key, page_data)
# Execute
result = pages.get(123)
# Verify cache was used, not API
client._request.assert_not_called()
assert result["id"] == 123
def test_get_with_cache_miss(self):
"""Test page retrieval calls API on cache miss."""
# Setup
cache = MemoryCache(ttl=300)
client = MagicMock()
client.cache = cache
# Mock the _post method on the endpoint
pages = PagesEndpoint(client)
pages._post = Mock(return_value={
"data": {"pages": {"single": {
"id": 123,
"title": "Test",
"path": "test",
"content": "Test content",
"description": "Test desc",
"isPublished": True,
"isPrivate": False,
"tags": [],
"locale": "en",
"authorId": 1,
"authorName": "Test User",
"authorEmail": "test@example.com",
"editor": "markdown",
"createdAt": "2023-01-01T00:00:00Z",
"updatedAt": "2023-01-02T00:00:00Z"
}}}
})
# Execute
result = pages.get(123)
# Verify API was called
pages._post.assert_called_once()
# Verify result was cached
cache_key = CacheKey("page", "123", "get")
cached = cache.get(cache_key)
assert cached is not None
def test_update_invalidates_cache(self):
"""Test page update invalidates cache."""
# Setup
cache = MemoryCache(ttl=300)
client = MagicMock()
client.cache = cache
pages = PagesEndpoint(client)
pages._post = Mock(return_value={
"data": {"updatePage": {
"id": 123,
"title": "New",
"path": "test",
"content": "Updated content",
"description": "Updated desc",
"isPublished": True,
"isPrivate": False,
"tags": [],
"locale": "en",
"authorId": 1,
"authorName": "Test User",
"authorEmail": "test@example.com",
"editor": "markdown",
"createdAt": "2023-01-01T00:00:00Z",
"updatedAt": "2023-01-02T00:00:00Z"
}}
})
# Pre-populate cache
cache_key = CacheKey("page", "123", "get")
cache.set(cache_key, {"id": 123, "title": "Old"})
# Verify cache is populated
assert cache.get(cache_key) is not None
# Execute update
pages.update(123, {"title": "New"})
# Verify cache was invalidated
cached = cache.get(cache_key)
assert cached is None
def test_delete_invalidates_cache(self):
"""Test page delete invalidates cache."""
# Setup
cache = MemoryCache(ttl=300)
client = MagicMock()
client.cache = cache
pages = PagesEndpoint(client)
pages._post = Mock(return_value={
"data": {"deletePage": {"success": True}}
})
# Pre-populate cache
cache_key = CacheKey("page", "123", "get")
cache.set(cache_key, {"id": 123, "title": "Test"})
# Verify cache is populated
assert cache.get(cache_key) is not None
# Execute delete
pages.delete(123)
# Verify cache was invalidated
cached = cache.get(cache_key)
assert cached is None
def test_get_without_cache(self):
"""Test page retrieval without cache configured."""
# Setup
client = MagicMock()
client.cache = None
pages = PagesEndpoint(client)
pages._post = Mock(return_value={
"data": {"pages": {"single": {
"id": 123,
"title": "Test",
"path": "test",
"content": "Test content",
"description": "Test desc",
"isPublished": True,
"isPrivate": False,
"tags": [],
"locale": "en",
"authorId": 1,
"authorName": "Test User",
"authorEmail": "test@example.com",
"editor": "markdown",
"createdAt": "2023-01-01T00:00:00Z",
"updatedAt": "2023-01-02T00:00:00Z"
}}}
})
# Execute
result = pages.get(123)
# Verify API was called
pages._post.assert_called_once()
assert result.id == 123