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>
296 lines
7.8 KiB
Python
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) |