"""Unit tests for NFO models.""" import pytest from pydantic import ValidationError from src.core.entities.nfo_models import ( ActorInfo, ImageInfo, NamedSeason, RatingInfo, TVShowNFO, UniqueID, ) class TestRatingInfo: """Test RatingInfo model.""" def test_rating_info_with_all_fields(self): """Test creating RatingInfo with all fields.""" rating = RatingInfo( name="themoviedb", value=8.5, votes=1234, max_rating=10, default=True ) assert rating.name == "themoviedb" assert rating.value == 8.5 assert rating.votes == 1234 assert rating.max_rating == 10 assert rating.default is True def test_rating_info_with_minimal_fields(self): """Test creating RatingInfo with only required fields.""" rating = RatingInfo(name="imdb", value=7.2) assert rating.name == "imdb" assert rating.value == 7.2 assert rating.votes is None assert rating.max_rating == 10 # default assert rating.default is False # default def test_rating_info_negative_value_rejected(self): """Test that negative rating values are rejected.""" with pytest.raises(ValidationError): RatingInfo(name="test", value=-1.0) def test_rating_info_excessive_value_rejected(self): """Test that rating values > 10 are rejected.""" with pytest.raises(ValidationError): RatingInfo(name="test", value=11.0) def test_rating_info_negative_votes_rejected(self): """Test that negative vote counts are rejected.""" with pytest.raises(ValidationError): RatingInfo(name="test", value=5.0, votes=-10) def test_rating_info_zero_values_accepted(self): """Test that zero values are accepted.""" rating = RatingInfo(name="test", value=0.0, votes=0) assert rating.value == 0.0 assert rating.votes == 0 class TestActorInfo: """Test ActorInfo model.""" def test_actor_info_with_all_fields(self): """Test creating ActorInfo with all fields.""" actor = ActorInfo( name="John Doe", role="Main Character", thumb="https://example.com/actor.jpg", profile="https://example.com/profile", tmdbid=12345 ) assert actor.name == "John Doe" assert actor.role == "Main Character" assert str(actor.thumb) == "https://example.com/actor.jpg" assert str(actor.profile) == "https://example.com/profile" assert actor.tmdbid == 12345 def test_actor_info_with_minimal_fields(self): """Test creating ActorInfo with only name.""" actor = ActorInfo(name="Jane Smith") assert actor.name == "Jane Smith" assert actor.role is None assert actor.thumb is None assert actor.profile is None assert actor.tmdbid is None def test_actor_info_invalid_url_rejected(self): """Test that invalid URLs are rejected.""" with pytest.raises(ValidationError): ActorInfo(name="Test", thumb="not-a-url") def test_actor_info_http_url_accepted(self): """Test that HTTP URLs are accepted.""" actor = ActorInfo( name="Test", thumb="http://example.com/image.jpg" ) assert str(actor.thumb) == "http://example.com/image.jpg" class TestImageInfo: """Test ImageInfo model.""" def test_image_info_with_all_fields(self): """Test creating ImageInfo with all fields.""" image = ImageInfo( url="https://image.tmdb.org/t/p/w500/poster.jpg", aspect="poster", season=1, type="season" ) assert str(image.url) == "https://image.tmdb.org/t/p/w500/poster.jpg" assert image.aspect == "poster" assert image.season == 1 assert image.type == "season" def test_image_info_with_minimal_fields(self): """Test creating ImageInfo with only URL.""" image = ImageInfo(url="https://example.com/image.jpg") assert str(image.url) == "https://example.com/image.jpg" assert image.aspect is None assert image.season is None assert image.type is None def test_image_info_invalid_url_rejected(self): """Test that invalid URLs are rejected.""" with pytest.raises(ValidationError): ImageInfo(url="invalid-url") def test_image_info_negative_season_rejected(self): """Test that season < -1 is rejected.""" with pytest.raises(ValidationError): ImageInfo( url="https://example.com/image.jpg", season=-2 ) def test_image_info_season_minus_one_accepted(self): """Test that season -1 is accepted (all seasons).""" image = ImageInfo( url="https://example.com/image.jpg", season=-1 ) assert image.season == -1 class TestNamedSeason: """Test NamedSeason model.""" def test_named_season_creation(self): """Test creating NamedSeason.""" season = NamedSeason(number=1, name="Season One") assert season.number == 1 assert season.name == "Season One" def test_named_season_negative_number_rejected(self): """Test that negative season numbers are rejected.""" with pytest.raises(ValidationError): NamedSeason(number=-1, name="Invalid") def test_named_season_zero_accepted(self): """Test that season 0 (specials) is accepted.""" season = NamedSeason(number=0, name="Specials") assert season.number == 0 class TestUniqueID: """Test UniqueID model.""" def test_unique_id_creation(self): """Test creating UniqueID.""" uid = UniqueID(type="tmdb", value="12345", default=True) assert uid.type == "tmdb" assert uid.value == "12345" assert uid.default is True def test_unique_id_default_false(self): """Test UniqueID with default=False.""" uid = UniqueID(type="imdb", value="tt1234567") assert uid.default is False class TestTVShowNFO: """Test TVShowNFO model.""" def test_tvshow_nfo_minimal_creation(self): """Test creating TVShowNFO with only required fields.""" nfo = TVShowNFO(title="Test Show") assert nfo.title == "Test Show" assert nfo.showtitle == "Test Show" # auto-set assert nfo.originaltitle == "Test Show" # auto-set assert nfo.year is None assert nfo.studio == [] assert nfo.genre == [] assert nfo.watched is False def test_tvshow_nfo_with_all_basic_fields(self): """Test creating TVShowNFO with all basic fields.""" nfo = TVShowNFO( title="Attack on Titan", originaltitle="Shingeki no Kyojin", showtitle="Attack on Titan", sorttitle="Attack on Titan", year=2013, plot="Humanity lives in fear of Titans.", outline="Titans attack humanity.", tagline="The world is cruel.", runtime=24, mpaa="TV-14", certification="14+", premiered="2013-04-07", status="Ended" ) assert nfo.title == "Attack on Titan" assert nfo.originaltitle == "Shingeki no Kyojin" assert nfo.year == 2013 assert nfo.plot == "Humanity lives in fear of Titans." assert nfo.runtime == 24 assert nfo.premiered == "2013-04-07" def test_tvshow_nfo_empty_title_rejected(self): """Test that empty title is rejected.""" with pytest.raises(ValidationError): TVShowNFO(title="") def test_tvshow_nfo_invalid_year_rejected(self): """Test that invalid years are rejected.""" with pytest.raises(ValidationError): TVShowNFO(title="Test", year=1800) with pytest.raises(ValidationError): TVShowNFO(title="Test", year=2200) def test_tvshow_nfo_negative_runtime_rejected(self): """Test that negative runtime is rejected.""" with pytest.raises(ValidationError): TVShowNFO(title="Test", runtime=-10) def test_tvshow_nfo_with_multi_value_fields(self): """Test TVShowNFO with lists.""" nfo = TVShowNFO( title="Test Show", studio=["Studio A", "Studio B"], genre=["Action", "Drama"], country=["Japan", "USA"], tag=["anime", "popular"] ) assert len(nfo.studio) == 2 assert "Studio A" in nfo.studio assert len(nfo.genre) == 2 assert len(nfo.country) == 2 assert len(nfo.tag) == 2 def test_tvshow_nfo_with_ratings(self): """Test TVShowNFO with ratings.""" nfo = TVShowNFO( title="Test Show", ratings=[ RatingInfo(name="tmdb", value=8.5, votes=1000, default=True), RatingInfo(name="imdb", value=8.2, votes=5000) ], userrating=9.0 ) assert len(nfo.ratings) == 2 assert nfo.ratings[0].name == "tmdb" assert nfo.ratings[0].default is True assert nfo.userrating == 9.0 def test_tvshow_nfo_invalid_userrating_rejected(self): """Test that userrating outside 0-10 is rejected.""" with pytest.raises(ValidationError): TVShowNFO(title="Test", userrating=-1) with pytest.raises(ValidationError): TVShowNFO(title="Test", userrating=11) def test_tvshow_nfo_with_ids(self): """Test TVShowNFO with various IDs.""" nfo = TVShowNFO( title="Test Show", tmdbid=12345, imdbid="tt1234567", tvdbid=67890, uniqueid=[ UniqueID(type="tmdb", value="12345"), UniqueID(type="imdb", value="tt1234567", default=True) ] ) assert nfo.tmdbid == 12345 assert nfo.imdbid == "tt1234567" assert nfo.tvdbid == 67890 assert len(nfo.uniqueid) == 2 def test_tvshow_nfo_invalid_imdbid_rejected(self): """Test that invalid IMDB IDs are rejected.""" with pytest.raises(ValidationError) as exc_info: TVShowNFO(title="Test", imdbid="12345") assert "must start with 'tt'" in str(exc_info.value) with pytest.raises(ValidationError) as exc_info: TVShowNFO(title="Test", imdbid="ttabc123") assert "followed by digits" in str(exc_info.value) def test_tvshow_nfo_valid_imdbid_accepted(self): """Test that valid IMDB IDs are accepted.""" nfo = TVShowNFO(title="Test", imdbid="tt1234567") assert nfo.imdbid == "tt1234567" def test_tvshow_nfo_premiered_date_validation(self): """Test premiered date format validation.""" # Valid format nfo = TVShowNFO(title="Test", premiered="2013-04-07") assert nfo.premiered == "2013-04-07" # Invalid formats with pytest.raises(ValidationError) as exc_info: TVShowNFO(title="Test", premiered="2013-4-7") assert "YYYY-MM-DD" in str(exc_info.value) with pytest.raises(ValidationError): TVShowNFO(title="Test", premiered="04/07/2013") with pytest.raises(ValidationError): TVShowNFO(title="Test", premiered="2013-13-01") # Invalid month def test_tvshow_nfo_dateadded_validation(self): """Test dateadded format validation.""" # Valid format nfo = TVShowNFO(title="Test", dateadded="2024-12-15 10:29:11") assert nfo.dateadded == "2024-12-15 10:29:11" # Invalid formats with pytest.raises(ValidationError) as exc_info: TVShowNFO(title="Test", dateadded="2024-12-15") assert "YYYY-MM-DD HH:MM:SS" in str(exc_info.value) with pytest.raises(ValidationError): TVShowNFO(title="Test", dateadded="2024-12-15 25:00:00") def test_tvshow_nfo_with_images(self): """Test TVShowNFO with image information.""" nfo = TVShowNFO( title="Test Show", thumb=[ ImageInfo( url="https://image.tmdb.org/t/p/w500/poster.jpg", aspect="poster" ), ImageInfo( url="https://image.tmdb.org/t/p/original/logo.png", aspect="clearlogo" ) ], fanart=[ ImageInfo( url="https://image.tmdb.org/t/p/original/fanart.jpg" ) ] ) assert len(nfo.thumb) == 2 assert nfo.thumb[0].aspect == "poster" assert len(nfo.fanart) == 1 def test_tvshow_nfo_with_actors(self): """Test TVShowNFO with cast information.""" nfo = TVShowNFO( title="Test Show", actors=[ ActorInfo( name="Actor One", role="Main Character", thumb="https://example.com/actor1.jpg", tmdbid=111 ), ActorInfo( name="Actor Two", role="Supporting Role" ) ] ) assert len(nfo.actors) == 2 assert nfo.actors[0].name == "Actor One" assert nfo.actors[0].role == "Main Character" assert nfo.actors[1].tmdbid is None def test_tvshow_nfo_with_named_seasons(self): """Test TVShowNFO with named seasons.""" nfo = TVShowNFO( title="Test Show", namedseason=[ NamedSeason(number=1, name="First Season"), NamedSeason(number=2, name="Second Season") ] ) assert len(nfo.namedseason) == 2 assert nfo.namedseason[0].number == 1 def test_tvshow_nfo_with_trailer(self): """Test TVShowNFO with trailer URL.""" nfo = TVShowNFO( title="Test Show", trailer="https://www.youtube.com/watch?v=abc123" ) assert nfo.trailer is not None assert "youtube.com" in str(nfo.trailer) def test_tvshow_nfo_watched_and_playcount(self): """Test TVShowNFO with viewing information.""" nfo = TVShowNFO( title="Test Show", watched=True, playcount=5 ) assert nfo.watched is True assert nfo.playcount == 5 def test_tvshow_nfo_negative_playcount_rejected(self): """Test that negative playcount is rejected.""" with pytest.raises(ValidationError): TVShowNFO(title="Test", playcount=-1) def test_tvshow_nfo_serialization(self): """Test TVShowNFO can be serialized to dict.""" nfo = TVShowNFO( title="Test Show", year=2020, genre=["Action", "Drama"], tmdbid=12345 ) data = nfo.model_dump() assert data["title"] == "Test Show" assert data["year"] == 2020 assert data["genre"] == ["Action", "Drama"] assert data["tmdbid"] == 12345 assert "showtitle" in data assert "originaltitle" in data def test_tvshow_nfo_deserialization(self): """Test TVShowNFO can be deserialized from dict.""" data = { "title": "Test Show", "year": 2020, "genre": ["Action"], "tmdbid": 12345, "premiered": "2020-01-01" } nfo = TVShowNFO(**data) assert nfo.title == "Test Show" assert nfo.year == 2020 assert nfo.genre == ["Action"] assert nfo.tmdbid == 12345 def test_tvshow_nfo_special_characters_in_title(self): """Test TVShowNFO handles special characters.""" nfo = TVShowNFO( title="Test: Show & Movie's \"Best\" ", plot="Special chars: < > & \" '" ) assert nfo.title == "Test: Show & Movie's \"Best\" " assert nfo.plot == "Special chars: < > & \" '" def test_tvshow_nfo_unicode_characters(self): """Test TVShowNFO handles Unicode characters.""" nfo = TVShowNFO( title="進撃の巨人", originaltitle="Shingeki no Kyojin", plot="日本のアニメシリーズ" ) assert nfo.title == "進撃の巨人" assert nfo.plot == "日本のアニメシリーズ" def test_tvshow_nfo_none_values(self): """Test TVShowNFO handles None values correctly.""" nfo = TVShowNFO( title="Test Show", plot=None, year=None, tmdbid=None ) assert nfo.title == "Test Show" assert nfo.plot is None assert nfo.year is None assert nfo.tmdbid is None def test_tvshow_nfo_empty_lists(self): """Test TVShowNFO with empty lists.""" nfo = TVShowNFO( title="Test Show", genre=[], actors=[], ratings=[] ) assert nfo.genre == [] assert nfo.actors == [] assert nfo.ratings == [] @pytest.mark.parametrize("year", [1900, 2000, 2025, 2100]) def test_tvshow_nfo_valid_years(self, year): """Test TVShowNFO accepts valid years.""" nfo = TVShowNFO(title="Test Show", year=year) assert nfo.year == year @pytest.mark.parametrize("year", [1899, 2101, -1]) def test_tvshow_nfo_invalid_years(self, year): """Test TVShowNFO rejects invalid years.""" with pytest.raises(ValidationError): TVShowNFO(title="Test Show", year=year) @pytest.mark.parametrize("imdbid", [ "tt0123456", "tt1234567", "tt12345678", "tt123456789" ]) def test_tvshow_nfo_valid_imdbids(self, imdbid): """Test TVShowNFO accepts valid IMDB IDs.""" nfo = TVShowNFO(title="Test Show", imdbid=imdbid) assert nfo.imdbid == imdbid @pytest.mark.parametrize("imdbid", [ "123456", "tt", "ttabc123", "TT123456", "tt-123456" ]) def test_tvshow_nfo_invalid_imdbids(self, imdbid): """Test TVShowNFO rejects invalid IMDB IDs.""" with pytest.raises(ValidationError): TVShowNFO(title="Test Show", imdbid=imdbid)