Files
dl-organizer/tests/test_state.py
T

388 lines
13 KiB
Python
Raw Normal View History

2026-02-09 17:43:35 +08:00
"""Unit tests for State Store operations."""
import json
import pytest
from datetime import datetime
from pathlib import Path
from vlm.state import (
load_state,
save_state,
StateManager,
VALID_STATUSES
)
from vlm.models import FileState, StateStore
class TestLoadSaveState:
"""Tests for load_state and save_state functions."""
def test_save_and_load_empty_state(self, tmp_path):
"""Test saving and loading an empty state store."""
state_path = tmp_path / "state.json"
# Create empty state store
store = StateStore(
states={},
version='1.0',
last_updated=datetime(2024, 1, 1, 12, 0, 0)
)
# Save and load
save_state(store, state_path)
loaded = load_state(state_path)
assert loaded.states == {}
assert loaded.version == '1.0'
assert loaded.last_updated == datetime(2024, 1, 1, 12, 0, 0)
def test_save_and_load_with_states(self, tmp_path):
"""Test saving and loading state store with file states."""
state_path = tmp_path / "state.json"
# Create state store with some states
file1 = Path("/videos/movie1.mp4")
file2 = Path("/videos/series/episode.mkv")
store = StateStore(
states={
str(file1): FileState(
file_path=file1,
status="reviewed",
reason="Checked manually",
updated_at=datetime(2024, 1, 1, 12, 0, 0)
),
str(file2): FileState(
file_path=file2,
status="ignored",
reason=None,
updated_at=datetime(2024, 1, 2, 12, 0, 0)
)
},
version='1.0',
last_updated=datetime(2024, 1, 2, 12, 0, 0)
)
# Save and load
save_state(store, state_path)
loaded = load_state(state_path)
assert len(loaded.states) == 2
assert str(file1) in loaded.states
assert str(file2) in loaded.states
state1 = loaded.states[str(file1)]
assert state1.file_path == file1
assert state1.status == "reviewed"
assert state1.reason == "Checked manually"
assert state1.updated_at == datetime(2024, 1, 1, 12, 0, 0)
state2 = loaded.states[str(file2)]
assert state2.file_path == file2
assert state2.status == "ignored"
assert state2.reason is None
assert state2.updated_at == datetime(2024, 1, 2, 12, 0, 0)
def test_save_creates_parent_directory(self, tmp_path):
"""Test that save_state creates parent directories if needed."""
state_path = tmp_path / "subdir" / "nested" / "state.json"
store = StateStore(
states={},
version='1.0',
last_updated=datetime.now()
)
save_state(store, state_path)
assert state_path.exists()
assert state_path.parent.exists()
def test_load_nonexistent_file_raises_error(self, tmp_path):
"""Test that loading a nonexistent file raises FileNotFoundError."""
state_path = tmp_path / "nonexistent.json"
with pytest.raises(FileNotFoundError):
load_state(state_path)
def test_load_invalid_json_raises_error(self, tmp_path):
"""Test that loading invalid JSON raises JSONDecodeError."""
state_path = tmp_path / "invalid.json"
state_path.write_text("not valid json {")
with pytest.raises(json.JSONDecodeError):
load_state(state_path)
def test_saved_json_is_valid(self, tmp_path):
"""Test that saved JSON is valid and human-readable."""
state_path = tmp_path / "state.json"
file1 = Path("/videos/movie.mp4")
store = StateStore(
states={
str(file1): FileState(
file_path=file1,
status="reviewed",
reason="Test",
updated_at=datetime(2024, 1, 1, 12, 0, 0)
)
},
version='1.0',
last_updated=datetime(2024, 1, 1, 12, 0, 0)
)
save_state(store, state_path)
# Verify JSON is valid by loading it directly
with open(state_path, 'r') as f:
data = json.load(f)
assert 'states' in data
assert 'version' in data
assert 'last_updated' in data
assert data['version'] == '1.0'
class TestStateManager:
"""Tests for StateManager class."""
def test_init_creates_new_state_if_not_exists(self, tmp_path):
"""Test that StateManager creates a new state store if file doesn't exist."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
assert manager.store.states == {}
assert manager.store.version == '1.0'
assert isinstance(manager.store.last_updated, datetime)
def test_init_loads_existing_state(self, tmp_path):
"""Test that StateManager loads existing state store."""
state_path = tmp_path / "state.json"
# Create existing state
file1 = Path("/videos/movie.mp4")
store = StateStore(
states={
str(file1): FileState(
file_path=file1,
status="reviewed",
reason="Test",
updated_at=datetime(2024, 1, 1, 12, 0, 0)
)
},
version='1.0',
last_updated=datetime(2024, 1, 1, 12, 0, 0)
)
save_state(store, state_path)
# Load with manager
manager = StateManager(state_path)
assert len(manager.store.states) == 1
assert str(file1) in manager.store.states
def test_get_file_state_returns_state(self, tmp_path):
"""Test getting state for a file."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "reviewed", "Test reason")
state = manager.get_file_state(file1)
assert state is not None
assert state.file_path == file1
assert state.status == "reviewed"
assert state.reason == "Test reason"
def test_get_file_state_returns_none_if_not_found(self, tmp_path):
"""Test that get_file_state returns None for unknown files."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
state = manager.get_file_state(file1)
assert state is None
def test_set_file_state_creates_new_state(self, tmp_path):
"""Test setting state for a new file."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "reviewed", "Checked")
state = manager.get_file_state(file1)
assert state.status == "reviewed"
assert state.reason == "Checked"
assert isinstance(state.updated_at, datetime)
def test_set_file_state_updates_existing_state(self, tmp_path):
"""Test that set_file_state is idempotent and updates timestamp."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
# Set initial state
manager.set_file_state(file1, "reviewed", "First check")
state1 = manager.get_file_state(file1)
# Update state
manager.set_file_state(file1, "reviewed", "Second check")
state2 = manager.get_file_state(file1)
assert state2.status == "reviewed"
assert state2.reason == "Second check"
assert state2.updated_at >= state1.updated_at
def test_set_file_state_validates_status(self, tmp_path):
"""Test that set_file_state validates status values."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
with pytest.raises(ValueError, match="Invalid status"):
manager.set_file_state(file1, "invalid_status")
def test_set_file_state_accepts_all_valid_statuses(self, tmp_path):
"""Test that all valid statuses are accepted."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
for status in VALID_STATUSES:
manager.set_file_state(file1, status)
state = manager.get_file_state(file1)
assert state.status == status
def test_set_file_state_without_reason(self, tmp_path):
"""Test setting state without a reason."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "ignored")
state = manager.get_file_state(file1)
assert state.status == "ignored"
assert state.reason is None
def test_query_by_status_returns_matching_files(self, tmp_path):
"""Test querying files by status."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie1.mp4")
file2 = Path("/videos/movie2.mp4")
file3 = Path("/videos/movie3.mp4")
manager.set_file_state(file1, "reviewed")
manager.set_file_state(file2, "ignored")
manager.set_file_state(file3, "reviewed")
reviewed = manager.query_by_status("reviewed")
assert len(reviewed) == 2
reviewed_paths = {state.file_path for state in reviewed}
assert file1 in reviewed_paths
assert file3 in reviewed_paths
def test_query_by_status_returns_empty_list_if_none_match(self, tmp_path):
"""Test that query_by_status returns empty list if no matches."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "reviewed")
quarantined = manager.query_by_status("quarantined")
assert quarantined == []
def test_clear_state_removes_file_state(self, tmp_path):
"""Test clearing state for a file."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "reviewed")
assert manager.get_file_state(file1) is not None
manager.clear_state(file1)
assert manager.get_file_state(file1) is None
def test_clear_state_on_nonexistent_file_does_nothing(self, tmp_path):
"""Test that clearing state on nonexistent file doesn't raise error."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
# Should not raise error
manager.clear_state(file1)
def test_save_persists_state_to_disk(self, tmp_path):
"""Test that save() persists state to disk."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "reviewed", "Test")
# Save to disk
manager.save()
# Load in new manager
manager2 = StateManager(state_path)
state = manager2.get_file_state(file1)
assert state is not None
assert state.status == "reviewed"
assert state.reason == "Test"
def test_state_updates_last_updated_timestamp(self, tmp_path):
"""Test that state operations update last_updated timestamp."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
initial_timestamp = manager.store.last_updated
file1 = Path("/videos/movie.mp4")
manager.set_file_state(file1, "reviewed")
assert manager.store.last_updated >= initial_timestamp
def test_multiple_files_with_different_statuses(self, tmp_path):
"""Test managing multiple files with different statuses."""
state_path = tmp_path / "state.json"
manager = StateManager(state_path)
files = [
(Path("/videos/movie1.mp4"), "reviewed"),
(Path("/videos/movie2.mp4"), "ignored"),
(Path("/videos/movie3.mp4"), "planned"),
(Path("/videos/movie4.mp4"), "executed"),
(Path("/videos/movie5.mp4"), "quarantined"),
]
for file_path, status in files:
manager.set_file_state(file_path, status)
# Verify all statuses
for file_path, expected_status in files:
state = manager.get_file_state(file_path)
assert state.status == expected_status
# Verify queries
for status in VALID_STATUSES:
results = manager.query_by_status(status)
expected_count = sum(1 for _, s in files if s == status)
assert len(results) == expected_count