2482a1fe58
- Add AGENTS.md for agentic coding guidelines - Add Oneupload and Smoothpre video player downloaders - Add MetadataEnrichment service with Kitsu API fallback - Add tests for metadata enrichment and provider detection - Update .gitignore to ignore runtime config files
480 lines
18 KiB
Python
480 lines
18 KiB
Python
"""
|
|
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
|