# Testing Architecture Enhancement - Gameplan

## Overview

This gameplan establishes a comprehensive testing architecture that supports the domain logic separation and data access pattern improvements while ensuring high code quality, maintainability, and confidence in changes across the MarkiTect codebase.

## Current Testing Architecture Problems

### 1. **Test Organization and Structure Issues**

#### **Inconsistent Test File Organization**
- **Problem**: Tests scattered across multiple directories without clear structure
- **Current**: Mix of `tests/` and module-specific test files
- **Impact**: Difficult to locate and maintain tests

#### **Poor Test Naming Conventions**
- **Problem**: Inconsistent naming patterns (e.g., `test_issue_11_*`, `test_issue_creator.py`)
- **Current**: Tests named after issue numbers rather than functionality
- **Impact**: Tests don't clearly indicate what they're testing

#### **Mixed Test Types**
- **Problem**: Unit tests, integration tests, and end-to-end tests mixed together
- **Current**: No clear separation between test types
- **Impact**: Slow test execution, unclear test purpose

### 2. **Test Coverage and Quality Issues**

#### **Missing Test Coverage Areas**
```python
# Current gaps identified:
- Domain logic testing (business rules not tested in isolation)
- Repository pattern testing (no mock strategies)
- Error handling scenarios (happy path bias)
- Performance and load testing (no performance regression detection)
- Configuration management testing (config scenarios not covered)
```

#### **Poor Test Isolation**
- **Problem**: Tests depend on external systems and state
- **Current**: Tests make real API calls, modify actual files
- **Impact**: Flaky tests, slow execution, test interference

### 3. **Testing Anti-patterns Identified**

#### **Services Module Testing Issues**
```python
# Current anti-pattern in services/issue_service.py tests
class TestIssueService:
    def test_get_issue_details(self):
        # Problem: Real API calls in unit tests
        service = IssueService()
        result = service.get_issue_details(123)  # Makes real HTTP request
        assert result is not None
```

#### **TDDAI Module Testing Problems**
```python
# Current anti-pattern in tddai tests
class TestProjectManager:
    def test_create_project(self):
        # Problem: File system dependencies
        manager = ProjectManager()
        manager.create_workspace("/tmp/test")  # Creates real directories
        assert os.path.exists("/tmp/test")  # Depends on file system state
```

## Testing Architecture Strategy

### **Test Pyramid Implementation**

```
                   E2E Tests (Few)
                  ├─ Workflow Tests
                  ├─ CLI Integration Tests
                  └─ API Integration Tests

               Integration Tests (Some)
              ├─ Service Layer Tests
              ├─ Repository Tests
              ├─ Database Tests
              └─ External API Tests

            Unit Tests (Many)
           ├─ Domain Model Tests
           ├─ Business Logic Tests
           ├─ Value Object Tests
           └─ Utility Function Tests
```

### **Testing Layer Architecture**

```python
tests/
├── unit/                    # Fast, isolated unit tests
│   ├── domain/             # Domain model and business logic tests
│   ├── application/        # Application service tests (mocked repos)
│   └── infrastructure/     # Infrastructure component tests
├── integration/            # Integration tests with real components
│   ├── repositories/       # Repository tests with real databases
│   ├── services/          # Service tests with real dependencies
│   └── external/          # External API integration tests
├── e2e/                   # End-to-end workflow tests
│   ├── cli/               # CLI command testing
│   ├── workflows/         # Complete user workflows
│   └── performance/       # Performance and load tests
├── fixtures/              # Test data and builders
│   ├── markdown_samples.py
│   ├── api_responses.py
│   └── database_seeds.py
└── utils/                 # Test utilities and helpers
    ├── test_builders.py
    ├── mock_factories.py
    └── assertions.py
```

## Implementation Gameplan

### **Phase 1: Foundation and Infrastructure (Week 1-2)**

#### **Task 1.1: Test Organization and Structure**
```python
# Create standardized test directory structure
tests/
├── conftest.py              # Global test configuration
├── pytest.ini              # Pytest configuration
├── requirements-test.txt    # Test dependencies
└── [organized structure as above]
```

#### **Task 1.2: Test Configuration Setup**
```python
# tests/conftest.py
import pytest
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock
from typing import Generator

@pytest.fixture(scope="session")
def test_workspace() -> Generator[Path, None, None]:
    """Create isolated test workspace."""
    temp_dir = tempfile.mkdtemp(prefix="markitect_test_")
    workspace_path = Path(temp_dir)
    yield workspace_path
    shutil.rmtree(temp_dir)

@pytest.fixture
def mock_database():
    """Provide mocked database for testing."""
    mock_db = Mock()
    mock_cursor = Mock()
    mock_db.cursor.return_value = mock_cursor
    mock_db.execute.return_value = mock_cursor
    mock_cursor.fetchone.return_value = None
    mock_cursor.fetchall.return_value = []
    return mock_db

@pytest.fixture
def mock_http_client():
    """Provide mocked HTTP client for API tests."""
    mock_client = Mock()
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"status": "success"}
    mock_client.get.return_value = mock_response
    mock_client.post.return_value = mock_response
    return mock_client
```

