241 lines
6.6 KiB
Python
241 lines
6.6 KiB
Python
"""Tests for CLI state commands."""
|
|
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from vlm.cli import main, default_config_path
|
|
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
"""Create a Click CLI test runner."""
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_state_file(tmp_path, monkeypatch):
|
|
"""Create a temporary state file and set up environment."""
|
|
state_dir = tmp_path / ".vlm"
|
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
state_file = state_dir / "state.json"
|
|
|
|
# Mock the home directory to use tmp_path
|
|
monkeypatch.setattr(Path, 'home', lambda: tmp_path)
|
|
|
|
return state_file
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_config(tmp_path):
|
|
"""Create a temporary config file."""
|
|
config_dir = tmp_path / ".vlm"
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
config_file = config_dir / "config.yaml"
|
|
|
|
# Create a minimal config
|
|
config_content = f"""
|
|
library_root: {tmp_path / "videos"}
|
|
video_extensions:
|
|
- .mp4
|
|
- .mkv
|
|
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_file.write_text(config_content)
|
|
|
|
return config_file
|
|
|
|
|
|
def test_state_set_and_show(runner, temp_state_file, temp_config):
|
|
"""Test setting and showing file state."""
|
|
test_file = Path("/test/movie.mkv")
|
|
|
|
# Set state
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'set', str(test_file),
|
|
'--status', 'reviewed',
|
|
'--reason', 'checked manually'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "State updated" in result.output
|
|
assert "reviewed" in result.output
|
|
|
|
# Verify state file was created
|
|
assert temp_state_file.exists()
|
|
|
|
# Show state
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'show', str(test_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "reviewed" in result.output
|
|
assert "checked manually" in result.output
|
|
|
|
|
|
def test_default_config_path_resolves_at_runtime(tmp_path, monkeypatch):
|
|
"""Test CLI default config path follows current home directory."""
|
|
monkeypatch.setattr(Path, 'home', lambda: tmp_path)
|
|
assert default_config_path() == tmp_path / ".vlm" / "config.yaml"
|
|
|
|
|
|
def test_state_query(runner, temp_state_file, temp_config):
|
|
"""Test querying files by status."""
|
|
test_files = [
|
|
Path("/test/movie1.mkv"),
|
|
Path("/test/movie2.mkv"),
|
|
Path("/test/movie3.mkv")
|
|
]
|
|
|
|
# Set states for multiple files
|
|
for i, test_file in enumerate(test_files):
|
|
status = 'ignored' if i < 2 else 'reviewed'
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'set', str(test_file),
|
|
'--status', status
|
|
])
|
|
assert result.exit_code == 0
|
|
|
|
# Query for ignored files
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'query',
|
|
'--status', 'ignored'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "movie1.mkv" in result.output
|
|
assert "movie2.mkv" in result.output
|
|
assert "movie3.mkv" not in result.output
|
|
assert "Total: 2 file(s)" in result.output
|
|
|
|
|
|
def test_state_clear(runner, temp_state_file, temp_config):
|
|
"""Test clearing file state."""
|
|
test_file = Path("/test/movie.mkv")
|
|
|
|
# Set state
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'set', str(test_file),
|
|
'--status', 'reviewed'
|
|
])
|
|
assert result.exit_code == 0
|
|
|
|
# Clear state
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'clear', str(test_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "State cleared" in result.output
|
|
|
|
# Verify state is cleared
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'show', str(test_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "No state found" in result.output
|
|
|
|
|
|
def test_state_set_invalid_status(runner, temp_state_file, temp_config):
|
|
"""Test setting state with invalid status."""
|
|
test_file = Path("/test/movie.mkv")
|
|
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'set', str(test_file),
|
|
'--status', 'invalid_status'
|
|
])
|
|
|
|
# Should fail due to invalid choice
|
|
assert result.exit_code != 0
|
|
|
|
|
|
def test_state_show_nonexistent(runner, temp_state_file, temp_config):
|
|
"""Test showing state for file that doesn't have state."""
|
|
test_file = Path("/test/nonexistent.mkv")
|
|
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'show', str(test_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "No state found" in result.output
|
|
|
|
|
|
def test_state_clear_nonexistent(runner, temp_state_file, temp_config):
|
|
"""Test clearing state for file that doesn't have state."""
|
|
test_file = Path("/test/nonexistent.mkv")
|
|
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'clear', str(test_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "No state found" in result.output
|
|
assert "Nothing to clear" in result.output
|
|
|
|
|
|
def test_state_query_empty(runner, temp_state_file, temp_config):
|
|
"""Test querying when no files have the status."""
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'query',
|
|
'--status', 'quarantined'
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "No files found" in result.output
|
|
|
|
|
|
def test_state_set_idempotent(runner, temp_state_file, temp_config):
|
|
"""Test that setting state multiple times is idempotent."""
|
|
test_file = Path("/test/movie.mkv")
|
|
|
|
# Set state first time
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'set', str(test_file),
|
|
'--status', 'reviewed',
|
|
'--reason', 'first check'
|
|
])
|
|
assert result.exit_code == 0
|
|
|
|
# Set state second time with different reason
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'set', str(test_file),
|
|
'--status', 'reviewed',
|
|
'--reason', 'second check'
|
|
])
|
|
assert result.exit_code == 0
|
|
|
|
# Verify the reason was updated
|
|
result = runner.invoke(main, [
|
|
'--config', str(temp_config),
|
|
'state', 'show', str(test_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert "second check" in result.output
|
|
assert "first check" not in result.output
|