refactor: remove db.expunge() anti-pattern from test fixtures

Remove db.expunge() calls that were causing DetachedInstanceError
when accessing lazy-loaded relationships in tests.

Changes:
- conftest.py: Add documentation about fixture best practices
- auth_fixtures: Remove expunge, keep objects attached to session
- customer_fixtures: Remove expunge, add proper relationship loading
- vendor_fixtures: Remove expunge, add test_company and other_company
  fixtures for proper company-vendor relationship setup
- marketplace_import_job_fixtures: Remove expunge calls
- marketplace_product_fixtures: Remove expunge calls

The db fixture already provides test isolation by dropping/recreating
tables after each test, so expunge is unnecessary and harmful.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-05 21:41:50 +01:00
parent 50cb4b0985
commit aaff799b5e
6 changed files with 193 additions and 98 deletions

View File

@@ -1,4 +1,23 @@
# tests/conftest.py - Updated main conftest with core fixtures only
"""
Core pytest configuration and fixtures.
IMPORTANT - Fixture Best Practices:
===================================
1. DO NOT use db.expunge() on fixtures - it detaches objects from the session
and breaks lazy loading of relationships (e.g., product.marketplace_product).
2. Test isolation is achieved through the db fixture which drops and recreates
all tables after each test - no need to manually detach objects.
3. If you need to ensure an object has fresh data, use db.refresh(obj) instead
of expunge/re-query patterns.
4. The session uses expire_on_commit=False to prevent objects from being expired
after commits, which helps with relationship access across operations.
See docs/testing/testing-guide.md for comprehensive testing documentation.
"""
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
@@ -16,7 +35,7 @@ SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///:memory:"
@pytest.fixture(scope="session")
def engine():
"""Create test database engine"""
"""Create test database engine."""
return create_engine(
SQLALCHEMY_TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
@@ -27,13 +46,34 @@ def engine():
@pytest.fixture(scope="session")
def testing_session_local(engine):
"""Create session factory for tests"""
return sessionmaker(autocommit=False, autoflush=False, bind=engine)
"""
Create session factory for tests.
Uses expire_on_commit=False to prevent objects from being expired after
commits. This allows fixtures to remain usable after database operations
without needing to refresh or re-query them.
"""
return sessionmaker(
autocommit=False,
autoflush=False,
bind=engine,
expire_on_commit=False, # Prevents lazy-load issues after commits
)
@pytest.fixture(scope="function")
def db(engine, testing_session_local):
"""Create a database session for direct database operations"""
"""
Create a database session for each test function.
Provides test isolation by:
- Creating fresh tables before each test
- Dropping all tables after each test completes
Note: Fixtures should NOT use db.expunge() as this detaches objects
from the session and breaks lazy loading. The table drop/create cycle
provides sufficient isolation between tests.
"""
# Create all tables
Base.metadata.create_all(bind=engine)