返回 last30days-skill
test_youtube_transcript_fallback_spend.py
根目录 / tests / test_youtube_transcript_fallback_spend.py
1 import os
2 from pathlib import Path
3 from unittest import mock
4
5 import pytest
6 from lib import env, youtube_yt
7
8
9 def test_youtube_tuning_loaded_from_env_file_reaches_lazy_readers(
10 tmp_path: Path,
11 monkeypatch: pytest.MonkeyPatch,
12 ) -> None:
13 config_file = tmp_path / ".env"
14 config_file.write_text(
15 "LAST30DAYS_YT_SUB_LANGS=en\n"
16 "LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT=25\n"
17 "LAST30DAYS_YT_PLAYER_CLIENT=tv_embedded\n",
18 encoding="utf-8",
19 )
20 config_file.chmod(0o600)
21 monkeypatch.setattr(env, "CONFIG_DIR", tmp_path)
22 monkeypatch.setattr(env, "CONFIG_FILE", config_file)
23 monkeypatch.setenv("LAST30DAYS_CONFIG_DIR", str(tmp_path))
24 monkeypatch.delenv("LAST30DAYS_YT_SUB_LANGS", raising=False)
25 monkeypatch.delenv("LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT", raising=False)
26 monkeypatch.delenv("LAST30DAYS_YT_PLAYER_CLIENT", raising=False)
27 monkeypatch.chdir(tmp_path)
28
29 with (
30 mock.patch.object(env, "_load_keychain", return_value={}),
31 mock.patch.object(env, "_load_pass", return_value={}),
32 ):
33 config = env.get_config()
34
35 assert config["LAST30DAYS_YT_SUB_LANGS"] == "en"
36 assert config["LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT"] == "25"
37 assert config["LAST30DAYS_YT_PLAYER_CLIENT"] == "tv_embedded"
38 assert youtube_yt._ytdlp_sub_langs() == "en"
39 assert youtube_yt._transcript_fast_timeout() == 25.0
40 assert youtube_yt._ytdlp_player_client() == "tv_embedded"
41 # get_config exports into os.environ; drop the leak so later lazy readers
42 # still see process defaults.
43 os.environ.pop("LAST30DAYS_YT_PLAYER_CLIENT", None)
44 os.environ.pop("LAST30DAYS_YT_SUB_LANGS", None)
45 os.environ.pop("LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT", None)
46
47
48 def test_empty_player_client_in_env_file_disables_android_default(
49 tmp_path: Path,
50 monkeypatch: pytest.MonkeyPatch,
51 ) -> None:
52 config_file = tmp_path / ".env"
53 config_file.write_text("LAST30DAYS_YT_PLAYER_CLIENT=\n", encoding="utf-8")
54 config_file.chmod(0o600)
55 monkeypatch.setattr(env, "CONFIG_DIR", tmp_path)
56 monkeypatch.setattr(env, "CONFIG_FILE", config_file)
57 monkeypatch.setenv("LAST30DAYS_CONFIG_DIR", str(tmp_path))
58 monkeypatch.delenv("LAST30DAYS_YT_PLAYER_CLIENT", raising=False)
59 monkeypatch.chdir(tmp_path)
60
61 with (
62 mock.patch.object(env, "_load_keychain", return_value={}),
63 mock.patch.object(env, "_load_pass", return_value={}),
64 ):
65 config = env.get_config()
66
67 assert config["LAST30DAYS_YT_PLAYER_CLIENT"] == ""
68 assert "LAST30DAYS_YT_PLAYER_CLIENT" in os.environ
69 assert os.environ["LAST30DAYS_YT_PLAYER_CLIENT"] == ""
70 assert youtube_yt._ytdlp_player_client() is None
71 cmd = ["yt-dlp", "ytsearch1:topic"]
72 assert youtube_yt._inject_youtube_player_client(cmd) == cmd
73 os.environ.pop("LAST30DAYS_YT_PLAYER_CLIENT", None)
74
75
76 @pytest.mark.parametrize(
77 "value",
78 ["", "not-a-number", "0", "-1", "nan", "inf"],
79 )
80 def test_invalid_or_empty_fast_timeout_uses_default(
81 value: str,
82 monkeypatch: pytest.MonkeyPatch,
83 ) -> None:
84 monkeypatch.setenv("LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT", value)
85
86 assert youtube_yt._transcript_fast_timeout() == 12.0
87
88
89 def test_timeout_reuses_completed_vtt_without_paid_fallback(
90 tmp_path: Path,
91 ) -> None:
92 (tmp_path / "video123.en.vtt").write_text(
93 "WEBVTT\n\n00:00:00.000 --> 00:00:02.000\nusable transcript\n",
94 encoding="utf-8",
95 )
96 status: dict[str, str] = {}
97
98 with mock.patch.object(
99 youtube_yt.subproc,
100 "run_with_timeout",
101 side_effect=youtube_yt.subproc.SubprocTimeout,
102 ) as run_mock:
103 transcript = youtube_yt._fetch_transcript_ytdlp(
104 "video123",
105 str(tmp_path),
106 status=status,
107 fast_fail=True,
108 )
109
110 assert transcript is not None
111 assert "usable transcript" in transcript
112 assert "ytdlp_error" not in status
113 assert run_mock.call_count == 1
114
115
116 def test_fast_timeout_override_controls_keyed_attempt(
117 tmp_path: Path,
118 monkeypatch: pytest.MonkeyPatch,
119 ) -> None:
120 monkeypatch.setenv("LAST30DAYS_YT_TRANSCRIPT_FAST_TIMEOUT", "25")
121 status: dict[str, str] = {}
122
123 with mock.patch.object(
124 youtube_yt.subproc,
125 "run_with_timeout",
126 side_effect=youtube_yt.subproc.SubprocTimeout,
127 ) as run_mock:
128 transcript = youtube_yt._fetch_transcript_ytdlp(
129 "video123",
130 str(tmp_path),
131 status=status,
132 fast_fail=True,
133 )
134
135 assert transcript is None
136 assert run_mock.call_args.kwargs["timeout"] == 25.0
137 assert status["ytdlp_error"] == "timed out after 25.0s"
138
138 lines PYTHON