#### **Task 1.3: Test Data Builders and Factories**
```python
# tests/fixtures/markdown_samples.py
class MarkdownDocumentBuilder:
    """Builder pattern for creating test markdown documents."""

    def __init__(self):
        self.content_parts = []
        self.metadata = {}

    def with_heading(self, text: str, level: int = 1):
        heading_marker = "#" * level
        self.content_parts.append(f"{heading_marker} {text}")
        return self

    def with_paragraph(self, text: str):
        self.content_parts.append(text)
        return self

    def with_metadata(self, key: str, value: str):
        self.metadata[key] = value
        return self

    def build(self) -> str:
        content = "\n\n".join(self.content_parts)
        if self.metadata:
            metadata_lines = [f"{k}: {v}" for k, v in self.metadata.items()]
            content = "---\n" + "\n".join(metadata_lines) + "\n---\n\n" + content
        return content

# tests/fixtures/api_responses.py
class GiteaApiResponseBuilder:
    """Builder for creating mock Gitea API responses."""

    def __init__(self):
        self.issue_data = {
            "number": 1,
            "title": "Test Issue",
            "state": "open",
            "labels": [],
            "milestone": None,
            "created_at": "2025-01-01T00:00:00Z",
            "updated_at": "2025-01-01T00:00:00Z"
        }

    def with_number(self, number: int):
        self.issue_data["number"] = number
        return self

    def with_title(self, title: str):
        self.issue_data["title"] = title
        return self

    def with_labels(self, *labels: str):
        self.issue_data["labels"] = [{"name": label} for label in labels]
        return self

    def build(self) -> dict:
        return self.issue_data.copy()
```

**Deliverables:**
- [ ] Standardized test directory structure
- [ ] Global test configuration and fixtures
- [ ] Test data builders and factories
- [ ] Test utilities and helpers

**Risk Level**: Low (foundation work, no breaking changes)

### **Phase 2: Unit Testing Framework (Week 2-3)**

#### **Task 2.1: Domain Model Unit Tests**
```python
# tests/unit/domain/test_issue_models.py
import pytest
from domain.issues.models import Issue, Label, IssueState

class TestIssue:
    """Test Issue domain model behavior."""

    def test_issue_creation_with_valid_data(self):
        # Arrange
        issue = Issue(
            number=123,
            title="Test Issue",
            state=IssueState.OPEN,
            labels=[Label("bug"), Label("priority:high")]
        )

        # Act & Assert
        assert issue.number == 123
        assert issue.title == "Test Issue"
        assert issue.state == IssueState.OPEN
        assert len(issue.labels) == 2

    def test_issue_state_transition_rules(self):
        # Arrange
        issue = Issue(number=1, title="Test", state=IssueState.OPEN)

        # Act
        issue.close()

        # Assert
        assert issue.state == IssueState.CLOSED
        assert issue.closed_at is not None

    def test_issue_label_categorization(self):
        # Arrange
        issue = Issue(
            number=1,
            title="Test",
            labels=[
                Label("bug"),           # type label
                Label("priority:high"), # priority label
                Label("status:ready"),  # state label
                Label("custom")         # other label
            ]
        )

        # Act
        categories = issue.categorize_labels()

        # Assert
        assert "bug" in categories.type_labels
        assert "priority:high" in categories.priority_labels
        assert "status:ready" in categories.state_labels
        assert "custom" in categories.other_labels
```

