# tests/conftest.py import os import uuid import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient # Set IS_TEST environment variable BEFORE importing app # This prevents the scheduler from starting during tests os.environ["IS_TEST"] = "True" from app.core.auth import get_password_hash from app.core.database import get_db from app.main import app from app.models.user import User from app.utils.test_utils import ( setup_async_test_db, setup_test_db, teardown_async_test_db, teardown_test_db, ) @pytest.fixture(scope="function") def db_session(): """ Creates a fresh SQLite in-memory database for each test function. Yields a SQLAlchemy session that can be used for testing. """ # Set up the database test_engine, TestingSessionLocal = setup_test_db() # Create a session with TestingSessionLocal() as session: yield session # Clean up teardown_test_db(test_engine) @pytest_asyncio.fixture(scope="function") # Function scope for isolation async def async_test_db(): """Fixture provides testing engine and session for each test. Each test gets a fresh database for complete isolation. """ test_engine, AsyncTestingSessionLocal = await setup_async_test_db() yield test_engine, AsyncTestingSessionLocal await teardown_async_test_db(test_engine) @pytest.fixture def user_create_data(): return { "email": "newtest@example.com", # Changed to avoid conflict with mock_user "password": "TestPassword123!", "first_name": "Test", "last_name": "User", "phone_number": "+1234567890", "is_superuser": False, "preferences": None, } @pytest.fixture def mock_user(db_session): """Fixture to create and return a mock User instance.""" mock_user = User( id=uuid.uuid4(), email="mockuser@example.com", password_hash="mockhashedpassword", first_name="Mock", last_name="User", phone_number="1234567890", is_active=True, is_superuser=False, preferences=None, ) db_session.add(mock_user) db_session.commit() return mock_user @pytest.fixture(scope="function") def test_db(): """ Creates a test database for integration tests. This creates a fresh database for each test to ensure isolation. """ test_engine, TestingSessionLocal = setup_test_db() # Create a session with TestingSessionLocal() as session: yield session # Clean up teardown_test_db(test_engine) @pytest_asyncio.fixture(scope="function") async def client(async_test_db): """ Create a FastAPI async test client with a test database. This overrides the get_db dependency to use the test database. """ _test_engine, AsyncTestingSessionLocal = async_test_db async def override_get_db(): async with AsyncTestingSessionLocal() as session: try: yield session finally: pass app.dependency_overrides[get_db] = override_get_db # Use ASGITransport for httpx >= 0.27 transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as test_client: yield test_client app.dependency_overrides.clear() @pytest.fixture def test_user(test_db): """ Create a test user in the database (sync version for legacy tests). Password: TestPassword123 """ user = User( id=uuid.uuid4(), email="testuser@example.com", password_hash=get_password_hash("TestPassword123!"), first_name="Test", last_name="User", phone_number="+1234567890", is_active=True, is_superuser=False, preferences=None, ) test_db.add(user) test_db.commit() test_db.refresh(user) return user @pytest.fixture def test_superuser(test_db): """ Create a test superuser in the database (sync version for legacy tests). Password: SuperPassword123 """ user = User( id=uuid.uuid4(), email="superuser@example.com", password_hash=get_password_hash("SuperPassword123!"), first_name="Super", last_name="User", phone_number="+9876543210", is_active=True, is_superuser=True, preferences=None, ) test_db.add(user) test_db.commit() test_db.refresh(user) return user @pytest_asyncio.fixture async def async_test_user(async_test_db): """ Create a test user in the database (async version). Password: TestPassword123 """ _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: user = User( id=uuid.uuid4(), email="testuser@example.com", password_hash=get_password_hash("TestPassword123!"), first_name="Test", last_name="User", phone_number="+1234567890", is_active=True, is_superuser=False, preferences=None, ) session.add(user) await session.commit() await session.refresh(user) return user @pytest_asyncio.fixture async def async_test_superuser(async_test_db): """ Create a test superuser in the database (async version). Password: SuperPassword123 """ _test_engine, AsyncTestingSessionLocal = async_test_db async with AsyncTestingSessionLocal() as session: user = User( id=uuid.uuid4(), email="superuser@example.com", password_hash=get_password_hash("SuperPassword123!"), first_name="Super", last_name="User", phone_number="+9876543210", is_active=True, is_superuser=True, preferences=None, ) session.add(user) await session.commit() await session.refresh(user) return user @pytest_asyncio.fixture async def user_token(client, async_test_user): """ Create an access token for the test user. Returns the access token string that can be used in Authorization headers. """ response = await client.post( "/api/v1/auth/login", json={ "email": async_test_user.email, "password": "TestPassword123!", }, ) assert response.status_code == 200, f"Login failed: {response.text}" tokens = response.json() return tokens["access_token"] @pytest_asyncio.fixture async def superuser_token(client, async_test_superuser): """ Create an access token for the test superuser. Returns the access token string that can be used in Authorization headers. """ response = await client.post( "/api/v1/auth/login", json={ "email": async_test_superuser.email, "password": "SuperPassword123!", }, ) assert response.status_code == 200, f"Login failed: {response.text}" tokens = response.json() return tokens["access_token"]