Stop tracking personal workflow artifacts at repo root, add CI and MIT license, align README and agent skills with artifacts/ defaults, and enable Ruff in dev/CI so releases are verifiable without local-only runs. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
377 lines
12 KiB
Python
377 lines
12 KiB
Python
"""Tests for CLI quarantine commands.
|
|
|
|
This module tests the CLI interface for quarantine operations.
|
|
"""
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from vlm.cli import main
|
|
|
|
|
|
@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"
|