返回 MoneyPrinterTurbo
test_cli.py
根目录 / test / services / test_cli.py
1 import sys
2 import unittest
3 from pathlib import Path
4 from unittest.mock import patch
5
6 sys.path.insert(0, str(Path(__file__).parent.parent.parent))
7
8 import cli
9
10
11 class TestCli(unittest.TestCase):
12 def test_build_video_params_with_local_materials(self):
13 args = cli.parse_args(
14 [
15 "--video-subject",
16 "测试主题",
17 "--video-source",
18 "local",
19 "--video-materials",
20 "a.mp4, ,b.jpg,",
21 "--video-terms",
22 "foo, bar",
23 ]
24 )
25
26 params = cli.build_video_params(args)
27 materials = params.video_materials
28
29 self.assertEqual(params.video_subject, "测试主题")
30 self.assertEqual(params.video_source, "local")
31 self.assertEqual([m.url for m in materials], ["a.mp4", "b.jpg"])
32 self.assertTrue(all(m.provider == "local" for m in materials))
33 self.assertEqual(params.video_terms, ["foo", "bar"])
34
35 def test_run_cli_dispatches_task_start(self):
36 with patch.object(cli.tm, "start", return_value={"script": "ok"}) as start, patch.object(
37 cli.utils, "get_uuid", return_value="task-123"
38 ), patch("builtins.print") as print_mock:
39 code = cli.run_cli(["--video-subject", "命令行测试", "--stop-at", "script"])
40
41 self.assertEqual(code, 0)
42 self.assertTrue(start.called)
43 kwargs = start.call_args.kwargs
44 self.assertEqual(kwargs["task_id"], "task-123")
45 self.assertEqual(kwargs["stop_at"], "script")
46 self.assertEqual(kwargs["params"].video_subject, "命令行测试")
47 print_mock.assert_called_once()
48
49 def test_run_cli_returns_error_when_task_fails(self):
50 with patch.object(cli.tm, "start", return_value=None), patch.object(
51 cli.utils, "get_uuid", return_value="task-456"
52 ), patch.object(cli.logger, "error") as log_error:
53 code = cli.run_cli(["--video-subject", "失败场景"])
54
55 self.assertEqual(code, 1)
56 log_error.assert_called_once()
57
58 def test_subtitle_enabled_by_default(self):
59 args = cli.parse_args(["--video-subject", "test"])
60 params = cli.build_video_params(args)
61 self.assertTrue(params.subtitle_enabled)
62
63 def test_subtitle_disabled_with_no_flag(self):
64 args = cli.parse_args(["--video-subject", "test", "--no-subtitle-enabled"])
65 params = cli.build_video_params(args)
66 self.assertFalse(params.subtitle_enabled)
67
68 def test_coverr_video_source_accepted(self):
69 args = cli.parse_args(["--video-subject", "test", "--video-source", "coverr"])
70 params = cli.build_video_params(args)
71 self.assertEqual(params.video_source, "coverr")
72
73 def test_build_video_params_with_script_video_and_audio_options(self):
74 args = cli.parse_args(
75 [
76 "--video-subject",
77 "test",
78 "--video-language",
79 "en",
80 "--paragraph-number",
81 "3",
82 "--video-script-prompt",
83 "use a lighter tone",
84 "--custom-system-prompt",
85 "write concise short-form scripts",
86 "--video-concat-mode",
87 "sequential",
88 "--video-transition-mode",
89 "fade-in",
90 "--video-clip-duration",
91 "4",
92 "--match-materials-to-script",
93 "--voice-volume",
94 "1.2",
95 "--voice-rate",
96 "1.1",
97 "--bgm-type",
98 "custom",
99 "--bgm-file",
100 "output001.mp3",
101 "--bgm-volume",
102 "0.3",
103 ]
104 )
105
106 params = cli.build_video_params(args)
107
108 self.assertEqual(params.video_language, "en")
109 self.assertEqual(params.paragraph_number, 3)
110 self.assertEqual(params.video_script_prompt, "use a lighter tone")
111 self.assertEqual(params.custom_system_prompt, "write concise short-form scripts")
112 self.assertEqual(params.video_concat_mode, "sequential")
113 self.assertEqual(params.video_transition_mode, "FadeIn")
114 self.assertEqual(params.video_clip_duration, 4)
115 self.assertTrue(params.match_materials_to_script)
116 self.assertEqual(params.voice_volume, 1.2)
117 self.assertEqual(params.voice_rate, 1.1)
118 self.assertEqual(params.bgm_type, "custom")
119 self.assertEqual(params.bgm_file, "output001.mp3")
120 self.assertEqual(params.bgm_volume, 0.3)
121
122 def test_build_video_params_with_subtitle_style_options(self):
123 args = cli.parse_args(
124 [
125 "--video-subject",
126 "test",
127 "--font-name",
128 "MicrosoftYaHeiBold.ttc",
129 "--subtitle-position",
130 "custom",
131 "--custom-position",
132 "42.5",
133 "--text-fore-color",
134 "#AABBCC",
135 "--font-size",
136 "72",
137 "--stroke-color",
138 "#112233",
139 "--stroke-width",
140 "2.5",
141 "--subtitle-background-color",
142 "#000001",
143 "--rounded-subtitle-background",
144 ]
145 )
146
147 params = cli.build_video_params(args)
148
149 self.assertEqual(params.font_name, "MicrosoftYaHeiBold.ttc")
150 self.assertEqual(params.subtitle_position, "custom")
151 self.assertEqual(params.custom_position, 42.5)
152 self.assertEqual(params.text_fore_color, "#AABBCC")
153 self.assertEqual(params.font_size, 72)
154 self.assertEqual(params.stroke_color, "#112233")
155 self.assertEqual(params.stroke_width, 2.5)
156 self.assertEqual(params.text_background_color, "#000001")
157 self.assertTrue(params.rounded_subtitle_background)
158
159 def test_subtitle_background_can_be_disabled_from_cli(self):
160 args = cli.parse_args(
161 [
162 "--video-subject",
163 "test",
164 "--no-subtitle-background-enabled",
165 "--rounded-subtitle-background",
166 ]
167 )
168
169 params = cli.build_video_params(args)
170
171 self.assertFalse(params.text_background_color)
172 self.assertFalse(params.rounded_subtitle_background)
173
174 def test_bgm_type_none_maps_to_disabled_background_music(self):
175 args = cli.parse_args(["--video-subject", "test", "--bgm-type", "none"])
176 params = cli.build_video_params(args)
177 self.assertEqual(params.bgm_type, "")
178
179 def test_local_material_filename_resolved_to_absolute_path(self):
180 """After preprocess_video, material.url should be an absolute path, not a bare filename."""
181 import os
182 import tempfile
183 from app.utils import utils
184 from app.services import video as vd
185 from app.models.schema import MaterialInfo
186
187 local_videos_dir = utils.storage_dir("local_videos", create=True)
188 # Create a minimal valid video file for testing
189 test_filename = "_cli_test_resolve.mp4"
190 test_filepath = os.path.join(local_videos_dir, test_filename)
191 # We need a real video file; use a tiny one via moviepy
192 try:
193 from moviepy import ColorClip
194 clip = ColorClip(size=(640, 640), color=(0, 0, 0), duration=1)
195 clip.write_videofile(test_filepath, fps=1, logger=None)
196 clip.close()
197 except Exception:
198 self.skipTest("moviepy not available for creating test video")
199
200 try:
201 materials = [MaterialInfo(provider="local", url=test_filename, duration=0)]
202 result = vd.preprocess_video(materials=materials, clip_duration=4)
203 self.assertTrue(len(result) > 0, "preprocess_video should return valid materials")
204 self.assertTrue(
205 os.path.isabs(result[0].url),
206 f"material url should be absolute path, got: {result[0].url}",
207 )
208 self.assertEqual(result[0].url, test_filepath)
209 finally:
210 if os.path.exists(test_filepath):
211 os.remove(test_filepath)
212
213
214 def test_local_source_requires_video_materials(self):
215 with self.assertRaises(SystemExit) as cm:
216 cli.parse_args(["--video-subject", "test", "--video-source", "local"])
217 self.assertNotEqual(cm.exception.code, 0)
218
219 def test_local_source_stop_at_terms_rejected(self):
220 with self.assertRaises(SystemExit) as cm:
221 cli.parse_args([
222 "--video-subject", "test",
223 "--video-source", "local",
224 "--video-materials", "a.mp4",
225 "--stop-at", "terms",
226 ])
227 self.assertNotEqual(cm.exception.code, 0)
228
229
230 if __name__ == "__main__":
231 unittest.main()
232
232 lines PYTHON