Files
markitect-main/tests/test_issue_18_config_management.py
tegwick f6c285b774
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
feat: implement configuration and environment management CLI (issue #18)
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>
2025-10-03 10:53:44 +02:00

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