153 lines
4.7 KiB
Python
153 lines
4.7 KiB
Python
"""Unit tests for anime Pydantic models.
|
|
|
|
This module tests all anime-related models including validation,
|
|
serialization, and field constraints.
|
|
"""
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from src.server.models.anime import (
|
|
AnimeSeriesResponse,
|
|
EpisodeInfo,
|
|
MissingEpisodeInfo,
|
|
SearchRequest,
|
|
SearchResult,
|
|
)
|
|
|
|
|
|
class TestEpisodeInfo:
|
|
"""Tests for EpisodeInfo model."""
|
|
|
|
def test_episode_info_basic(self):
|
|
"""Test creating a basic episode info."""
|
|
ep = EpisodeInfo(episode_number=1, title="Pilot", duration_seconds=1500)
|
|
assert ep.episode_number == 1
|
|
assert ep.title == "Pilot"
|
|
assert ep.duration_seconds == 1500
|
|
assert ep.available is True
|
|
|
|
def test_episode_info_without_optional_fields(self):
|
|
"""Test episode info with only required fields."""
|
|
ep = EpisodeInfo(episode_number=5)
|
|
assert ep.episode_number == 5
|
|
assert ep.title is None
|
|
assert ep.duration_seconds is None
|
|
assert ep.available is True
|
|
|
|
def test_invalid_episode_number(self):
|
|
"""Test that episode number must be positive."""
|
|
with pytest.raises(ValidationError):
|
|
EpisodeInfo(episode_number=0)
|
|
|
|
|
|
class TestMissingEpisodeInfo:
|
|
"""Tests for MissingEpisodeInfo model."""
|
|
|
|
def test_missing_episode_count(self):
|
|
"""Test count property calculation."""
|
|
m = MissingEpisodeInfo(from_episode=5, to_episode=7)
|
|
assert m.count == 3
|
|
|
|
def test_single_missing_episode(self):
|
|
"""Test count for single missing episode."""
|
|
m = MissingEpisodeInfo(from_episode=5, to_episode=5)
|
|
assert m.count == 1
|
|
|
|
|
|
class TestAnimeSeriesResponse:
|
|
"""Tests for AnimeSeriesResponse model."""
|
|
|
|
def test_anime_series_response_with_key(self):
|
|
"""Test creating series response with key as identifier."""
|
|
ep = EpisodeInfo(episode_number=1, title="Ep1")
|
|
series = AnimeSeriesResponse(
|
|
key="attack-on-titan",
|
|
title="Attack on Titan",
|
|
folder="Attack on Titan (2013)",
|
|
episodes=[ep],
|
|
)
|
|
|
|
assert series.key == "attack-on-titan"
|
|
assert series.title == "Attack on Titan"
|
|
assert series.folder == "Attack on Titan (2013)"
|
|
assert series.episodes[0].title == "Ep1"
|
|
|
|
def test_key_normalization(self):
|
|
"""Test that key is normalized to lowercase."""
|
|
series = AnimeSeriesResponse(
|
|
key="ATTACK-ON-TITAN",
|
|
title="Attack on Titan"
|
|
)
|
|
assert series.key == "attack-on-titan"
|
|
|
|
def test_key_whitespace_stripped(self):
|
|
"""Test that key whitespace is stripped."""
|
|
series = AnimeSeriesResponse(
|
|
key=" attack-on-titan ",
|
|
title="Attack on Titan"
|
|
)
|
|
assert series.key == "attack-on-titan"
|
|
|
|
def test_folder_is_optional(self):
|
|
"""Test that folder is optional metadata."""
|
|
series = AnimeSeriesResponse(
|
|
key="my-anime",
|
|
title="My Anime"
|
|
)
|
|
assert series.folder is None
|
|
|
|
|
|
class TestSearchRequest:
|
|
"""Tests for SearchRequest model."""
|
|
|
|
def test_search_request_validation(self):
|
|
"""Test valid search request."""
|
|
req = SearchRequest(query="naruto", limit=5)
|
|
assert req.query == "naruto"
|
|
assert req.limit == 5
|
|
|
|
def test_search_request_empty_query_rejected(self):
|
|
"""Test that empty query is rejected."""
|
|
with pytest.raises(ValidationError):
|
|
SearchRequest(query="", limit=5)
|
|
|
|
def test_search_request_defaults(self):
|
|
"""Test default values."""
|
|
req = SearchRequest(query="test")
|
|
assert req.limit == 10
|
|
assert req.include_adult is False
|
|
|
|
|
|
class TestSearchResult:
|
|
"""Tests for SearchResult model."""
|
|
|
|
def test_search_result_with_key(self):
|
|
"""Test search result with key as identifier."""
|
|
res = SearchResult(
|
|
key="naruto",
|
|
title="Naruto",
|
|
folder="Naruto (2002)",
|
|
snippet="A ninja story",
|
|
score=0.9
|
|
)
|
|
assert res.key == "naruto"
|
|
assert res.title == "Naruto"
|
|
assert res.folder == "Naruto (2002)"
|
|
assert res.score == 0.9
|
|
|
|
def test_key_normalization(self):
|
|
"""Test that key is normalized to lowercase."""
|
|
res = SearchResult(key="NARUTO", title="Naruto")
|
|
assert res.key == "naruto"
|
|
|
|
def test_folder_is_optional(self):
|
|
"""Test that folder is optional metadata."""
|
|
res = SearchResult(key="test", title="Test")
|
|
assert res.folder is None
|
|
|
|
def test_optional_fields(self):
|
|
"""Test optional fields."""
|
|
res = SearchResult(key="s1", title="T1", snippet="snip", score=0.9)
|
|
assert res.score == 0.9
|
|
assert res.snippet == "snip"
|