- 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
162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
"""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
|