Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,346 @@
"""
Pytest Fixtures Template
Reusable pytest fixtures for CLI testing with Click.testing.CliRunner
Provides common setup, teardown, and test utilities
"""
import pytest
import os
import tempfile
import shutil
from pathlib import Path
from click.testing import CliRunner
from mycli.cli import cli
# Basic Fixtures
@pytest.fixture
def runner():
"""Create a CliRunner instance for testing"""
return CliRunner()
@pytest.fixture
def isolated_runner():
"""Create a CliRunner with isolated filesystem"""
runner = CliRunner()
with runner.isolated_filesystem():
yield runner
# Configuration Fixtures
@pytest.fixture
def temp_config_dir(tmp_path):
"""Create a temporary configuration directory"""
config_dir = tmp_path / '.mycli'
config_dir.mkdir()
return config_dir
@pytest.fixture
def config_file(temp_config_dir):
"""Create a temporary configuration file"""
config_path = temp_config_dir / 'config.yaml'
config_content = """
api_key: your_test_key_here
environment: development
verbose: false
timeout: 30
"""
config_path.write_text(config_content)
return config_path
@pytest.fixture
def env_with_config(temp_config_dir, monkeypatch):
"""Set up environment with config directory"""
monkeypatch.setenv('MYCLI_CONFIG_DIR', str(temp_config_dir))
return temp_config_dir
# File System Fixtures
@pytest.fixture
def temp_workspace(tmp_path):
"""Create a temporary workspace directory"""
workspace = tmp_path / 'workspace'
workspace.mkdir()
return workspace
@pytest.fixture
def sample_project(temp_workspace):
"""Create a sample project structure"""
project = temp_workspace / 'sample-project'
project.mkdir()
# Create sample files
(project / 'package.json').write_text('{"name": "sample", "version": "1.0.0"}')
(project / 'README.md').write_text('# Sample Project')
src_dir = project / 'src'
src_dir.mkdir()
(src_dir / 'index.js').write_text('console.log("Hello, World!");')
return project
@pytest.fixture
def sample_files(temp_workspace):
"""Create sample files for testing"""
files = {
'input.txt': 'test input data\n',
'config.yaml': 'key: value\n',
'data.json': '{"id": 1, "name": "test"}\n'
}
created_files = {}
for filename, content in files.items():
file_path = temp_workspace / filename
file_path.write_text(content)
created_files[filename] = file_path
return created_files
# Mock Fixtures
@pytest.fixture
def mock_api_key(monkeypatch):
"""Mock API key environment variable"""
monkeypatch.setenv('MYCLI_API_KEY', 'test_api_key_123')
return 'test_api_key_123'
@pytest.fixture
def mock_home_dir(tmp_path, monkeypatch):
"""Mock home directory"""
home = tmp_path / 'home'
home.mkdir()
monkeypatch.setenv('HOME', str(home))
return home
@pytest.fixture
def mock_no_config(monkeypatch):
"""Remove all configuration environment variables"""
vars_to_remove = [
'MYCLI_CONFIG_DIR',
'MYCLI_API_KEY',
'MYCLI_ENVIRONMENT',
]
for var in vars_to_remove:
monkeypatch.delenv(var, raising=False)
# State Management Fixtures
@pytest.fixture
def cli_state(temp_workspace):
"""Create a CLI state file"""
state_file = temp_workspace / '.mycli-state'
state = {
'initialized': True,
'last_command': None,
'history': []
}
import json
state_file.write_text(json.dumps(state, indent=2))
return state_file
@pytest.fixture
def clean_state(temp_workspace):
"""Ensure no state file exists"""
state_file = temp_workspace / '.mycli-state'
if state_file.exists():
state_file.unlink()
return temp_workspace
# Helper Function Fixtures
@pytest.fixture
def run_cli_command(runner):
"""Helper function to run CLI commands and return parsed results"""
def _run(args, input_data=None, env=None):
"""
Run a CLI command and return structured results
Args:
args: List of command arguments
input_data: Optional input for interactive prompts
env: Optional environment variables dict
Returns:
dict with keys: exit_code, output, lines, success
"""
result = runner.invoke(cli, args, input=input_data, env=env)
return {
'exit_code': result.exit_code,
'output': result.output,
'lines': result.output.splitlines(),
'success': result.exit_code == 0
}
return _run
@pytest.fixture
def assert_cli_success(runner):
"""Helper to assert successful CLI execution"""
def _assert(args, expected_in_output=None):
"""
Run CLI command and assert success
Args:
args: List of command arguments
expected_in_output: Optional string expected in output
"""
result = runner.invoke(cli, args)
assert result.exit_code == 0, f"Command failed: {result.output}"
if expected_in_output:
assert expected_in_output in result.output
return result
return _assert
@pytest.fixture
def assert_cli_failure(runner):
"""Helper to assert CLI command failure"""
def _assert(args, expected_in_output=None):
"""
Run CLI command and assert failure
Args:
args: List of command arguments
expected_in_output: Optional string expected in output
"""
result = runner.invoke(cli, args)
assert result.exit_code != 0, f"Command should have failed: {result.output}"
if expected_in_output:
assert expected_in_output in result.output
return result
return _assert
# Cleanup Fixtures
@pytest.fixture(autouse=True)
def cleanup_temp_files(request):
"""Automatically clean up temporary files after tests"""
temp_files = []
def _register(filepath):
temp_files.append(filepath)
request.addfinalizer(lambda: [
os.remove(f) for f in temp_files if os.path.exists(f)
])
return _register
@pytest.fixture(scope='session')
def test_data_dir():
"""Provide path to test data directory"""
return Path(__file__).parent / 'test_data'
# Parametrized Fixtures
@pytest.fixture(params=['json', 'yaml', 'table'])
def output_format(request):
"""Parametrize tests across different output formats"""
return request.param
@pytest.fixture(params=[True, False])
def verbose_mode(request):
"""Parametrize tests with and without verbose mode"""
return request.param
@pytest.fixture(params=['development', 'staging', 'production'])
def environment(request):
"""Parametrize tests across different environments"""
return request.param
# Integration Test Fixtures
@pytest.fixture
def integration_workspace(tmp_path):
"""
Create a complete integration test workspace with all necessary files
"""
workspace = tmp_path / 'integration'
workspace.mkdir()
# Create directory structure
(workspace / 'src').mkdir()
(workspace / 'tests').mkdir()
(workspace / 'config').mkdir()
(workspace / 'data').mkdir()
# Create config files
(workspace / 'config' / 'dev.yaml').write_text('env: development\n')
(workspace / 'config' / 'prod.yaml').write_text('env: production\n')
# Initialize CLI
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=workspace):
runner.invoke(cli, ['init'])
return workspace
@pytest.fixture
def mock_external_service(monkeypatch):
"""Mock external service API calls"""
class MockService:
def __init__(self):
self.calls = []
def call_api(self, endpoint, method='GET', data=None):
self.calls.append({
'endpoint': endpoint,
'method': method,
'data': data
})
return {'status': 'success', 'data': 'mock response'}
mock = MockService()
# Replace actual service with mock
monkeypatch.setattr('mycli.services.api', mock)
return mock
# Snapshot Testing Fixtures
@pytest.fixture
def snapshot_dir(tmp_path):
"""Create directory for snapshot testing"""
snapshot = tmp_path / 'snapshots'
snapshot.mkdir()
return snapshot
@pytest.fixture
def compare_output(snapshot_dir):
"""Compare CLI output with saved snapshot"""
def _compare(output, snapshot_name):
snapshot_file = snapshot_dir / f'{snapshot_name}.txt'
if not snapshot_file.exists():
# Create snapshot
snapshot_file.write_text(output)
return True
# Compare with existing snapshot
expected = snapshot_file.read_text()
return output == expected
return _compare