返回 douyin-downloader
test_config_loader.py
根目录 / tests / test_config_loader.py
1 import pytest
2
3 from config import ConfigLoader
4
5
6 def test_config_loader_merges_file_and_defaults(tmp_path, monkeypatch):
7 config_file = tmp_path / "config.yml"
8 config_file.write_text(
9 """
10 link:
11 - https://www.douyin.com/video/1
12 path: ./Custom/
13 thread: 3
14 """
15 )
16
17 monkeypatch.setenv("DOUYIN_THREAD", "8")
18
19 loader = ConfigLoader(str(config_file))
20
21 # Environment variable should override file
22 assert loader.get("thread") == 8
23 # File values should override defaults
24 assert loader.get("path") == "./Custom/"
25 # Links should be normalized to list
26 assert loader.get_links() == ["https://www.douyin.com/video/1"]
27
28
29 def test_config_validation_requires_links_and_path(tmp_path):
30 config_file = tmp_path / "config.yml"
31 config_file.write_text("{}")
32
33 loader = ConfigLoader(str(config_file))
34 assert not loader.validate()
35
36 loader.update(link=["https://www.douyin.com/video/1"], path="./Downloaded/")
37 assert loader.validate() is True
38
39
40 def test_config_loader_sanitizes_invalid_cookie_keys(tmp_path):
41 config_file = tmp_path / "config.yml"
42 config_file.write_text(
43 """
44 link:
45 - https://www.douyin.com/video/1
46 path: ./Downloaded/
47 cookies:
48 "": douyin.com
49 ttwid: abc
50 msToken: token
51 """
52 )
53
54 loader = ConfigLoader(str(config_file))
55 cookies = loader.get_cookies()
56
57 assert "" not in cookies
58 assert cookies["ttwid"] == "abc"
59 assert cookies["msToken"] == "token"
60
61
62 def test_config_loader_reads_auto_cookies_from_default_file(tmp_path):
63 config_file = tmp_path / "config.yml"
64 config_file.write_text(
65 """
66 link:
67 - https://www.douyin.com/note/1
68 path: ./Downloaded/
69 cookies: auto
70 """
71 )
72 cookie_dir = tmp_path / "config"
73 cookie_dir.mkdir()
74 (cookie_dir / "cookies.json").write_text(
75 """
76 {
77 "ttwid": "auto-ttwid",
78 "msToken": "auto-ms-token",
79 "_waftokenid": "auto-waf-token"
80 }
81 """.strip(),
82 encoding="utf-8",
83 )
84
85 loader = ConfigLoader(str(config_file))
86 cookies = loader.get_cookies()
87
88 assert cookies["ttwid"] == "auto-ttwid"
89 assert cookies["msToken"] == "auto-ms-token"
90 assert cookies["_waftokenid"] == "auto-waf-token"
91
92
93 def test_config_loader_reads_auto_cookies_for_nested_config_path(tmp_path, monkeypatch):
94 workspace = tmp_path
95 config_dir = workspace / "config"
96 config_dir.mkdir()
97 config_file = config_dir / "config.yml"
98 config_file.write_text(
99 """
100 link:
101 - https://www.douyin.com/note/1
102 path: ./Downloaded/
103 cookies: auto
104 """
105 )
106 (workspace / "config" / "cookies.json").write_text(
107 """
108 {
109 "ttwid": "nested-ttwid",
110 "msToken": "nested-ms-token"
111 }
112 """.strip(),
113 encoding="utf-8",
114 )
115 monkeypatch.chdir(workspace)
116
117 loader = ConfigLoader(str(config_file))
118 cookies = loader.get_cookies()
119
120 assert cookies["ttwid"] == "nested-ttwid"
121 assert cookies["msToken"] == "nested-ms-token"
122
123
124 def test_config_loader_reads_auto_cookies_when_auto_cookie_enabled(tmp_path, monkeypatch):
125 workspace = tmp_path
126 config_file = workspace / "config.yml"
127 config_file.write_text(
128 """
129 link:
130 - https://www.douyin.com/note/1
131 path: ./Downloaded/
132 auto_cookie: true
133 """
134 )
135 cookie_dir = workspace / "config"
136 cookie_dir.mkdir()
137 (cookie_dir / "cookies.json").write_text(
138 """
139 {
140 "ttwid": "auto-ttwid",
141 "msToken": "auto-ms-token"
142 }
143 """.strip(),
144 encoding="utf-8",
145 )
146 monkeypatch.chdir(workspace)
147
148 loader = ConfigLoader(str(config_file))
149 cookies = loader.get_cookies()
150
151 assert cookies["ttwid"] == "auto-ttwid"
152 assert cookies["msToken"] == "auto-ms-token"
153
154
155 def test_config_loader_skips_auto_cookies_when_auto_cookie_disabled(tmp_path, monkeypatch):
156 workspace = tmp_path
157 config_file = workspace / "config.yml"
158 config_file.write_text(
159 """
160 link:
161 - https://www.douyin.com/note/1
162 path: ./Downloaded/
163 auto_cookie: false
164 """
165 )
166 cookie_dir = workspace / "config"
167 cookie_dir.mkdir()
168 (cookie_dir / "cookies.json").write_text(
169 """
170 {
171 "ttwid": "auto-ttwid",
172 "msToken": "auto-ms-token"
173 }
174 """.strip(),
175 encoding="utf-8",
176 )
177 monkeypatch.chdir(workspace)
178
179 loader = ConfigLoader(str(config_file))
180
181 assert loader.get_cookies() == {}
182
183
184 def test_config_loader_warns_for_non_object_auto_cookie_file(tmp_path, caplog, monkeypatch):
185 config_file = tmp_path / "config.yml"
186 config_file.write_text(
187 """
188 link:
189 - https://www.douyin.com/note/1
190 path: ./Downloaded/
191 cookies: auto
192 """
193 )
194 cookie_dir = tmp_path / "config"
195 cookie_dir.mkdir()
196 (cookie_dir / "cookies.json").write_text("[]", encoding="utf-8")
197 monkeypatch.chdir(tmp_path)
198
199 loader = ConfigLoader(str(config_file))
200 cookies = loader.get_cookies()
201
202 assert cookies == {}
203 assert any("is not a JSON object" in record.message for record in caplog.records)
204
205
206 def test_progress_quiet_logs_default_enabled(tmp_path):
207 config_file = tmp_path / "config.yml"
208 config_file.write_text(
209 """
210 link:
211 - https://www.douyin.com/video/1
212 path: ./Downloaded/
213 """
214 )
215
216 loader = ConfigLoader(str(config_file))
217 progress = loader.get("progress", {})
218
219 assert isinstance(progress, dict)
220 assert progress.get("quiet_logs") is True
221
222
223 def test_progress_quiet_logs_can_be_overridden(tmp_path):
224 config_file = tmp_path / "config.yml"
225 config_file.write_text(
226 """
227 link:
228 - https://www.douyin.com/video/1
229 path: ./Downloaded/
230 progress:
231 quiet_logs: false
232 """
233 )
234
235 loader = ConfigLoader(str(config_file))
236 progress = loader.get("progress", {})
237
238 assert isinstance(progress, dict)
239 assert progress.get("quiet_logs") is False
240
241
242 def test_config_loader_supports_proxy_from_env(tmp_path, monkeypatch):
243 config_file = tmp_path / "config.yml"
244 config_file.write_text(
245 """
246 link:
247 - https://www.douyin.com/video/1
248 path: ./Downloaded/
249 proxy: http://127.0.0.1:7890
250 """
251 )
252
253 monkeypatch.setenv("DOUYIN_PROXY", "http://127.0.0.1:8899")
254
255 loader = ConfigLoader(str(config_file))
256
257 assert loader.get("proxy") == "http://127.0.0.1:8899"
258
259
260 def test_nested_defaults_do_not_leak_between_loader_instances(tmp_path):
261 config_file = tmp_path / "config.yml"
262 config_file.write_text(
263 """
264 link:
265 - https://www.douyin.com/video/1
266 path: ./Downloaded/
267 """
268 )
269
270 loader_a = ConfigLoader(str(config_file))
271 loader_a.update(progress={"quiet_logs": False})
272
273 loader_b = ConfigLoader(str(config_file))
274 assert loader_b.get("progress", {}).get("quiet_logs") is True
275
276
277 @pytest.mark.parametrize(
278 "number_cfg,increase_cfg,expected_mix_number,expected_mix_increase,expect_warning",
279 [
280 ({"mix": 9}, {"mix": True}, 9, True, False),
281 ({"allmix": 7}, {"allmix": True}, 7, True, False),
282 ({"mix": 8, "allmix": 8}, {"mix": False, "allmix": False}, 8, False, False),
283 ({"mix": 5, "allmix": 3}, {"mix": False, "allmix": True}, 5, False, True),
284 ({}, {}, 0, False, False),
285 ],
286 )
287 def test_config_loader_normalizes_mix_aliases(
288 tmp_path,
289 caplog,
290 number_cfg,
291 increase_cfg,
292 expected_mix_number,
293 expected_mix_increase,
294 expect_warning,
295 ):
296 config_file = tmp_path / "config.yml"
297 config_file.write_text(
298 f"""
299 link:
300 - https://www.douyin.com/video/1
301 path: ./Downloaded/
302 number: {number_cfg}
303 increase: {increase_cfg}
304 """
305 )
306
307 loader = ConfigLoader(str(config_file))
308 number = loader.get("number", {})
309 increase = loader.get("increase", {})
310
311 assert number.get("mix") == expected_mix_number
312 assert increase.get("mix") == expected_mix_increase
313 # 内部统一后,allmix 与 mix 保持一致,避免后续使用双语义。
314 assert number.get("allmix") == expected_mix_number
315 assert increase.get("allmix") == expected_mix_increase
316
317 warning_logs = [record.message for record in caplog.records if record.levelname == "WARNING"]
318 if expect_warning:
319 assert any("mix/allmix conflict" in message for message in warning_logs)
320 else:
321 assert not any("mix/allmix conflict" in message for message in warning_logs)
322
322 lines PYTHON