#### **Task 2.2: Business Logic Unit Tests**
```python
# tests/unit/domain/test_issue_services.py
import pytest
from domain.issues.services import IssueStatusService
from domain.issues.models import Issue, Label, IssueState

class TestIssueStatusService:
    """Test business logic for issue status determination."""

    @pytest.fixture
    def service(self):
        return IssueStatusService()

    def test_determine_kanban_column_for_new_issue(self, service):
        # Arrange
        issue = Issue(
            number=1,
            title="New Issue",
            state=IssueState.OPEN,
            labels=[Label("status:new")]
        )

        # Act
        column = service.determine_kanban_column(issue)

        # Assert
        assert column == "Todo"

    def test_determine_kanban_column_for_in_progress_issue(self, service):
        # Arrange
        issue = Issue(
            number=1,
            title="In Progress Issue",
            state=IssueState.OPEN,
            labels=[Label("status:in-progress")]
        )

        # Act
        column = service.determine_kanban_column(issue)

        # Assert
        assert column == "In Progress"

    @pytest.mark.parametrize("labels,expected_priority", [
        ([Label("priority:low")], "Low"),
        ([Label("priority:medium")], "Medium"),
        ([Label("priority:high")], "High"),
        ([Label("priority:critical")], "Critical"),
        ([], "Medium"),  # Default priority
    ])
    def test_extract_priority_info(self, service, labels, expected_priority):
        # Arrange
        issue = Issue(number=1, title="Test", labels=labels)

        # Act
        priority = service.extract_priority_info(issue)

        # Assert
        assert priority.level == expected_priority
```

#### **Task 2.3: Application Service Unit Tests (with Mocks)**
```python
# tests/unit/application/test_issue_application_service.py
import pytest
from unittest.mock import Mock, AsyncMock
from application.issue_application_service import IssueApplicationService
from domain.issues.models import Issue, IssueState
from infrastructure.unit_of_work import UnitOfWork

class TestIssueApplicationService:
    """Test application service coordination logic."""

    @pytest.fixture
    def mock_uow(self):
        uow = Mock(spec=UnitOfWork)
        uow.issues = AsyncMock()
        uow.projects = AsyncMock()
        uow.__aenter__ = AsyncMock(return_value=uow)
        uow.__aexit__ = AsyncMock(return_value=None)
        return uow

    @pytest.fixture
    def service(self, mock_uow):
        return IssueApplicationService(mock_uow)

    async def test_get_issue_details_success(self, service, mock_uow):
        # Arrange
        issue = Issue(number=123, title="Test Issue", state=IssueState.OPEN)
        project_info = Mock()
        project_info.kanban_columns = ["Todo", "In Progress", "Done"]

        mock_uow.issues.get_issue.return_value = issue
        mock_uow.projects.get_issue_project_info.return_value = project_info

        # Act
        result = await service.get_issue_details(123)

        # Assert
        assert result.issue == issue
        assert result.project_info == project_info
        mock_uow.issues.get_issue.assert_called_once_with(123)
        mock_uow.projects.get_issue_project_info.assert_called_once_with(123)

    async def test_get_issue_details_issue_not_found(self, service, mock_uow):
        # Arrange
        from domain.issues.exceptions import IssueNotFoundError
        mock_uow.issues.get_issue.side_effect = IssueNotFoundError("Issue not found")

        # Act & Assert
        with pytest.raises(IssueNotFoundError):
            await service.get_issue_details(999)
```

**Deliverables:**
- [ ] Unit tests for all domain models
- [ ] Unit tests for business logic services
- [ ] Unit tests for application services with mocks
- [ ] Parameterized tests for edge cases

**Risk Level**: Low (isolated unit tests, no external dependencies)

### **Phase 3: Integration Testing Framework (Week 3-4)**

#### **Task 3.1: Repository Integration Tests**
```python
# tests/integration/repositories/test_gitea_issue_repository.py
import pytest
import aiohttp
from infrastructure.repositories.gitea_issue_repository import GiteaIssueRepository
from infrastructure.connection_manager import ConnectionManager
from tests.fixtures.api_responses import GiteaApiResponseBuilder

class TestGiteaIssueRepository:
    """Integration tests for Gitea API repository."""

    @pytest.fixture
    async def repository(self, test_config):
        connection_manager = ConnectionManager(test_config)
        repo = GiteaIssueRepository(connection_manager)
        yield repo
        await connection_manager.close()

    @pytest.fixture
    def mock_server(self, aioresponses):
        """Mock HTTP responses for integration tests."""
        return aioresponses

    async def test_get_issue_success(self, repository, mock_server):
        # Arrange
        issue_data = (GiteaApiResponseBuilder()
                     .with_number(123)
                     .with_title("Test Issue")
                     .with_labels("bug", "priority:high")
                     .build())

        mock_server.get(
            "http://test-gitea.com/api/v1/repos/test/repo/issues/123",
            payload=issue_data
        )

        # Act
        issue = await repository.get_issue(123)

        # Assert
        assert issue.number == 123
        assert issue.title == "Test Issue"
        assert len(issue.labels) == 2

    async def test_get_issue_not_found(self, repository, mock_server):
        # Arrange
        mock_server.get(
            "http://test-gitea.com/api/v1/repos/test/repo/issues/999",
            status=404
        )

        # Act & Assert
        from domain.issues.exceptions import IssueNotFoundError
        with pytest.raises(IssueNotFoundError):
            await repository.get_issue(999)

    async def test_get_issue_with_retry_on_network_error(self, repository, mock_server):
        # Arrange - First two requests fail, third succeeds
        issue_data = GiteaApiResponseBuilder().with_number(123).build()

        mock_server.get(
            "http://test-gitea.com/api/v1/repos/test/repo/issues/123",
            exception=aiohttp.ClientError("Network error")
        )
        mock_server.get(
            "http://test-gitea.com/api/v1/repos/test/repo/issues/123",
            exception=aiohttp.ClientError("Network error")
        )
        mock_server.get(
            "http://test-gitea.com/api/v1/repos/test/repo/issues/123",
            payload=issue_data
        )

        # Act
        issue = await repository.get_issue(123)

        # Assert
        assert issue.number == 123
        # Verify retry mechanism worked (3 calls total)
        assert len(mock_server.requests) == 3
```

