Files
openrouter-watcher/tests/conftest.py
Luca Sacchi Ricciardi d274970358 test(public-api): T40 add comprehensive public API endpoint tests
- Schema tests: 25 tests (100% coverage)
- Rate limit tests: 18 tests (98% coverage)
- Endpoint tests: 27 tests for stats/usage/keys
- Security tests: JWT rejection, inactive tokens, missing auth
- Total: 70 tests for public API v1
2026-04-07 16:16:18 +02:00

226 lines
6.8 KiB
Python

"""Pytest configuration and fixtures.
This module contains shared fixtures and configuration for all tests.
"""
import sys
import os
import pytest
import pytest_asyncio
# Add src to path for importing in tests
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
# Markers for test organization
pytest_plugins = ['pytest_asyncio']
def pytest_configure(config):
"""Configure pytest with custom markers."""
config.addinivalue_line("markers", "unit: Unit tests (no external dependencies)")
config.addinivalue_line("markers", "integration: Integration tests (with mocked dependencies)")
config.addinivalue_line("markers", "e2e: End-to-end tests (full workflow)")
config.addinivalue_line("markers", "slow: Slow tests (skip in quick mode)")
@pytest.fixture(scope='session')
def project_root():
"""Return the project root directory."""
return os.path.dirname(os.path.dirname(__file__))
@pytest.fixture(scope='session')
def src_path(project_root):
"""Return the src directory path."""
return os.path.join(project_root, 'src')
@pytest.fixture
def temp_dir(tmp_path):
"""Provide a temporary directory for tests."""
return tmp_path
@pytest.fixture
def mock_env_vars(monkeypatch):
"""Set up mock environment variables for testing."""
monkeypatch.setenv('SECRET_KEY', 'test-secret-key-min-32-characters-long')
monkeypatch.setenv('ENCRYPTION_KEY', 'test-32-byte-encryption-key!!')
monkeypatch.setenv('DATABASE_URL', 'sqlite:///./test.db')
monkeypatch.setenv('DEBUG', 'true')
monkeypatch.setenv('LOG_LEVEL', 'DEBUG')
@pytest.fixture
def mock_db():
"""Create a mock database session for unit tests."""
from unittest.mock import MagicMock
return MagicMock()
@pytest.fixture
def mock_user():
"""Create a mock authenticated user for testing."""
from unittest.mock import MagicMock
user = MagicMock()
user.id = 1
user.email = "test@example.com"
user.is_active = True
return user
@pytest.fixture
def mock_encryption_service():
"""Create a mock encryption service for testing."""
from unittest.mock import MagicMock
mock = MagicMock()
mock.encrypt.return_value = "encrypted_key_value"
mock.decrypt.return_value = "sk-or-v1-decrypted"
return mock
@pytest.fixture
def client():
"""Create a test client with fresh database."""
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from openrouter_monitor.database import Base, get_db
from openrouter_monitor.main import app
# Setup in-memory test database
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
"""Override get_db dependency for testing."""
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
Base.metadata.create_all(bind=engine)
with TestClient(app) as c:
yield c
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def db_session(client):
"""Get database session from client dependency override."""
from openrouter_monitor.database import get_db
from openrouter_monitor.main import app
# Get the override function
override = app.dependency_overrides.get(get_db)
if override:
db = next(override())
yield db
db.close()
else:
# Fallback - create new session
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from openrouter_monitor.database import Base
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base.metadata.create_all(bind=engine)
db = SessionLocal()
yield db
db.close()
@pytest.fixture
def auth_headers(client):
"""Create a user and return JWT auth headers."""
from openrouter_monitor.models import User
# Create test user via API
user_data = {
"email": "testuser@example.com",
"password": "TestPassword123!"
}
# Register user
response = client.post("/api/auth/register", json=user_data)
if response.status_code == 400: # User might already exist
pass
# Login to get token
response = client.post("/api/auth/login", json=user_data)
if response.status_code == 200:
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
# Fallback - create token directly
# Get user from db
from openrouter_monitor.database import get_db
from openrouter_monitor.main import app
from openrouter_monitor.services.jwt import create_access_token
override = app.dependency_overrides.get(get_db)
if override:
db = next(override())
user = db.query(User).filter(User.email == user_data["email"]).first()
if user:
token = create_access_token(data={"sub": str(user.id)})
return {"Authorization": f"Bearer {token}"}
return {}
@pytest.fixture
def authorized_client(client, auth_headers):
"""Create an authorized test client with JWT token."""
# Return client with auth headers pre-configured
original_get = client.get
original_post = client.post
original_put = client.put
original_delete = client.delete
def auth_get(url, **kwargs):
headers = kwargs.pop("headers", {})
headers.update(auth_headers)
return original_get(url, headers=headers, **kwargs)
def auth_post(url, **kwargs):
headers = kwargs.pop("headers", {})
headers.update(auth_headers)
return original_post(url, headers=headers, **kwargs)
def auth_put(url, **kwargs):
headers = kwargs.pop("headers", {})
headers.update(auth_headers)
return original_put(url, headers=headers, **kwargs)
def auth_delete(url, **kwargs):
headers = kwargs.pop("headers", {})
headers.update(auth_headers)
return original_delete(url, headers=headers, **kwargs)
client.get = auth_get
client.post = auth_post
client.put = auth_put
client.delete = auth_delete
yield client
# Restore original methods
client.get = original_get
client.post = original_post
client.put = original_put
client.delete = original_delete