Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
144 lines
6.0 KiB
Python
144 lines
6.0 KiB
Python
"""Offline tests for shared Seedance asset creation and ID reuse."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
|
|
PACKAGE_DIR = Path(__file__).resolve().parents[1]
|
|
COMFY_ROOT = PACKAGE_DIR.parent.parent
|
|
sys.path.insert(0, str(COMFY_ROOT))
|
|
sys.path.insert(0, str(PACKAGE_DIR.parent))
|
|
|
|
package = types.ModuleType("comfyui_o1key")
|
|
package.__path__ = [str(PACKAGE_DIR)]
|
|
sys.modules["comfyui_o1key"] = package
|
|
for child in ("utils", "clients"):
|
|
module = types.ModuleType(f"comfyui_o1key.{child}")
|
|
module.__path__ = [str(PACKAGE_DIR / child)]
|
|
sys.modules[f"comfyui_o1key.{child}"] = module
|
|
|
|
from comfyui_o1key.utils.seedance_assets import ( # noqa: E402
|
|
SeedanceAssetService,
|
|
seedance_asset_fingerprint,
|
|
seedance_asset_request_type,
|
|
)
|
|
|
|
|
|
class SeedanceAssetServiceTests(unittest.IsolatedAsyncioTestCase):
|
|
def test_video_routes_map_to_the_create_node_request_types(self):
|
|
self.assertEqual(seedance_asset_request_type("overseas_hc"), "hc")
|
|
self.assertEqual(seedance_asset_request_type("domestic"), "doubao")
|
|
with self.assertRaisesRegex(ValueError, "素材线路"):
|
|
seedance_asset_request_type("unknown")
|
|
|
|
async def test_active_content_cache_skips_upload_and_creation(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
media_path = Path(temp_dir, "reference.bin")
|
|
media_path.write_bytes(b"same-content")
|
|
cache_path = Path(temp_dir, "seedance_asset_cache.json")
|
|
fingerprint = seedance_asset_fingerprint(str(media_path), "doubao", "video")
|
|
|
|
first_client = type("FirstClient", (), {})()
|
|
first_client.create_hc_asset_and_wait = AsyncMock(return_value={
|
|
"Id": "asset-safe-id",
|
|
"Status": "Active",
|
|
})
|
|
first_client.get_hc_asset = AsyncMock()
|
|
first_upload = AsyncMock(return_value="https://upload.example.invalid/reference-video")
|
|
first = SeedanceAssetService(client=first_client, cache_path=str(cache_path))
|
|
created = await first.create_from_url(
|
|
name="reference",
|
|
asset_url_factory=first_upload,
|
|
asset_type="video",
|
|
request_type="doubao",
|
|
fingerprint=fingerprint,
|
|
)
|
|
self.assertFalse(created["_reused"])
|
|
first_upload.assert_awaited_once()
|
|
first_client.create_hc_asset_and_wait.assert_awaited_once()
|
|
|
|
second_client = type("SecondClient", (), {})()
|
|
second_client.get_hc_asset = AsyncMock(return_value={
|
|
"Id": "asset-safe-id",
|
|
"Status": "Active",
|
|
})
|
|
second_client.create_hc_asset_and_wait = AsyncMock()
|
|
second_upload = AsyncMock(return_value="https://upload.example.invalid/should-not-upload")
|
|
second = SeedanceAssetService(client=second_client, cache_path=str(cache_path))
|
|
reused = await second.create_from_url(
|
|
name="reference",
|
|
asset_url_factory=second_upload,
|
|
asset_type="video",
|
|
request_type="doubao",
|
|
fingerprint=fingerprint,
|
|
)
|
|
self.assertTrue(reused["_reused"])
|
|
self.assertEqual(reused["Id"], "asset-safe-id")
|
|
second_upload.assert_not_awaited()
|
|
second_client.create_hc_asset_and_wait.assert_not_awaited()
|
|
|
|
cache_payload = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
serialized = json.dumps(cache_payload, ensure_ascii=False)
|
|
self.assertIn("asset-safe-id", serialized)
|
|
self.assertNotIn("upload.example.invalid", serialized)
|
|
self.assertNotIn(str(media_path), serialized)
|
|
|
|
stale_client = type("StaleClient", (), {})()
|
|
stale_client.get_hc_asset = AsyncMock(
|
|
side_effect=RuntimeError("查询 Doubao 素材失败 (404): missing")
|
|
)
|
|
stale_client.create_hc_asset_and_wait = AsyncMock(return_value={
|
|
"Id": "asset-recreated-id",
|
|
"Status": "Active",
|
|
})
|
|
stale_upload = AsyncMock(return_value="https://upload.example.invalid/recreated")
|
|
stale = SeedanceAssetService(client=stale_client, cache_path=str(cache_path))
|
|
recreated = await stale.create_from_url(
|
|
name="reference",
|
|
asset_url_factory=stale_upload,
|
|
asset_type="video",
|
|
request_type="doubao",
|
|
fingerprint=fingerprint,
|
|
)
|
|
self.assertFalse(recreated["_reused"])
|
|
self.assertEqual(recreated["Id"], "asset-recreated-id")
|
|
stale_upload.assert_awaited_once()
|
|
stale_client.create_hc_asset_and_wait.assert_awaited_once()
|
|
|
|
async def test_cache_write_failure_does_not_discard_created_asset(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
media_path = Path(temp_dir, "reference.bin")
|
|
media_path.write_bytes(b"content")
|
|
client = type("Client", (), {})()
|
|
client.create_hc_asset_and_wait = AsyncMock(return_value={
|
|
"Id": "asset-created-id",
|
|
"Status": "Active",
|
|
})
|
|
client.get_hc_asset = AsyncMock()
|
|
service = SeedanceAssetService(
|
|
client=client,
|
|
cache_path=str(Path(temp_dir, "cache.json")),
|
|
)
|
|
service.cache.put = Mock(side_effect=OSError("read only"))
|
|
|
|
result = await service.create_from_url(
|
|
name="reference",
|
|
asset_url="https://upload.example.invalid/reference",
|
|
asset_type="image",
|
|
request_type="hc",
|
|
fingerprint=seedance_asset_fingerprint(str(media_path), "hc", "image"),
|
|
)
|
|
|
|
self.assertEqual(result["Id"], "asset-created-id")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|