Files
Jony ba920f2b66 Publish current ComfyUI O1Key code baseline
Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
2026-09-24 19:56:48 +08:00

204 lines
7.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Offline regression tests for the current O1Key Grok Video API contract."""
import sys
import unittest
from inspect import signature
from pathlib import Path
from unittest.mock import AsyncMock, patch
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PLUGIN_ROOT.parent))
from comfyui_o1key.clients.grok_video_client import GrokVideoClient
from comfyui_o1key.nodes import grok_video
class GrokVideoPayloadTests(unittest.TestCase):
def test_poll_deadline_is_shared_2000_seconds(self):
self.assertEqual(GrokVideoClient.POLL_DEADLINE_SECONDS, 2000)
self.assertEqual(
signature(GrokVideoClient.run_video_sync)
.parameters["timeout"].default,
2000,
)
def build(self, **overrides):
values = {
"operation": "generate",
"prompt": "cinematic shot",
"model": "grok-imagine-video-1.5",
"duration": 8,
"aspect_ratio": "16:9",
"resolution": "480p",
}
values.update(overrides)
return GrokVideoClient.build_video_body(**values)
def test_text_generation_supports_15_seconds_and_1080p_on_15(self):
body = self.build(duration=15, resolution="1080p")
self.assertEqual(body, {
"model": "grok-imagine-video-1.5",
"prompt": "cinematic shot",
"duration": 15,
"aspect_ratio": "16:9",
"resolution": "1080p",
})
def test_image_generation_prompt_is_optional_and_preserves_image_url(self):
body = self.build(
prompt="",
resolution="1080p",
image={"image_url": "data:image/png;base64,AAAA"},
)
self.assertNotIn("prompt", body)
self.assertEqual(body["image"], {"image_url": "data:image/png;base64,AAAA"})
def test_multireference_generation_supports_15_seconds_on_both_models(self):
for model in GrokVideoClient.MODEL_OPTIONS:
with self.subTest(model=model):
body = self.build(
model=model,
duration=15,
resolution="720p",
reference_images=[{"url": "https://example.invalid/character.png"}],
reference_audios=[{"voice_id": "nova"}],
)
self.assertEqual(body["duration"], 15)
self.assertEqual(body["reference_audios"], [{"voice_id": "nova"}])
def test_multireference_generation_rejects_1080p(self):
with self.assertRaisesRegex(ValueError, "不支持 1080p"):
self.build(
resolution="1080p",
reference_images=[{"url": "https://example.invalid/reference.png"}],
)
def test_base_model_rejects_1080p(self):
with self.assertRaisesRegex(ValueError, "仅支持 grok-imagine-video-1.5"):
self.build(model="grok-imagine-video", resolution="1080p")
def test_image_and_reference_images_are_mutually_exclusive(self):
with self.assertRaisesRegex(ValueError, "image 和 reference_images"):
self.build(
image={"url": "https://example.invalid/input.png"},
reference_images=[{"url": "https://example.invalid/reference.png"}],
)
def test_reference_audio_accepts_only_url_or_voice_id_and_caps_at_three(self):
body = self.build(reference_audios=[
{"url": "https://example.invalid/one.wav"},
{"voice_id": "nova"},
{"voice_id": "alloy"},
])
self.assertEqual(len(body["reference_audios"]), 3)
with self.assertRaisesRegex(ValueError, "最多支持 3"):
self.build(reference_audios=[{"voice_id": str(index)} for index in range(4)])
with self.assertRaisesRegex(ValueError, "url、voice_id"):
self.build(reference_audios=[{"file_id": "not-supported"}])
def test_edit_and_extension_payloads_use_their_exact_parameter_sets(self):
for model in GrokVideoClient.MODEL_OPTIONS:
with self.subTest(operation="edit", model=model):
edit = self.build(
operation="edit",
model=model,
video={"file_id": "file_grok_1"},
duration=9,
resolution="1080p",
)
self.assertEqual(edit, {
"model": model,
"prompt": "cinematic shot",
"video": {"file_id": "file_grok_1"},
})
with self.subTest(operation="extend", model=model):
extension = self.build(
operation="extend",
model=model,
video={"url": "data:video/mp4;base64,AAAA"},
duration=5,
)
self.assertEqual(extension, {
"model": model,
"prompt": "cinematic shot",
"video": {"url": "data:video/mp4;base64,AAAA"},
"duration": 5,
})
def test_extension_duration_is_limited_to_two_through_ten_seconds(self):
for duration in (1, 11):
with self.subTest(duration=duration):
with self.assertRaisesRegex(ValueError, "2 到 10 秒"):
self.build(
operation="extend",
video={"url": "https://example.invalid/input.mp4"},
duration=duration,
)
def test_error_sanitizer_removes_temporary_urls_and_base64(self):
message = GrokVideoClient._safe_error_message(
"failed https://cdn.example.invalid/result.mp4?signature=secret "
"data:video/mp4;base64,QUJDREVGRw=="
)
self.assertNotIn("signature=secret", message)
self.assertNotIn("QUJDREVGRw", message)
self.assertIn("<temporary URL omitted>", message)
self.assertIn("<base64 omitted>", message)
class GrokVideoNodeTests(unittest.TestCase):
def test_generation_schema_exposes_current_models_defaults_and_three_audio_inputs(self):
required = grok_video.O1keyGrokVideo.INPUT_TYPES()["required"]
optional = grok_video.O1keyGrokVideo.INPUT_TYPES()["optional"]
self.assertEqual(required["模型"][1]["default"], "grok-imagine-video-1.5")
self.assertEqual(required["分辨率"][1]["default"], "480p")
self.assertIn("参考音色ID(逗号分隔)", required)
self.assertEqual(
[name for name in optional if name.startswith("音频素材")],
["音频素材", "音频素材2", "音频素材3"],
)
def test_edit_schema_appends_model_selector(self):
required = grok_video.O1keyGrokVideoEdit.INPUT_TYPES()["required"]
self.assertEqual(
list(required),
["操作", "提示词", "续写时长(秒)", "模型"],
)
self.assertEqual(required["模型"][1]["default"], "grok-imagine-video-1.5")
def test_edit_rejects_video_over_87_seconds_before_upload(self):
video = type("Video", (), {"get_duration": lambda self: 8.71})()
with (
patch.object(grok_video, "VideoFromFile", object),
patch.object(grok_video, "upload_video", new=AsyncMock()) as upload,
):
with self.assertRaisesRegex(ValueError, "不能超过 8.7 秒"):
grok_video.O1keyGrokVideoEdit().generate(**{
"操作": "编辑视频",
"提示词": "make the sky golden",
"续写时长(秒)": 6,
"模型": "grok-imagine-video-1.5",
"视频素材": video,
})
upload.assert_not_awaited()
def test_voice_ids_split_on_chinese_comma_comma_and_newline(self):
self.assertEqual(
grok_video._parse_voice_ids("novaalloy\necho"),
["nova", "alloy", "echo"],
)
if __name__ == "__main__":
unittest.main()