""" Shared fixtures for all tests. Strategy: - Override `get_db` with an async generator that yields a mock asyncpg connection. - Override `get_current_user` / `require_admin` directly with plain async functions. - Patch `app.database.get_pool` so the lifespan startup never touches a real DB. - Patch `app.services.stripe_service` globally so no real Stripe calls are made. """ import asyncio from datetime import datetime, date, time from unittest.mock import AsyncMock, MagicMock, patch from uuid import UUID import pytest from httpx import AsyncClient, ASGITransport from main import app from app.dependencies import get_db, get_current_user, get_current_user_optional, require_admin # ── Sample data ─────────────────────────────────────────────────────────────── USER_ID = "11111111-1111-1111-1111-111111111111" ADMIN_ID = "22222222-2222-2222-2222-222222222222" PRODUCT_ID = "33333333-3333-3333-3333-333333333333" SLOT_ID = "44444444-4444-4444-4444-444444444444" BOOKING_ID = "55555555-5555-5555-5555-555555555555" ORDER_ID = "66666666-6666-6666-6666-666666666666" SAMPLE_USER = { "id": USER_ID, "email": "user@test.com", "full_name": "Test User", "phone": "+49123456789", "role": "client", "is_blocked": False, } SAMPLE_ADMIN = { "id": ADMIN_ID, "email": "admin@test.com", "full_name": "Admin User", "phone": None, "role": "admin", "is_blocked": False, } SAMPLE_PRODUCT = { "id": UUID(PRODUCT_ID), "name": "Extensions Clip-In Luxe", "description": "Top quality", "price": 189.0, "original_price": 229.0, "category": "clip-in", "images": ["https://example.com/img.jpg"], "_raw_images": ["https://example.com/img.jpg"], "image": "https://example.com/img.jpg", "colors": ["Black", "Brown"], "lengths": ["40cm", "50cm"], "features": ["100% Remy"], "stock_quantity": 10, "is_featured": False, "is_hidden": False, "is_new": True, "is_bestseller": True, "rating": 4.8, "review_count": 124, "created_at": datetime(2026, 1, 1), "updated_at": datetime(2026, 1, 1), } SAMPLE_SLOT = { "id": UUID(SLOT_ID), "date": date(2026, 6, 1), "start_time": time(10, 0), "end_time": time(11, 0), "is_blocked": False, "block_reason": None, "is_booked": False, } SAMPLE_BOOKING = { "id": UUID(BOOKING_ID), "user_id": UUID(USER_ID), "slot_id": UUID(SLOT_ID), "slot_date": date(2026, 6, 1), "slot_start": "10:00", "slot_end": "11:00", "service_note": "Box braids", "client_name": "Test User", "client_email": "user@test.com", "client_phone": "+49123456789", "status": "confirmed", "amount_paid": 50.0, "stripe_payment_intent_id": "pi_test123", "admin_notes": None, "created_at": datetime(2026, 1, 1), "updated_at": datetime(2026, 1, 1), } SAMPLE_SERVICE = { "id": UUID("77777777-7777-7777-7777-777777777777"), "name": "Pose complète", "description": "Full extension installation", "duration_minutes": 150, "price": 150.0, } # ── Mock asyncpg transaction context manager ────────────────────────────────── class MockTransaction: async def __aenter__(self): return self async def __aexit__(self, *args): return False # ── DB mock factory ─────────────────────────────────────────────────────────── def make_mock_db() -> AsyncMock: db = AsyncMock() db.fetchrow = AsyncMock(return_value=None) db.fetch = AsyncMock(return_value=[]) db.fetchval = AsyncMock(return_value=0) db.execute = AsyncMock(return_value="INSERT 1") db.transaction = MagicMock(return_value=MockTransaction()) return db def db_override(mock_conn): async def _override(): yield mock_conn return _override # ── Fixtures ────────────────────────────────────────────────────────────────── @pytest.fixture def mock_db(): return make_mock_db() @pytest.fixture(autouse=True) def patch_pool(): """Prevent lifespan from opening a real DB pool.""" with patch("app.database.get_pool", new_callable=AsyncMock) as m: m.return_value = AsyncMock() yield m @pytest.fixture(autouse=True) def patch_stripe(): """Prevent any real Stripe calls.""" intent = MagicMock() intent.id = "pi_test123" intent.client_secret = "pi_test123_secret" with patch("app.services.stripe_service.create_payment_intent", new_callable=AsyncMock, return_value=intent): with patch("app.services.stripe_service.create_refund", new_callable=AsyncMock): with patch("app.services.stripe_service.verify_webhook") as mock_wh: mock_wh.return_value = MagicMock( type="payment_intent.succeeded", get=MagicMock(return_value={ "object": {"id": "pi_test123", "metadata": {"entity_type": "order", "entity_id": ORDER_ID}} }), ) yield @pytest.fixture(autouse=True) def patch_stripe_modify(): """Patch the inline stripe.PaymentIntent.modify call in booking_service.""" with patch("stripe.PaymentIntent.modify", return_value=MagicMock()): yield @pytest.fixture async def anon_client(mock_db): app.dependency_overrides[get_db] = db_override(mock_db) async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c app.dependency_overrides.clear() @pytest.fixture async def auth_client(mock_db): app.dependency_overrides[get_db] = db_override(mock_db) app.dependency_overrides[get_current_user] = lambda: SAMPLE_USER app.dependency_overrides[get_current_user_optional] = lambda: SAMPLE_USER async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c app.dependency_overrides.clear() @pytest.fixture async def admin_client(mock_db): app.dependency_overrides[get_db] = db_override(mock_db) app.dependency_overrides[get_current_user] = lambda: SAMPLE_ADMIN app.dependency_overrides[require_admin] = lambda: SAMPLE_ADMIN async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c app.dependency_overrides.clear()