""" Integration test for the complete subscription creation flow Tests user registration, subscription creation, tenant creation, and linking """ import pytest import asyncio import httpx import stripe import os from datetime import datetime, timezone from typing import Dict, Any, Optional class SubscriptionCreationFlowTester: """Test the complete subscription creation flow""" def __init__(self): self.base_url = "https://bakery-ia.local" self.timeout = 30.0 self.test_user_email = f"test_{datetime.now().strftime('%Y%m%d%H%M%S')}@example.com" self.test_user_password = "SecurePassword123!" self.test_user_full_name = "Test User" self.test_plan_id = "starter" # Valid plans: starter, professional, enterprise self.test_payment_method_id = None # Will be created dynamically # Initialize Stripe with API key from environment stripe_key = os.environ.get('STRIPE_SECRET_KEY') if stripe_key: stripe.api_key = stripe_key print(f"โœ… Stripe initialized with test mode API key") else: print(f"โš ๏ธ Warning: STRIPE_SECRET_KEY not found in environment") # Store created resources for cleanup self.created_user_id = None self.created_subscription_id = None self.created_tenant_id = None self.created_payment_method_id = None def _create_test_payment_method(self) -> str: """ Create a real Stripe test payment method using Stripe's pre-made test tokens This simulates what happens in production when a user enters their card details In production: Frontend uses Stripe.js to tokenize card โ†’ creates PaymentMethod In testing: We use Stripe's pre-made test tokens (tok_visa, tok_mastercard, etc.) See: https://stripe.com/docs/testing#cards """ try: print(f"๐Ÿ’ณ Creating Stripe test payment method...") # Use Stripe's pre-made test token tok_visa # This is the recommended approach for testing and mimics production flow # In production, Stripe.js creates a similar token from card details payment_method = stripe.PaymentMethod.create( type="card", card={"token": "tok_visa"} # Stripe's pre-made test token ) self.created_payment_method_id = payment_method.id print(f"โœ… Created Stripe test payment method: {payment_method.id}") print(f" This simulates a real card in production") return payment_method.id except Exception as e: print(f"โŒ Failed to create payment method: {str(e)}") print(f" Tip: Ensure raw card API is enabled in Stripe dashboard:") print(f" https://dashboard.stripe.com/settings/integration") raise async def test_complete_flow(self): """Test the complete subscription creation flow""" print(f"๐Ÿงช Starting subscription creation flow test for {self.test_user_email}") try: # Step 0: Create a real Stripe test payment method # This is EXACTLY what happens in production when user enters card details self.test_payment_method_id = self._create_test_payment_method() print(f"โœ… Step 0: Test payment method created") # Step 1: Register user with subscription user_data = await self._register_user_with_subscription() print(f"โœ… Step 1: User registered successfully - user_id: {user_data['user']['id']}") # Step 2: Verify user was created in database await self._verify_user_in_database(user_data['user']['id']) print(f"โœ… Step 2: User verified in database") # Step 3: Verify subscription was created (tenant-independent) subscription_data = await self._verify_subscription_created(user_data['user']['id']) print(f"โœ… Step 3: Tenant-independent subscription verified - subscription_id: {subscription_data['subscription_id']}") # Step 4: Create tenant and link subscription tenant_data = await self._create_tenant_and_link_subscription(user_data['user']['id'], subscription_data['subscription_id']) print(f"โœ… Step 4: Tenant created and subscription linked - tenant_id: {tenant_data['tenant_id']}") # Step 5: Verify subscription is linked to tenant await self._verify_subscription_linked_to_tenant(subscription_data['subscription_id'], tenant_data['tenant_id']) print(f"โœ… Step 5: Subscription-tenant link verified") # Step 6: Verify tenant can access subscription await self._verify_tenant_subscription_access(tenant_data['tenant_id']) print(f"โœ… Step 6: Tenant subscription access verified") print(f"๐ŸŽ‰ All tests passed! Complete flow working correctly") return True except Exception as e: print(f"โŒ Test failed: {str(e)}") return False finally: # Cleanup (optional - comment out if you want to inspect the data) # await self._cleanup_resources() pass async def _register_user_with_subscription(self) -> Dict[str, Any]: """Register a new user with subscription""" url = f"{self.base_url}/api/v1/auth/register-with-subscription" payload = { "email": self.test_user_email, "password": self.test_user_password, "full_name": self.test_user_full_name, "subscription_plan": self.test_plan_id, "payment_method_id": self.test_payment_method_id } async with httpx.AsyncClient(timeout=self.timeout, verify=False) as client: response = await client.post(url, json=payload) if response.status_code != 200: error_msg = f"User registration failed: {response.status_code} - {response.text}" print(f"๐Ÿšจ {error_msg}") raise Exception(error_msg) result = response.json() self.created_user_id = result['user']['id'] return result async def _verify_user_in_database(self, user_id: str): """Verify user was created in the database""" # This would be a direct database query in a real test # For now, we'll just check that the user ID is valid if not user_id or len(user_id) != 36: # UUID should be 36 characters raise Exception(f"Invalid user ID: {user_id}") print(f"๐Ÿ“‹ User ID validated: {user_id}") async def _verify_subscription_created(self, user_id: str) -> Dict[str, Any]: """Verify that a tenant-independent subscription was created""" # Check the onboarding progress to see if subscription data was stored url = f"{self.base_url}/api/v1/auth/me/onboarding/progress" # Get access token for the user access_token = await self._get_user_access_token() async with httpx.AsyncClient(timeout=self.timeout, verify=False) as client: headers = {"Authorization": f"Bearer {access_token}"} response = await client.get(url, headers=headers) if response.status_code != 200: error_msg = f"Failed to get onboarding progress: {response.status_code} - {response.text}" print(f"๐Ÿšจ {error_msg}") raise Exception(error_msg) progress_data = response.json() # Check if subscription data is in the progress subscription_data = None for step in progress_data.get('steps', []): if step.get('step_name') == 'subscription': subscription_data = step.get('step_data', {}) break if not subscription_data: raise Exception("No subscription data found in onboarding progress") # Store subscription ID for later steps subscription_id = subscription_data.get('subscription_id') if not subscription_id: raise Exception("No subscription ID found in onboarding progress") self.created_subscription_id = subscription_id return { 'subscription_id': subscription_id, 'plan_id': subscription_data.get('plan_id'), 'payment_method_id': subscription_data.get('payment_method_id'), 'billing_cycle': subscription_data.get('billing_cycle') } async def _get_user_access_token(self) -> str: """Get access token for the test user""" url = f"{self.base_url}/api/v1/auth/login" payload = { "email": self.test_user_email, "password": self.test_user_password } async with httpx.AsyncClient(timeout=self.timeout, verify=False) as client: response = await client.post(url, json=payload) if response.status_code != 200: error_msg = f"User login failed: {response.status_code} - {response.text}" print(f"๐Ÿšจ {error_msg}") raise Exception(error_msg) result = response.json() return result['access_token'] async def _create_tenant_and_link_subscription(self, user_id: str, subscription_id: str) -> Dict[str, Any]: """Create a tenant and link the subscription to it""" # This would typically be done during the onboarding flow # For testing purposes, we'll simulate this by calling the tenant service directly url = f"{self.base_url}/api/v1/tenants" # Get access token for the user access_token = await self._get_user_access_token() payload = { "name": f"Test Bakery {datetime.now().strftime('%Y%m%d%H%M%S')}", "description": "Test bakery for integration testing", "subscription_id": subscription_id, "user_id": user_id } async with httpx.AsyncClient(timeout=self.timeout, verify=False) as client: headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } response = await client.post(url, json=payload, headers=headers) if response.status_code != 201: error_msg = f"Tenant creation failed: {response.status_code} - {response.text}" print(f"๐Ÿšจ {error_msg}") raise Exception(error_msg) result = response.json() self.created_tenant_id = result['id'] return { 'tenant_id': result['id'], 'name': result['name'], 'status': result['status'] } async def _verify_subscription_linked_to_tenant(self, subscription_id: str, tenant_id: str): """Verify that the subscription is properly linked to the tenant""" url = f"{self.base_url}/api/v1/tenants/{tenant_id}/subscription/status" # Get access token for the user access_token = await self._get_user_access_token() async with httpx.AsyncClient(timeout=self.timeout, verify=False) as client: headers = {"Authorization": f"Bearer {access_token}"} response = await client.get(url, headers=headers) if response.status_code != 200: error_msg = f"Failed to get subscription status: {response.status_code} - {response.text}" print(f"๐Ÿšจ {error_msg}") raise Exception(error_msg) subscription_status = response.json() # Verify that the subscription is active and linked to the tenant if subscription_status['status'] not in ['active', 'trialing']: raise Exception(f"Subscription status is {subscription_status['status']}, expected 'active' or 'trialing'") if subscription_status['tenant_id'] != tenant_id: raise Exception(f"Subscription linked to wrong tenant: {subscription_status['tenant_id']} != {tenant_id}") print(f"๐Ÿ“‹ Subscription status verified: {subscription_status['status']}") print(f"๐Ÿ“‹ Subscription linked to tenant: {subscription_status['tenant_id']}") async def _verify_tenant_subscription_access(self, tenant_id: str): """Verify that the tenant can access its subscription""" url = f"{self.base_url}/api/v1/tenants/{tenant_id}/subscription/details" # Get access token for the user access_token = await self._get_user_access_token() async with httpx.AsyncClient(timeout=self.timeout, verify=False) as client: headers = {"Authorization": f"Bearer {access_token}"} response = await client.get(url, headers=headers) if response.status_code != 200: error_msg = f"Failed to get active subscription: {response.status_code} - {response.text}" print(f"๐Ÿšจ {error_msg}") raise Exception(error_msg) subscription_data = response.json() # Verify that the subscription data is complete required_fields = ['id', 'status', 'plan', 'current_period_start', 'current_period_end'] for field in required_fields: if field not in subscription_data: raise Exception(f"Missing required field in subscription data: {field}") print(f"๐Ÿ“‹ Active subscription verified for tenant {tenant_id}") print(f"๐Ÿ“‹ Subscription plan: {subscription_data['plan']}") print(f"๐Ÿ“‹ Subscription status: {subscription_data['status']}") async def _cleanup_resources(self): """Clean up test resources""" print("๐Ÿงน Cleaning up test resources...") # In a real test, you would delete the user, tenant, and subscription # For now, we'll just print what would be cleaned up print(f"Would delete user: {self.created_user_id}") print(f"Would delete subscription: {self.created_subscription_id}") print(f"Would delete tenant: {self.created_tenant_id}") @pytest.mark.asyncio async def test_subscription_creation_flow(): """Test the complete subscription creation flow""" tester = SubscriptionCreationFlowTester() result = await tester.test_complete_flow() assert result is True, "Subscription creation flow test failed" if __name__ == "__main__": # Run the test import asyncio print("๐Ÿš€ Starting subscription creation flow integration test...") # Create and run the test tester = SubscriptionCreationFlowTester() # Run the test success = asyncio.run(tester.test_complete_flow()) if success: print("\n๐ŸŽ‰ Integration test completed successfully!") print("\nTest Summary:") print(f"โœ… User registration with subscription") print(f"โœ… User verification in database") print(f"โœ… Tenant-independent subscription creation") print(f"โœ… Tenant creation and subscription linking") print(f"โœ… Subscription-tenant link verification") print(f"โœ… Tenant subscription access verification") print(f"\nAll components working together correctly! ๐Ÿš€") else: print("\nโŒ Integration test failed!") exit(1)