Files
markitect-main/tests/conftest.py
tegwick 21a5d1d734
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
feat: Implement comprehensive Testing Architecture Enhancement
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>
2025-09-26 22:36:35 +02:00

296 lines
7.8 KiB
Python

"""
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)