# ================================================================ # services/auth/tests/test_subscription_fetcher.py # ================================================================ """ Test suite for subscription fetcher functionality """ import pytest from unittest.mock import Mock, AsyncMock, patch from fastapi import HTTPException, status from app.utils.subscription_fetcher import SubscriptionFetcher from app.services.auth_service import EnhancedAuthService class TestSubscriptionFetcher: """Tests for SubscriptionFetcher""" @pytest.mark.asyncio @pytest.mark.unit async def test_subscription_fetcher_correct_url(self): """Test that subscription fetcher uses the correct URL""" fetcher = SubscriptionFetcher("http://tenant-service:8000") # Mock httpx.AsyncClient to capture the URL being called with patch('httpx.AsyncClient') as mock_client_class: mock_client = AsyncMock() mock_client_class.return_value.__aenter__.return_value = mock_client # Mock the response mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = [] mock_client.get.return_value = mock_response # Call the method try: await fetcher.get_user_subscription_context("test-user-id", "test-service-token") except Exception: pass # We're just testing the URL, not the full flow # Verify the correct URL was called mock_client.get.assert_called_once() called_url = mock_client.get.call_args[0][0] # Should use the corrected URL assert called_url == "http://tenant-service:8000/api/v1/tenants/members/user/test-user-id" assert called_url != "http://tenant-service:8000/api/v1/users/test-user-id/memberships" @pytest.mark.asyncio @pytest.mark.unit async def test_service_token_creation(self): """Test that service tokens are created properly""" # Test the JWT handler directly from shared.auth.jwt_handler import JWTHandler handler = JWTHandler("test-secret-key") # Create a service token service_token = handler.create_service_token("auth-service") # Verify it's a valid JWT assert isinstance(service_token, str) assert len(service_token) > 0 # Verify we can decode it (without verification for testing) import jwt decoded = jwt.decode(service_token, options={"verify_signature": False}) # Verify service token structure assert decoded["type"] == "service" assert decoded["service"] == "auth-service" assert decoded["is_service"] is True assert decoded["role"] == "admin" @pytest.mark.asyncio @pytest.mark.unit async def test_auth_service_uses_correct_token(self): """Test that EnhancedAuthService uses proper service tokens""" # Mock the database manager mock_db_manager = Mock() mock_session = AsyncMock() mock_db_manager.get_session.return_value.__aenter__.return_value = mock_session # Create auth service auth_service = EnhancedAuthService(mock_db_manager) # Mock the JWT handler to capture calls with patch('app.core.security.SecurityManager.create_service_token') as mock_create_token: mock_create_token.return_value = "test-service-token" # Call the method that generates service tokens service_token = await auth_service._get_service_token() # Verify it was called correctly mock_create_token.assert_called_once_with("auth-service") assert service_token == "test-service-token" class TestServiceTokenValidation: """Tests for service token validation in tenant service""" @pytest.mark.asyncio @pytest.mark.unit async def test_service_token_validation(self): """Test that service tokens are properly validated""" from shared.auth.jwt_handler import JWTHandler from shared.auth.decorators import extract_user_from_jwt # Create a service token handler = JWTHandler("test-secret-key") service_token = handler.create_service_token("auth-service") # Create a mock request with the service token mock_request = Mock() mock_request.headers = { "authorization": f"Bearer {service_token}" } # Extract user from JWT user_context = extract_user_from_jwt(f"Bearer {service_token}") # Verify service user context assert user_context is not None assert user_context["type"] == "service" assert user_context["is_service"] is True assert user_context["role"] == "admin" assert user_context["service"] == "auth-service" class TestIntegrationFlow: """Integration tests for the complete login flow""" @pytest.mark.asyncio @pytest.mark.integration async def test_complete_login_flow_mocked(self): """Test the complete login flow with mocked services""" # Mock database manager mock_db_manager = Mock() mock_session = AsyncMock() mock_db_manager.get_session.return_value.__aenter__.return_value = mock_session # Create auth service auth_service = EnhancedAuthService(mock_db_manager) # Mock user authentication mock_user = Mock() mock_user.id = "test-user-id" mock_user.email = "test@bakery.es" mock_user.full_name = "Test User" mock_user.is_active = True mock_user.is_verified = True mock_user.role = "admin" # Mock repositories mock_user_repo = AsyncMock() mock_user_repo.authenticate_user.return_value = mock_user mock_user_repo.update_last_login.return_value = None mock_token_repo = AsyncMock() mock_token_repo.revoke_all_user_tokens.return_value = None mock_token_repo.create_token.return_value = None # Mock UnitOfWork mock_uow = AsyncMock() mock_uow.register_repository.side_effect = lambda name, repo_class, model: { "users": mock_user_repo, "tokens": mock_token_repo }[name] mock_uow.commit.return_value = None # Mock subscription fetcher with patch('app.utils.subscription_fetcher.SubscriptionFetcher') as mock_fetcher_class: mock_fetcher = AsyncMock() mock_fetcher_class.return_value = mock_fetcher # Mock subscription data mock_fetcher.get_user_subscription_context.return_value = { "tenant_id": "test-tenant-id", "tenant_role": "owner", "subscription": { "tier": "professional", "status": "active", "valid_until": "2025-02-15T00:00:00Z" }, "tenant_access": [] } # Mock service token generation with patch.object(auth_service, '_get_service_token', return_value="test-service-token"): # Mock SecurityManager methods with patch('app.core.security.SecurityManager.create_access_token', return_value="access-token"): with patch('app.core.security.SecurityManager.create_refresh_token', return_value="refresh-token"): # Create login data from app.schemas.auth import UserLogin login_data = UserLogin( email="test@bakery.es", password="password123" ) # Call login result = await auth_service.login_user(login_data) # Verify the result assert result is not None assert result.access_token == "access-token" assert result.refresh_token == "refresh-token" # Verify subscription fetcher was called with correct URL mock_fetcher.get_user_subscription_context.assert_called_once() call_args = mock_fetcher.get_user_subscription_context.call_args # Check that the fetcher was initialized with correct URL fetcher_init_call = mock_fetcher_class.call_args assert "tenant-service:8000" in str(fetcher_init_call) # Verify service token was used assert call_args[1]["service_token"] == "test-service-token" class TestErrorHandling: """Tests for error handling in subscription fetching""" @pytest.mark.asyncio @pytest.mark.unit async def test_subscription_fetcher_404_handling(self): """Test handling of 404 errors from tenant service""" fetcher = SubscriptionFetcher("http://tenant-service:8000") with patch('httpx.AsyncClient') as mock_client_class: mock_client = AsyncMock() mock_client_class.return_value.__aenter__.return_value = mock_client # Mock 404 response mock_response = Mock() mock_response.status_code = 404 mock_client.get.return_value = mock_response # This should raise an HTTPException with pytest.raises(HTTPException) as exc_info: await fetcher.get_user_subscription_context("test-user-id", "test-service-token") assert exc_info.value.status_code == 500 assert "Failed to fetch user memberships" in str(exc_info.value.detail) @pytest.mark.asyncio @pytest.mark.unit async def test_subscription_fetcher_500_handling(self): """Test handling of 500 errors from tenant service""" fetcher = SubscriptionFetcher("http://tenant-service:8000") with patch('httpx.AsyncClient') as mock_client_class: mock_client = AsyncMock() mock_client_class.return_value.__aenter__.return_value = mock_client # Mock 500 response mock_response = Mock() mock_response.status_code = 500 mock_client.get.return_value = mock_response # This should raise an HTTPException with pytest.raises(HTTPException) as exc_info: await fetcher.get_user_subscription_context("test-user-id", "test-service-token") assert exc_info.value.status_code == 500 assert "Failed to fetch user memberships" in str(exc_info.value.detail) class TestUrlCorrection: """Tests to verify the URL correction is working""" @pytest.mark.unit def test_url_pattern_correction(self): """Test that the URL pattern is correctly fixed""" # This test documents the fix that was made # OLD (incorrect) URL pattern old_url = "http://tenant-service:8000/api/v1/users/{user_id}/memberships" # NEW (correct) URL pattern new_url = "http://tenant-service:8000/api/v1/tenants/members/user/{user_id}" # Verify they're different assert old_url != new_url # Verify the new URL follows the correct pattern assert "/api/v1/tenants/" in new_url assert "/members/user/" in new_url assert "{user_id}" in new_url # Verify the old URL is not used assert "/api/v1/users/" not in new_url assert "/memberships" not in new_url