diff --git a/config/watchlist.json b/config/watchlist.json index c79695f..bcaa62d 100644 --- a/config/watchlist.json +++ b/config/watchlist.json @@ -6,7 +6,7 @@ "anime_url": "https://anime-sama.si/catalogue/test/vostfr/", "provider_id": "animesama", "lang": "vostfr", - "last_checked": null, + "last_checked": "2026-02-24T20:36:13.793406", "last_episode_downloaded": 0, "total_episodes": null, "auto_download": true, @@ -17,6 +17,26 @@ "synopsis": null, "genres": [], "added_at": "2026-01-29T21:53:38.078765", - "updated_at": "2026-01-29T21:53:38.078765" + "updated_at": "2026-02-24T20:36:13.793425" + }, + "39000af5-81a9-4850-9047-ec9679887150": { + "id": "39000af5-81a9-4850-9047-ec9679887150", + "user_id": "4eaae75f1df2f52bda44f6b18a400542", + "anime_title": "Https%3A%2F%2Fanime Sama.Tv%2Fcatalogue%2Ffrieren%2Fsaison1%2Fvostfr%2F", + "anime_url": "https%3A%2F%2Fanime-sama.tv%2Fcatalogue%2Ffrieren%2Fsaison1%2Fvostfr%2F", + "provider_id": "anime-sama", + "lang": "vostfr", + "last_checked": "2026-02-24T20:36:13.817243", + "last_episode_downloaded": 0, + "total_episodes": null, + "auto_download": true, + "quality_preference": "auto", + "status": "active", + "poster_image": null, + "cover_image": null, + "synopsis": null, + "genres": [], + "added_at": "2026-02-24T12:41:19.221430", + "updated_at": "2026-02-24T20:36:13.817256" } } \ No newline at end of file diff --git a/tests/test_provider_detection.py b/tests/test_provider_detection.py deleted file mode 100644 index 2443793..0000000 --- a/tests/test_provider_detection.py +++ /dev/null @@ -1,479 +0,0 @@ -""" -Unit tests for provider detection and routing -Tests URL-to-provider matching and downloader factory -""" -import pytest -from app.providers import ( - detect_provider_from_url, - ANIME_PROVIDERS, - FILE_HOSTS -) -from app.downloaders import get_downloader, get_anime_site, get_series_site, get_video_player - - -class TestDetectProviderFromURL: - """Tests for detect_provider_from_url function""" - - def test_detect_anime_sama(self): - """Test detection of Anime-Sama provider""" - urls = [ - "https://anime-sama.si/catalogue/naruto/s1/vostfr/", - "https://www.anime-sama.fi/anime/test", - "https://anime-sama.pw/test", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "anime-sama" - - def test_detect_neko_sama(self): - """Test detection of Neko-Sama provider""" - urls = [ - "https://neko-sama.fr/anime/naruto", - "https://www.neko-sama.fr/anime/one-piece", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "neko-sama" - - def test_detect_anime_ultime(self): - """Test detection of Anime-Ultime provider""" - urls = [ - "https://anime-ultime.net/fiche-anime/naruto", - "https://www.anime-ultime.net/anime/test", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "anime-ultime" - - def test_detect_vostfree(self): - """Test detection of Vostfree provider""" - urls = [ - "https://vostfree.cc/anime/naruto", - "https://www.vostfree.cc/anime/test", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "vostfree" - - def test_detect_french_manga(self): - """Test detection of French-Manga provider""" - urls = [ - "https://french-manga.net/anime/naruto", - "https://www.french-manga.net/anime/test", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "french-manga" - - def test_detect_fs7(self): - """Test detection of FS7 (French Stream) provider""" - urls = [ - "https://fs7.space/series/test", - "https://www.fs7.space/series/breaking-bad", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "fs7" - - def test_detect_file_hosts(self): - """Test detection of file hosting services""" - test_cases = [ - ("https://doodstream.com/test/abc", "doodstream"), - ("https://ds2play.com/test/abc", "doodstream"), - ("https://rapidfile.com/test/abc", "rapidfile"), - ("https://uptobox.com/test/abc", "uptobox"), - ("https://1fichier.com/test", "unfichier"), - ("https://vidmoly.to/test", "vidmoly"), - ("https://sendvid.com/test", "sendvid"), - ("https://sibnet.ru/test", "sibnet"), - ("https://lpayer.com/test", "lpayer"), - ("https://vidzy.com/test", "vidzy"), - ("https://luluv.com/test", "luluv"), - ("https://uqload.com/test", "uqload"), - ] - for url, expected_name in test_cases: - provider = detect_provider_from_url(url) - assert provider is not None, f"Failed to detect {expected_name} from {url}" - assert provider["name"] == expected_name - - def test_detect_unknown_provider(self): - """Test that unknown URLs return None""" - unknown_urls = [ - "https://unknown-site.com/test", - "https://google.com/search", - "https://example.com/anime", - ] - for url in unknown_urls: - provider = detect_provider_from_url(url) - assert provider is None - - def test_detect_empty_url(self): - """Test detection with empty URL""" - assert detect_provider_from_url("") is None - assert detect_provider_from_url(None) is None - - def test_detect_case_insensitive(self): - """Test that detection is case-insensitive for domains""" - url = "https://Anime-Sama.si/test" - provider = detect_provider_from_url(url) - assert provider is not None - assert provider["name"] == "anime-sama" - - def test_detect_with_path_and_query(self): - """Test detection with complex paths and query strings""" - urls = [ - "https://anime-sama.si/catalogue/naruto/s1/vostfr/?page=1", - "https://neko-sama.fr/anime/one-piece?ep=1", - "https://doodstream.com/e/abc123#start=0", - ] - for url in urls: - provider = detect_provider_from_url(url) - assert provider is not None - - def test_provider_structure(self): - """Test that detected provider has correct structure""" - provider = detect_provider_from_url("https://anime-sama.si/test") - assert "name" in provider - assert "icon" in provider - assert "color" in provider - assert "domains" in provider - assert isinstance(provider["domains"], list) - - -class TestAnimeProvidersConfig: - """Tests for ANIME_PROVIDERS configuration""" - - def test_anime_providers_structure(self): - """Test that all anime providers have required fields""" - for provider_name, provider_data in ANIME_PROVIDERS.items(): - assert "name" in provider_data - assert "domains" in provider_data - assert "icon" in provider_data - assert "color" in provider_data - assert "url_pattern" in provider_data - assert isinstance(provider_data["domains"], list) - - def test_known_anime_providers_exist(self): - """Test that known anime providers are configured""" - known_providers = [ - "anime-sama", - "neko-sama", - "anime-ultime", - "vostfree", - "french-manga" - ] - for provider in known_providers: - assert provider in ANIME_PROVIDERS - - def test_anime_provider_domains(self): - """Test that anime providers have valid domains""" - for provider_data in ANIME_PROVIDERS.values(): - assert len(provider_data["domains"]) > 0 - for domain in provider_data["domains"]: - assert isinstance(domain, str) - assert "." in domain # Basic domain validation - - def test_anime_provider_url_patterns(self): - """Test that URL patterns are valid""" - for provider_data in ANIME_PROVIDERS.values(): - pattern = provider_data["url_pattern"] - assert isinstance(pattern, str) - assert len(pattern) > 0 - - -class TestFileHostsConfig: - """Tests for FILE_HOSTS configuration""" - - def test_file_hosts_structure(self): - """Test that all file hosts have required fields""" - for host_name, host_data in FILE_HOSTS.items(): - assert "name" in host_data - assert "domains" in host_data - assert "icon" in host_data - assert "color" in host_data - assert isinstance(host_data["domains"], list) - - def test_known_file_hosts_exist(self): - """Test that known file hosts are configured""" - known_hosts = [ - "unfichier", - "doodstream", - "rapidfile", - "uptobox", - "vidmoly", - "sendvid", - "sibnet", - "lpayer", - "vidzy", - "luluv", - "uqload" - ] - for host in known_hosts: - assert host in FILE_HOSTS - - def test_file_host_domains(self): - """Test that file hosts have valid domains""" - for host_data in FILE_HOSTS.values(): - assert len(host_data["domains"]) > 0 - for domain in host_data["domains"]: - assert isinstance(domain, str) - assert "." in domain - - -class TestGetDownloader: - """Tests for get_downloader factory function""" - - @pytest.mark.asyncio - async def test_get_anime_site_downloader(self): - """Test getting anime site downloader""" - url = "https://anime-sama.si/catalogue/naruto/" - downloader = await get_downloader(url) - assert downloader is not None - # Should return an anime site downloader - - @pytest.mark.asyncio - async def test_get_series_site_downloader(self): - """Test getting series site downloader""" - url = "https://fs7.space/series/test" - downloader = await get_downloader(url) - assert downloader is not None - # Should return a series site downloader - - @pytest.mark.asyncio - async def test_get_video_player_downloader(self): - """Test getting video player downloader""" - url = "https://doodstream.com/e/abc123" - downloader = await get_downloader(url) - assert downloader is not None - # Should return a video player downloader - - @pytest.mark.asyncio - async def test_get_unknown_url_downloader(self): - """Test getting generic downloader for unknown URL""" - url = "https://unknown-site.com/video" - downloader = await get_downloader(url) - assert downloader is not None - # Should return GenericDownloader - - -class TestGetAnimeSite: - """Tests for get_anime_site factory function""" - - @pytest.mark.asyncio - async def test_get_anime_sama_site(self): - """Test getting Anime-Sama site""" - from app.downloaders.anime_sites import AnimeSamaDownloader - url = "https://anime-sama.si/catalogue/naruto/" - downloader = await get_anime_site(url) - assert isinstance(downloader, AnimeSamaDownloader) - - @pytest.mark.asyncio - async def test_get_neko_sama_site(self): - """Test getting Neko-Sama site""" - from app.downloaders.anime_sites import NekoSamaDownloader - url = "https://neko-sama.fr/anime/one-piece" - downloader = await get_anime_site(url) - assert isinstance(downloader, NekoSamaDownloader) - - @pytest.mark.asyncio - async def test_get_anime_site_with_series_url(self): - """Test that series URL returns None for anime site""" - url = "https://fs7.space/series/test" - downloader = await get_anime_site(url) - assert downloader is None - - @pytest.mark.asyncio - async def test_get_anime_site_with_video_player_url(self): - """Test that video player URL returns None for anime site""" - url = "https://doodstream.com/e/abc123" - downloader = await get_anime_site(url) - assert downloader is None - - -class TestGetSeriesSite: - """Tests for get_series_site factory function""" - - @pytest.mark.asyncio - async def test_get_fs7_site(self): - """Test getting FS7 series site""" - from app.downloaders.series_sites import FS7Downloader - url = "https://fs7.space/series/test" - downloader = await get_series_site(url) - assert isinstance(downloader, FS7Downloader) - - @pytest.mark.asyncio - async def test_get_series_site_with_anime_url(self): - """Test that anime URL returns None for series site""" - url = "https://anime-sama.si/catalogue/naruto/" - downloader = await get_series_site(url) - assert downloader is None - - @pytest.mark.asyncio - async def test_get_series_site_with_video_player_url(self): - """Test that video player URL returns None for series site""" - url = "https://doodstream.com/e/abc123" - downloader = await get_series_site(url) - assert downloader is None - - -class TestGetVideoPlayer: - """Tests for get_video_player factory function""" - - @pytest.mark.asyncio - async def test_get_doodstream_player(self): - """Test getting Doodstream player""" - from app.downloaders.video_players import DoodstreamDownloader - url = "https://doodstream.com/e/abc123" - player = await get_video_player(url) - assert isinstance(player, DoodstreamDownloader) - - @pytest.mark.asyncio - async def test_get_unfichier_player(self): - """Test getting 1fichier player""" - from app.downloaders.video_players import UnFichierDownloader - url = "https://1fichier.com/?abc123" - player = await get_video_player(url) - assert isinstance(player, UnFichierDownloader) - - @pytest.mark.asyncio - async def test_get_vidmoly_player(self): - """Test getting VidMoly player""" - from app.downloaders.video_players import VidMolyDownloader - url = "https://vidmoly.to/abc123" - player = await get_video_player(url) - assert isinstance(player, VidMolyDownloader) - - @pytest.mark.asyncio - async def test_get_video_player_with_anime_url(self): - """Test that anime site URL returns None for video player""" - url = "https://anime-sama.si/catalogue/naruto/" - player = await get_video_player(url) - assert player is None - - @pytest.mark.asyncio - async def test_get_video_player_with_unknown_url(self): - """Test that unknown URL returns None for video player""" - url = "https://unknown-site.com/video" - player = await get_video_player(url) - assert player is None - - -class TestDownloaderPriority: - """Tests for downloader priority and routing""" - - @pytest.mark.asyncio - async def test_anime_site_has_priority_over_series(self): - """Test that anime sites are checked before series sites""" - # This is implicit in the get_downloader implementation - # We just verify it works correctly - url = "https://anime-sama.si/catalogue/naruto/" - downloader = await get_downloader(url) - assert downloader is not None - # Should be an anime site, not series site or video player - from app.downloaders.anime_sites import BaseAnimeSite - assert isinstance(downloader, BaseAnimeSite) - - @pytest.mark.asyncio - async def test_series_site_has_priority_over_video_player(self): - """Test that series sites are checked before video players""" - url = "https://fs7.space/series/test" - downloader = await get_downloader(url) - assert downloader is not None - # Should be a series site, not video player - from app.downloaders.series_sites import BaseSeriesSite - assert isinstance(downloader, BaseSeriesSite) - - -class TestProviderDomains: - """Tests for provider domain matching""" - - def test_anime_sama_domains(self): - """Test Anime-Sama domain variations""" - from app.downloaders.anime_sites import AnimeSamaDownloader - downloader = AnimeSamaDownloader() - - # These should be handled - assert downloader.can_handle("https://anime-sama.si/test") - assert downloader.can_handle("https://www.anime-sama.fi/test") - - # These should not - assert not downloader.can_handle("https://neko-sama.fr/test") - assert not downloader.can_handle("https://doodstream.com/test") - - def test_neko_sama_domains(self): - """Test Neko-Sama domain variations""" - from app.downloaders.anime_sites import NekoSamaDownloader - downloader = NekoSamaDownloader() - - assert downloader.can_handle("https://neko-sama.fr/anime/test") - assert not downloader.can_handle("https://anime-sama.si/test") - - def test_doodstream_domains(self): - """Test Doodstream domain variations""" - from app.downloaders.video_players import DoodstreamDownloader - downloader = DoodstreamDownloader() - - assert downloader.can_handle("https://doodstream.com/e/abc") - assert downloader.can_handle("https://ds2play.com/e/abc") - assert not downloader.can_handle("https://vidmoly.to/abc") - - def test_subdomain_handling(self): - """Test that subdomains are handled correctly""" - from app.downloaders.anime_sites import AnimeSamaDownloader - downloader = AnimeSamaDownloader() - - # With and without www - assert downloader.can_handle("https://anime-sama.si/test") - assert downloader.can_handle("https://www.anime-sama.si/test") - - def test_protocol_handling(self): - """Test that both HTTP and HTTPS are handled""" - from app.downloaders.anime_sites import AnimeSamaDownloader - downloader = AnimeSamaDownloader() - - assert downloader.can_handle("https://anime-sama.si/test") - # HTTP should also work (though less secure) - assert downloader.can_handle("http://anime-sama.si/test") - - -class TestProviderEdgeCases: - """Tests for edge cases in provider detection""" - - def test_url_with_port(self): - """Test URL with port number""" - provider = detect_provider_from_url("https://anime-sama.si:443/test") - assert provider is not None - assert provider["name"] == "anime-sama" - - def test_url_with_fragment(self): - """Test URL with fragment identifier""" - provider = detect_provider_from_url("https://anime-sama.si/test#section") - assert provider is not None - assert provider["name"] == "anime-sama" - - def test_url_with_auth(self): - """Test URL with authentication (should not happen in practice)""" - # URLs with auth @ should still be detected - provider = detect_provider_from_url("https://user:pass@anime-sama.si/test") - # Detection might fail due to parsing, but shouldn't crash - assert provider is not None or provider is None - - def test_idn_domains(self): - """Test internationalized domain names""" - # Most providers use ASCII domains, but let's test the logic - url = "https://xn--anime-sama-test.si/catalogue/test" - provider = detect_provider_from_url(url) - # Should not crash - - def test_punycode_domains(self): - """Test punycode-encoded domains""" - # ASCII encoding of international domains - url = "https://anime-sama.si/catalogue/test" - provider = detect_provider_from_url(url) - assert provider is not None diff --git a/tests/test_watchlist.py b/tests/test_watchlist.py index 2c24479..9b66de1 100644 --- a/tests/test_watchlist.py +++ b/tests/test_watchlist.py @@ -29,7 +29,7 @@ class TestWatchlistManager: @pytest.fixture def watchlist_manager(self, temp_watchlist_file): """Create a WatchlistManager instance with temporary storage""" - manager = WatchlistManager(json_path=str(temp_watchlist_file)) + manager = WatchlistManager(db_file=str(temp_watchlist_file)) yield manager # Cleanup if temp_watchlist_file.exists(): @@ -393,7 +393,7 @@ class TestWatchlistIntegration: @pytest.fixture def watchlist_manager(self, temp_watchlist_file): """Create a WatchlistManager instance""" - manager = WatchlistManager(json_path=str(temp_watchlist_file)) + manager = WatchlistManager(db_file=str(temp_watchlist_file)) yield manager if temp_watchlist_file.exists(): temp_watchlist_file.unlink()