Files
CMT/backend/tests/integration/conftest.py
T
linmihigo c086f64363 feat: implement complete CMT backend with API endpoints and test suite
- Add 7 core API endpoints: users, transactions, partners, products, inventory, payments, credit
- Implement role-based authentication (admin/write/read-only access)
- Add comprehensive database models with proper relationships
- Include full test coverage for all endpoints and business logic
- Set up Alembic migrations and Docker configuration
- Configure FastAPI app with CORS and database integration
2025-09-14 21:04:07 +02:00

254 lines
8.3 KiB
Python

"""Integration test configuration and fixtures.
This module provides fixtures and utilities for integration testing that involve
real database operations, Alembic migrations, and end-to-end API testing.
"""
import pytest
import tempfile
import os
from pathlib import Path
from sqlmodel import Session, SQLModel, create_engine, select, text
from sqlmodel.pool import StaticPool
from fastapi.testclient import TestClient
from alembic import command
from alembic.config import Config
from alembic.script import ScriptDirectory
from alembic.runtime.environment import EnvironmentContext
from app.main import app
from app.core.db import get_session
from app.core.config import settings
from app.schemas.models import User, Partner, Product
from app.schemas.base import UserRole
from app.core.auth import get_password_hash
class IntegrationTestConfig:
"""Configuration for integration tests."""
@staticmethod
def get_test_database_url():
"""Get test database URL. Uses a separate test database."""
# Use in-memory SQLite for integration tests to ensure writeability
return "sqlite:///:memory:"
@pytest.fixture(name="integration_engine", scope="session")
def integration_engine_fixture():
"""Create a test engine for integration tests."""
database_url = IntegrationTestConfig.get_test_database_url()
if database_url.startswith("sqlite"):
# For SQLite, use file-based database for integration tests
engine = create_engine(
database_url,
connect_args={"check_same_thread": False},
echo=False # Set to True for SQL debugging
)
else:
# For PostgreSQL
engine = create_engine(database_url, echo=False)
return engine
@pytest.fixture(name="integration_session", scope="function")
def integration_session_fixture(integration_engine):
"""Create a database session for integration tests with proper cleanup."""
# Create all tables
SQLModel.metadata.create_all(integration_engine)
with Session(integration_engine) as session:
yield session
# Clean up: drop all tables after each test
SQLModel.metadata.drop_all(integration_engine)
@pytest.fixture(name="integration_client")
def integration_client_fixture(integration_session):
"""Create a test client with integration database session."""
def get_session_override():
return integration_session
app.dependency_overrides[get_session] = get_session_override
client = TestClient(app)
yield client
app.dependency_overrides.clear()
@pytest.fixture(name="alembic_config")
def alembic_config_fixture(integration_engine):
"""Create Alembic configuration for migration testing."""
# Create a temporary alembic.ini for testing
config = Config()
config.set_main_option("script_location", "app/alembic")
config.set_main_option("sqlalchemy.url", str(integration_engine.url))
return config
@pytest.fixture(name="migration_context")
def migration_context_fixture(integration_engine, alembic_config):
"""Create migration context for testing migrations."""
script = ScriptDirectory.from_config(alembic_config)
def run_migrations(connection, config):
context = EnvironmentContext(config, script)
context.configure(
connection=connection,
target_metadata=SQLModel.metadata
)
with context.begin_transaction():
context.run_migrations()
with integration_engine.connect() as connection:
yield {
'connection': connection,
'config': alembic_config,
'script': script,
'run_migrations': lambda: run_migrations(connection, alembic_config)
}
@pytest.fixture(name="integration_admin_user")
def integration_admin_user_fixture(integration_session):
"""Create an admin user for integration tests."""
admin_user = User(
username="integration_admin",
password_hash=get_password_hash("admin_password"),
role=UserRole.ADMIN
)
integration_session.add(admin_user)
integration_session.commit()
integration_session.refresh(admin_user)
return admin_user
@pytest.fixture(name="integration_write_user")
def integration_write_user_fixture(integration_session):
"""Create a write user for integration tests."""
write_user = User(
username="integration_write",
password_hash=get_password_hash("write_password"),
role=UserRole.WRITE
)
integration_session.add(write_user)
integration_session.commit()
integration_session.refresh(write_user)
return write_user
@pytest.fixture(name="integration_read_user")
def integration_read_user_fixture(integration_session):
"""Create a read-only user for integration tests."""
read_user = User(
username="integration_read",
password_hash=get_password_hash("read_password"),
role=UserRole.READ_ONLY
)
integration_session.add(read_user)
integration_session.commit()
integration_session.refresh(read_user)
return read_user
@pytest.fixture(name="integration_admin_token")
def integration_admin_token_fixture(integration_client, integration_admin_user):
"""Get admin authentication headers for integration tests."""
response = integration_client.post("/api/v1/users/login", json={
"username": "integration_admin",
"password": "admin_password"
})
assert response.status_code == 200
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture(name="integration_write_token")
def integration_write_token_fixture(integration_client, integration_write_user):
"""Get write user authentication headers for integration tests."""
response = integration_client.post("/api/v1/users/login", json={
"username": "integration_write",
"password": "write_password"
})
assert response.status_code == 200
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture(name="integration_read_token")
def integration_read_token_fixture(integration_client, integration_read_user):
"""Get read-only user authentication headers for integration tests."""
response = integration_client.post("/api/v1/users/login", json={
"username": "integration_read",
"password": "read_password"
})
assert response.status_code == 200
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
def cleanup_test_database():
"""Utility function to clean up test database files."""
test_db_files = ["test_integration.db", "test_integration.db-wal", "test_integration.db-shm"]
for file_name in test_db_files:
if os.path.exists(file_name):
try:
os.remove(file_name)
except OSError:
pass # File might be in use or already deleted
def verify_database_integrity(session: Session) -> dict:
"""Verify database integrity and return diagnostics."""
try:
# Check if we can query basic tables using SQLModel queries
users = session.exec(select(User)).all()
partners = session.exec(select(Partner)).all()
products = session.exec(select(Product)).all()
return {
"status": "healthy",
"users": len(users),
"partners": len(partners),
"products": len(products),
"tables_accessible": True
}
except Exception as e:
return {
"status": "error",
"error": str(e),
"tables_accessible": False
}
# Pytest configuration for integration tests
def pytest_configure(config):
"""Configure pytest for integration tests."""
config.addinivalue_line(
"markers", "integration: mark test as integration test"
)
config.addinivalue_line(
"markers", "slow: mark test as slow running"
)
config.addinivalue_line(
"markers", "database: mark test as requiring database"
)
config.addinivalue_line(
"markers", "migration: mark test as testing migrations"
)
def pytest_runtest_setup(item):
"""Setup for each integration test."""
# Clean up any leftover test database files
cleanup_test_database()
def pytest_runtest_teardown(item):
"""Teardown for each integration test."""
# Clean up test database files after each test
cleanup_test_database()