#### **Task 3.2: Database Integration Tests**
```python
# tests/integration/repositories/test_sqlite_document_repository.py
import pytest
import sqlite3
from pathlib import Path
from infrastructure.repositories.sqlite_document_repository import SqliteDocumentRepository
from domain.documents.models import Document

class TestSqliteDocumentRepository:
    """Integration tests for SQLite document repository."""

    @pytest.fixture
    def test_db_path(self, test_workspace):
        return test_workspace / "test.db"

    @pytest.fixture
    def repository(self, test_db_path):
        repo = SqliteDocumentRepository(test_db_path)
        repo.initialize_schema()
        yield repo
        repo.close()

    async def test_store_and_retrieve_document(self, repository):
        # Arrange
        document = Document(
            filename="test.md",
            content="# Test Document\nContent here",
            ast_data={"type": "document", "children": []}
        )

        # Act
        document_id = await repository.store_document(document)
        retrieved = await repository.get_document(document_id)

        # Assert
        assert retrieved.filename == "test.md"
        assert retrieved.content == "# Test Document\nContent here"
        assert retrieved.ast_data["type"] == "document"

    async def test_store_duplicate_filename_raises_error(self, repository):
        # Arrange
        document1 = Document(filename="duplicate.md", content="Content 1")
        document2 = Document(filename="duplicate.md", content="Content 2")

        # Act
        await repository.store_document(document1)

        # Assert
        from infrastructure.exceptions import DocumentStoreError
        with pytest.raises(DocumentStoreError) as exc_info:
            await repository.store_document(document2)

        assert "already exists" in str(exc_info.value)

    async def test_transaction_rollback_on_error(self, repository):
        # Arrange
        document = Document(filename="test.md", content="Valid content")

        # Simulate a database error during storage
        with pytest.raises(sqlite3.Error):
            async with repository.unit_of_work():
                await repository.store_document(document)
                # Force an error that should rollback the transaction
                await repository.execute_raw_sql("INVALID SQL")

        # Assert - Document should not be stored due to rollback
        documents = await repository.list_all_documents()
        assert len(documents) == 0
```

#### **Task 3.3: Service Integration Tests**
```python
# tests/integration/services/test_document_service_integration.py
import pytest
from pathlib import Path
from application.document_service import DocumentService
from infrastructure.unit_of_work import UnitOfWork
from tests.fixtures.markdown_samples import MarkdownDocumentBuilder

class TestDocumentServiceIntegration:
    """Integration tests for document service with real repositories."""

    @pytest.fixture
    def service(self, test_workspace):
        uow = UnitOfWork(database_path=test_workspace / "test.db")
        uow.initialize()
        yield DocumentService(uow)
        uow.close()

    async def test_ingest_markdown_file_complete_workflow(self, service, test_workspace):
        # Arrange
        markdown_content = (MarkdownDocumentBuilder()
                           .with_heading("Test Document")
                           .with_paragraph("This is a test paragraph.")
                           .with_heading("Section 2", level=2)
                           .build())

        test_file = test_workspace / "test.md"
        test_file.write_text(markdown_content)

        # Act
        result = await service.ingest_file(test_file)

        # Assert
        assert result.document_id is not None
        assert result.parse_time > 0
        assert result.cache_path.exists()

        # Verify document was stored correctly
        document = await service.get_document(result.document_id)
        assert document.filename == "test.md"
        assert "Test Document" in document.content
        assert document.ast_data is not None

    async def test_bulk_ingestion_with_transaction(self, service, test_workspace):
        # Arrange
        files = []
        for i in range(5):
            content = f"# Document {i}\nContent for document {i}"
            file_path = test_workspace / f"doc_{i}.md"
            file_path.write_text(content)
            files.append(file_path)

        # Act
        results = await service.ingest_bulk(files)

        # Assert
        assert len(results) == 5
        for result in results:
            assert result.document_id is not None
            assert result.parse_time > 0

        # Verify all documents are stored
        all_docs = await service.list_documents()
        assert len(all_docs) == 5
```

