From e652e4b7b3cb5cd469bf0017f3243ccef0a1d3bf Mon Sep 17 00:00:00 2001 From: Daniel Bedeleanu Date: Sat, 18 Apr 2026 16:54:53 +0000 Subject: [PATCH] test: improve conftest.py code quality - add type hints, docstrings, DRY refactoring --- backend/tests/conftest.py | 116 ++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 90555dd5..78783fc1 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,6 +1,8 @@ import pytest +import json +from typing import Generator, Callable, Optional from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, Session from sqlalchemy.pool import StaticPool from fastapi.testclient import TestClient from unittest.mock import patch, MagicMock @@ -13,6 +15,18 @@ from backend.config_manager import ConfigManager from backend.auth import get_current_admin, TokenData +# Test data constants +TEST_ADMIN_USERNAME = "admin" +TEST_USER_USERNAME = "user" +TEST_HASHED_PASSWORD = "hashed_password" +TEST_LDAP_DN = "uid=testuser,ou=people,dc=example,dc=com" +TEST_AI_RESPONSE = { + "name": "Test Item", + "part_number": "PN-12345", + "category": "Electronics", + "quantity": 5 +} + # Use in-memory SQLite for tests SQLALCHEMY_DATABASE_URL = "sqlite://" @@ -25,7 +39,7 @@ TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engin @pytest.fixture(scope="function", autouse=True) -def mock_scheduler(): +def mock_scheduler() -> Generator[None, None, None]: """Shutdown scheduler before and after each test to avoid conflicts.""" from backend.scheduler import scheduler if scheduler.running: @@ -36,7 +50,7 @@ def mock_scheduler(): @pytest.fixture(scope="function") -def test_db(): +def test_db() -> Generator[Session, None, None]: """Create in-memory SQLite database for tests.""" Base.metadata.create_all(bind=engine) session = TestingSessionLocal() @@ -46,10 +60,10 @@ def test_db(): @pytest.fixture(scope="function") -def test_client(test_db): +def test_client(test_db: Session) -> Generator[TestClient, None, None]: """Create FastAPI test client with mocked database.""" - def override_get_db(): + def override_get_db() -> Generator[Session, None, None]: try: yield test_db finally: @@ -63,7 +77,7 @@ def test_client(test_db): @pytest.fixture(scope="function") -def mock_ldap(): +def mock_ldap() -> Generator[MagicMock, None, None]: """Mock LDAP authentication.""" with patch("backend.auth.ldap3.Server") as mock_server, \ patch("backend.auth.ldap3.Connection") as mock_conn_class: @@ -72,7 +86,7 @@ def mock_ldap(): mock_conn.bind.return_value = True mock_conn.search.return_value = True mock_conn.entries = [ - MagicMock(entry_dn="uid=testuser,ou=people,dc=example,dc=com", + MagicMock(entry_dn=TEST_LDAP_DN, uid=["testuser"]) ] mock_conn_class.return_value = mock_conn @@ -81,31 +95,21 @@ def mock_ldap(): @pytest.fixture(scope="function") -def mock_gemini(): +def mock_gemini() -> Generator[MagicMock, None, None]: """Mock Google Gemini AI extraction.""" with patch("backend.ai.gemini_extractor.generate_content") as mock_gen: mock_response = MagicMock() - mock_response.text = '''{ - "name": "Test Item", - "part_number": "PN-12345", - "category": "Electronics", - "quantity": 5 - }''' + mock_response.text = json.dumps(TEST_AI_RESPONSE) mock_gen.return_value = mock_response yield mock_gen @pytest.fixture(scope="function") -def mock_claude(): +def mock_claude() -> Generator[MagicMock, None, None]: """Mock Anthropic Claude AI extraction.""" with patch("backend.ai.claude_extractor.generate_content") as mock_gen: mock_content = MagicMock() - mock_content.text = '''{ - "name": "Test Item", - "part_number": "PN-12345", - "category": "Electronics", - "quantity": 5 - }''' + mock_content.text = json.dumps(TEST_AI_RESPONSE) mock_response = MagicMock() mock_response.content = [mock_content] mock_gen.return_value = mock_response @@ -113,7 +117,7 @@ def mock_claude(): @pytest.fixture(scope="function") -def mock_config(): +def mock_config() -> Generator[MagicMock, None, None]: """Mock ConfigManager.""" with patch.object(ConfigManager, "get_config") as mock_get: mock_get.return_value = { @@ -124,59 +128,51 @@ def mock_config(): @pytest.fixture(scope="function") -def admin_token(test_db): - """Create a test admin user and return JWT token string.""" - from backend.auth import create_access_token +def create_test_user(test_db: Session) -> Callable[[str, str], str]: + """Factory fixture to create test users with tokens.""" + def _create_user(username: str, role: str) -> str: + from backend.auth import create_access_token - admin = User( - username="admin", - hashed_password="hashed_password", - role="admin", - origin="local" - ) - test_db.add(admin) - test_db.commit() - test_db.refresh(admin) + user = User( + username=username, + hashed_password=TEST_HASHED_PASSWORD, + role=role, + origin="local" + ) + test_db.add(user) + test_db.commit() + test_db.refresh(user) + return create_access_token(user_id=user.id, username=username, role=role) - # Return JWT token string (for use in "Bearer {token}" headers) - token = create_access_token(user_id=admin.id, username="admin", role="admin") - return token + return _create_user @pytest.fixture(scope="function") -def user_token(test_db): - """Create a test regular user and return JWT token string.""" - from backend.auth import create_access_token - - user = User( - username="user", - hashed_password="hashed_password", - role="user", - origin="local" - ) - test_db.add(user) - test_db.commit() - test_db.refresh(user) - - # Return JWT token string (for use in "Bearer {token}" headers) - token = create_access_token(user_id=user.id, username="user", role="user") - return token +def admin_token(create_test_user: Callable[[str, str], str]) -> str: + """Create test admin user and return JWT token.""" + return create_test_user(TEST_ADMIN_USERNAME, "admin") @pytest.fixture(scope="function") -def admin_client(test_client, test_db, admin_token): +def user_token(create_test_user: Callable[[str, str], str]) -> str: + """Create test regular user and return JWT token.""" + return create_test_user(TEST_USER_USERNAME, "user") + + +@pytest.fixture(scope="function") +def admin_client(test_client: TestClient, test_db: Session, admin_token: str) -> Generator[TestClient, None, None]: """Create a test client authenticated as admin.""" from backend.auth import get_current_user # Create TokenData from the JWT token's user info admin_token_data = TokenData( sub=1, # First admin user created has id=1 - username="admin", + username=TEST_ADMIN_USERNAME, role="admin", exp=datetime.now(timezone.utc) ) - def override_get_current_user(): + def override_get_current_user() -> TokenData: return admin_token_data app.dependency_overrides[get_current_user] = override_get_current_user @@ -187,19 +183,19 @@ def admin_client(test_client, test_db, admin_token): @pytest.fixture(scope="function") -def user_client(test_client, test_db, user_token): +def user_client(test_client: TestClient, test_db: Session, user_token: str) -> Generator[TestClient, None, None]: """Create a test client authenticated as regular user.""" from backend.auth import get_current_user # Create TokenData from the JWT token's user info user_token_data = TokenData( sub=2, # Second user created has id=2 - username="user", + username=TEST_USER_USERNAME, role="user", exp=datetime.now(timezone.utc) ) - def override_get_current_user(): + def override_get_current_user() -> TokenData: return user_token_data app.dependency_overrides[get_current_user] = override_get_current_user