---
title: "Testing Guide"
description: "Comprehensive guide to running and writing tests for TracerTM"
---

## Overview

TracerTM uses **pytest** as its primary testing framework with comprehensive test coverage across unit, integration, and end-to-end tests. This guide covers everything you need to know about testing in the project.

## Test Framework Stack

- **pytest** 9.0.0+ - Main test framework
- **pytest-asyncio** - Async test support
- **pytest-cov** - Coverage reporting
- **pytest-mock** - Mocking utilities
- **pytest-xdist** - Parallel test execution
- **pytest-benchmark** - Performance testing
- **hypothesis** - Property-based testing
- **faker** - Test data generation
- **factory-boy** - Test fixture factories

## Quick Start

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run all tests
pytest

# Run with coverage
pytest --cov=src/tracertm --cov-report=html

# Run specific test category
pytest -m unit           # Unit tests only
pytest -m integration    # Integration tests only
pytest -m e2e           # End-to-end tests only

# Run tests in parallel
pytest -n auto

# Run specific test file
pytest tests/unit/api/test_api_comprehensive.py

# Run specific test function
pytest tests/unit/api/test_api_comprehensive.py::test_client_initialization
```

## Test Categories

### Unit Tests (`@pytest.mark.unit`)

Fast, isolated tests with no external dependencies. Use mocks for database and external services.

```bash
# Run all unit tests
pytest -m unit

# Run unit tests with coverage
pytest -m unit --cov=src/tracertm --cov-report=term-missing
```

**Location:** `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/unit/`

**Characteristics:**
- Fast execution (< 100ms per test)
- No database or file system access
- Heavy use of mocks and stubs
- 90%+ code coverage target

### Integration Tests (`@pytest.mark.integration`)

Tests that verify component interactions with real database and file system.

```bash
# Run all integration tests
pytest -m integration

# Run integration tests for specific module
pytest tests/integration/api/
pytest tests/integration/storage/
pytest tests/integration/repositories/
```

**Location:** `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/integration/`

**Characteristics:**
- Uses real SQLite in-memory database
- Tests repository and service interactions
- Validates data persistence
- 80%+ code coverage target

### Component Tests

Tests between unit and integration layers, focusing on service contracts and repository operations.

```bash
# Run component tests
pytest tests/component/

# Run specific component test suites
pytest tests/component/services/
pytest tests/component/storage/
```

**Location:** `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/component/`

### End-to-End Tests (`@pytest.mark.e2e`)

Full workflow tests simulating real user scenarios.

```bash
# Run E2E tests
pytest -m e2e

# Run E2E tests with verbose output
pytest -m e2e -vv
```

**Location:** `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/e2e/`

## Test Markers

TracerTM uses pytest markers to categorize and filter tests:

```python
@pytest.mark.unit          # Unit tests (fast, isolated)
@pytest.mark.integration   # Integration tests (database, file system)
@pytest.mark.e2e          # End-to-end tests (full workflows)
@pytest.mark.cli          # CLI command tests
@pytest.mark.slow         # Tests taking > 1s
@pytest.mark.agent        # Agent coordination tests
@pytest.mark.asyncio      # Async tests (auto-applied)
@pytest.mark.performance  # Performance and load tests
@pytest.mark.property     # Property-based tests
@pytest.mark.benchmark    # Benchmark tests
@pytest.mark.smoke        # Critical path smoke tests
```

### Running Tests by Marker

```bash
# Run only fast unit tests
pytest -m "unit and not slow"

# Run integration and component tests
pytest -m "integration or component"

# Run all tests except slow ones
pytest -m "not slow"

# Run async tests only
pytest -m asyncio

# Combine markers
pytest -m "unit and cli"
```

## Database Fixtures

### Root Fixtures (conftest.py)

The project uses a multi-level fixture system:

```python
# Root-level fixtures (/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/conftest.py)
@pytest_asyncio.fixture(scope="session")
async def test_db_engine():
    """Session-scoped test database engine"""
    # Creates SQLite in-memory database
    # All tables created from Base.metadata