**Deliverables:**
- [ ] Repository integration tests with real databases/APIs
- [ ] Service integration tests with transaction testing
- [ ] Error handling and retry mechanism tests
- [ ] Performance and load integration tests

**Risk Level**: Medium (involves real external dependencies)

### **Phase 4: End-to-End Testing Framework (Week 4-5)**

#### **Task 4.1: CLI Command Testing**
```python
# tests/e2e/cli/test_issue_commands.py
import pytest
import subprocess
from pathlib import Path

class TestIssueCommands:
    """End-to-end tests for issue management CLI commands."""

    @pytest.fixture
    def isolated_environment(self, test_workspace):
        """Set up isolated environment for CLI testing."""
        env = {
            "MARKITECT_WORKSPACE_DIR": str(test_workspace),
            "MARKITECT_GITEA_URL": "http://test-gitea.com",
            "MARKITECT_REPO_OWNER": "test",
            "MARKITECT_REPO_NAME": "repo"
        }
        return env

    def test_issue_show_command(self, isolated_environment):
        # Act
        result = subprocess.run(
            ["python", "tddai_cli.py", "show-issue", "123"],
            env=isolated_environment,
            capture_output=True,
            text=True
        )

        # Assert
        assert result.returncode == 0
        assert "Issue #123 Details" in result.stdout
        assert "Title:" in result.stdout
        assert "Status:" in result.stdout

    def test_issue_start_workflow(self, isolated_environment):
        # Act - Start working on an issue
        result = subprocess.run(
            ["python", "tddai_cli.py", "start-issue", "456"],
            env=isolated_environment,
            capture_output=True,
            text=True
        )

        # Assert
        assert result.returncode == 0
        assert "Starting work on issue #456" in result.stdout

        # Verify workspace was created
        workspace_path = Path(isolated_environment["MARKITECT_WORKSPACE_DIR"]) / "issue_456"
        assert workspace_path.exists()
        assert (workspace_path / "requirements.md").exists()
        assert (workspace_path / "test_plan.md").exists()

    def test_complete_issue_workflow(self, isolated_environment):
        # Act - Complete workflow: start -> add tests -> finish
        commands = [
            ["python", "tddai_cli.py", "start-issue", "789"],
            ["python", "tddai_cli.py", "add-test", "test_scenario"],
            ["python", "tddai_cli.py", "finish-issue"]
        ]

        for cmd in commands:
            result = subprocess.run(
                cmd,
                env=isolated_environment,
                capture_output=True,
                text=True
            )
            assert result.returncode == 0

        # Assert - Workspace should be cleaned up
        workspace_path = Path(isolated_environment["MARKITECT_WORKSPACE_DIR"]) / "issue_789"
        assert not workspace_path.exists()
```

#### **Task 4.2: Workflow Integration Tests**
```python
# tests/e2e/workflows/test_document_processing_workflow.py
import pytest
from pathlib import Path
import asyncio
from application.document_service import DocumentService
from application.workspace_service import WorkspaceService
from infrastructure.unit_of_work import UnitOfWork

class TestDocumentProcessingWorkflow:
    """End-to-end tests for complete document processing workflows."""

    @pytest.fixture
    async def services(self, test_workspace):
        uow = UnitOfWork(database_path=test_workspace / "test.db")
        await uow.initialize()

        doc_service = DocumentService(uow)
        workspace_service = WorkspaceService(uow)

        yield doc_service, workspace_service

        await uow.close()

    async def test_full_document_lifecycle(self, services, test_workspace):
        doc_service, workspace_service = services

        # Arrange - Create test documents
        docs_dir = test_workspace / "documents"
        docs_dir.mkdir()

        # Create various document types
        (docs_dir / "readme.md").write_text("# Project README\nDescription here")
        (docs_dir / "api.md").write_text("# API Documentation\n## Endpoints")
        (docs_dir / "guide.md").write_text("# User Guide\n### Getting Started")

        # Act - Process all documents
        ingestion_results = []
        for md_file in docs_dir.glob("*.md"):
            result = await doc_service.ingest_file(md_file)
            ingestion_results.append(result)

        # Generate workspace summary
        workspace_summary = await workspace_service.generate_summary()

        # Act - Search functionality
        search_results = await doc_service.search_content("API")

        # Assert - All documents processed
        assert len(ingestion_results) == 3
        for result in ingestion_results:
            assert result.document_id is not None
            assert result.parse_time > 0

        # Assert - Workspace summary generated
        assert workspace_summary.total_documents == 3
        assert workspace_summary.total_size > 0

        # Assert - Search functionality works
        assert len(search_results) >= 1
        assert any("api.md" in result.filename for result in search_results)

    async def test_large_document_processing_performance(self, services, test_workspace):
        doc_service, _ = services

        # Arrange - Create large document (1MB)
        from tests.fixtures.markdown_samples import LargeMarkdownGenerator
        generator = LargeMarkdownGenerator()
        large_content = generator.generate_document(size='1mb')

        large_file = test_workspace / "large_document.md"
        large_file.write_text(large_content)

        # Act - Measure processing time
        import time
        start_time = time.time()
        result = await doc_service.ingest_file(large_file)
        processing_time = time.time() - start_time

        # Assert - Performance requirements
        assert result.document_id is not None
        assert processing_time < 10.0  # Should process 1MB in under 10 seconds
        assert result.parse_time < 5.0  # AST parsing should be under 5 seconds

        # Verify cache was created for performance
        assert result.cache_path.exists()
        cache_size = result.cache_path.stat().st_size
        assert cache_size > 0
```

