fix(douyin): require explicit self-declaration

This commit is contained in:
lmr
2026-07-18 02:07:55 +08:00
parent d32280c98b
commit e89d3a875a
4 changed files with 154 additions and 8 deletions
+98
View File
@@ -0,0 +1,98 @@
# Language: 中文
import asyncio
import unittest
from unittest.mock import AsyncMock, MagicMock, patch
from uploader.douyin_uploader import main as douyin_main
from uploader.douyin_uploader.main import DouYinVideo
class DouyinDeclarationTests(unittest.TestCase):
def test_upload_only_sets_explicit_declaration(self):
video = DouYinVideo(
"标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json",
declaration="已确认声明原文",
)
self.assertEqual(video.declaration, "已确认声明原文")
def test_missing_declaration_does_not_fall_back_to_personal_opinion(self):
video = DouYinVideo("标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json")
self.assertIsNone(video.declaration)
def test_legacy_positional_runtime_flags_keep_their_meaning(self):
video = DouYinVideo(
"标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json",
None, "", "", None, "",
"scheduled", False, False,
)
self.assertEqual(video.publish_strategy, "scheduled")
self.assertFalse(video.debug)
self.assertFalse(video.headless)
self.assertIsNone(video.declaration)
def test_apply_declaration_skips_when_unspecified(self):
video = DouYinVideo("标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json")
video.set_self_declaration = AsyncMock()
asyncio.run(video.apply_self_declaration(object()))
video.set_self_declaration.assert_not_awaited()
def test_apply_declaration_uses_exact_explicit_text(self):
page = object()
video = DouYinVideo(
"标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json",
declaration="已确认声明原文",
)
video.set_self_declaration = AsyncMock(return_value=True)
asyncio.run(video.apply_self_declaration(page))
video.set_self_declaration.assert_awaited_once_with(page, "已确认声明原文")
def test_explicit_declaration_failure_blocks_publish(self):
video = DouYinVideo(
"标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json",
declaration="已确认声明原文",
)
video.set_self_declaration = AsyncMock(return_value=False)
with self.assertRaisesRegex(RuntimeError, "自主声明"):
asyncio.run(video.apply_self_declaration(object()))
def test_declaration_failure_closes_browser_resources(self):
video = DouYinVideo(
"标题", "/tmp/demo.mp4", [], 0, "/tmp/cookie.json",
declaration="已确认声明原文",
)
video.validate_upload_args = AsyncMock()
video.fill_title_and_description = AsyncMock()
video.set_thumbnail = AsyncMock()
video.apply_self_declaration = AsyncMock(side_effect=RuntimeError("抖音自主声明设置失败"))
locator = MagicMock()
locator.set_input_files = AsyncMock()
locator.count = AsyncMock(return_value=1)
page = MagicMock()
page.goto = AsyncMock()
page.wait_for_url = AsyncMock()
page.wait_for_selector = AsyncMock()
page.locator.return_value = locator
context = MagicMock()
context.new_page = AsyncMock(return_value=page)
context.close = AsyncMock(side_effect=OSError("close failed"))
browser = MagicMock()
browser.new_context = AsyncMock(return_value=context)
browser.close = AsyncMock()
playwright = MagicMock()
playwright.chromium.launch = AsyncMock(return_value=browser)
with (
patch.object(douyin_main, "set_init_script", AsyncMock(return_value=context)),
patch.object(douyin_main.asyncio, "sleep", AsyncMock()),
self.assertRaisesRegex(RuntimeError, "自主声明"),
):
asyncio.run(video.upload(playwright))
context.close.assert_awaited_once()
browser.close.assert_awaited_once()
if __name__ == "__main__":
unittest.main()
+24
View File
@@ -68,6 +68,28 @@ class BrowserCliParserTests(unittest.TestCase):
self.assertEqual(args.thumbnail_landscape, landscape_path)
self.assertEqual(args.thumbnail_portrait, portrait_path)
def test_douyin_upload_video_accepts_explicit_declaration(self):
with tempfile.TemporaryDirectory() as tmp_dir:
video_path = Path(tmp_dir) / "demo.mp4"
video_path.write_bytes(b"video")
parser = sau_cli.build_parser()
args = parser.parse_args([
"douyin", "upload-video", "--account", "creator",
"--file", str(video_path), "--title", "标题",
"--declaration", "已确认声明原文",
])
self.assertEqual(args.declaration, "已确认声明原文")
def test_douyin_upload_video_has_no_implicit_declaration(self):
with tempfile.TemporaryDirectory() as tmp_dir:
video_path = Path(tmp_dir) / "demo.mp4"
video_path.write_bytes(b"video")
args = sau_cli.build_parser().parse_args([
"douyin", "upload-video", "--account", "creator",
"--file", str(video_path), "--title", "标题",
])
self.assertIsNone(args.declaration)
def test_tencent_upload_video_accepts_dual_thumbnail_aspects(self):
with tempfile.TemporaryDirectory() as tmp_dir:
video_path = Path(tmp_dir) / "demo.mp4"
@@ -210,6 +232,7 @@ class BrowserCliDispatchTests(unittest.TestCase):
thumbnail_portrait=Path("portrait.png"),
product_link="",
product_title="",
declaration="已确认声明原文",
debug=False,
headless=True,
)
@@ -219,6 +242,7 @@ class BrowserCliDispatchTests(unittest.TestCase):
request = mock_upload.await_args.args[0]
self.assertEqual(request.thumbnail_landscape_file, Path("landscape.png"))
self.assertEqual(request.thumbnail_portrait_file, Path("portrait.png"))
self.assertEqual(request.declaration, "已确认声明原文")
def test_dispatch_tencent_upload_video_uses_dual_thumbnail_request_fields(self):
args = Namespace(