Initial Commit

This commit is contained in:
belviskhoremk
2026-05-12 00:34:21 +00:00
commit d2dc43b16f
57 changed files with 6056 additions and 0 deletions

202
tests/conftest.py Normal file
View File

@@ -0,0 +1,202 @@
"""
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()