findFilms/app/test_app.py
vrubelroman a5497eef26 fix(app): исправление скачивания торрентов
- generate_clean_magnet: убраны мёртвые трекеры (coppersurfer.tk, leechers-paradise.org),
  добавлены рабочие (tamersunion.org, exodus.desync.com, moeking.me),
  включено &dn= с URL-кодированием кириллицы
- extract_hash_from_result: новая единая функция извлечения хэша из 5 источников
  (Hash, InfoHash, Magnet, btih: в URL, Id)
- /api/add-torrent: убран ложный success — после Ok. от qBittorrent идёт реальная
  верификация (торрент появился в списке по хэшу или названию). Если не появился — error.
- /api/proxy-torrent-download: новый endpoint для скачивания .torrent файлов
  через NL-прокси (обходит DPI-блокировку)
- torrents.html: кнопка Копировать magnet (Clipboard API + fallback),
  proxy-ссылки для .torrent, disabled-состояния для пустых magnet/torrent_url
- tmdb-proxy: добавлен /proxy-torrent endpoint
- urlencode filter для Jinja2
- test_app.py: 47 тестов на чистые функции
2026-06-03 19:27:14 +00:00

273 lines
9.4 KiB
Python

#!/usr/bin/env python3
"""
Тесты для app.py — изолированные, без внешних зависимостей.
Запуск: python3 -m unittest test_app -v
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import unittest
from app import (
extract_hash_from_result,
generate_clean_magnet,
parse_size_to_bytes,
parse_size,
extract_resolution,
extract_quality,
generate_search_variants,
score_torrent,
normalize_search_term,
is_movie_torrent,
)
class TestExtractHash(unittest.TestCase):
def test_direct_hash_field(self):
result = {"Hash": "08ada5a7a6183aae1e09d831df6748d566095a10"}
self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10")
def test_hash_from_magnet(self):
result = {
"Hash": "",
"Magnet": "magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=test"
}
self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10")
def test_hash_from_info_hash(self):
result = {"InfoHash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"}
self.assertEqual(
extract_hash_from_result(result),
"A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0"
)
def test_empty_result(self):
self.assertEqual(extract_hash_from_result({}), "")
self.assertEqual(extract_hash_from_result({"Hash": ""}), "")
def test_hash_from_torrent_url_no_btih(self):
result = {"Torrent": "https://rutracker.org/forum/dl.php?t=123456"}
self.assertEqual(extract_hash_from_result(result), "")
def test_case_insensitive_hash(self):
result = {"Hash": "08ada5a7a6183aae1e09d831df6748d566095a10"}
self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10")
def test_id_field_as_hash(self):
result = {"Id": "08ada5a7a6183aae1e09d831df6748d566095a10"}
self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10")
def test_non_hash_id_field(self):
result = {"Id": "12345", "Hash": ""}
self.assertEqual(extract_hash_from_result(result), "")
def test_hash_from_lowercase_field(self):
result = {"hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"}
self.assertEqual(
extract_hash_from_result(result),
"A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0"
)
class TestGenerateCleanMagnet(unittest.TestCase):
def test_basic_magnet(self):
magnet = generate_clean_magnet("08ada5a7a6183aae1e09d831df6748d566095a10", "Test Movie")
self.assertTrue(magnet.startswith("magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10"))
self.assertIn("&dn=Test%20Movie", magnet)
self.assertIn("&tr=udp://tracker.opentrackr.org:1337/announce", magnet)
self.assertIn("&tr=udp://tracker.openbittorrent.com:6969/announce", magnet)
def test_empty_hash(self):
self.assertEqual(generate_clean_magnet(""), "")
self.assertEqual(generate_clean_magnet(None), "")
def test_no_title(self):
magnet = generate_clean_magnet("08ada5a7a6183aae1e09d831df6748d566095a10")
self.assertTrue(magnet.startswith("magnet:?xt=urn:btih:"))
self.assertNotIn("&dn=", magnet)
def test_cyrillic_title(self):
magnet = generate_clean_magnet("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0", "Терминатор 2")
self.assertIn("&dn=%D0%A2%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B0%D1%82%D0%BE%D1%80%202", magnet)
def test_has_trackers(self):
magnet = generate_clean_magnet("a" * 40)
expected_trackers = [
"tracker.opentrackr.org",
"open.stealth.si",
"tracker.openbittorrent.com",
"tracker.tamersunion.org",
"exodus.desync.com",
"tracker.moeking.me",
]
for tracker in expected_trackers:
self.assertIn(tracker, magnet, f"Missing tracker: {tracker}")
def test_no_dead_trackers(self):
magnet = generate_clean_magnet("a" * 40)
self.assertNotIn("coppersurfer.tk", magnet)
self.assertNotIn("leechers-paradise.org", magnet)
class TestParseSize(unittest.TestCase):
def test_gb(self):
bytes_size, readable = parse_size("25.3 GB")
self.assertEqual(bytes_size, int(25.3 * 1024**3))
self.assertEqual(readable, "25.3 GB")
def test_mb(self):
bytes_size, readable = parse_size("6.2 MB")
self.assertEqual(bytes_size, int(6.2 * 1024**2))
def test_kb(self):
bytes_size, readable = parse_size("512 KB")
self.assertEqual(bytes_size, 512 * 1024)
def test_empty(self):
bytes_size, readable = parse_size("")
self.assertEqual(bytes_size, 0)
self.assertEqual(readable, "Неизвестно")
def test_none(self):
bytes_size, readable = parse_size(None)
self.assertEqual(bytes_size, 0)
self.assertEqual(readable, "Неизвестно")
class TestParseSizeToBytes(unittest.TestCase):
def test_gb(self):
result = parse_size_to_bytes("25.3 GB")
self.assertAlmostEqual(result, 25.3 * 1024**3, delta=1)
def test_mb(self):
result = parse_size_to_bytes("500 MB")
self.assertEqual(result, 500 * 1024**2)
def test_empty(self):
self.assertEqual(parse_size_to_bytes(""), 0)
class TestExtractResolution(unittest.TestCase):
def test_2160p(self):
self.assertEqual(extract_resolution("Movie 2024 UHD 2160p BluRay"), "2160p")
def test_4k(self):
self.assertEqual(extract_resolution("Movie 2024 4K HDR"), "2160p")
def test_1080p(self):
self.assertEqual(extract_resolution("Movie 2024 1080p WEB-DL"), "1080p")
def test_720p(self):
self.assertEqual(extract_resolution("Movie 2024 720p HDTV"), "720p")
def test_unknown(self):
self.assertEqual(extract_resolution("Movie Unknown Quality"), "Неизвестно")
class TestExtractQuality(unittest.TestCase):
def test_bluray(self):
self.assertEqual(extract_quality("Movie 2024 Bluray 1080p"), "BluRay")
def test_bdrip(self):
self.assertEqual(extract_quality("Movie 2024 BDRip 720p"), "BluRay")
def test_webdl(self):
self.assertEqual(extract_quality("Movie 2024 WEB-DL 1080p"), "WEB-DL")
def test_webrip(self):
self.assertEqual(extract_quality("Movie 2024 WEBRip 720p"), "WEBRip")
def test_hdtv(self):
self.assertEqual(extract_quality("Movie 2024 HDTV 720p"), "HDTV")
def test_unknown(self):
self.assertEqual(extract_quality("Movie 2024 Some Quality"), "Unknown")
class TestNormalizeSearchTerm(unittest.TestCase):
def test_basic(self):
self.assertEqual(normalize_search_term("The Terminator"), "Terminator")
def test_with_articles(self):
self.assertEqual(normalize_search_term("The Matrix Revolutions"), "Matrix.Revolutions")
def test_empty(self):
self.assertEqual(normalize_search_term(""), "")
self.assertEqual(normalize_search_term(None), "")
def test_spaces_to_dots(self):
self.assertEqual(normalize_search_term("Harry Potter"), "Harry.Potter")
class TestGenerateSearchVariants(unittest.TestCase):
def test_basic(self):
variants = generate_search_variants("Terminator", "The Terminator", "1984")
self.assertIn("Terminator", variants)
self.assertIn("The Terminator", variants)
self.assertIn("Terminator 1984", variants)
def test_terminator_special(self):
variants = generate_search_variants("Терминатор 2", "Terminator 2", "1991")
self.assertIn("T2", variants)
self.assertIn("T2.Judgment.Day", variants)
def test_no_duplicates(self):
variants = generate_search_variants("Terminator", "Terminator", "1984")
self.assertEqual(len(variants), len(set(variants)))
class TestScoreTorrent(unittest.TestCase):
def test_high_score_exact_match(self):
torrent = {
"title": "The Terminator 1984 1080p BluRay",
"quality": "BluRay",
"resolution": "1080p",
"seeds": 150,
}
score = score_torrent(torrent, "Terminator", "The Terminator", "1984")
self.assertGreater(score, 0.8)
def test_low_score_no_match(self):
torrent = {
"title": "Some Other Movie 2023 720p WEB-DL",
"quality": "WEB-DL",
"resolution": "720p",
"seeds": 5,
}
score = score_torrent(torrent, "Terminator", "The Terminator", "1984")
self.assertAlmostEqual(score, 0.3)
def test_score_capped_at_one(self):
torrent = {
"title": "The Terminator 1984 2160p BluRay",
"quality": "BluRay",
"resolution": "2160p",
"seeds": 200,
}
score = score_torrent(torrent, "The Terminator", "The Terminator", "1984")
self.assertLessEqual(score, 1.0)
class TestIsMovieTorrent(unittest.TestCase):
def test_is_movie(self):
self.assertTrue(is_movie_torrent("The Terminator 1984 BluRay", "Terminator", "The Terminator"))
def test_not_movie_excluded_keyword(self):
self.assertFalse(is_movie_torrent("The Game 1997 1080p BluRay", "The Game", ""))
def test_not_movie_unknown_title(self):
self.assertFalse(is_movie_torrent("Some Other Movie 2023 720p", "Terminator", "The Terminator"))
if __name__ == "__main__":
unittest.main()