import unittest
from importlib.machinery import SourceFileLoader
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
MODULE = SourceFileLoader("youknow_spider", str(ROOT / "youknow.py")).load_module()
Spider = MODULE.Spider
class TestYouKnowSpider(unittest.TestCase):
def setUp(self):
self.spider = Spider()
self.spider.init()
def test_home_content_exposes_expected_categories(self):
content = self.spider.homeContent(False)
class_ids = [item["type_id"] for item in content["class"]]
self.assertEqual(class_ids, ["index", "drama", "movie", "variety", "anime", "short", "doc"])
def test_parse_list_cards_extracts_compact_vod_id(self):
html = """
更新至10集
"""
cards = self.spider._parse_list_cards(html)
self.assertEqual(
cards,
[
{
"vod_id": "1234",
"vod_name": "示例影片",
"vod_pic": "https://www.youknow.tv/cover.jpg",
"vod_remarks": "更新至10集",
}
],
)
def test_parse_list_cards_prefers_nested_lazyload_original_over_placeholder_src(self):
html = """
更新中
"""
cards = self.spider._parse_list_cards(html)
self.assertEqual(cards[0]["vod_pic"], "https://www.youknow.tv/upload/vod/20260409-1/f1d0233129bc59adb6d2e8fa6396e681.jpg")
@patch.object(Spider, "_request_html")
def test_home_video_content_uses_today_updates_page(self, mock_request_html):
mock_request_html.return_value = """
HD
"""
result = self.spider.homeVideoContent()
self.assertEqual(result["list"][0]["vod_id"], "111")
self.assertEqual(result["list"][0]["vod_name"], "今日更新")
@patch.object(Spider, "_request_html")
def test_category_content_builds_page_result(self, mock_request_html):
mock_request_html.return_value = """
完结
"""
result = self.spider.categoryContent("movie", "2", False, {})
self.assertEqual(result["page"], 2)
self.assertEqual(mock_request_html.call_args.args[0], "/show/2--------2---/")
self.assertEqual(result["list"][0]["vod_id"], "222")
@patch.object(Spider, "_request_html")
def test_category_content_uses_page1_path_without_page_number(self, mock_request_html):
mock_request_html.return_value = """
HD
"""
result = self.spider.categoryContent("movie", "1", False, {})
self.assertEqual(mock_request_html.call_args.args[0], "/show/2-----------/")
self.assertEqual(result["list"][0]["vod_id"], "555")
@patch.object(Spider, "_request_html")
def test_search_content_reuses_card_parser(self, mock_request_html):
mock_request_html.return_value = """
抢先版
"""
result = self.spider.searchContent("繁花", False, "1")
self.assertEqual(result["list"][0]["vod_id"], "333")
self.assertEqual(result["list"][0]["vod_name"], "搜索影片")
def test_parse_detail_page_extracts_fields_and_aligns_episode_sources(self):
html = """
示例剧
"""
result = self.spider._parse_detail_page(html, "888")
vod = result["list"][0]
self.assertEqual(vod["vod_id"], "888")
self.assertEqual(vod["path"], "https://www.youknow.tv/d/888/")
self.assertEqual(vod["vod_name"], "示例剧")
self.assertEqual(vod["type_name"], "剧情")
self.assertEqual(vod["vod_area"], "大陆")
self.assertEqual(vod["vod_year"], "2026")
self.assertEqual(vod["vod_lang"], "国语")
self.assertEqual(vod["vod_remarks"], "更新至2集")
self.assertEqual(vod["vod_director"], "张三")
self.assertEqual(vod["vod_actor"], "李四,王五")
self.assertEqual(vod["vod_content"], "一段剧情简介")
self.assertEqual(vod["vod_play_from"], "YouKnowTV")
first_episode = vod["vod_play_url"].split("#")[0]
self.assertTrue(first_episode.startswith("第1集$"))
payload = self.spider._decode_episode_payload(first_episode.split("$", 1)[1])
self.assertEqual(payload["vod_id"], "888")
self.assertEqual(payload["episode_index"], 1)
self.assertEqual(len(payload["candidates"]), 2)
@patch.object(Spider, "_request_html")
def test_detail_content_builds_detail_request_url_from_vod_id(self, mock_request_html):
mock_request_html.return_value = '详情影片
'
result = self.spider.detailContent(["123"])
self.assertEqual(mock_request_html.call_args.args[0], "https://www.youknow.tv/d/123/")
self.assertEqual(result["list"][0]["vod_id"], "123")
def test_extract_player_config_reads_player_aaaa(self):
html = ''
data = self.spider._parse_player_config(html)
self.assertEqual(data["encrypt"], "1")
def test_extract_player_config_supports_nested_object_literal(self):
html = """
"""
data = self.spider._parse_player_config(html)
self.assertEqual(data["encrypt"], 2)
self.assertEqual(data["vod_data"]["vod_name"], "示例影片")
def test_decode_player_url_supports_encrypt_1_and_2(self):
self.assertEqual(
self.spider._decode_player_url("https%3A%2F%2Fvideo.example%2Fa.m3u8", "1"),
"https://video.example/a.m3u8",
)
encoded = "aHR0cHMlM0ElMkYlMkZ2aWRlby5leGFtcGxlJTJGYi5tM3U4"
self.assertEqual(
self.spider._decode_player_url(encoded, "2"),
"https://video.example/b.m3u8",
)
@patch.object(Spider, "_request_html")
def test_player_content_collects_candidates_from_page_and_iframe(self, mock_request_html):
payload = self.spider._encode_episode_payload(
{
"vod_id": "888",
"episode_index": 1,
"title": "第1集",
"candidates": [
{"source": "线路A", "source_id": "1", "episode_url": "https://www.youknow.tv/p/888-1-1/"}
],
}
)
mock_request_html.side_effect = [
'',
'https://video.example/iframe-final.m3u8',
]
result = self.spider.playerContent("YouKnowTV", payload, {})
self.assertEqual(result["parse"], 0)
self.assertEqual(result["url"], "https://video.example/page.m3u8")
self.assertEqual(result["header"]["Referer"], "https://www.youknow.tv/")
@patch.object(Spider, "_request_html")
def test_player_content_handles_nested_real_world_player_config(self, mock_request_html):
payload = self.spider._encode_episode_payload(
{
"vod_id": "202774",
"episode_index": 1,
"title": "HD中字",
"candidates": [
{"source": "线路1", "source_id": "1", "episode_url": "https://www.youknow.tv/p/202774-1-1/"}
],
}
)
mock_request_html.return_value = """
"""
result = self.spider.playerContent("YouKnowTV", payload, {})
self.assertEqual(result["parse"], 0)
self.assertEqual(result["url"], "https://vip.dytt-network.com/20260325/21334_18856b47/index.m3u8")
@patch.object(Spider, "_request_html")
def test_player_content_tries_next_candidate_when_first_fails(self, mock_request_html):
payload = self.spider._encode_episode_payload(
{
"vod_id": "888",
"episode_index": 1,
"title": "第1集",
"candidates": [
{"source": "线路A", "source_id": "1", "episode_url": "https://www.youknow.tv/p/888-1-1/"},
{"source": "线路B", "source_id": "2", "episode_url": "https://www.youknow.tv/p/888-2-1/"},
],
}
)
mock_request_html.side_effect = [
"empty",
"https://video.example/backup.m3u8",
]
result = self.spider.playerContent("YouKnowTV", payload, {})
self.assertEqual(result["url"], "https://video.example/backup.m3u8")
if __name__ == "__main__":
unittest.main()