"""Tests for CLI state commands.""" from pathlib import Path import pytest from click.testing import CliRunner from vlm.cli import default_config_path, main @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