#### **Task 4.3: Performance and Load Testing**
```python
# tests/e2e/performance/test_system_performance.py
import pytest
import asyncio
import time
import statistics
from concurrent.futures import ThreadPoolExecutor
from application.document_service import DocumentService
from tests.fixtures.markdown_samples import MarkdownDocumentBuilder

class TestSystemPerformance:
    """Performance and load testing for the system."""

    @pytest.fixture
    async def service(self, test_workspace):
        from infrastructure.unit_of_work import UnitOfWork
        uow = UnitOfWork(database_path=test_workspace / "perf_test.db")
        await uow.initialize()
        yield DocumentService(uow)
        await uow.close()

    async def test_concurrent_document_ingestion(self, service, test_workspace):
        """Test system behavior under concurrent load."""

        # Arrange - Create multiple test documents
        docs_dir = test_workspace / "concurrent_docs"
        docs_dir.mkdir()

        doc_files = []
        for i in range(20):
            content = (MarkdownDocumentBuilder()
                      .with_heading(f"Document {i}")
                      .with_paragraph(f"Content for document {i}")
                      .build())

            doc_file = docs_dir / f"doc_{i}.md"
            doc_file.write_text(content)
            doc_files.append(doc_file)

        # Act - Process documents concurrently
        start_time = time.time()
        tasks = [service.ingest_file(doc_file) for doc_file in doc_files]
        results = await asyncio.gather(*tasks)
        total_time = time.time() - start_time

        # Assert - Performance requirements
        assert len(results) == 20
        assert all(result.document_id is not None for result in results)

        # Should process 20 small documents in under 30 seconds
        assert total_time < 30.0

        # Calculate processing statistics
        parse_times = [result.parse_time for result in results]
        avg_parse_time = statistics.mean(parse_times)
        max_parse_time = max(parse_times)

        assert avg_parse_time < 1.0  # Average parse time under 1 second
        assert max_parse_time < 5.0  # Max parse time under 5 seconds

    async def test_memory_usage_under_load(self, service, test_workspace):
        """Test memory usage patterns during heavy processing."""
        import psutil
        import os

        # Measure initial memory
        process = psutil.Process(os.getpid())
        initial_memory = process.memory_info().rss

        # Arrange - Create multiple large documents
        from tests.fixtures.markdown_samples import LargeMarkdownGenerator
        generator = LargeMarkdownGenerator()

        large_docs = []
        for i in range(5):
            content = generator.generate_document(size='1mb')
            doc_file = test_workspace / f"large_{i}.md"
            doc_file.write_text(content)
            large_docs.append(doc_file)

        # Act - Process large documents
        for doc_file in large_docs:
            await service.ingest_file(doc_file)

        # Measure final memory
        final_memory = process.memory_info().rss
        memory_increase = final_memory - initial_memory
        memory_increase_mb = memory_increase / (1024 * 1024)

        # Assert - Memory usage should be reasonable
        # Should not use more than 100MB additional memory for 5MB of documents
        assert memory_increase_mb < 100

        print(f"Memory increase: {memory_increase_mb:.2f} MB")

    @pytest.mark.slow
    async def test_system_stability_over_time(self, service, test_workspace):
        """Long-running stability test."""

        # Run continuous processing for 5 minutes
        start_time = time.time()
        duration = 300  # 5 minutes
        operation_count = 0
        errors = []

        while time.time() - start_time < duration:
            try:
                # Create and process a document
                content = (MarkdownDocumentBuilder()
                          .with_heading(f"Stability Test {operation_count}")
                          .with_paragraph("Long-running test content")
                          .build())

                doc_file = test_workspace / f"stability_{operation_count}.md"
                doc_file.write_text(content)

                await service.ingest_file(doc_file)
                operation_count += 1

                # Small delay between operations
                await asyncio.sleep(0.1)

            except Exception as e:
                errors.append(str(e))

        # Assert - System should remain stable
        error_rate = len(errors) / operation_count if operation_count > 0 else 1
        assert error_rate < 0.01  # Less than 1% error rate
        assert operation_count > 100  # Should process at least 100 operations

        print(f"Operations completed: {operation_count}")
        print(f"Error rate: {error_rate:.2%}")
```

