"""Tests for CLI quarantine commands. This module tests the CLI interface for quarantine operations. """ import json from pathlib import Path from click.testing import CliRunner import pytest from vlm.cli import main from vlm.config import Config @pytest.fixture def temp_library(tmp_path): """Create a temporary library structure for testing.""" library_root = tmp_path / "library" # Create category directories (library_root / "movie").mkdir(parents=True) (library_root / "series").mkdir(parents=True) (library_root / "anime").mkdir(parents=True) (library_root / "other").mkdir(parents=True) return library_root @pytest.fixture def config_file(tmp_path, temp_library): """Create a temporary config file.""" config_path = tmp_path / "config.yaml" config_content = f""" library_root: "{temp_library}" video_extensions: - .mp4 - .mkv - .avi templates: movie_dir: "movie/{{title}} ({{year}})/" series_dir: "series/{{title}}/Season {{season:02d}}/" movie_filename: "{{title}} ({{year}}){{ext}}" series_filename: "S{{season:02d}}E{{episode:02d}}{{ext}}" quarantine_dir: ".quarantine" log_level: "INFO" """ config_path.write_text(config_content) return config_path class TestQuarantineListCommand: """Tests for 'vlm quarantine list' command.""" def test_list_empty_quarantine(self, config_file): """Test listing when no files are quarantined.""" runner = CliRunner() result = runner.invoke(main, ['--config', str(config_file), 'quarantine', 'list']) assert result.exit_code == 0 assert "No quarantined files found" in result.output def test_list_with_quarantined_files(self, config_file, temp_library): """Test listing quarantined files.""" runner = CliRunner() # Create a test movie file movie_file = temp_library / "movie" / "Test Movie (2020).mkv" movie_file.write_text("test content") # Quarantine the file first result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie_file) ]) assert result.exit_code == 0 # Now list quarantined files result = runner.invoke(main, ['--config', str(config_file), 'quarantine', 'list']) assert result.exit_code == 0 assert "Test Movie (2020).mkv" in result.output assert "Category: movie" in result.output def test_list_filter_by_category(self, config_file, temp_library): """Test listing with category filter.""" runner = CliRunner() # Create and quarantine a movie file movie_file = temp_library / "movie" / "Movie.mkv" movie_file.write_text("movie content") result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie_file) ]) assert result.exit_code == 0 # List only movie category result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'list', '--category', 'movie' ]) assert result.exit_code == 0 assert "Movie.mkv" in result.output assert "category 'movie'" in result.output class TestQuarantineAddCommand: """Tests for 'vlm quarantine add' command.""" def test_add_movie_file(self, config_file, temp_library): """Test adding a movie file to quarantine.""" runner = CliRunner() # Create a test movie file movie_file = temp_library / "movie" / "Test Movie (2020).mkv" movie_file.write_text("test content") # Quarantine the file result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie_file) ]) assert result.exit_code == 0 assert "successfully quarantined" in result.output assert not movie_file.exists() # Original file should be moved # Check quarantine location quarantine_path = temp_library / "movie" / ".quarantine" / "Test Movie (2020).mkv" assert quarantine_path.exists() assert quarantine_path.read_text() == "test content" def test_add_series_file(self, config_file, temp_library): """Test adding a series file to quarantine.""" runner = CliRunner() # Create a test series file series_file = temp_library / "series" / "Show Name" / "S01E01.mkv" series_file.parent.mkdir(parents=True) series_file.write_text("series content") # Quarantine the file result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(series_file) ]) assert result.exit_code == 0 assert "successfully quarantined" in result.output assert not series_file.exists() def test_add_with_reason(self, config_file, temp_library): """Test adding file with a reason.""" runner = CliRunner() # Create a test movie file movie_file = temp_library / "movie" / "Duplicate.mkv" movie_file.write_text("test content") # Quarantine with reason result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie_file), '--reason', 'duplicate file' ]) assert result.exit_code == 0 assert "Reason: duplicate file" in result.output assert "successfully quarantined" in result.output def test_add_anime_file_rejected(self, config_file, temp_library): """Test that anime files are rejected.""" runner = CliRunner() # Create a test anime file anime_file = temp_library / "anime" / "Anime Show.mkv" anime_file.write_text("anime content") # Try to quarantine (should fail) result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(anime_file) ]) assert result.exit_code == 1 assert "not supported" in result.output.lower() assert anime_file.exists() # File should still exist def test_add_nonexistent_file(self, config_file, temp_library): """Test adding a file that doesn't exist.""" runner = CliRunner() # Try to quarantine non-existent file nonexistent = temp_library / "movie" / "DoesNotExist.mkv" result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(nonexistent) ]) # Click should catch this before our code runs assert result.exit_code != 0 class TestQuarantineRestoreCommand: """Tests for 'vlm quarantine restore' command.""" def test_restore_movie_file(self, config_file, temp_library): """Test restoring a movie file from quarantine.""" runner = CliRunner() # Create and quarantine a movie file movie_file = temp_library / "movie" / "Test Movie.mkv" movie_file.write_text("test content") result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie_file) ]) assert result.exit_code == 0 # Get quarantine path quarantine_path = temp_library / "movie" / ".quarantine" / "Test Movie.mkv" assert quarantine_path.exists() # Restore the file result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'restore', str(quarantine_path) ]) assert result.exit_code == 0 assert "successfully restored" in result.output assert movie_file.exists() # Original location should have file assert not quarantine_path.exists() # Quarantine should be empty assert movie_file.read_text() == "test content" def test_restore_series_file(self, config_file, temp_library): """Test restoring a series file from quarantine.""" runner = CliRunner() # Create and quarantine a series file series_file = temp_library / "series" / "Show" / "S01E01.mkv" series_file.parent.mkdir(parents=True) series_file.write_text("series content") result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(series_file) ]) assert result.exit_code == 0 # Get quarantine path quarantine_path = temp_library / "series" / ".quarantine" / "Show" / "S01E01.mkv" assert quarantine_path.exists() # Restore the file result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'restore', str(quarantine_path) ]) assert result.exit_code == 0 assert "successfully restored" in result.output assert series_file.exists() def test_restore_conflict(self, config_file, temp_library): """Test restoring when original location is occupied.""" runner = CliRunner() # Create and quarantine a movie file movie_file = temp_library / "movie" / "Movie.mkv" movie_file.write_text("original content") result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie_file) ]) assert result.exit_code == 0 # Create a new file at the original location movie_file.write_text("new content") # Try to restore (should fail due to conflict) quarantine_path = temp_library / "movie" / ".quarantine" / "Movie.mkv" result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'restore', str(quarantine_path) ]) assert result.exit_code == 1 assert "Cannot restore" in result.output or "already exists" in result.output assert movie_file.read_text() == "new content" # Original location unchanged assert quarantine_path.exists() # File still in quarantine class TestQuarantineIntegration: """Integration tests for quarantine workflow.""" def test_full_quarantine_workflow(self, config_file, temp_library): """Test complete workflow: add -> list -> restore.""" runner = CliRunner() # Create test files movie1 = temp_library / "movie" / "Movie1.mkv" movie2 = temp_library / "movie" / "Movie2.mkv" movie1.write_text("content1") movie2.write_text("content2") # Add both to quarantine result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie1), '--reason', 'duplicate' ]) assert result.exit_code == 0 result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'add', str(movie2), '--reason', 'low quality' ]) assert result.exit_code == 0 # List quarantined files result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'list' ]) assert result.exit_code == 0 assert "Movie1.mkv" in result.output assert "Movie2.mkv" in result.output assert "Total: 2 quarantined file(s)" in result.output # Restore one file quarantine_path1 = temp_library / "movie" / ".quarantine" / "Movie1.mkv" result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'restore', str(quarantine_path1) ]) assert result.exit_code == 0 # List again (should show only 1 file now) result = runner.invoke(main, [ '--config', str(config_file), 'quarantine', 'list' ]) assert result.exit_code == 0 assert "Movie1.mkv" not in result.output assert "Movie2.mkv" in result.output assert "Total: 1 quarantined file(s)" in result.output # Verify restored file assert movie1.exists() assert movie1.read_text() == "content1"