Files
dl-organizer/tests/test_cli_quarantine.py
T
windyboy dfa18ed405 refactor: consolidate skill docs, add anti-drift tests, and apply audit fixes
DLO-13: Restructure vlm-library-workflow skill as safety contract layer.
- Rewrite SKILL.md (69 lines): safety contract, execution threshold semantics,
  six-step high-risk loop, decision rules, phase skeleton
- Delete redundant references (cli-reference, workflow, command-recipes, dev-guide)
- Add triage.md (failure mapping + preflight) and dev-map.md (module→test mapping)
- Add tests/test_docs_consistency.py: 78 parametrized tests verifying documented
  vlm commands exist in CLI registry
- Add CSV path mismatch test to test_plan_review.py (4th safety gate path)
- Delete vlm-expert.skill (Gemini package, 7 months stale) and README Gemini section

DLO-2 audit fixes: rate limiter injection, symmetric quarantine categories,
review-plan safety gates, parser improvements, planner validation.

CLI modularization: commands/ directory with one module per command group.
2026-09-25 13:50:09 +08:00

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_supported(self, config_file, temp_library):
"""Test that anime files can be quarantined (configured category)."""
runner = CliRunner()
# Create a test anime file
anime_file = temp_library / "anime" / "Anime Show.mkv"
anime_file.write_text("anime content")
# Quarantine should succeed
result = runner.invoke(main, [
'--config', str(config_file),
'quarantine', 'add',
str(anime_file)
])
assert result.exit_code == 0
assert "successfully quarantined" in result.output
assert not anime_file.exists()
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"