""" Tests for app/middleware/rate_limit_decorators.py This module tests the rate limiting decorators for API endpoints. """ from unittest.mock import MagicMock, patch import pytest @pytest.mark.unit class TestRateLimitDecorators: """Test rate limit decorator functions.""" def test_get_limiter_initialization(self): """Test that get_limiter initializes limiter from app state.""" from app.middleware import rate_limit_decorators # Reset the global limiter rate_limit_decorators._limiter = None # Try to get limiter - will import app and get limiter from state # This test just verifies the function can be called try: # This may fail if app not fully initialized, which is okay for unit test limiter = rate_limit_decorators.get_limiter() # If it succeeds, limiter should not be None assert limiter is not None or rate_limit_decorators._limiter is None except Exception: # If it fails, that's okay - we're testing the logic path exists pass def test_get_limiter_caching(self): """Test that get_limiter caches the limiter instance.""" from app.middleware import rate_limit_decorators # Set up mock limiter directly mock_limiter = MagicMock() rate_limit_decorators._limiter = mock_limiter # Get limiter multiple times limiter1 = rate_limit_decorators.get_limiter() limiter2 = rate_limit_decorators.get_limiter() # Should return same instance assert limiter1 is limiter2 assert limiter1 is mock_limiter @patch("app.middleware.rate_limit_decorators.get_limiter") def test_limit_decorator(self, mock_get_limiter): """Test the limit decorator applies rate limit.""" from app.middleware.rate_limit_decorators import limit # Mock limiter mock_limiter = MagicMock() mock_limiter.limit = MagicMock(return_value=lambda f: f) mock_get_limiter.return_value = mock_limiter # Create a test function @limit("10/minute") async def test_endpoint(): return {"message": "success"} # Verify limiter.limit was called with correct rate mock_limiter.limit.assert_called_once_with("10/minute") @patch("app.middleware.rate_limit_decorators.get_limiter") def test_limit_decorator_with_different_rates(self, mock_get_limiter): """Test limit decorator with various rate limit strings.""" from app.middleware.rate_limit_decorators import limit # Mock limiter mock_limiter = MagicMock() mock_limiter.limit = MagicMock(return_value=lambda f: f) mock_get_limiter.return_value = mock_limiter # Test different rate limits rates = ["5/second", "100/hour", "1000/day"] for rate in rates: mock_limiter.limit.reset_mock() @limit(rate) async def test_endpoint(): return {"message": "success"} mock_limiter.limit.assert_called_once_with(rate) @patch("app.middleware.rate_limit_decorators.get_limiter") def test_exempt_decorator(self, mock_get_limiter): """Test the exempt decorator exempts endpoint from rate limiting.""" from app.middleware.rate_limit_decorators import exempt # Mock limiter mock_limiter = MagicMock() mock_limiter.exempt = MagicMock(return_value=lambda f: f) mock_get_limiter.return_value = mock_limiter # Create a test function @exempt() async def test_endpoint(): return {"message": "success"} # Verify limiter.exempt was called mock_limiter.exempt.assert_called_once() @patch("app.middleware.rate_limit_decorators.get_limiter") def test_limit_decorator_preserves_function(self, mock_get_limiter): """Test that limit decorator preserves the original function.""" from app.middleware.rate_limit_decorators import limit # Mock limiter to return the function unchanged mock_limiter = MagicMock() mock_limiter.limit = MagicMock(return_value=lambda f: f) mock_get_limiter.return_value = mock_limiter # Original function async def original_function(): return "original" # Decorate it @limit("10/minute") async def decorated_function(): return "original" # Function should still work import asyncio result = asyncio.run(decorated_function()) assert result == "original" @patch("app.middleware.rate_limit_decorators.get_limiter") def test_exempt_decorator_preserves_function(self, mock_get_limiter): """Test that exempt decorator preserves the original function.""" from app.middleware.rate_limit_decorators import exempt # Mock limiter to return a simple passthrough decorator mock_limiter = MagicMock() mock_limiter.exempt.side_effect = lambda f: f mock_get_limiter.return_value = mock_limiter # Decorate function @exempt() async def decorated_function(): return "exempted" # Function should still work import asyncio result = asyncio.run(decorated_function()) assert result == "exempted" def test_module_imports(self): """Test that the module can be imported without errors.""" from app.middleware import rate_limit_decorators assert hasattr(rate_limit_decorators, "get_limiter") assert hasattr(rate_limit_decorators, "limit") assert hasattr(rate_limit_decorators, "exempt") assert callable(rate_limit_decorators.get_limiter) assert callable(rate_limit_decorators.limit) assert callable(rate_limit_decorators.exempt)