124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
|
|
# Copyright (C) 2026 Artificial Sweetener and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
"""Tests for the Comfy authored-image file adapter."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from importlib import import_module
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import torch
|
|
from PIL import Image
|
|
|
|
from simple_syrup.runtime.image_file_loader import ImageFileLoader
|
|
|
|
|
|
def patch_path(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
path: Path,
|
|
*,
|
|
exists: bool = True,
|
|
) -> None:
|
|
"""Route Comfy annotated-path helpers to one temporary file."""
|
|
|
|
folder_paths = import_module("folder_paths")
|
|
monkeypatch.setattr(import_module("nodes"), "folder_paths", folder_paths)
|
|
monkeypatch.setattr(folder_paths, "exists_annotated_filepath", lambda value: exists)
|
|
monkeypatch.setattr(folder_paths, "get_annotated_filepath", lambda value: str(path))
|
|
|
|
|
|
def test_load_uses_native_image_loader_and_returns_one_singleton(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Static files are decoded through Comfy's LoadImage behavior."""
|
|
|
|
path = tmp_path / "image.png"
|
|
Image.new("RGB", (3, 2), color=(255, 0, 0)).save(path)
|
|
patch_path(monkeypatch, path)
|
|
calls: list[str] = []
|
|
|
|
class FakeNativeLoader:
|
|
"""Return one recognizable native image."""
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(cls, value: str) -> bool:
|
|
"""Accept the annotated path through the native contract."""
|
|
|
|
del cls, value
|
|
return True
|
|
|
|
def load_image(self, value: str) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""Record the native loader argument."""
|
|
|
|
calls.append(value)
|
|
return torch.full((1, 2, 3, 3), 0.75), torch.zeros((1, 2, 3))
|
|
|
|
monkeypatch.setattr(import_module("nodes"), "LoadImage", FakeNativeLoader)
|
|
|
|
result = ImageFileLoader().load("image.png")
|
|
|
|
assert calls == ["image.png"]
|
|
assert tuple(result.shape) == (1, 2, 3, 3)
|
|
assert torch.all(result == 0.75)
|
|
|
|
|
|
def test_available_files_uses_native_image_loader_choices(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""The node's file combo is sourced from native LoadImage inputs."""
|
|
|
|
class FakeNativeLoader:
|
|
"""Expose recognizable native widget choices."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls) -> dict[str, dict[str, tuple[list[str], dict[str, bool]]]]:
|
|
"""Return the native image-upload declaration shape."""
|
|
|
|
del cls
|
|
return {"required": {"image": (["z.png", "a.png"], {"image_upload": True})}}
|
|
|
|
monkeypatch.setattr(import_module("nodes"), "LoadImage", FakeNativeLoader)
|
|
|
|
assert ImageFileLoader().available_files() == ("z.png", "a.png")
|
|
|
|
|
|
def test_rejects_multiframe_and_missing_files(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""One selected file cannot silently expand into several list items."""
|
|
|
|
path = tmp_path / "animated.gif"
|
|
first = Image.new("RGB", (2, 2), color=0)
|
|
second = Image.new("RGB", (2, 2), color=255)
|
|
first.save(path, save_all=True, append_images=[second])
|
|
patch_path(monkeypatch, path)
|
|
loader = ImageFileLoader()
|
|
|
|
with pytest.raises(ValueError, match="contains 2 frames"):
|
|
loader.load("animated.gif")
|
|
|
|
patch_path(monkeypatch, path, exists=False)
|
|
with pytest.raises(ValueError, match="does not exist"):
|
|
loader.fingerprint("missing.png")
|
|
|
|
|
|
def test_fingerprint_reads_validated_file_contents(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Content changes invalidate the image-list node cache."""
|
|
|
|
path = tmp_path / "image.png"
|
|
path.write_bytes(b"first")
|
|
patch_path(monkeypatch, path)
|
|
loader = ImageFileLoader()
|
|
first = loader.fingerprint("image.png")
|
|
path.write_bytes(b"second")
|
|
|
|
assert loader.fingerprint("image.png") != first
|