zcbot/tests/test_seedance.py

166 lines
5.5 KiB
Python

import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from uuid import uuid4
import tools.seedance as seedance_module
from tools.seedance import SeedanceTool
class _FakeArkClient:
submitted_body = None
def __init__(self, *_args, **_kwargs):
pass
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def post_json(self, _endpoint, body, **_kwargs):
type(self).submitted_body = body
return {"id": "cgt-test"}
def get_json(self, _url, **_kwargs):
return {
"status": "succeeded",
"content": {"video_url": "https://example.com/result.mp4"},
}
def download(self, _url, dest, **_kwargs):
Path(dest).write_bytes(b"video")
class SeedanceToolTest(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name) / "user"
self.root.mkdir()
_FakeArkClient.submitted_body = None
def tearDown(self):
self._tmp.cleanup()
def make_tool(self) -> SeedanceTool:
return SeedanceTool(
ark_cfg=object(),
video_variant_cfg={
"model_id": "doubao-seedance-test",
"default_resolution": "720p",
"default_ratio": "16:9",
"default_duration": 5,
"default_watermark": False,
"default_generate_audio": False,
"price_cny_per_mtoken_text2video": 37.0,
"price_cny_per_mtoken_video2video": 22.0,
"fps": 24,
"poll_interval_s": 0,
},
variant_key="seedance_test",
working_dir=self.root,
user_root=self.root,
task_id=uuid4(),
user_id=uuid4(),
)
def test_image_to_video_builds_first_frame_and_uses_i2v_price(self):
(self.root / "source.png").write_bytes(b"\x89PNG\r\n\x1a\n")
usage = {}
with (
patch.object(seedance_module, "ArkClient", _FakeArkClient),
patch.object(
seedance_module,
"record_usage_safe",
side_effect=lambda _name, _fn, **kw: usage.update(kw),
),
):
result = self.make_tool().execute(prompt="让水流动起来", image="source.png")
content = _FakeArkClient.submitted_body["content"]
self.assertEqual(content[0], {"type": "text", "text": "让水流动起来"})
self.assertEqual(content[1]["type"], "image_url")
self.assertEqual(content[1]["role"], "first_frame")
self.assertTrue(content[1]["image_url"]["url"].startswith("data:image/png;base64,"))
self.assertIs(usage["has_video_input"], True)
self.assertEqual(usage["price_cny_per_mtoken"], 22.0)
self.assertIn("mode=image_to_video", result)
self.assertIn("image=source.png", result)
def test_text_to_video_remains_backward_compatible(self):
usage = {}
with (
patch.object(seedance_module, "ArkClient", _FakeArkClient),
patch.object(
seedance_module,
"record_usage_safe",
side_effect=lambda _name, _fn, **kw: usage.update(kw),
),
):
result = self.make_tool().execute(prompt="水泥浆缓慢流动")
self.assertEqual(
_FakeArkClient.submitted_body["content"],
[{"type": "text", "text": "水泥浆缓慢流动"}],
)
self.assertIs(usage["has_video_input"], False)
self.assertEqual(usage["price_cny_per_mtoken"], 37.0)
self.assertIn("mode=text_to_video", result)
def test_multi_image_references_use_reference_role(self):
for name in ("product-front.png", "product-side.png"):
(self.root / name).write_bytes(b"\x89PNG\r\n\x1a\n")
usage = {}
with (
patch.object(seedance_module, "ArkClient", _FakeArkClient),
patch.object(
seedance_module,
"record_usage_safe",
side_effect=lambda _name, _fn, **kw: usage.update(kw),
),
):
result = self.make_tool().execute(
prompt="参考图片1和图片2生成产品环绕展示",
reference_images=["product-front.png", "product-side.png"],
)
content = _FakeArkClient.submitted_body["content"]
self.assertEqual([part.get("role") for part in content[1:]], [
"reference_image",
"reference_image",
])
self.assertEqual(
usage["extra_units"]["reference_images"],
["product-front.png", "product-side.png"],
)
self.assertIn("mode=image_to_video", result)
self.assertIn("images=2", result)
def test_rejects_more_than_nine_images(self):
result = self.make_tool().execute(
prompt="多图参考",
reference_images=[f"image-{i}.png" for i in range(10)],
)
self.assertEqual(
result,
"[Error] Seedance 最多支持 9 张输入图片(image + reference_images 合计)。",
)
def test_rejects_image_outside_user_root(self):
outside = self.root.parent / "outside.png"
outside.write_bytes(b"\x89PNG\r\n\x1a\n")
result = self.make_tool().execute(prompt="动起来", image=str(outside))
self.assertTrue(result.startswith("[Error] 图片找不到或越界:"))
if __name__ == "__main__":
unittest.main()