@pytest_asyncio.fixture(scope="function")
async def db_session(test_db_engine):
    """Function-scoped database session with automatic rollback"""
    # Each test gets a clean session
    # Automatic rollback after test
```

### Test-Specific Fixtures

```python
# Integration test fixtures (tests/integration/conftest.py)
@pytest.fixture(scope="function")
def test_db():
    """Create temporary SQLite database file"""

@pytest.fixture(scope="function")
def db_session(test_db):
    """Session connected to temporary database"""

@pytest.fixture(scope="function")
def db_with_sample_data(db_session):
    """Pre-populated database with projects, items, links, and events"""
```

### Component Test Fixtures

```python
# Component fixtures (tests/component/conftest.py)
@pytest.fixture
def sync_session():
    """Synchronous SQLAlchemy session on in-memory SQLite"""

@pytest_asyncio.fixture
async def async_session():
    """Async SQLAlchemy session on in-memory SQLite"""
```

### Factory Fixtures

```python
# Using factory fixtures
@pytest_asyncio.fixture
def project_factory(db_session):
    """Factory for creating test projects"""
    async def create_project(name="Test Project", description="Test project", metadata=None):
        from tracertm.repositories.project_repository import ProjectRepository
        repo = ProjectRepository(db_session)
        project = await repo.create(name=name, description=description, metadata=metadata)
        await db_session.flush()
        return project
    return create_project

@pytest_asyncio.fixture
def item_factory(db_session):
    """Factory for creating test items"""
    async def create_item(project_id, title="Test Item", view="FEATURE", **kwargs):
        from tracertm.repositories.item_repository import ItemRepository
        repo = ItemRepository(db_session)
        item = await repo.create(project_id=project_id, title=title, view=view, **kwargs)
        await db_session.flush()
        return item
    return create_item
```

### Using Fixtures in Tests

```python
import pytest

@pytest.mark.asyncio
async def test_create_project(db_session, project_factory):
    """Test project creation using factory fixture"""
    # Use factory to create test data
    project = await project_factory(name="My Project")

    # Verify creation
    assert project.id is not None
    assert project.name == "My Project"

@pytest.mark.asyncio
async def test_create_item(db_session, project_factory, item_factory):
    """Test item creation with dependencies"""
    # Create project first (foreign key requirement)
    project = await project_factory()

    # Create item
    item = await item_factory(
        project_id=project.id,
        title="Test Feature",
        status="todo"
    )

    assert item.project_id == project.id
```

## Mocking and Patching

### Basic Mocking

```python
from unittest.mock import Mock, MagicMock, patch, AsyncMock

def test_with_mock():
    """Test using Mock"""
    mock_obj = Mock()
    mock_obj.method.return_value = "mocked result"

    result = mock_obj.method()
    assert result == "mocked result"
    mock_obj.method.assert_called_once()

@pytest.mark.asyncio
async def test_with_async_mock():
    """Test using AsyncMock"""
    mock_async = AsyncMock()
    mock_async.async_method.return_value = "async result"

    result = await mock_async.async_method()
    assert result == "async result"
```

### Patching

```python
@patch("tracertm.api.client.DatabaseConnection")
def test_with_patch(mock_db_connection):
    """Test with patched dependency"""
    # Configure mock
    mock_db_connection.return_value.engine = MagicMock()

    # Use in test
    from tracertm.api.client import TraceRTMClient
    client = TraceRTMClient()

    # Verify
    mock_db_connection.assert_called_once()

@pytest.fixture
def mock_config_manager():
    """Fixture-based mocking for reuse"""
    with patch("tracertm.api.client.ConfigManager") as mock_cls:
        manager = MagicMock()
        manager.get.side_effect = lambda key, default=None: {
            "database_url": "sqlite:///test.db",
            "current_project_id": "test-project-123",
        }.get(key, default)
        mock_cls.return_value = manager
        yield manager
```

### pytest-mock Plugin

```python
def test_with_mocker(mocker):
    """Test using pytest-mock's mocker fixture"""
    # Patch using mocker
    mock_func = mocker.patch("module.function")
    mock_func.return_value = "mocked"

    # Use in test
    result = module.function()
    assert result == "mocked"