**Deliverables:**
- [ ] CLI command end-to-end tests
- [ ] Complete workflow integration tests
- [ ] Performance and load testing framework
- [ ] System stability and reliability tests

**Risk Level**: Low-Medium (end-to-end tests, performance requirements)

### **Phase 5: Test Migration and Optimization (Week 5-6)**

#### **Task 5.1: Migrate Existing Tests**
```python
# Migration strategy for existing tests
# Example: Migrating tests/test_issue_creator.py

# Before (current structure)
class TestIssueCreator:
    def test_create_issue_success(self):
        creator = IssueCreator(auth_token="test-token")
        result = creator.create_issue("Test Issue", "Description")
        assert result is not None

# After (new structure)
# tests/unit/application/test_issue_creator.py
class TestIssueCreator:
    @pytest.fixture
    def mock_repository(self):
        return Mock(spec=IssueRepository)

    @pytest.fixture
    def creator(self, mock_repository):
        return IssueCreator(mock_repository)

    async def test_create_issue_success(self, creator, mock_repository):
        # Arrange
        mock_repository.create_issue.return_value = Issue(number=123, title="Test Issue")

        # Act
        result = await creator.create_issue("Test Issue", "Description")

        # Assert
        assert result.number == 123
        mock_repository.create_issue.assert_called_once()
```

#### **Task 5.2: Test Performance Optimization**
```python
# tests/utils/performance_optimization.py
import pytest
import asyncio
from typing import List, Callable

class TestPerformanceOptimizer:
    """Utilities for optimizing test execution performance."""

    @staticmethod
    def parallelize_tests(test_functions: List[Callable]):
        """Run multiple test functions in parallel."""
        async def run_parallel():
            tasks = [asyncio.create_task(test_func()) for test_func in test_functions]
            return await asyncio.gather(*tasks)

        return asyncio.run(run_parallel())

    @staticmethod
    def cache_expensive_fixtures():
        """Cache expensive test fixtures across test sessions."""
        # Implementation for fixture caching
        pass

# pytest configuration for performance
# pytest.ini
[tool:pytest]
addopts =
    --strict-markers
    --strict-config
    --verbose
    --tb=short
    --cov=src
    --cov-report=term-missing
    --cov-report=html
    --cov-fail-under=90
    --maxfail=1
    --durations=10
markers =
    slow: marks tests as slow (deselect with '-m "not slow"')
    integration: marks tests as integration tests
    e2e: marks tests as end-to-end tests
    performance: marks tests as performance tests
```

#### **Task 5.3: CI/CD Integration**
```yaml
# .github/workflows/test.yml
name: Test Suite

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

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install -r requirements-test.txt

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

    - name: Upload coverage
      uses: codecov/codecov-action@v3

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install -r requirements-test.txt

    - name: Run integration tests
      run: pytest tests/integration/ -v

  e2e-tests:
    runs-on: ubuntu-latest
    needs: [unit-tests, integration-tests]
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install -r requirements-test.txt

    - name: Run end-to-end tests
      run: pytest tests/e2e/ -v -m "not slow"

    - name: Run performance tests
      run: pytest tests/e2e/performance/ -v
      if: github.event_name == 'push' && github.ref == 'refs/heads/main'
```

**Deliverables:**
- [ ] Migration of all existing tests to new architecture
- [ ] Test performance optimization and parallelization
- [ ] CI/CD pipeline integration
- [ ] Test coverage and quality gates

**Risk Level**: Medium (migration changes, CI/CD dependencies)

### **Phase 6: Advanced Testing Features (Week 6-7)**

