"""Tests for CLI rollback command.""" import json from datetime import datetime, timezone from pathlib import Path import pytest from click.testing import CliRunner from vlm.cli import main from vlm.models import FileOperation, OperationResult, RollbackLog @pytest.fixture def cli_runner(): """Create a Click CLI test runner.""" return CliRunner() @pytest.fixture def sample_rollback_log(tmp_path): """Create a sample rollback log file for testing.""" # Create test files source1 = tmp_path / "source1.txt" source2 = tmp_path / "source2.txt" dest1 = tmp_path / "dest1.txt" dest2 = tmp_path / "dest2.txt" source1.write_text("content1") source2.write_text("content2") # Move files to simulate execution source1.rename(dest1) source2.rename(dest2) # Create rollback log operations = [ OperationResult( operation=FileOperation( operation_type="move", source_path=source1, destination_path=dest1, reason="test move 1", has_conflict=False, conflict_reason=None ), success=True, error_message=None, executed_at=datetime.now(timezone.utc) ), OperationResult( operation=FileOperation( operation_type="move", source_path=source2, destination_path=dest2, reason="test move 2", has_conflict=False, conflict_reason=None ), success=True, error_message=None, executed_at=datetime.now(timezone.utc) ) ] rollback_log = RollbackLog( log_id="test-log-id", execution_plan_id="test-plan-id", executed_at=datetime.now(timezone.utc), operations=operations ) # Save rollback log to file log_path = tmp_path / "rollback_test.json" log_data = { "log_id": rollback_log.log_id, "execution_plan_id": rollback_log.execution_plan_id, "executed_at": rollback_log.executed_at.isoformat(), "operations": [ { "operation_type": op.operation.operation_type, "source_path": str(op.operation.source_path), "destination_path": str(op.operation.destination_path), "reason": op.operation.reason, "success": op.success, "error_message": op.error_message, "executed_at": op.executed_at.isoformat() } for op in rollback_log.operations ] } with open(log_path, 'w', encoding='utf-8') as f: json.dump(log_data, f, indent=2) return { "log_path": log_path, "source1": source1, "source2": source2, "dest1": dest1, "dest2": dest2 } class TestRollbackCommand: """Tests for the rollback CLI command.""" def test_rollback_help(self, cli_runner): """Test that rollback command shows help text.""" result = cli_runner.invoke(main, ['rollback', '--help']) assert result.exit_code == 0 assert "Rollback previous execution" in result.output assert "--log" in result.output assert "best-effort" in result.output def test_rollback_with_log_file(self, cli_runner, sample_rollback_log): """Test rollback command with explicit log file.""" log_path = sample_rollback_log["log_path"] dest1 = sample_rollback_log["dest1"] dest2 = sample_rollback_log["dest2"] source1 = sample_rollback_log["source1"] source2 = sample_rollback_log["source2"] # Verify files are at destination before rollback assert dest1.exists() assert dest2.exists() assert not source1.exists() assert not source2.exists() # Run rollback command with auto-confirmation result = cli_runner.invoke( main, ['rollback', '--log', str(log_path)], input='y\n' # Confirm rollback ) # Check command succeeded assert result.exit_code == 0 assert "Rollback log loaded" in result.output assert "Rolling back" in result.output assert "Rollback Summary" in result.output # Verify files were moved back to source assert source1.exists() assert source2.exists() assert not dest1.exists() assert not dest2.exists() def test_rollback_cancel_confirmation(self, cli_runner, sample_rollback_log): """Test that rollback can be cancelled at confirmation prompt.""" log_path = sample_rollback_log["log_path"] dest1 = sample_rollback_log["dest1"] dest2 = sample_rollback_log["dest2"] # Run rollback command and cancel result = cli_runner.invoke( main, ['rollback', '--log', str(log_path)], input='n\n' # Cancel rollback ) # Check command was cancelled assert result.exit_code == 0 assert "Rollback cancelled" in result.output # Verify files were NOT moved (still at destination) assert dest1.exists() assert dest2.exists() def test_rollback_missing_log_file(self, cli_runner, tmp_path): """Test rollback command with missing log file.""" missing_log = tmp_path / "nonexistent.json" result = cli_runner.invoke( main, ['rollback', '--log', str(missing_log)] ) # Check command failed with appropriate error # Click returns exit code 2 for file validation errors assert result.exit_code == 2 assert "Error" in result.output or "does not exist" in result.output def test_rollback_no_log_specified_no_logs_exist(self, cli_runner, tmp_path, monkeypatch): """Test rollback command without log file when no logs exist.""" # Mock home directory to use tmp_path fake_home = tmp_path / "fake_home" fake_home.mkdir() monkeypatch.setattr(Path, 'home', lambda: fake_home) result = cli_runner.invoke(main, ['rollback']) # Check command failed with appropriate error assert result.exit_code == 1 assert "No rollback logs found" in result.output