```

## Coverage Reporting

### Running with Coverage

```bash
# Basic coverage report
pytest --cov=src/tracertm

# Coverage with missing lines
pytest --cov=src/tracertm --cov-report=term-missing

# HTML coverage report
pytest --cov=src/tracertm --cov-report=html
# Open htmlcov/index.html in browser

# XML report for CI/CD
pytest --cov=src/tracertm --cov-report=xml

# Multiple report formats
pytest --cov=src/tracertm --cov-report=term --cov-report=html --cov-report=xml
```

### Coverage Configuration

Coverage settings are in `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/pyproject.toml`:

```toml
[tool.coverage.run]
source = ["src/tracertm"]
omit = [
    "*/tests/*",
    "*/migrations/*",
    "*/__pycache__/*",
    "*/site-packages/*",
]
branch = true

[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
exclude_lines = [
    "pragma: no cover",
    "def __repr__",
    "raise AssertionError",
    "raise NotImplementedError",
    "if __name__ == .__main__.:",
    "if TYPE_CHECKING:",
]

[tool.coverage.html]
directory = "htmlcov"
```

### Coverage Targets

- **Unit tests:** 90%+ coverage
- **Integration tests:** 80%+ coverage
- **Overall project:** 85%+ coverage

## Writing New Tests

### Test File Structure

Follow this template from `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/TEST-TEMPLATE.py`:

```python
"""
Module description and test purpose.

Tests for: tracertm.module.submodule
Coverage target: 90%+
"""

import pytest

# ============================================================================
# FIXTURES
# ============================================================================

@pytest.fixture
def sample_fixture():
    """
    Fixture: Sample Setup

    Provides: Test data and setup
    Cleanup: Automatic teardown
    """
    data = {"key": "value"}
    yield data
    # Teardown code here

# ============================================================================
# TEST CLASSES
# ============================================================================

class TestFeatureName:
    """
    Test Suite: Feature Name

    Tests: Feature functionality
    Type: Unit|Integration|E2E
    """

    # ========================================================================
    # POSITIVE TEST CASES
    # ========================================================================

    @pytest.mark.unit
    def test_successful_operation(self, sample_fixture):
        """
        Test: Successful operation

        Given: Valid input
        When: Operation is performed
        Then: Expected result occurs
        """
        # Arrange
        data = sample_fixture

        # Act
        result = data["key"]

        # Assert
        assert result == "value"

    # ========================================================================
    # NEGATIVE TEST CASES
    # ========================================================================

    @pytest.mark.unit
    def test_error_handling(self):
        """
        Test: Error handling

        Given: Invalid input
        When: Operation attempted
        Then: Error is raised
        """
        # Arrange
        data = None

        # Act & Assert
        with pytest.raises(TypeError):
            _ = data["key"]

    # ========================================================================
    # EDGE CASES
    # ========================================================================

    @pytest.mark.unit
    def test_edge_case_empty_input(self):
        """
        Test: Edge case with empty input

        Given: Empty input
        When: Operation attempted
        Then: Graceful handling occurs
        """
        # Arrange
        data = {}

        # Act
        result = data.get("key", "default")

        # Assert
        assert result == "default"
```

### Async Test Example

```python
import pytest

@pytest.mark.asyncio
async def test_async_operation(db_session, project_factory):
    """Test async database operation"""
    # Arrange
    project = await project_factory(name="Async Test")

    # Act
    from tracertm.repositories.project_repository import ProjectRepository
    repo = ProjectRepository(db_session)
    retrieved = await repo.get_by_id(project.id)

    # Assert
    assert retrieved is not None
    assert retrieved.name == "Async Test"
```

### Parametrized Tests

```python
@pytest.mark.unit
@pytest.mark.parametrize("input_val,expected", [
    ("value1", "value1"),
    ("value2", "value2"),
    ("", ""),
    (None, None),
])
def test_parametrized_scenarios(input_val, expected):
    """Test multiple scenarios with different inputs"""
    # Arrange
    data = {"key": input_val}

    # Act
    result = data["key"]

    # Assert
    assert result == expected
```

## Test Naming Conventions

### File Names

- `test_*.py` - Test files
- `*_test.py` - Alternative test file naming
- `conftest.py` - Fixture configuration files

### Test Function Names

```python
# Pattern: test_<what>_<condition>_<expected_result>
test_create_project_with_valid_data_succeeds()
test_create_project_without_name_raises_error()
test_get_project_when_not_found_returns_none()

# For async tests
async def test_async_operation_completes_successfully()

# For parametrized tests
def test_validation_with_various_inputs()
```

### Test Class Names

```python
# Pattern: Test<FeatureName>
class TestProjectCreation:
    """Test suite for project creation"""

class TestItemRepository:
    """Test suite for item repository operations"""

class TestAPIEndpoints:
    """Test suite for API endpoint functionality"""
```

### Test Documentation

Every test should have a docstring following this pattern:

```python
def test_feature_name():
    """
    Test: Brief description

    Given: Preconditions
    When: Action performed
    Then: Expected outcome
    And: Additional expectations (optional)
    """
```

## Common Testing Patterns

### Testing Repositories

```python
import pytest
from tracertm.repositories.item_repository import ItemRepository

@pytest.mark.asyncio
async def test_item_repository_create(db_session, sample_project):
    """Test creating an item through repository"""
    # Arrange
    repo = ItemRepository(db_session)

    # Act
    item = await repo.create(
        project_id=sample_project.id,
        title="Test Item",
        view="FEATURE",
        item_type="feature",
        status="todo"
    )

    # Assert
    assert item.id is not None
    assert item.title == "Test Item"
    assert item.project_id == sample_project.id
```

### Testing Services

```python
import pytest
from tracertm.services.item_service import ItemService

@pytest.mark.integration
async def test_item_service_update(async_session):
    """Test item service update with event logging"""
    # Arrange
    # Seed initial item
    from tracertm.models.item import Item
    item = Item(
        id="item-1",
        project_id="proj-1",
        title="Item",
        view="FEATURE",
        item_type="feature",
        status="todo",
        version=1,
    )
    async_session.add(item)
    await async_session.commit()

    # Act
    svc = ItemService(async_session)
    updated = await svc.update_item(
        item.id,
        agent_id="agent",
        status="in_progress",
        owner="alice"
    )

    # Assert
    assert updated.status == "in_progress"
    assert updated.owner == "alice"
```

### Testing CLI Commands

```python
import pytest
from typer.testing import CliRunner
from tracertm.cli.main import app

runner = CliRunner()

def test_cli_init_command(isolated_cli_environment):
    """Test CLI init command"""
    # Act
    result = runner.invoke(app, ["init", "--name", "Test Project"])

    # Assert
    assert result.exit_code == 0
    assert "Project initialized" in result.stdout
```

### Testing with Temporary Files

```python
import pytest
from pathlib import Path

def test_file_watcher(tmp_path):
    """Test file watcher with temporary directory"""
    # Arrange
    test_file = tmp_path / "test.md"
    test_file.write_text("# Test")

    # Act
    from tracertm.storage.file_watcher import FileWatcher
    watcher = FileWatcher(str(tmp_path))

    # Assert
    assert watcher.root_path == tmp_path
```

## CI/CD Integration

### GitHub Actions

Tests run automatically on push and pull requests via `/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/.github/workflows/test.yml`:

```yaml
name: Test Suite

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  cli-tests:
    name: CLI Tests (Python)
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11']
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"

      - name: Run tests
        run: pytest --cov=src/tracertm --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml
```

### Running CI Tests Locally

```bash
# Simulate CI environment
pip install -e ".[dev]"
pytest --cov=src/tracertm --cov-report=xml -v

# Run with strict markers (as in CI)
pytest --strict-markers

# Run with all CI options
pytest -ra -vv --strict-markers --tb=short
```

## Common Testing Issues

### Issue: SQLAlchemy Table Not Found

**Problem:** Test fails with "Table 'items' doesn't exist" or similar.

**Solution:** Ensure all models are imported in conftest.py:

```python
# In conftest.py or fixtures.py
from tracertm.models.base import Base
from tracertm.models.agent import Agent
from tracertm.models.agent_event import AgentEvent
from tracertm.models.agent_lock import AgentLock
from tracertm.models.event import Event
from tracertm.models.item import Item
from tracertm.models.link import Link
from tracertm.models.project import Project
```

### Issue: Async Fixture Errors

**Problem:** `fixture 'db_session' not found` or `ScopeMismatch` errors.

**Solution:** Use `@pytest_asyncio.fixture` for async fixtures:

```python
import pytest_asyncio

@pytest_asyncio.fixture  # Not @pytest.fixture
async def db_session(test_db_engine):
    """Async fixture requires pytest_asyncio decorator"""
    # ...
```

### Issue: Foreign Key Constraint Failures

**Problem:** IntegrityError when creating items without parent project.

**Solution:** Use factory fixtures with proper dependencies:

```python
@pytest.mark.asyncio
async def test_create_item(db_session, project_factory, item_factory):
    # Create project FIRST (foreign key requirement)
    project = await project_factory()

    # Then create item with project_id
    item = await item_factory(project_id=project.id)
```

### Issue: Tests Pass Individually but Fail Together

**Problem:** Tests work when run alone but fail when run together.

**Solution:** Ensure test isolation with proper cleanup:

```python
@pytest_asyncio.fixture(scope="function")  # function scope, not session
async def db_session(test_db_engine):
    async with async_session_maker() as session:
        try:
            yield session
        finally:
            await session.rollback()  # Clean up after each test
            await session.close()
```

### Issue: Mocks Not Working as Expected

**Problem:** Patched objects not being used by code under test.

**Solution:** Patch where the object is used, not where it's defined:

```python
# Wrong
@patch("tracertm.database.connection.DatabaseConnection")

# Correct
@patch("tracertm.api.client.DatabaseConnection")  # Where it's imported and used
```

### Issue: pytest-asyncio Warnings

**Problem:** `PytestUnraisableExceptionWarning` or event loop warnings.

**Solution:** Configure asyncio mode in pyproject.toml:

```toml
[tool.pytest.ini_options]
asyncio_mode = "auto"

[tool.pytest_asyncio]
asyncio_default_fixture_loop_scope = "function"
```

### Issue: Coverage Reports Missing Files

**Problem:** Coverage report doesn't include some modules.

**Solution:** Check coverage configuration in pyproject.toml:

```toml
[tool.coverage.run]
source = ["src/tracertm"]  # Ensure correct source path
omit = [
    "*/tests/*",
    "*/__pycache__/*",
]
```

## Advanced Testing

### Property-Based Testing

```python
from hypothesis import given, strategies as st

@pytest.mark.property
@given(name=st.text(min_size=1, max_size=100))
def test_project_name_validation(name):
    """Property-based test for project name validation"""
    from tracertm.models.project import Project
    project = Project(name=name, description="Test")
    assert project.name == name
```

### Performance Testing

```python
@pytest.mark.benchmark
def test_query_performance(benchmark, db_session):
    """Benchmark database query performance"""
    from tracertm.repositories.item_repository import ItemRepository
    repo = ItemRepository(db_session)

    # Benchmark the query
    result = benchmark(repo.get_all)
    assert result is not None
```

### Parallel Test Execution

```bash
# Run tests in parallel (auto-detect CPUs)
pytest -n auto

# Run with specific number of workers
pytest -n 4

# Disable parallel execution for debugging
pytest -n 0
```

## Resources

- [Pytest Documentation](https://docs.pytest.org/)
- [pytest-asyncio Guide](https://pytest-asyncio.readthedocs.io/)
- [Coverage.py Documentation](https://coverage.readthedocs.io/)
- [Test Template](/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/TEST-TEMPLATE.py)
- [Project Fixtures](/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/tests/fixtures.py)
- [Root conftest.py](/Users/kooshapari/temp-PRODVERCEL/485/kush/trace/conftest.py)
