524 lines
17 KiB
Python
524 lines
17 KiB
Python
"""Edge case tests for domain entities and value objects."""
|
|
import pytest
|
|
from datetime import datetime
|
|
|
|
from alfred.domain.movies.entities import Movie
|
|
from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
|
|
from alfred.domain.shared.exceptions import ValidationError
|
|
from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId
|
|
from alfred.domain.subtitles.entities import Subtitle
|
|
from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
|
|
from alfred.domain.tv_shows.entities import TVShow
|
|
from alfred.domain.tv_shows.value_objects import ShowStatus
|
|
|
|
|
|
class TestImdbIdEdgeCases:
|
|
"""Edge case tests for ImdbId."""
|
|
|
|
def test_valid_imdb_id(self):
|
|
"""Should accept valid IMDb ID."""
|
|
imdb_id = ImdbId("tt1375666")
|
|
assert str(imdb_id) == "tt1375666"
|
|
|
|
def test_imdb_id_with_leading_zeros(self):
|
|
"""Should accept IMDb ID with leading zeros."""
|
|
imdb_id = ImdbId("tt0000001")
|
|
assert str(imdb_id) == "tt0000001"
|
|
|
|
def test_imdb_id_long_number(self):
|
|
"""Should accept IMDb ID with 8 digits."""
|
|
imdb_id = ImdbId("tt12345678")
|
|
assert str(imdb_id) == "tt12345678"
|
|
|
|
def test_imdb_id_lowercase(self):
|
|
"""Should accept lowercase tt prefix."""
|
|
imdb_id = ImdbId("tt1234567")
|
|
assert str(imdb_id) == "tt1234567"
|
|
|
|
def test_imdb_id_uppercase(self):
|
|
"""Should handle uppercase TT prefix."""
|
|
# Behavior depends on implementation
|
|
try:
|
|
imdb_id = ImdbId("TT1234567")
|
|
# If accepted, should work
|
|
assert imdb_id is not None
|
|
except (ValidationError, ValueError):
|
|
# If rejected, that's also valid
|
|
pass
|
|
|
|
def test_imdb_id_without_prefix(self):
|
|
"""Should reject ID without tt prefix."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ImdbId("1234567")
|
|
|
|
def test_imdb_id_empty(self):
|
|
"""Should reject empty string."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ImdbId("")
|
|
|
|
def test_imdb_id_none(self):
|
|
"""Should reject None."""
|
|
with pytest.raises((ValidationError, ValueError, TypeError)):
|
|
ImdbId(None)
|
|
|
|
def test_imdb_id_with_spaces(self):
|
|
"""Should reject ID with spaces."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ImdbId("tt 1234567")
|
|
|
|
def test_imdb_id_with_special_chars(self):
|
|
"""Should reject ID with special characters."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ImdbId("tt1234567!")
|
|
|
|
def test_imdb_id_equality(self):
|
|
"""Should compare equal IDs."""
|
|
id1 = ImdbId("tt1234567")
|
|
id2 = ImdbId("tt1234567")
|
|
assert id1 == id2 or str(id1) == str(id2)
|
|
|
|
def test_imdb_id_hash(self):
|
|
"""Should be hashable for use in sets/dicts."""
|
|
id1 = ImdbId("tt1234567")
|
|
id2 = ImdbId("tt1234567")
|
|
|
|
# Should be usable in set
|
|
_s = {id1, id2} # Test hashability
|
|
# Depending on implementation, might be 1 or 2 items
|
|
|
|
|
|
class TestFilePathEdgeCases:
|
|
"""Edge case tests for FilePath."""
|
|
|
|
def test_absolute_path(self):
|
|
"""Should accept absolute path."""
|
|
path = FilePath("/home/user/movies/movie.mkv")
|
|
assert "/home/user/movies/movie.mkv" in str(path)
|
|
|
|
def test_relative_path(self):
|
|
"""Should accept relative path."""
|
|
path = FilePath("movies/movie.mkv")
|
|
assert "movies/movie.mkv" in str(path)
|
|
|
|
def test_path_with_spaces(self):
|
|
"""Should accept path with spaces."""
|
|
path = FilePath("/home/user/My Movies/movie file.mkv")
|
|
assert "My Movies" in str(path)
|
|
|
|
def test_path_with_unicode(self):
|
|
"""Should accept path with unicode."""
|
|
path = FilePath("/home/user/映画/日本語.mkv")
|
|
assert "映画" in str(path)
|
|
|
|
def test_windows_path(self):
|
|
"""Should handle Windows-style path."""
|
|
path = FilePath("C:\\Users\\user\\Movies\\movie.mkv")
|
|
assert "movie.mkv" in str(path)
|
|
|
|
def test_empty_path(self):
|
|
"""Should handle empty path."""
|
|
try:
|
|
path = FilePath("")
|
|
# If accepted, may return "." for current directory
|
|
assert str(path) in ["", "."]
|
|
except (ValidationError, ValueError):
|
|
# If rejected, that's also valid
|
|
pass
|
|
|
|
def test_path_with_dots(self):
|
|
"""Should handle path with . and .."""
|
|
path = FilePath("/home/user/../other/./movie.mkv")
|
|
assert "movie.mkv" in str(path)
|
|
|
|
|
|
class TestFileSizeEdgeCases:
|
|
"""Edge case tests for FileSize."""
|
|
|
|
def test_zero_size(self):
|
|
"""Should accept zero size."""
|
|
size = FileSize(0)
|
|
assert size.bytes == 0
|
|
|
|
def test_very_large_size(self):
|
|
"""Should accept very large size (petabytes)."""
|
|
size = FileSize(1024**5) # 1 PB
|
|
assert size.bytes == 1024**5
|
|
|
|
def test_negative_size(self):
|
|
"""Should reject negative size."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
FileSize(-1)
|
|
|
|
def test_human_readable_bytes(self):
|
|
"""Should format bytes correctly."""
|
|
size = FileSize(500)
|
|
readable = size.to_human_readable()
|
|
assert "500" in readable or "B" in readable
|
|
|
|
def test_human_readable_kb(self):
|
|
"""Should format KB correctly."""
|
|
size = FileSize(1024)
|
|
readable = size.to_human_readable()
|
|
assert "KB" in readable or "1" in readable
|
|
|
|
def test_human_readable_mb(self):
|
|
"""Should format MB correctly."""
|
|
size = FileSize(1024 * 1024)
|
|
readable = size.to_human_readable()
|
|
assert "MB" in readable or "1" in readable
|
|
|
|
def test_human_readable_gb(self):
|
|
"""Should format GB correctly."""
|
|
size = FileSize(1024 * 1024 * 1024)
|
|
readable = size.to_human_readable()
|
|
assert "GB" in readable or "1" in readable
|
|
|
|
|
|
class TestMovieTitleEdgeCases:
|
|
"""Edge case tests for MovieTitle."""
|
|
|
|
def test_normal_title(self):
|
|
"""Should accept normal title."""
|
|
title = MovieTitle("Inception")
|
|
assert title.value == "Inception"
|
|
|
|
def test_title_with_year(self):
|
|
"""Should accept title with year."""
|
|
title = MovieTitle("Blade Runner 2049")
|
|
assert "2049" in title.value
|
|
|
|
def test_title_with_special_chars(self):
|
|
"""Should accept title with special characters."""
|
|
title = MovieTitle("Se7en")
|
|
assert title.value == "Se7en"
|
|
|
|
def test_title_with_colon(self):
|
|
"""Should accept title with colon."""
|
|
title = MovieTitle("Star Wars: A New Hope")
|
|
assert ":" in title.value
|
|
|
|
def test_title_with_unicode(self):
|
|
"""Should accept unicode title."""
|
|
title = MovieTitle("千と千尋の神隠し")
|
|
assert title.value == "千と千尋の神隠し"
|
|
|
|
def test_empty_title(self):
|
|
"""Should reject empty title."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
MovieTitle("")
|
|
|
|
def test_whitespace_title(self):
|
|
"""Should handle whitespace title (may strip or reject)."""
|
|
try:
|
|
title = MovieTitle(" ")
|
|
# If accepted after stripping, that's valid
|
|
assert title.value is not None
|
|
except (ValidationError, ValueError):
|
|
# If rejected, that's also valid
|
|
pass
|
|
|
|
def test_very_long_title(self):
|
|
"""Should handle very long title."""
|
|
long_title = "A" * 1000
|
|
try:
|
|
title = MovieTitle(long_title)
|
|
assert len(title.value) == 1000
|
|
except (ValidationError, ValueError):
|
|
# If there's a length limit, that's valid
|
|
pass
|
|
|
|
|
|
class TestReleaseYearEdgeCases:
|
|
"""Edge case tests for ReleaseYear."""
|
|
|
|
def test_valid_year(self):
|
|
"""Should accept valid year."""
|
|
year = ReleaseYear(2024)
|
|
assert year.value == 2024
|
|
|
|
def test_old_movie_year(self):
|
|
"""Should accept old movie year."""
|
|
year = ReleaseYear(1895) # First movie ever
|
|
assert year.value == 1895
|
|
|
|
def test_future_year(self):
|
|
"""Should accept near future year."""
|
|
year = ReleaseYear(2030)
|
|
assert year.value == 2030
|
|
|
|
def test_very_old_year(self):
|
|
"""Should reject very old year."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ReleaseYear(1800)
|
|
|
|
def test_very_future_year(self):
|
|
"""Should reject very future year."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ReleaseYear(3000)
|
|
|
|
def test_negative_year(self):
|
|
"""Should reject negative year."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ReleaseYear(-2024)
|
|
|
|
def test_zero_year(self):
|
|
"""Should reject zero year."""
|
|
with pytest.raises((ValidationError, ValueError)):
|
|
ReleaseYear(0)
|
|
|
|
|
|
class TestQualityEdgeCases:
|
|
"""Edge case tests for Quality."""
|
|
|
|
def test_standard_qualities(self):
|
|
"""Should accept standard qualities."""
|
|
qualities = [
|
|
(Quality.SD, "480p"),
|
|
(Quality.HD, "720p"),
|
|
(Quality.FULL_HD, "1080p"),
|
|
(Quality.UHD_4K, "2160p"),
|
|
]
|
|
for quality_enum, expected_value in qualities:
|
|
assert quality_enum.value == expected_value
|
|
|
|
def test_unknown_quality(self):
|
|
"""Should accept unknown quality."""
|
|
quality = Quality.UNKNOWN
|
|
assert quality.value == "unknown"
|
|
|
|
def test_from_string_quality(self):
|
|
"""Should parse quality from string."""
|
|
assert Quality.from_string("1080p") == Quality.FULL_HD
|
|
assert Quality.from_string("720p") == Quality.HD
|
|
assert Quality.from_string("2160p") == Quality.UHD_4K
|
|
assert Quality.from_string("HDTV") == Quality.UNKNOWN
|
|
|
|
def test_empty_quality(self):
|
|
"""Should handle empty quality string."""
|
|
quality = Quality.from_string("")
|
|
assert quality == Quality.UNKNOWN
|
|
|
|
|
|
class TestShowStatusEdgeCases:
|
|
"""Edge case tests for ShowStatus."""
|
|
|
|
def test_all_statuses(self):
|
|
"""Should have all expected statuses."""
|
|
assert ShowStatus.ONGOING is not None
|
|
assert ShowStatus.ENDED is not None
|
|
assert ShowStatus.UNKNOWN is not None
|
|
|
|
def test_from_string_valid(self):
|
|
"""Should parse valid status strings."""
|
|
assert ShowStatus.from_string("ongoing") == ShowStatus.ONGOING
|
|
assert ShowStatus.from_string("ended") == ShowStatus.ENDED
|
|
|
|
def test_from_string_case_insensitive(self):
|
|
"""Should be case insensitive."""
|
|
assert ShowStatus.from_string("ONGOING") == ShowStatus.ONGOING
|
|
assert ShowStatus.from_string("Ended") == ShowStatus.ENDED
|
|
|
|
def test_from_string_unknown(self):
|
|
"""Should return UNKNOWN for invalid strings."""
|
|
assert ShowStatus.from_string("invalid") == ShowStatus.UNKNOWN
|
|
assert ShowStatus.from_string("") == ShowStatus.UNKNOWN
|
|
|
|
|
|
class TestLanguageEdgeCases:
|
|
"""Edge case tests for Language."""
|
|
|
|
def test_common_languages(self):
|
|
"""Should have common languages."""
|
|
assert Language.ENGLISH is not None
|
|
assert Language.FRENCH is not None
|
|
|
|
def test_from_code_valid(self):
|
|
"""Should parse valid language codes."""
|
|
assert Language.from_code("en") == Language.ENGLISH
|
|
assert Language.from_code("fr") == Language.FRENCH
|
|
|
|
def test_from_code_case_insensitive(self):
|
|
"""Should be case insensitive."""
|
|
assert Language.from_code("EN") == Language.ENGLISH
|
|
assert Language.from_code("Fr") == Language.FRENCH
|
|
|
|
def test_from_code_unknown(self):
|
|
"""Should handle unknown codes."""
|
|
# Behavior depends on implementation
|
|
try:
|
|
lang = Language.from_code("xx")
|
|
# If it returns something, that's valid
|
|
assert lang is not None
|
|
except (ValidationError, ValueError, KeyError):
|
|
# If it raises, that's also valid
|
|
pass
|
|
|
|
|
|
class TestSubtitleFormatEdgeCases:
|
|
"""Edge case tests for SubtitleFormat."""
|
|
|
|
def test_common_formats(self):
|
|
"""Should have common formats."""
|
|
assert SubtitleFormat.SRT is not None
|
|
assert SubtitleFormat.ASS is not None
|
|
|
|
def test_from_extension_with_dot(self):
|
|
"""Should handle extension with dot."""
|
|
fmt = SubtitleFormat.from_extension(".srt")
|
|
assert fmt == SubtitleFormat.SRT
|
|
|
|
def test_from_extension_without_dot(self):
|
|
"""Should handle extension without dot."""
|
|
fmt = SubtitleFormat.from_extension("srt")
|
|
assert fmt == SubtitleFormat.SRT
|
|
|
|
def test_from_extension_case_insensitive(self):
|
|
"""Should be case insensitive."""
|
|
assert SubtitleFormat.from_extension("SRT") == SubtitleFormat.SRT
|
|
assert SubtitleFormat.from_extension(".ASS") == SubtitleFormat.ASS
|
|
|
|
|
|
class TestTimingOffsetEdgeCases:
|
|
"""Edge case tests for TimingOffset."""
|
|
|
|
def test_zero_offset(self):
|
|
"""Should accept zero offset."""
|
|
offset = TimingOffset(0)
|
|
assert offset.milliseconds == 0
|
|
|
|
def test_positive_offset(self):
|
|
"""Should accept positive offset."""
|
|
offset = TimingOffset(5000)
|
|
assert offset.milliseconds == 5000
|
|
|
|
def test_negative_offset(self):
|
|
"""Should accept negative offset."""
|
|
offset = TimingOffset(-5000)
|
|
assert offset.milliseconds == -5000
|
|
|
|
def test_very_large_offset(self):
|
|
"""Should accept very large offset."""
|
|
offset = TimingOffset(3600000) # 1 hour
|
|
assert offset.milliseconds == 3600000
|
|
|
|
|
|
class TestMovieEntityEdgeCases:
|
|
"""Edge case tests for Movie entity."""
|
|
|
|
def test_minimal_movie(self):
|
|
"""Should create movie with minimal fields."""
|
|
movie = Movie(
|
|
imdb_id=ImdbId("tt1234567"),
|
|
title=MovieTitle("Test"),
|
|
quality=Quality.UNKNOWN,
|
|
)
|
|
assert movie.imdb_id is not None
|
|
|
|
def test_full_movie(self):
|
|
"""Should create movie with all fields."""
|
|
movie = Movie(
|
|
imdb_id=ImdbId("tt1234567"),
|
|
title=MovieTitle("Test Movie"),
|
|
release_year=ReleaseYear(2024),
|
|
quality=Quality.FULL_HD,
|
|
file_path=FilePath("/movies/test.mkv"),
|
|
file_size=FileSize(1000000000),
|
|
tmdb_id=12345,
|
|
added_at=datetime.now(),
|
|
)
|
|
assert movie.tmdb_id == 12345
|
|
|
|
def test_movie_without_optional_fields(self):
|
|
"""Should handle None optional fields."""
|
|
movie = Movie(
|
|
imdb_id=ImdbId("tt1234567"),
|
|
title=MovieTitle("Test"),
|
|
release_year=None,
|
|
quality=Quality.UNKNOWN,
|
|
file_path=None,
|
|
file_size=None,
|
|
tmdb_id=None,
|
|
)
|
|
assert movie.release_year is None
|
|
assert movie.file_path is None
|
|
|
|
|
|
class TestTVShowEntityEdgeCases:
|
|
"""Edge case tests for TVShow entity."""
|
|
|
|
def test_minimal_show(self):
|
|
"""Should create show with minimal fields."""
|
|
show = TVShow(
|
|
imdb_id=ImdbId("tt1234567"),
|
|
title="Test Show",
|
|
seasons_count=1,
|
|
status=ShowStatus.UNKNOWN,
|
|
)
|
|
assert show.title == "Test Show"
|
|
|
|
def test_show_with_zero_seasons(self):
|
|
"""Should handle show with zero seasons."""
|
|
show = TVShow(
|
|
imdb_id=ImdbId("tt1234567"),
|
|
title="Upcoming Show",
|
|
seasons_count=0,
|
|
status=ShowStatus.ONGOING,
|
|
)
|
|
assert show.seasons_count == 0
|
|
|
|
def test_show_with_many_seasons(self):
|
|
"""Should handle show with many seasons."""
|
|
show = TVShow(
|
|
imdb_id=ImdbId("tt1234567"),
|
|
title="Long Running Show",
|
|
seasons_count=50,
|
|
status=ShowStatus.ONGOING,
|
|
)
|
|
assert show.seasons_count == 50
|
|
|
|
|
|
class TestSubtitleEntityEdgeCases:
|
|
"""Edge case tests for Subtitle entity."""
|
|
|
|
def test_minimal_subtitle(self):
|
|
"""Should create subtitle with minimal fields."""
|
|
subtitle = Subtitle(
|
|
media_imdb_id=ImdbId("tt1234567"),
|
|
language=Language.ENGLISH,
|
|
format=SubtitleFormat.SRT,
|
|
file_path=FilePath("/subs/test.srt"),
|
|
)
|
|
assert subtitle.language == Language.ENGLISH
|
|
|
|
def test_subtitle_for_episode(self):
|
|
"""Should create subtitle for specific episode."""
|
|
subtitle = Subtitle(
|
|
media_imdb_id=ImdbId("tt1234567"),
|
|
language=Language.ENGLISH,
|
|
format=SubtitleFormat.SRT,
|
|
file_path=FilePath("/subs/s01e01.srt"),
|
|
season_number=1,
|
|
episode_number=1,
|
|
)
|
|
assert subtitle.season_number == 1
|
|
assert subtitle.episode_number == 1
|
|
|
|
def test_subtitle_with_all_metadata(self):
|
|
"""Should create subtitle with all metadata."""
|
|
subtitle = Subtitle(
|
|
media_imdb_id=ImdbId("tt1234567"),
|
|
language=Language.ENGLISH,
|
|
format=SubtitleFormat.SRT,
|
|
file_path=FilePath("/subs/test.srt"),
|
|
timing_offset=TimingOffset(500),
|
|
hearing_impaired=True,
|
|
forced=True,
|
|
source="OpenSubtitles",
|
|
uploader="user123",
|
|
download_count=10000,
|
|
rating=9.5,
|
|
)
|
|
assert subtitle.hearing_impaired is True
|
|
assert subtitle.forced is True
|
|
assert subtitle.rating == 9.5
|