cli/docs/tests: harden editor launch and refine docs/test coverage

This commit is contained in:
Andreas Michael Fleckl
2026-02-17 16:23:45 +01:00
parent 421191ccaf
commit f1fa7768f3
4 changed files with 62 additions and 143 deletions

View File

@@ -145,8 +145,8 @@ class TestConfigCommands:
assert result.exit_code == 0
assert '.config/aitbc/config.yaml' in result.output
@patch('os.system')
def test_edit_command(self, mock_system, runner, mock_config, tmp_path):
@patch('aitbc_cli.commands.config.subprocess.run')
def test_edit_command(self, mock_run, runner, mock_config, tmp_path):
"""Test editing configuration file"""
# Change to the tmp_path directory
@@ -160,9 +160,10 @@ class TestConfigCommands:
assert result.exit_code == 0
# Verify editor was called
mock_system.assert_called_once()
assert 'nano' in mock_system.call_args[0][0]
assert str(actual_config_file) in mock_system.call_args[0][0]
mock_run.assert_called_once()
args = mock_run.call_args[0][0]
assert args[0] == 'nano'
assert str(actual_config_file) in args
def test_reset_config_cancelled(self, runner, mock_config, temp_config_file):
"""Test config reset cancelled by user"""
@@ -251,6 +252,38 @@ class TestConfigCommands:
assert data['coordinator_url'] == 'http://test:8000'
assert data['api_key'] == '***REDACTED***'
def test_export_empty_yaml(self, runner, mock_config, tmp_path):
"""Test exporting an empty YAML config file"""
with runner.isolated_filesystem(temp_dir=tmp_path):
local_config = Path.cwd() / ".aitbc.yaml"
local_config.write_text("")
result = runner.invoke(config, [
'export',
'--format', 'json'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
data = json.loads(result.output)
assert data == {}
def test_export_empty_yaml_yaml_format(self, runner, mock_config, tmp_path):
"""Test exporting an empty YAML config file as YAML"""
with runner.isolated_filesystem(temp_dir=tmp_path):
local_config = Path.cwd() / ".aitbc.yaml"
local_config.write_text("")
result = runner.invoke(config, [
'export',
'--format', 'yaml'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
data = yaml.safe_load(result.output)
assert data == {}
def test_export_no_config(self, runner, mock_config):
"""Test export when no config file exists"""
with runner.isolated_filesystem():
@@ -422,123 +455,6 @@ class TestConfigCommands:
assert result.exit_code == 0
assert 'CLIENT_API_KEY' in result.output
def test_profiles_save(self, runner, mock_config, tmp_path):
"""Test saving a configuration profile"""
profiles_dir = tmp_path / ".config" / "aitbc" / "profiles"
result = runner.invoke(config, [
'profiles',
'save',
'test_profile'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
assert 'Profile test_profile saved' in result.output
# Verify profile was created
profile_file = profiles_dir / "test_profile.yaml"
assert profile_file.exists()
with open(profile_file) as f:
profile_data = yaml.safe_load(f)
assert profile_data['coordinator_url'] == 'http://127.0.0.1:18000'
def test_profiles_list(self, runner, mock_config, tmp_path):
"""Test listing configuration profiles"""
# Create test profiles
profiles_dir = tmp_path / ".config" / "aitbc" / "profiles"
profiles_dir.mkdir(parents=True, exist_ok=True)
profile1 = profiles_dir / "profile1.yaml"
profile1.write_text(yaml.dump({"coordinator_url": "http://test1:8000"}))
profile2 = profiles_dir / "profile2.yaml"
profile2.write_text(yaml.dump({"coordinator_url": "http://test2:8000"}))
# Patch Path.home to return tmp_path
with patch('pathlib.Path.home') as mock_home:
mock_home.return_value = tmp_path
result = runner.invoke(config, [
'profiles',
'list'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
assert 'profile1' in result.output
assert 'profile2' in result.output
def test_profiles_load(self, runner, mock_config, tmp_path):
"""Test loading a configuration profile"""
# Create test profile
profiles_dir = tmp_path / ".config" / "aitbc" / "profiles"
profiles_dir.mkdir(parents=True, exist_ok=True)
profile_file = profiles_dir / "test.yaml"
profile_data = {
"coordinator_url": "http://loaded:8000",
"timeout": 75
}
profile_file.write_text(yaml.dump(profile_data))
config_file = tmp_path / ".aitbc.yaml"
with runner.isolated_filesystem(temp_dir=tmp_path):
# Patch Path.home to return tmp_path
with patch('pathlib.Path.home') as mock_home:
mock_home.return_value = tmp_path
result = runner.invoke(config, [
'profiles',
'load',
'test'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
assert 'Profile test loaded' in result.output
def test_validate_invalid_url(self, runner, mock_config):
"""Test validating config with invalid URL"""
mock_config.coordinator_url = "invalid-url"
result = runner.invoke(config, [
'validate'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code != 0
assert 'validation failed' in result.output
def test_validate_short_api_key(self, runner, mock_config):
"""Test validating config with short API key"""
mock_config.api_key = "short"
result = runner.invoke(config, [
'validate'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code != 0
assert 'validation failed' in result.output
def test_validate_no_api_key(self, runner, mock_config):
"""Test validating config without API key (warning)"""
mock_config.api_key = None
result = runner.invoke(config, [
'validate'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
assert 'valid with warnings' in result.output
@patch.dict(os.environ, {'CLIENT_API_KEY': 'env_key_123'})
def test_environments(self, runner, mock_config):
"""Test listing environment variables"""
result = runner.invoke(config, [
'environments'
], obj={'config': mock_config, 'output_format': 'table'})
assert result.exit_code == 0
assert 'CLIENT_API_KEY' in result.output
def test_profiles_save(self, runner, mock_config, tmp_path):
"""Test saving a configuration profile"""
# Patch Path.home to return tmp_path