- 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>
219 lines
8.9 KiB
Python
219 lines
8.9 KiB
Python
"""Tests for JWT authentication."""
|
|
|
|
import time
|
|
from datetime import timedelta
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from wikijs.auth.jwt import JWTAuth
|
|
from wikijs.exceptions import AuthenticationError
|
|
|
|
|
|
class TestJWTAuth:
|
|
"""Test JWTAuth implementation."""
|
|
|
|
def test_init_with_valid_token(self, mock_jwt_token):
|
|
"""Test initialization with valid JWT token."""
|
|
auth = JWTAuth(mock_jwt_token)
|
|
assert auth._token == mock_jwt_token
|
|
assert auth._refresh_token is None
|
|
assert auth._expires_at is None
|
|
|
|
def test_init_with_all_parameters(self, mock_jwt_token):
|
|
"""Test initialization with all parameters."""
|
|
refresh_token = "refresh-token-123"
|
|
expires_at = time.time() + 3600
|
|
|
|
auth = JWTAuth(mock_jwt_token, refresh_token, expires_at)
|
|
assert auth._token == mock_jwt_token
|
|
assert auth._refresh_token == refresh_token
|
|
assert auth._expires_at == expires_at
|
|
|
|
def test_init_with_whitespace_token(self):
|
|
"""Test initialization trims whitespace from token."""
|
|
auth = JWTAuth(" test-token ")
|
|
assert auth._token == "test-token"
|
|
|
|
def test_init_with_empty_token_raises_error(self):
|
|
"""Test that empty JWT token raises ValueError."""
|
|
with pytest.raises(ValueError, match="JWT token cannot be empty"):
|
|
JWTAuth("")
|
|
|
|
def test_init_with_whitespace_only_token_raises_error(self):
|
|
"""Test that whitespace-only JWT token raises ValueError."""
|
|
with pytest.raises(ValueError, match="JWT token cannot be empty"):
|
|
JWTAuth(" ")
|
|
|
|
def test_init_with_none_raises_error(self):
|
|
"""Test that None JWT token raises ValueError."""
|
|
with pytest.raises(ValueError, match="JWT token cannot be empty"):
|
|
JWTAuth(None)
|
|
|
|
def test_get_headers_returns_bearer_token(self, jwt_auth, mock_jwt_token):
|
|
"""Test that get_headers returns proper Authorization header."""
|
|
headers = jwt_auth.get_headers()
|
|
|
|
expected_headers = {
|
|
"Authorization": f"Bearer {mock_jwt_token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
assert headers == expected_headers
|
|
|
|
def test_get_headers_attempts_refresh_if_invalid(self, mock_jwt_token):
|
|
"""Test that get_headers attempts refresh if token is invalid."""
|
|
# Create JWT with expired token
|
|
expires_at = time.time() - 3600 # Expired 1 hour ago
|
|
refresh_token = "refresh-token-123"
|
|
|
|
auth = JWTAuth(mock_jwt_token, refresh_token, expires_at)
|
|
|
|
# Mock the refresh method to avoid actual implementation
|
|
with patch.object(auth, "refresh") as mock_refresh:
|
|
mock_refresh.side_effect = AuthenticationError("Refresh not implemented")
|
|
|
|
with pytest.raises(AuthenticationError):
|
|
auth.get_headers()
|
|
|
|
mock_refresh.assert_called_once()
|
|
|
|
def test_is_valid_returns_true_for_valid_token_no_expiry(self, jwt_auth):
|
|
"""Test that is_valid returns True for valid token without expiry."""
|
|
assert jwt_auth.is_valid() is True
|
|
|
|
def test_is_valid_returns_true_for_non_expired_token(self, mock_jwt_token):
|
|
"""Test that is_valid returns True for non-expired token."""
|
|
expires_at = time.time() + 3600 # Expires in 1 hour
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
assert auth.is_valid() is True
|
|
|
|
def test_is_valid_returns_false_for_expired_token(self, mock_jwt_token):
|
|
"""Test that is_valid returns False for expired token."""
|
|
expires_at = time.time() - 3600 # Expired 1 hour ago
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
assert auth.is_valid() is False
|
|
|
|
def test_is_valid_considers_refresh_buffer(self, mock_jwt_token):
|
|
"""Test that is_valid considers refresh buffer."""
|
|
# Token expires in 4 minutes (less than 5 minute buffer)
|
|
expires_at = time.time() + 240
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
assert auth.is_valid() is False # Should be invalid due to buffer
|
|
|
|
def test_is_valid_returns_false_for_empty_token(self, mock_jwt_token):
|
|
"""Test that is_valid handles edge cases."""
|
|
auth = JWTAuth(mock_jwt_token)
|
|
auth._token = ""
|
|
assert auth.is_valid() is False
|
|
|
|
auth._token = None
|
|
assert auth.is_valid() is False
|
|
|
|
def test_refresh_raises_error_without_refresh_token(self, jwt_auth):
|
|
"""Test that refresh raises error when no refresh token available."""
|
|
with pytest.raises(
|
|
AuthenticationError,
|
|
match="JWT token expired and no refresh token available",
|
|
):
|
|
jwt_auth.refresh()
|
|
|
|
def test_refresh_raises_not_implemented_error(self, mock_jwt_token):
|
|
"""Test that refresh raises not implemented error."""
|
|
refresh_token = "refresh-token-123"
|
|
auth = JWTAuth(mock_jwt_token, refresh_token)
|
|
|
|
with pytest.raises(
|
|
AuthenticationError, match="JWT token refresh not yet implemented"
|
|
):
|
|
auth.refresh()
|
|
|
|
def test_is_expired_returns_false_no_expiry(self, jwt_auth):
|
|
"""Test that is_expired returns False when no expiry set."""
|
|
assert jwt_auth.is_expired() is False
|
|
|
|
def test_is_expired_returns_false_for_valid_token(self, mock_jwt_token):
|
|
"""Test that is_expired returns False for valid token."""
|
|
expires_at = time.time() + 3600 # Expires in 1 hour
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
assert auth.is_expired() is False
|
|
|
|
def test_is_expired_returns_true_for_expired_token(self, mock_jwt_token):
|
|
"""Test that is_expired returns True for expired token."""
|
|
expires_at = time.time() - 3600 # Expired 1 hour ago
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
assert auth.is_expired() is True
|
|
|
|
def test_time_until_expiry_returns_none_no_expiry(self, jwt_auth):
|
|
"""Test that time_until_expiry returns None when no expiry set."""
|
|
assert jwt_auth.time_until_expiry() is None
|
|
|
|
def test_time_until_expiry_returns_correct_delta(self, mock_jwt_token):
|
|
"""Test that time_until_expiry returns correct timedelta."""
|
|
expires_at = time.time() + 3600 # Expires in 1 hour
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
|
|
time_left = auth.time_until_expiry()
|
|
assert isinstance(time_left, timedelta)
|
|
# Should be approximately 1 hour (allowing for small time differences)
|
|
assert 3550 <= time_left.total_seconds() <= 3600
|
|
|
|
def test_time_until_expiry_returns_zero_for_expired(self, mock_jwt_token):
|
|
"""Test that time_until_expiry returns zero for expired token."""
|
|
expires_at = time.time() - 3600 # Expired 1 hour ago
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
|
|
time_left = auth.time_until_expiry()
|
|
assert time_left.total_seconds() == 0
|
|
|
|
def test_token_preview_masks_token(self, mock_jwt_token):
|
|
"""Test that token_preview masks the token for security."""
|
|
auth = JWTAuth(mock_jwt_token)
|
|
preview = auth.token_preview
|
|
|
|
assert preview != mock_jwt_token # Should not show full token
|
|
assert preview.startswith(mock_jwt_token[:10])
|
|
assert preview.endswith(mock_jwt_token[-10:])
|
|
assert "..." in preview
|
|
|
|
def test_token_preview_handles_short_token(self):
|
|
"""Test that token_preview handles short tokens."""
|
|
short_token = "short"
|
|
auth = JWTAuth(short_token)
|
|
preview = auth.token_preview
|
|
|
|
assert preview == "*****" # Should be all asterisks
|
|
|
|
def test_token_preview_handles_none_token(self, mock_jwt_token):
|
|
"""Test that token_preview handles None token."""
|
|
auth = JWTAuth(mock_jwt_token)
|
|
auth._token = None
|
|
|
|
assert auth.token_preview == "None"
|
|
|
|
def test_repr_shows_masked_token(self, mock_jwt_token):
|
|
"""Test that __repr__ shows masked token."""
|
|
expires_at = time.time() + 3600
|
|
auth = JWTAuth(mock_jwt_token, expires_at=expires_at)
|
|
repr_str = repr(auth)
|
|
|
|
assert "JWTAuth" in repr_str
|
|
assert mock_jwt_token not in repr_str # Real token should not appear
|
|
assert auth.token_preview in repr_str # Masked token should appear
|
|
assert str(expires_at) in repr_str
|
|
|
|
def test_validate_credentials_succeeds_for_valid_token(self, jwt_auth):
|
|
"""Test that validate_credentials succeeds for valid token."""
|
|
# Should not raise any exception
|
|
jwt_auth.validate_credentials()
|
|
assert jwt_auth.is_valid() is True
|
|
|
|
def test_refresh_token_whitespace_handling(self, mock_jwt_token):
|
|
"""Test that refresh token whitespace is handled correctly."""
|
|
refresh_token = " refresh-token-123 "
|
|
auth = JWTAuth(mock_jwt_token, refresh_token)
|
|
assert auth._refresh_token == "refresh-token-123"
|
|
|
|
# Test None refresh token
|
|
auth = JWTAuth(mock_jwt_token, None)
|
|
assert auth._refresh_token is None
|