feat: add anime metadata editing and NFO diagnostics
- Add PUT /anime/{key} endpoint for updating anime key, tmdb_id, tvdb_id
- Add NFO diagnostics and repair endpoints (GET/POST /nfo/diagnostics)
- Add edit modal UI with context menu integration
- Add frontend JS modules for context-menu and edit-modal
- Add comprehensive tests for edit, rename, and NFO repair flows
This commit is contained in:
161
tests/unit/test_anime_key_rename.py
Normal file
161
tests/unit/test_anime_key_rename.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Unit tests for anime key rename logic and validation."""
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from src.server.models.anime import AnimeMetadataUpdate, KEY_PATTERN
|
||||
|
||||
|
||||
class TestKeyValidation:
|
||||
"""Tests for AnimeMetadataUpdate key validation."""
|
||||
|
||||
def test_valid_key_simple(self):
|
||||
"""Test simple valid key."""
|
||||
model = AnimeMetadataUpdate(key="attack-on-titan")
|
||||
assert model.key == "attack-on-titan"
|
||||
|
||||
def test_valid_key_single_char(self):
|
||||
"""Test single character key is valid."""
|
||||
model = AnimeMetadataUpdate(key="a")
|
||||
assert model.key == "a"
|
||||
|
||||
def test_valid_key_numbers(self):
|
||||
"""Test key with numbers."""
|
||||
model = AnimeMetadataUpdate(key="86-eighty-six")
|
||||
assert model.key == "86-eighty-six"
|
||||
|
||||
def test_valid_key_allows_hyphens(self):
|
||||
"""Test hyphens in key are allowed."""
|
||||
model = AnimeMetadataUpdate(key="my-anime-key")
|
||||
assert model.key == "my-anime-key"
|
||||
|
||||
def test_valid_key_normalizes_to_lowercase(self):
|
||||
"""Test key is normalized to lowercase."""
|
||||
model = AnimeMetadataUpdate(key="Attack-On-Titan")
|
||||
assert model.key == "attack-on-titan"
|
||||
|
||||
def test_valid_key_strips_whitespace(self):
|
||||
"""Test key strips leading/trailing whitespace."""
|
||||
model = AnimeMetadataUpdate(key=" my-key ")
|
||||
assert model.key == "my-key"
|
||||
|
||||
def test_invalid_key_spaces(self):
|
||||
"""Test key with spaces is rejected."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AnimeMetadataUpdate(key="my anime key")
|
||||
assert "Key must contain only" in str(exc_info.value)
|
||||
|
||||
def test_invalid_key_uppercase_special(self):
|
||||
"""Test key with special characters is rejected."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AnimeMetadataUpdate(key="anime!@#key")
|
||||
assert "Key must contain only" in str(exc_info.value)
|
||||
|
||||
def test_invalid_key_empty(self):
|
||||
"""Test empty key is rejected."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AnimeMetadataUpdate(key="")
|
||||
assert "cannot be empty" in str(exc_info.value)
|
||||
|
||||
def test_invalid_key_only_whitespace(self):
|
||||
"""Test whitespace-only key is rejected."""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
AnimeMetadataUpdate(key=" ")
|
||||
assert "cannot be empty" in str(exc_info.value)
|
||||
|
||||
def test_invalid_key_starts_with_hyphen(self):
|
||||
"""Test key starting with hyphen is rejected."""
|
||||
with pytest.raises(ValidationError):
|
||||
AnimeMetadataUpdate(key="-my-key")
|
||||
|
||||
def test_invalid_key_ends_with_hyphen(self):
|
||||
"""Test key ending with hyphen is rejected."""
|
||||
with pytest.raises(ValidationError):
|
||||
AnimeMetadataUpdate(key="my-key-")
|
||||
|
||||
def test_key_none_is_allowed(self):
|
||||
"""Test None key (no change requested) is allowed."""
|
||||
model = AnimeMetadataUpdate(key=None)
|
||||
assert model.key is None
|
||||
|
||||
def test_key_omitted_is_allowed(self):
|
||||
"""Test omitting key entirely is allowed."""
|
||||
model = AnimeMetadataUpdate(tmdb_id=1234)
|
||||
assert model.key is None
|
||||
|
||||
|
||||
class TestTmdbIdValidation:
|
||||
"""Tests for tmdb_id validation."""
|
||||
|
||||
def test_valid_tmdb_id(self):
|
||||
"""Test valid positive TMDB ID."""
|
||||
model = AnimeMetadataUpdate(tmdb_id=1429)
|
||||
assert model.tmdb_id == 1429
|
||||
|
||||
def test_tmdb_id_none(self):
|
||||
"""Test None tmdb_id is allowed."""
|
||||
model = AnimeMetadataUpdate(tmdb_id=None)
|
||||
assert model.tmdb_id is None
|
||||
|
||||
def test_tmdb_id_negative_rejected(self):
|
||||
"""Test negative tmdb_id is rejected."""
|
||||
with pytest.raises(ValidationError):
|
||||
AnimeMetadataUpdate(tmdb_id=-1)
|
||||
|
||||
def test_tmdb_id_zero_rejected(self):
|
||||
"""Test zero tmdb_id is rejected."""
|
||||
with pytest.raises(ValidationError):
|
||||
AnimeMetadataUpdate(tmdb_id=0)
|
||||
|
||||
|
||||
class TestTvdbIdValidation:
|
||||
"""Tests for tvdb_id validation."""
|
||||
|
||||
def test_valid_tvdb_id(self):
|
||||
"""Test valid positive TVDB ID."""
|
||||
model = AnimeMetadataUpdate(tvdb_id=267440)
|
||||
assert model.tvdb_id == 267440
|
||||
|
||||
def test_tvdb_id_none(self):
|
||||
"""Test None tvdb_id is allowed."""
|
||||
model = AnimeMetadataUpdate(tvdb_id=None)
|
||||
assert model.tvdb_id is None
|
||||
|
||||
def test_tvdb_id_negative_rejected(self):
|
||||
"""Test negative tvdb_id is rejected."""
|
||||
with pytest.raises(ValidationError):
|
||||
AnimeMetadataUpdate(tvdb_id=-5)
|
||||
|
||||
def test_tvdb_id_zero_rejected(self):
|
||||
"""Test zero tvdb_id is rejected."""
|
||||
with pytest.raises(ValidationError):
|
||||
AnimeMetadataUpdate(tvdb_id=0)
|
||||
|
||||
|
||||
class TestKeyPattern:
|
||||
"""Tests for the KEY_PATTERN regex directly."""
|
||||
|
||||
@pytest.mark.parametrize("key", [
|
||||
"a",
|
||||
"abc",
|
||||
"attack-on-titan",
|
||||
"86-eighty-six",
|
||||
"a1b2c3",
|
||||
"x",
|
||||
"1",
|
||||
])
|
||||
def test_valid_patterns(self, key):
|
||||
"""Test keys that should match the pattern."""
|
||||
assert KEY_PATTERN.match(key) is not None
|
||||
|
||||
@pytest.mark.parametrize("key", [
|
||||
"-start",
|
||||
"end-",
|
||||
"has space",
|
||||
"UPPER",
|
||||
"special!char",
|
||||
"under_score",
|
||||
"",
|
||||
])
|
||||
def test_invalid_patterns(self, key):
|
||||
"""Test keys that should not match the pattern."""
|
||||
assert KEY_PATTERN.match(key) is None
|
||||
Reference in New Issue
Block a user