返回 douyin-downloader
test_config_loader_save.py
根目录 / tests / test_config_loader_save.py
1 """Tests for ``ConfigLoader.save()`` — settings persistence.
2
3 These cover the scenario fixed by the desktop settings-persistence bug:
4 the REST settings endpoint mutates the in-memory config, and on a real
5 ``config.yml`` path that mutation must round-trip to disk so it survives
6 a sidecar (and app) restart.
7 """
8
9 from __future__ import annotations
10
11 import yaml
12
13 from config import ConfigLoader
14
15
16 def test_save_without_config_path_is_noop(tmp_path):
17 """ConfigLoader(None) has nothing to write to; save() must return False
18 without raising so the server handler can stay oblivious."""
19 loader = ConfigLoader(None)
20 loader.update(path=str(tmp_path), thread=9)
21
22 assert loader.save() is False
23
24
25 def test_save_writes_ui_keys_to_yaml(tmp_path):
26 """After save() the target YAML must contain the UI-editable keys the
27 user tweaked — this is the core guarantee the bug was violating."""
28 config_path = tmp_path / "config.yml"
29
30 loader = ConfigLoader(str(config_path))
31 loader.update(path=str(tmp_path / "downloads"), thread=12, rate_limit=3.5)
32
33 assert loader.save() is True
34 assert config_path.exists()
35
36 written = yaml.safe_load(config_path.read_text(encoding="utf-8"))
37 assert written["path"] == str(tmp_path / "downloads")
38 assert written["thread"] == 12
39 assert written["rate_limit"] == 3.5
40
41
42 def test_save_roundtrips_across_new_loader(tmp_path):
43 """Persistence is only useful if a fresh ConfigLoader picks the values
44 back up from disk — emulates the desktop app restarting the sidecar."""
45 config_path = tmp_path / "config.yml"
46
47 first = ConfigLoader(str(config_path))
48 first.update(path=str(tmp_path / "downloads"), thread=7)
49 first.save()
50
51 second = ConfigLoader(str(config_path))
52 assert second.get("thread") == 7
53 assert second.get("path") == str(tmp_path / "downloads")
54
55
56 def test_save_preserves_unrelated_user_keys(tmp_path):
57 """Users may have edited their config.yml by hand (e.g. ``link``,
58 ``cookies``). save() must never drop those just because the UI doesn't
59 know about them."""
60 config_path = tmp_path / "config.yml"
61 config_path.write_text(
62 "link:\n - https://www.douyin.com/video/123\ncookies:\n sessionid_ss: abc\nthread: 2\n",
63 encoding="utf-8",
64 )
65
66 loader = ConfigLoader(str(config_path))
67 loader.update(thread=8)
68 loader.save()
69
70 written = yaml.safe_load(config_path.read_text(encoding="utf-8"))
71 # Unrelated user-authored keys survive…
72 assert written["link"] == ["https://www.douyin.com/video/123"]
73 assert written["cookies"] == {"sessionid_ss": "abc"}
74 # …and the UI-driven update is reflected.
75 assert written["thread"] == 8
76
77
78 def test_save_creates_parent_directory(tmp_path):
79 """Desktop writes config to ``<userData>/config.yml``; on a fresh install
80 that directory may not exist yet. save() must materialise it."""
81 config_path = tmp_path / "nested" / "subdir" / "config.yml"
82
83 loader = ConfigLoader(str(config_path))
84 loader.update(thread=4)
85 assert loader.save() is True
86 assert config_path.exists()
87
88
89 def test_save_nested_sub_models(tmp_path):
90 """Nested sub-models (comments/live/transcript/notifications) must be
91 written as nested dicts, not flattened or dropped."""
92 config_path = tmp_path / "config.yml"
93
94 loader = ConfigLoader(str(config_path))
95 loader.update(
96 comments={"enabled": True, "max_comments": 100},
97 notifications={"enabled": True, "providers": [{"type": "bark", "url": "https://x"}]},
98 )
99 loader.save()
100
101 written = yaml.safe_load(config_path.read_text(encoding="utf-8"))
102 assert written["comments"]["enabled"] is True
103 assert written["comments"]["max_comments"] == 100
104 assert written["notifications"]["enabled"] is True
105 assert written["notifications"]["providers"] == [{"type": "bark", "url": "https://x"}]
106
106 lines PYTHON