feat: Implement comprehensive Testing Architecture Enhancement
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Establishes robust testing framework with clean architecture patterns: ## Phase 1: Test Infrastructure Foundation - Global test configuration with pytest.ini and conftest.py - Isolated test workspaces and environment management - Comprehensive fixture library for all test types - Test requirements and dependency management ## Phase 2: Advanced Testing Patterns - Test builders using builder pattern for domain objects - Mock factories for repositories, services, and configs - API response builders for external system simulation - Enhanced unit tests with proper mocking and isolation ## Phase 3: Test Performance and Quality - Performance testing framework with benchmarks - Memory usage monitoring and leak detection - Custom assertions for domain-specific validation - Parametrized testing for comprehensive coverage ## Phase 4: CI/CD Integration - GitHub Actions workflow for automated testing - Multi-stage testing: unit → integration → e2e → performance - Code quality checks with flake8, mypy, black, isort - Security scanning with safety and bandit ## Testing Architecture Benefits ✅ 100+ new test infrastructure components ✅ Standardized test organization (unit/integration/e2e) ✅ Mock-based testing with no external dependencies ✅ Performance regression detection ✅ Comprehensive fixture library ✅ CI/CD pipeline with quality gates The testing framework supports the domain logic separation and provides a solid foundation for maintaining high code quality as the system evolves. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
296
tests/conftest.py
Normal file
296
tests/conftest.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""
|
||||
Global test configuration and fixtures for MarkiTect project.
|
||||
|
||||
Provides shared fixtures, utilities, and configuration for all test types.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, AsyncMock
|
||||
from typing import Generator, Dict, Any
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create an instance of the default event loop for the test session."""
|
||||
loop = asyncio.new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_workspace() -> Generator[Path, None, None]:
|
||||
"""Create isolated test workspace for file operations."""
|
||||
temp_dir = tempfile.mkdtemp(prefix="markitect_test_")
|
||||
workspace_path = Path(temp_dir)
|
||||
|
||||
# Create subdirectories
|
||||
(workspace_path / "documents").mkdir()
|
||||
(workspace_path / "cache").mkdir()
|
||||
(workspace_path / "workspaces").mkdir()
|
||||
|
||||
yield workspace_path
|
||||
|
||||
# Cleanup
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_database_path(test_workspace) -> Path:
|
||||
"""Provide path for test database."""
|
||||
return test_workspace / "test.db"
|
||||
|
||||
|
||||
@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 = []
|
||||
mock_cursor.lastrowid = 1
|
||||
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_response.text = '{"status": "success"}'
|
||||
mock_client.get.return_value = mock_response
|
||||
mock_client.post.return_value = mock_response
|
||||
mock_client.put.return_value = mock_response
|
||||
mock_client.delete.return_value = mock_response
|
||||
return mock_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_async_http_client():
|
||||
"""Provide mocked async HTTP client for API tests."""
|
||||
mock_client = AsyncMock()
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status = 200
|
||||
mock_response.json = AsyncMock(return_value={"status": "success"})
|
||||
mock_response.text = AsyncMock(return_value='{"status": "success"}')
|
||||
mock_client.get.return_value = mock_response
|
||||
mock_client.post.return_value = mock_response
|
||||
mock_client.put.return_value = mock_response
|
||||
mock_client.delete.return_value = mock_response
|
||||
return mock_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_config(test_workspace) -> Dict[str, Any]:
|
||||
"""Provide test configuration dictionary."""
|
||||
return {
|
||||
"workspace_dir": str(test_workspace / "workspaces"),
|
||||
"database_path": str(test_workspace / "test.db"),
|
||||
"cache_dir": str(test_workspace / "cache"),
|
||||
"gitea_url": "http://test-gitea.com",
|
||||
"gitea_token": "test-token",
|
||||
"repo_owner": "test",
|
||||
"repo_name": "repo",
|
||||
"log_level": "DEBUG"
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clean_environment():
|
||||
"""Provide clean environment variables for testing."""
|
||||
original_env = dict(os.environ)
|
||||
|
||||
# Clear relevant environment variables
|
||||
test_env_vars = [
|
||||
"MARKITECT_WORKSPACE_DIR",
|
||||
"MARKITECT_GITEA_URL",
|
||||
"MARKITECT_GITEA_TOKEN",
|
||||
"MARKITECT_REPO_OWNER",
|
||||
"MARKITECT_REPO_NAME"
|
||||
]
|
||||
|
||||
for var in test_env_vars:
|
||||
os.environ.pop(var, None)
|
||||
|
||||
yield
|
||||
|
||||
# Restore original environment
|
||||
os.environ.clear()
|
||||
os.environ.update(original_env)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isolated_environment(test_workspace, clean_environment):
|
||||
"""Set up isolated environment for CLI testing."""
|
||||
env = {
|
||||
"MARKITECT_WORKSPACE_DIR": str(test_workspace / "workspaces"),
|
||||
"MARKITECT_GITEA_URL": "http://test-gitea.com",
|
||||
"MARKITECT_GITEA_TOKEN": "test-token",
|
||||
"MARKITECT_REPO_OWNER": "test",
|
||||
"MARKITECT_REPO_NAME": "repo",
|
||||
"PYTHONPATH": "."
|
||||
}
|
||||
|
||||
# Update current process environment
|
||||
for key, value in env.items():
|
||||
os.environ[key] = value
|
||||
|
||||
yield env
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_markdown_content():
|
||||
"""Provide sample markdown content for testing."""
|
||||
return """---
|
||||
title: Test Document
|
||||
author: Test Author
|
||||
tags: [test, sample]
|
||||
---
|
||||
|
||||
# Test Document
|
||||
|
||||
This is a test document with **bold** and *italic* text.
|
||||
|
||||
## Section 1
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
## Section 2
|
||||
|
||||
Here's a code block:
|
||||
|
||||
```python
|
||||
def hello_world():
|
||||
print("Hello, World!")
|
||||
```
|
||||
|
||||
And a link: [Test Link](https://example.com)
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_issue_data():
|
||||
"""Provide sample issue data for testing."""
|
||||
return {
|
||||
"number": 123,
|
||||
"title": "Test Issue",
|
||||
"body": "This is a test issue description",
|
||||
"state": "open",
|
||||
"labels": [
|
||||
{"name": "bug"},
|
||||
{"name": "priority:high"},
|
||||
{"name": "status:in-progress"}
|
||||
],
|
||||
"milestone": {
|
||||
"id": 1,
|
||||
"title": "Version 1.0",
|
||||
"description": "First release"
|
||||
},
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"updated_at": "2025-01-01T12:00:00Z"
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_project_data():
|
||||
"""Provide sample project data for testing."""
|
||||
return {
|
||||
"name": "Test Project",
|
||||
"description": "A test project for testing",
|
||||
"state": "active",
|
||||
"milestones": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Version 1.0",
|
||||
"description": "First release",
|
||||
"due_date": "2025-12-31T23:59:59Z",
|
||||
"state": "open",
|
||||
"open_issues": 5,
|
||||
"closed_issues": 3
|
||||
}
|
||||
],
|
||||
"kanban_columns": ["Todo", "In Progress", "Review", "Done"],
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2025-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
|
||||
# Performance testing fixtures
|
||||
@pytest.fixture
|
||||
def performance_timer():
|
||||
"""Timer fixture for performance testing."""
|
||||
import time
|
||||
|
||||
class Timer:
|
||||
def __init__(self):
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
|
||||
def stop(self):
|
||||
self.end_time = time.time()
|
||||
|
||||
@property
|
||||
def elapsed(self) -> float:
|
||||
if self.start_time is None:
|
||||
raise ValueError("Timer not started")
|
||||
if self.end_time is None:
|
||||
return time.time() - self.start_time
|
||||
return self.end_time - self.start_time
|
||||
|
||||
return Timer()
|
||||
|
||||
|
||||
# Async test helpers
|
||||
@pytest.fixture
|
||||
def async_test_timeout():
|
||||
"""Default timeout for async tests."""
|
||||
return 30.0 # 30 seconds
|
||||
|
||||
|
||||
# Test markers configuration
|
||||
def pytest_configure(config):
|
||||
"""Configure pytest markers."""
|
||||
config.addinivalue_line(
|
||||
"markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "integration: marks tests as integration tests"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "e2e: marks tests as end-to-end tests"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "performance: marks tests as performance tests"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "unit: marks tests as unit tests"
|
||||
)
|
||||
|
||||
|
||||
# Collection hooks
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
"""Modify test collection to add markers based on test location."""
|
||||
for item in items:
|
||||
# Add markers based on test file location
|
||||
if "unit" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.unit)
|
||||
elif "integration" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.integration)
|
||||
elif "e2e" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.e2e)
|
||||
elif "performance" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.performance)
|
||||
Reference in New Issue
Block a user