Files
wikijs-sdk-python/tests/auth/test_jwt.py
2025-07-29 20:16:11 -04:00

213 lines
8.9 KiB
Python

"""Tests for JWT authentication."""
import time
from datetime import datetime, 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