#### **Task 6.1: Property-Based Testing**
```python
# tests/property/test_markdown_processing.py
import pytest
from hypothesis import given, strategies as st
from domain.documents.models import Document
from application.document_service import DocumentService

class TestMarkdownProcessingProperties:
    """Property-based tests for markdown processing."""

    @given(st.text(alphabet=st.characters(blacklist_categories=('Cc', 'Cs'))))
    async def test_any_valid_text_can_be_processed(self, text, document_service):
        """Any valid unicode text should be processable."""
        # Arrange
        document = Document(filename="test.md", content=text)

        # Act - Should not raise exception
        result = await document_service.process_document(document)

        # Assert
        assert result is not None
        assert result.ast_data is not None

    @given(st.text(min_size=1, max_size=1000))
    async def test_processing_is_deterministic(self, content, document_service):
        """Same content should always produce same AST."""
        # Arrange
        document = Document(filename="test.md", content=content)

        # Act
        result1 = await document_service.process_document(document)
        result2 = await document_service.process_document(document)

        # Assert
        assert result1.ast_data == result2.ast_data

    @given(st.lists(st.text(min_size=1), min_size=1, max_size=10))
    async def test_batch_processing_order_independence(self, contents, document_service):
        """Batch processing should be order-independent."""
        # Arrange
        documents1 = [Document(f"doc_{i}.md", content) for i, content in enumerate(contents)]
        documents2 = list(reversed(documents1))

        # Act
        results1 = await document_service.process_batch(documents1)
        results2 = await document_service.process_batch(documents2)

        # Assert - Results should be equivalent regardless of order
        results1_by_name = {r.filename: r.ast_data for r in results1}
        results2_by_name = {r.filename: r.ast_data for r in results2}
        assert results1_by_name == results2_by_name
```

#### **Task 6.2: Mutation Testing**
```python
# tests/mutation/test_coverage_quality.py
"""
Mutation testing to verify test quality.
Uses mutmut or similar tools to verify tests catch logic errors.
"""

# Configuration for mutation testing
# pyproject.toml
[tool.mutmut]
paths_to_mutate = "src/"
backup = false
runner = "python -m pytest tests/unit/"
tests_dir = "tests/"

# Mutation testing command
# mutmut run --paths-to-mutate src/domain/
```

#### **Task 6.3: Contract Testing**
```python
# tests/contract/test_api_contracts.py
import pytest
from pact import Consumer, Provider
from application.issue_service import IssueService

class TestGiteaApiContract:
    """Contract tests for Gitea API integration."""

    @pytest.fixture
    def pact(self):
        pact = Consumer('markitect').has_pact_with(Provider('gitea'))
        pact.start()
        yield pact
        pact.stop()

    def test_get_issue_contract(self, pact):
        # Define expected interaction
        expected = {
            'number': 123,
            'title': 'Test Issue',
            'state': 'open',
            'labels': [{'name': 'bug'}]
        }

        (pact
         .given('issue 123 exists')
         .upon_receiving('a request for issue 123')
         .with_request('GET', '/api/v1/repos/test/repo/issues/123')
         .will_respond_with(200, body=expected))

        # Test the interaction
        with pact:
            issue_service = IssueService(base_url=pact.uri)
            issue = issue_service.get_issue(123)
            assert issue.number == 123
```

**Deliverables:**
- [ ] Property-based testing framework
- [ ] Mutation testing setup
- [ ] Contract testing for external APIs
- [ ] Advanced test analysis and reporting

**Risk Level**: Low (advanced features, non-breaking additions)

## Success Criteria and Metrics

### **Implementation Success Indicators:**

#### **Coverage Metrics:**
- **Unit Test Coverage**: >90% for domain and application layers
- **Integration Test Coverage**: >80% for infrastructure layer
- **E2E Test Coverage**: >70% for critical user workflows

#### **Performance Metrics:**
- **Unit Tests**: All execute in <30 seconds total
- **Integration Tests**: All execute in <5 minutes total
- **E2E Tests**: Critical workflows tested in <15 minutes

#### **Quality Metrics:**
- **Test Reliability**: <1% flakiness rate
- **Test Maintainability**: Clear organization and documentation
- **CI/CD Integration**: Tests run automatically on all commits
- **Error Detection**: Mutation testing score >85%

### **Test Architecture Benefits:**

#### **Developer Experience:**
- **Fast Feedback**: Unit tests provide immediate feedback
- **Reliable Tests**: Consistent results across environments
- **Easy Debugging**: Clear test failure messages and context
- **Comprehensive Coverage**: All critical paths tested

#### **System Quality:**
- **Regression Prevention**: Automated detection of breaking changes
- **Performance Monitoring**: Continuous performance validation
- **Error Handling**: Comprehensive error scenario testing
- **Stability Assurance**: Long-running stability validation

This comprehensive testing architecture enhancement gameplan provides a robust foundation for ensuring code quality, catching regressions early, and maintaining confidence in the system as it evolves through domain logic separation and data access improvements.