Some checks failed
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
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
Complete implementation of configuration management capabilities for MarkiTect CLI: New CLI Commands: - markitect config-show: Display current configuration with multiple output formats - markitect config-set: Set configuration values with validation and persistence - markitect config-init: Initialize configuration for new project with interactive setup - markitect config-validate: Validate current configuration and show issues - markitect config-help: Get help information for configuration keys Core Features: - Comprehensive configuration management with multiple sources (files, env vars, defaults) - Support for YAML, JSON, and simple output formats - Sensitive data masking for secure configuration display - Interactive project initialization with intelligent defaults - Configuration validation with path creation and URL validation - Environment variable integration with MARKITECT_ prefix - Nested configuration support with dot notation (e.g., gitea.url) - Type conversion for boolean, numeric, and string values - Project-specific configuration files (.markitect.yml/yaml/json) Technical Implementation: - ConfigurationManager class with robust error handling - Integration with existing configuration system - File-based configuration with automatic format detection - Configuration validation and help system - Support for custom configuration file locations - Graceful fallback when advanced config system unavailable Configuration Features: - Multiple file format support (YAML, JSON) - Environment variable precedence - Sensitive data protection - Directory structure validation and creation - URL and path validation - Interactive and non-interactive modes Testing: - 58 comprehensive tests covering all functionality - CLI integration tests with isolated environments - Edge cases: permissions, invalid paths, complex structures - Configuration file parsing and saving tests - Environment variable handling tests - Validation and error handling scenarios All acceptance criteria fulfilled: ✅ Configuration display and management ✅ Project initialization functionality ✅ Configuration validation ✅ Integration with existing config system ✅ Comprehensive test coverage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
897 lines
32 KiB
Python
897 lines
32 KiB
Python
"""
|
|
Tests for Issue #18: Configuration and Environment Management CLI
|
|
|
|
This test suite verifies the configuration management functionality including:
|
|
- Configuration display and management
|
|
- Project initialization functionality
|
|
- Configuration validation
|
|
- Integration with existing config system
|
|
- Environment variable handling
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import shutil
|
|
import os
|
|
import json
|
|
import yaml
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
from click.testing import CliRunner
|
|
|
|
from markitect.config_manager import ConfigurationManager
|
|
from markitect.cli import cli
|
|
|
|
|
|
class TestConfigurationManager:
|
|
"""Test the core ConfigurationManager functionality."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_dir = Path(self.temp_dir)
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_get_current_config(self):
|
|
"""Test getting current configuration."""
|
|
config_manager = ConfigurationManager()
|
|
config = config_manager.get_current_config()
|
|
|
|
# Should have basic configuration keys
|
|
expected_keys = [
|
|
'gitea_url', 'repo_owner', 'repo_name', 'api_token',
|
|
'workspace_dir', 'database_path', 'cache_dir', 'tests_dir'
|
|
]
|
|
|
|
for key in expected_keys:
|
|
assert key in config
|
|
|
|
# Should have metadata
|
|
assert '_meta' in config
|
|
assert 'config_sources' in config['_meta']
|
|
assert 'env_variables' in config['_meta']
|
|
assert 'working_directory' in config['_meta']
|
|
|
|
def test_display_config_yaml_format(self):
|
|
"""Test configuration display in YAML format."""
|
|
config_manager = ConfigurationManager()
|
|
output = config_manager.display_config(output_format='yaml')
|
|
|
|
# Should be valid YAML
|
|
parsed = yaml.safe_load(output)
|
|
assert isinstance(parsed, dict)
|
|
assert 'gitea_url' in parsed
|
|
|
|
def test_display_config_json_format(self):
|
|
"""Test configuration display in JSON format."""
|
|
config_manager = ConfigurationManager()
|
|
output = config_manager.display_config(output_format='json')
|
|
|
|
# Should be valid JSON
|
|
parsed = json.loads(output)
|
|
assert isinstance(parsed, dict)
|
|
assert 'gitea_url' in parsed
|
|
|
|
def test_display_config_simple_format(self):
|
|
"""Test configuration display in simple format."""
|
|
config_manager = ConfigurationManager()
|
|
output = config_manager.display_config(output_format='simple')
|
|
|
|
# Should contain key=value pairs
|
|
lines = output.split('\n')
|
|
assert any('gitea_url =' in line for line in lines)
|
|
assert any('repo_owner =' in line for line in lines)
|
|
|
|
def test_mask_sensitive_data(self):
|
|
"""Test masking of sensitive configuration data."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Create config with sensitive data
|
|
config = {
|
|
'api_token': 'secret123',
|
|
'password': 'mypassword',
|
|
'gitea_url': 'http://localhost:3000',
|
|
'repo_name': 'test-repo'
|
|
}
|
|
|
|
masked = config_manager._mask_sensitive_data(config)
|
|
|
|
# Sensitive fields should be masked
|
|
assert masked['api_token'] == '***MASKED***'
|
|
assert masked['password'] == '***MASKED***'
|
|
|
|
# Non-sensitive fields should remain
|
|
assert masked['gitea_url'] == 'http://localhost:3000'
|
|
assert masked['repo_name'] == 'test-repo'
|
|
|
|
def test_mask_sensitive_data_preserves_empty_values(self):
|
|
"""Test that empty sensitive values are not masked."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
config = {
|
|
'api_token': '',
|
|
'secret': None,
|
|
'repo_name': 'test-repo'
|
|
}
|
|
|
|
masked = config_manager._mask_sensitive_data(config)
|
|
|
|
# Empty/None values should not be masked
|
|
assert masked['api_token'] == ''
|
|
assert masked['secret'] is None
|
|
assert masked['repo_name'] == 'test-repo'
|
|
|
|
def test_set_config_value_simple(self):
|
|
"""Test setting a simple configuration value."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
success = config_manager.set_config_value('repo_name', 'test-repository')
|
|
assert success
|
|
|
|
# Verify file was created
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
assert config_file.exists()
|
|
|
|
# Verify content
|
|
content = yaml.safe_load(config_file.read_text())
|
|
assert content['repo_name'] == 'test-repository'
|
|
|
|
def test_set_config_value_nested(self):
|
|
"""Test setting nested configuration value with dot notation."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
success = config_manager.set_config_value('gitea.url', 'http://example.com')
|
|
assert success
|
|
|
|
# Verify nested structure
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
content = yaml.safe_load(config_file.read_text())
|
|
assert content['gitea']['url'] == 'http://example.com'
|
|
|
|
def test_set_config_value_type_conversion(self):
|
|
"""Test automatic type conversion for configuration values."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Test boolean conversion
|
|
config_manager.set_config_value('debug', 'true')
|
|
config_manager.set_config_value('verbose', 'false')
|
|
|
|
# Test number conversion
|
|
config_manager.set_config_value('port', '3000')
|
|
config_manager.set_config_value('timeout', '30.5')
|
|
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
content = yaml.safe_load(config_file.read_text())
|
|
|
|
assert content['debug'] is True
|
|
assert content['verbose'] is False
|
|
assert content['port'] == 3000
|
|
assert content['timeout'] == 30.5
|
|
|
|
def test_set_config_value_validation_error(self):
|
|
"""Test configuration validation during value setting."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Test invalid URL
|
|
with pytest.raises(ValueError, match="not a valid URL format"):
|
|
config_manager.set_config_value('gitea_url', 'invalid-url')
|
|
|
|
def test_set_config_value_existing_file(self):
|
|
"""Test setting values in existing configuration file."""
|
|
# Create existing config file
|
|
existing_config = {
|
|
'repo_name': 'old-name',
|
|
'gitea_url': 'http://localhost:3000'
|
|
}
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
config_file.write_text(yaml.dump(existing_config))
|
|
|
|
config_manager = ConfigurationManager()
|
|
success = config_manager.set_config_value('repo_name', 'new-name')
|
|
assert success
|
|
|
|
# Verify update
|
|
content = yaml.safe_load(config_file.read_text())
|
|
assert content['repo_name'] == 'new-name'
|
|
assert content['gitea_url'] == 'http://localhost:3000' # Should be preserved
|
|
|
|
def test_set_config_value_custom_file(self):
|
|
"""Test setting values in custom configuration file."""
|
|
custom_file = self.test_dir / 'custom.yml'
|
|
|
|
config_manager = ConfigurationManager()
|
|
success = config_manager.set_config_value('repo_name', 'test', str(custom_file))
|
|
assert success
|
|
|
|
assert custom_file.exists()
|
|
content = yaml.safe_load(custom_file.read_text())
|
|
assert content['repo_name'] == 'test'
|
|
|
|
def test_initialize_project_config(self):
|
|
"""Test project configuration initialization."""
|
|
config_manager = ConfigurationManager()
|
|
result = config_manager.initialize_project_config(self.test_dir, interactive=False)
|
|
|
|
# Verify config file created
|
|
config_file = Path(result['config_file'])
|
|
assert config_file.exists()
|
|
assert config_file.name == '.markitect.yml'
|
|
|
|
# Verify directories created
|
|
assert len(result['created_directories']) > 0
|
|
for directory in result['created_directories']:
|
|
assert Path(directory).exists()
|
|
|
|
# Verify config structure
|
|
config = result['config']
|
|
assert 'gitea_url' in config
|
|
assert 'repo_name' in config
|
|
assert config['repo_name'] == self.test_dir.name
|
|
|
|
def test_initialize_project_config_custom_dir(self):
|
|
"""Test project initialization in custom directory."""
|
|
project_dir = self.test_dir / 'my-project'
|
|
|
|
config_manager = ConfigurationManager()
|
|
result = config_manager.initialize_project_config(project_dir, interactive=False)
|
|
|
|
# Verify correct location
|
|
config_file = Path(result['config_file'])
|
|
assert config_file.parent == project_dir
|
|
assert project_dir.exists()
|
|
|
|
def test_validate_configuration_success(self):
|
|
"""Test configuration validation with valid config."""
|
|
config = {
|
|
'gitea_url': 'http://localhost:3000',
|
|
'database_path': str(self.test_dir / 'db.sqlite'),
|
|
'workspace_dir': str(self.test_dir / 'workspace'),
|
|
'cache_dir': str(self.test_dir / 'cache'),
|
|
'tests_dir': str(self.test_dir / 'tests')
|
|
}
|
|
|
|
config_manager = ConfigurationManager()
|
|
results = config_manager.validate_configuration(config)
|
|
|
|
# Should have validation results
|
|
assert len(results) > 0
|
|
|
|
# Check for required field validations
|
|
required_checks = [r for r in results if r['key'] in ['gitea_url', 'database_path']]
|
|
assert len(required_checks) > 0
|
|
|
|
def test_validate_configuration_missing_required(self):
|
|
"""Test configuration validation with missing required fields."""
|
|
config = {
|
|
'repo_name': 'test'
|
|
# Missing gitea_url and database_path
|
|
}
|
|
|
|
config_manager = ConfigurationManager()
|
|
results = config_manager.validate_configuration(config)
|
|
|
|
# Should have errors for missing required fields
|
|
errors = [r for r in results if r['status'] == 'error']
|
|
assert len(errors) > 0
|
|
|
|
error_keys = [r['key'] for r in errors]
|
|
assert 'gitea_url' in error_keys or 'database_path' in error_keys
|
|
|
|
def test_validate_configuration_path_creation(self):
|
|
"""Test configuration validation creates missing directories."""
|
|
config = {
|
|
'gitea_url': 'http://localhost:3000',
|
|
'database_path': str(self.test_dir / 'data' / 'db.sqlite'),
|
|
'workspace_dir': str(self.test_dir / 'new_workspace'),
|
|
'cache_dir': str(self.test_dir / 'new_cache'),
|
|
'tests_dir': str(self.test_dir / 'new_tests')
|
|
}
|
|
|
|
config_manager = ConfigurationManager()
|
|
results = config_manager.validate_configuration(config)
|
|
|
|
# Directories should be created
|
|
assert (self.test_dir / 'new_workspace').exists()
|
|
assert (self.test_dir / 'new_cache').exists()
|
|
assert (self.test_dir / 'new_tests').exists()
|
|
assert (self.test_dir / 'data').exists() # Database parent directory
|
|
|
|
# Should have warnings for created directories
|
|
warnings = [r for r in results if r['status'] == 'warning']
|
|
assert len(warnings) > 0
|
|
|
|
def test_list_config_keys(self):
|
|
"""Test listing available configuration keys."""
|
|
config_manager = ConfigurationManager()
|
|
keys = config_manager.list_config_keys()
|
|
|
|
# Should return list of tuples
|
|
assert isinstance(keys, list)
|
|
assert len(keys) > 0
|
|
|
|
# Each item should be (key, description, default)
|
|
for item in keys:
|
|
assert len(item) == 3
|
|
assert isinstance(item[0], str) # key
|
|
assert isinstance(item[1], str) # description
|
|
|
|
# Should include expected keys
|
|
key_names = [item[0] for item in keys]
|
|
assert 'gitea_url' in key_names
|
|
assert 'repo_name' in key_names
|
|
assert 'api_token' in key_names
|
|
|
|
def test_get_config_help_specific_key(self):
|
|
"""Test getting help for specific configuration key."""
|
|
config_manager = ConfigurationManager()
|
|
help_text = config_manager.get_config_help('gitea_url')
|
|
|
|
assert 'gitea_url' in help_text
|
|
assert 'description' in help_text.lower() or ':' in help_text
|
|
|
|
def test_get_config_help_unknown_key(self):
|
|
"""Test getting help for unknown configuration key."""
|
|
config_manager = ConfigurationManager()
|
|
help_text = config_manager.get_config_help('unknown_key')
|
|
|
|
assert 'unknown' in help_text.lower()
|
|
|
|
def test_get_config_help_general(self):
|
|
"""Test getting general configuration help."""
|
|
config_manager = ConfigurationManager()
|
|
help_text = config_manager.get_config_help()
|
|
|
|
assert 'available' in help_text.lower() or 'configuration' in help_text.lower()
|
|
assert 'gitea_url' in help_text
|
|
assert 'repo_name' in help_text
|
|
|
|
def test_convert_value_booleans(self):
|
|
"""Test value conversion for boolean types."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# True values
|
|
for true_val in ['true', 'True', 'TRUE', 'yes', 'YES', 'on', 'ON', '1']:
|
|
assert config_manager._convert_value(true_val) is True
|
|
|
|
# False values
|
|
for false_val in ['false', 'False', 'FALSE', 'no', 'NO', 'off', 'OFF', '0']:
|
|
assert config_manager._convert_value(false_val) is False
|
|
|
|
def test_convert_value_numbers(self):
|
|
"""Test value conversion for numeric types."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Integers
|
|
assert config_manager._convert_value('42') == 42
|
|
assert config_manager._convert_value('-10') == -10
|
|
|
|
# Floats
|
|
assert config_manager._convert_value('3.14') == 3.14
|
|
assert config_manager._convert_value('-2.5') == -2.5
|
|
|
|
# Strings that look like numbers but aren't
|
|
assert config_manager._convert_value('not-a-number') == 'not-a-number'
|
|
|
|
def test_is_valid_url(self):
|
|
"""Test URL validation."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Valid URLs
|
|
valid_urls = [
|
|
'http://localhost:3000',
|
|
'https://example.com',
|
|
'http://192.168.1.1:8080',
|
|
'https://api.github.com/repos'
|
|
]
|
|
for url in valid_urls:
|
|
assert config_manager._is_valid_url(url), f"Should be valid: {url}"
|
|
|
|
# Invalid URLs
|
|
invalid_urls = [
|
|
'not-a-url',
|
|
'ftp://example.com',
|
|
'localhost:3000',
|
|
'http://',
|
|
''
|
|
]
|
|
for url in invalid_urls:
|
|
assert not config_manager._is_valid_url(url), f"Should be invalid: {url}"
|
|
|
|
def test_is_valid_path(self):
|
|
"""Test path validation."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Valid paths
|
|
valid_paths = [
|
|
'/absolute/path',
|
|
'./relative/path',
|
|
'~/home/path',
|
|
'simple-path',
|
|
str(self.test_dir / 'subdir')
|
|
]
|
|
for path in valid_paths:
|
|
assert config_manager._is_valid_path(path), f"Should be valid: {path}"
|
|
|
|
# Edge case: empty string (should be considered valid as it can represent current directory)
|
|
assert config_manager._is_valid_path('')
|
|
|
|
|
|
class TestConfigFileParsing:
|
|
"""Test configuration file parsing and saving."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_dir = Path(self.temp_dir)
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_load_yaml_config_file(self):
|
|
"""Test loading YAML configuration file."""
|
|
config_content = {
|
|
'gitea_url': 'http://localhost:3000',
|
|
'repo_name': 'test-repo',
|
|
'nested': {
|
|
'key': 'value'
|
|
}
|
|
}
|
|
|
|
config_file = self.test_dir / 'test.yml'
|
|
config_file.write_text(yaml.dump(config_content))
|
|
|
|
config_manager = ConfigurationManager()
|
|
loaded = config_manager._load_config_file(config_file)
|
|
|
|
assert loaded == config_content
|
|
|
|
def test_load_json_config_file(self):
|
|
"""Test loading JSON configuration file."""
|
|
config_content = {
|
|
'gitea_url': 'http://localhost:3000',
|
|
'repo_name': 'test-repo',
|
|
'debug': True
|
|
}
|
|
|
|
config_file = self.test_dir / 'test.json'
|
|
config_file.write_text(json.dumps(config_content))
|
|
|
|
config_manager = ConfigurationManager()
|
|
loaded = config_manager._load_config_file(config_file)
|
|
|
|
assert loaded == config_content
|
|
|
|
def test_save_yaml_config_file(self):
|
|
"""Test saving YAML configuration file."""
|
|
config_content = {
|
|
'gitea_url': 'http://localhost:3000',
|
|
'repo_name': 'test-repo'
|
|
}
|
|
|
|
config_file = self.test_dir / 'output.yml'
|
|
config_manager = ConfigurationManager()
|
|
config_manager._save_config_file(config_content, config_file)
|
|
|
|
assert config_file.exists()
|
|
loaded = yaml.safe_load(config_file.read_text())
|
|
assert loaded == config_content
|
|
|
|
def test_save_json_config_file(self):
|
|
"""Test saving JSON configuration file."""
|
|
config_content = {
|
|
'gitea_url': 'http://localhost:3000',
|
|
'repo_name': 'test-repo'
|
|
}
|
|
|
|
config_file = self.test_dir / 'output.json'
|
|
config_manager = ConfigurationManager()
|
|
config_manager._save_config_file(config_content, config_file)
|
|
|
|
assert config_file.exists()
|
|
loaded = json.loads(config_file.read_text())
|
|
assert loaded == config_content
|
|
|
|
def test_load_invalid_config_file(self):
|
|
"""Test loading invalid configuration file."""
|
|
config_file = self.test_dir / 'invalid.yml'
|
|
config_file.write_text('invalid: yaml: content: [')
|
|
|
|
config_manager = ConfigurationManager()
|
|
with pytest.raises(ValueError, match="Failed to load config file"):
|
|
config_manager._load_config_file(config_file)
|
|
|
|
def test_get_target_config_file_existing(self):
|
|
"""Test getting target config file when one exists."""
|
|
# Create an existing config file
|
|
existing_file = self.test_dir / '.markitect.yml'
|
|
existing_file.write_text('test: value')
|
|
|
|
config_manager = ConfigurationManager()
|
|
target = config_manager._get_target_config_file()
|
|
|
|
# Should be relative path that matches the existing file
|
|
assert target.name == existing_file.name
|
|
assert target.exists()
|
|
|
|
def test_get_target_config_file_default(self):
|
|
"""Test getting target config file when none exists."""
|
|
config_manager = ConfigurationManager()
|
|
target = config_manager._get_target_config_file()
|
|
|
|
# Should be the default config file name
|
|
assert target.name == '.markitect.yml'
|
|
|
|
def test_get_target_config_file_custom(self):
|
|
"""Test getting custom target config file."""
|
|
custom_file = 'custom-config.yml'
|
|
|
|
config_manager = ConfigurationManager()
|
|
target = config_manager._get_target_config_file(custom_file)
|
|
|
|
assert target == Path(custom_file)
|
|
|
|
|
|
class TestCLIIntegration:
|
|
"""Test CLI command integration."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_dir = Path(self.temp_dir)
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
self.runner = CliRunner()
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_config_show_command(self):
|
|
"""Test config-show CLI command."""
|
|
result = self.runner.invoke(cli, ['config-show'])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'gitea_url' in result.output
|
|
|
|
def test_config_show_command_json_format(self):
|
|
"""Test config-show with JSON format."""
|
|
result = self.runner.invoke(cli, ['config-show', '--format', 'json'])
|
|
|
|
assert result.exit_code == 0
|
|
# Output should be valid JSON
|
|
try:
|
|
json.loads(result.output)
|
|
except json.JSONDecodeError:
|
|
pytest.fail("Output is not valid JSON")
|
|
|
|
def test_config_show_command_simple_format(self):
|
|
"""Test config-show with simple format."""
|
|
result = self.runner.invoke(cli, ['config-show', '--format', 'simple'])
|
|
|
|
assert result.exit_code == 0
|
|
assert '=' in result.output # Should contain key=value pairs
|
|
|
|
def test_config_set_command(self):
|
|
"""Test config-set CLI command."""
|
|
result = self.runner.invoke(cli, [
|
|
'config-set', 'repo_name', 'test-repository'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'Configuration updated' in result.output
|
|
|
|
# Verify file was created
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
assert config_file.exists()
|
|
|
|
content = yaml.safe_load(config_file.read_text())
|
|
assert content['repo_name'] == 'test-repository'
|
|
|
|
def test_config_set_command_nested_key(self):
|
|
"""Test config-set with nested key."""
|
|
result = self.runner.invoke(cli, [
|
|
'config-set', 'gitea.url', 'http://example.com'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
content = yaml.safe_load(config_file.read_text())
|
|
assert content['gitea']['url'] == 'http://example.com'
|
|
|
|
def test_config_set_command_custom_file(self):
|
|
"""Test config-set with custom config file."""
|
|
custom_file = 'custom.yml'
|
|
result = self.runner.invoke(cli, [
|
|
'config-set', 'repo_name', 'test',
|
|
'--config-file', custom_file
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert Path(custom_file).exists()
|
|
|
|
def test_config_set_command_no_validate(self):
|
|
"""Test config-set with validation disabled."""
|
|
result = self.runner.invoke(cli, [
|
|
'config-set', 'repo_name', 'test',
|
|
'--no-validate'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
def test_config_set_command_validation_error(self):
|
|
"""Test config-set with validation error."""
|
|
result = self.runner.invoke(cli, [
|
|
'config-set', 'gitea_url', 'invalid-url'
|
|
])
|
|
|
|
assert result.exit_code == 1
|
|
assert 'Configuration error' in result.output
|
|
|
|
def test_config_init_command_non_interactive(self):
|
|
"""Test config-init CLI command in non-interactive mode."""
|
|
result = self.runner.invoke(cli, [
|
|
'config-init', '--no-interactive'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'initialized successfully' in result.output
|
|
|
|
# Verify config file created
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
assert config_file.exists()
|
|
|
|
# Verify directories created
|
|
assert (self.test_dir / '.markitect_workspace').exists()
|
|
assert (self.test_dir / '.ast_cache').exists()
|
|
assert (self.test_dir / 'tests').exists()
|
|
|
|
def test_config_init_command_custom_directory(self):
|
|
"""Test config-init with custom project directory."""
|
|
project_dir = self.test_dir / 'my-project'
|
|
|
|
result = self.runner.invoke(cli, [
|
|
'config-init',
|
|
'--project-dir', str(project_dir),
|
|
'--no-interactive'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
# Verify in correct location
|
|
config_file = project_dir / '.markitect.yml'
|
|
assert config_file.exists()
|
|
|
|
def test_config_init_command_existing_file_no_force(self):
|
|
"""Test config-init with existing file without force."""
|
|
# Create existing config file
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
config_file.write_text('existing: config')
|
|
|
|
result = self.runner.invoke(cli, [
|
|
'config-init', '--no-interactive'
|
|
])
|
|
|
|
assert result.exit_code == 1
|
|
assert 'already exists' in result.output
|
|
|
|
def test_config_init_command_existing_file_with_force(self):
|
|
"""Test config-init with existing file with force."""
|
|
# Create existing config file
|
|
config_file = self.test_dir / '.markitect.yml'
|
|
config_file.write_text('existing: config')
|
|
|
|
result = self.runner.invoke(cli, [
|
|
'config-init', '--no-interactive', '--force'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'initialized successfully' in result.output
|
|
|
|
def test_config_validate_command(self):
|
|
"""Test config-validate CLI command."""
|
|
result = self.runner.invoke(cli, ['config-validate'])
|
|
|
|
assert result.exit_code in [0, 1] # May have warnings/errors
|
|
assert 'Configuration Validation Summary' in result.output
|
|
|
|
def test_config_validate_command_verbose(self):
|
|
"""Test config-validate with verbose output."""
|
|
result = self.runner.invoke(cli, ['config-validate', '--verbose'])
|
|
|
|
assert result.exit_code in [0, 1]
|
|
assert 'Configuration Validation Summary' in result.output
|
|
|
|
def test_config_help_command_general(self):
|
|
"""Test config-help CLI command."""
|
|
result = self.runner.invoke(cli, ['config-help'])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'available' in result.output.lower() or 'configuration' in result.output.lower()
|
|
assert 'gitea_url' in result.output
|
|
|
|
def test_config_help_command_specific_key(self):
|
|
"""Test config-help for specific key."""
|
|
result = self.runner.invoke(cli, ['config-help', 'gitea_url'])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'gitea_url' in result.output
|
|
|
|
def test_config_help_command_unknown_key(self):
|
|
"""Test config-help for unknown key."""
|
|
result = self.runner.invoke(cli, ['config-help', 'unknown_key'])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'unknown' in result.output.lower()
|
|
|
|
|
|
class TestEnvironmentVariables:
|
|
"""Test environment variable handling."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_dir = Path(self.temp_dir)
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
# Store original environment
|
|
self.original_env = dict(os.environ)
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
# Restore original environment
|
|
os.environ.clear()
|
|
os.environ.update(self.original_env)
|
|
|
|
def test_get_relevant_env_vars(self):
|
|
"""Test getting MARKITECT-related environment variables."""
|
|
# Set some test environment variables
|
|
os.environ['MARKITECT_GITEA_URL'] = 'http://test.com'
|
|
os.environ['MARKITECT_REPO_NAME'] = 'test-repo'
|
|
os.environ['OTHER_VAR'] = 'should-be-ignored'
|
|
|
|
config_manager = ConfigurationManager()
|
|
env_vars = config_manager._get_relevant_env_vars()
|
|
|
|
assert 'MARKITECT_GITEA_URL' in env_vars
|
|
assert 'MARKITECT_REPO_NAME' in env_vars
|
|
assert 'OTHER_VAR' not in env_vars
|
|
assert env_vars['MARKITECT_GITEA_URL'] == 'http://test.com'
|
|
|
|
def test_config_with_env_vars(self):
|
|
"""Test configuration loading with environment variables."""
|
|
# Set environment variables
|
|
os.environ['MARKITECT_GITEA_URL'] = 'http://env-test.com'
|
|
os.environ['MARKITECT_REPO_NAME'] = 'env-repo'
|
|
|
|
config_manager = ConfigurationManager()
|
|
config = config_manager.get_current_config()
|
|
|
|
# Environment variables should be reflected in config
|
|
assert config['gitea_url'] == 'http://env-test.com'
|
|
assert config['repo_name'] == 'env-repo'
|
|
|
|
# Should have env vars in metadata
|
|
assert 'MARKITECT_GITEA_URL' in config['_meta']['env_variables']
|
|
assert 'MARKITECT_REPO_NAME' in config['_meta']['env_variables']
|
|
|
|
|
|
class TestEdgeCases:
|
|
"""Test edge cases and error conditions."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_dir = Path(self.temp_dir)
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_config_manager_with_permission_error(self):
|
|
"""Test handling permission errors."""
|
|
# Create a directory we can't write to
|
|
restricted_dir = self.test_dir / 'restricted'
|
|
restricted_dir.mkdir(mode=0o444) # Read-only
|
|
|
|
config_manager = ConfigurationManager()
|
|
|
|
# This should not crash, but may warn about permission issues
|
|
try:
|
|
config = config_manager.get_current_config()
|
|
assert isinstance(config, dict)
|
|
except PermissionError:
|
|
# Acceptable to fail with permission error
|
|
pass
|
|
|
|
def test_set_config_value_invalid_file_path(self):
|
|
"""Test setting config value with invalid file path."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Try to write to a path that doesn't exist and can't be created
|
|
# Use a more reliable invalid path that doesn't depend on system permissions
|
|
invalid_path = '/nonexistent_directory_12345/config.yml'
|
|
with pytest.raises(ValueError):
|
|
config_manager.set_config_value('test', 'value', invalid_path)
|
|
|
|
def test_validate_configuration_with_none(self):
|
|
"""Test configuration validation with None input."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
# Should not crash with None input
|
|
results = config_manager.validate_configuration(None)
|
|
assert isinstance(results, list)
|
|
|
|
def test_mask_sensitive_data_with_complex_structure(self):
|
|
"""Test masking sensitive data in complex nested structure."""
|
|
config_manager = ConfigurationManager()
|
|
|
|
complex_config = {
|
|
'database': {
|
|
'password': 'secret123',
|
|
'host': 'localhost'
|
|
},
|
|
'apis': [
|
|
{'name': 'github', 'token': 'ghp_secret'},
|
|
{'name': 'gitea', 'url': 'http://localhost:3000'}
|
|
],
|
|
'secrets': {
|
|
'nested': {
|
|
'api_key': 'very_secret'
|
|
}
|
|
}
|
|
}
|
|
|
|
masked = config_manager._mask_sensitive_data(complex_config)
|
|
|
|
# Sensitive fields should be masked at any level
|
|
assert masked['database']['password'] == '***MASKED***'
|
|
assert masked['apis'][0]['token'] == '***MASKED***'
|
|
|
|
# The 'secrets' key itself gets masked because it's a sensitive keyword
|
|
# This is actually correct behavior
|
|
assert masked['secrets'] == '***MASKED***'
|
|
|
|
# Non-sensitive fields should remain
|
|
assert masked['database']['host'] == 'localhost'
|
|
assert masked['apis'][1]['url'] == 'http://localhost:3000'
|
|
|
|
def test_get_config_sources_empty_directory(self):
|
|
"""Test getting config sources in empty directory."""
|
|
config_manager = ConfigurationManager()
|
|
sources = config_manager._get_config_sources()
|
|
|
|
# Should always include defaults
|
|
assert len(sources) > 0
|
|
assert any('defaults' in source.lower() for source in sources)
|
|
|
|
def test_initialize_project_config_existing_directories(self):
|
|
"""Test project initialization with existing directories."""
|
|
# Pre-create some directories
|
|
(self.test_dir / '.markitect_workspace').mkdir()
|
|
(self.test_dir / 'tests').mkdir()
|
|
|
|
config_manager = ConfigurationManager()
|
|
result = config_manager.initialize_project_config(self.test_dir, interactive=False)
|
|
|
|
# Should succeed even with existing directories
|
|
assert 'config_file' in result
|
|
assert Path(result['config_file']).exists() |