295 lines
12 KiB
Python
295 lines
12 KiB
Python
# ================================================================
|
|
# 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 |