Files
gokayfem-ComfyUI-fal-API/tests/test_schema_to_inputs.py
Gökay Aydoğan b75f731abf feat: v2.0.0 — full fal catalog coverage, dynamic nodes, core rewrite (#75)
Auto-generated nodes for the entire live fal catalog (1,391 models →
1,482 total nodes) built at startup from a committed registry, plus a
full rewrite of the core layer. All 87 v1 node keys, inputs and outputs
are preserved — existing workflows load and run unchanged.

New:
- data/fal_registry.json + scripts/build_registry.py: distills fal's
  catalog and per-endpoint OpenAPI schemas into a deterministic registry
- nodes/dynamic/: schema→node factory (typed widgets, tooltips, pricing
  in node help, IMAGE/VIDEO/AUDIO auto-upload, native VIDEO/AUDIO
  outputs, seed -1=random, force_rerun), plus a generic Fal Any Endpoint
  node for arbitrary endpoints
- nodes/utils/: rewritten core — FalApiError surfacing fal's real error
  payloads, queue progress logs, ComfyUI cancel support, retries and
  timeouts everywhere, parallel uploads/downloads, structured logging
- tests/ (45 tests incl. a legacy-key snapshot lock), CI lint+test
  workflow, weekly registry-refresh workflow that opens a PR on changes
- README regenerated from the registry (scripts/build_readme.py)

Fixed:
- GPTImage2 / GPTImage2Edit were defined but never registered
- Topaz video upscale sent a nonexistent API field (desired_increase)
- SeedVR video upscaler had its error handling commented out + wrong
  model label; ProRes enum value mismatch translated at argument level
- 10x `is ""` comparisons, bare excepts, missing HTTP timeouts,
  temp-file leaks, fps=0 crash in LoadVideoURL, duplicate enum entry
- opencv-python & friends missing from pyproject (Registry installs)
- API failures now raise visible errors instead of silently returning
  blank images or "Error:" strings that downstream nodes tried to load

BREAKING (behavioral only): failed API calls raise instead of returning
blank/black images or error strings. Node signatures are unchanged.
2026-07-02 12:46:22 +03:00

118 lines
4.1 KiB
Python

"""Unit tests for the schema→INPUT_TYPES converter."""
from __future__ import annotations
from helpers import _input, _model
def test_required_and_optional_buckets(schema_to_inputs):
model = _model([
_input("prompt", "string", required=True, multiline=True),
_input("guidance", "number", default=3.5, min=1, max=20),
])
it = schema_to_inputs.build_input_types(model)
assert "prompt" in it["required"]
assert "guidance" in it["optional"]
assert it["required"]["prompt"][0] == "STRING"
assert it["required"]["prompt"][1]["multiline"] is True
def test_enum_becomes_dropdown(schema_to_inputs):
model = _model([_input("style", "enum", enum=["a", "b"], default="b")])
it = schema_to_inputs.build_input_types(model)
spec = it["optional"]["style"]
assert spec[0] == ["a", "b"]
assert spec[1]["default"] == "b"
def test_int_range_and_default_clamp(schema_to_inputs):
model = _model([_input("steps", "integer", default=28, min=1, max=50)])
it = schema_to_inputs.build_input_types(model)
typ, opts = it["optional"]["steps"]
assert typ == "INT"
assert opts["min"] == 1 and opts["max"] == 50 and opts["default"] == 28
def test_seed_spec(schema_to_inputs):
model = _model([_input("seed", "integer", required=True)])
it = schema_to_inputs.build_input_types(model)
# seed is always optional regardless of the API marking it required
typ, opts = it["optional"]["seed"]
assert typ == "INT"
assert opts["default"] == -1
assert opts["min"] == -1
assert opts.get("control_after_generate") is True
def test_media_inputs(schema_to_inputs):
model = _model([
_input("image_url", "string", required=True, media_kind="image"),
_input("video_url", "string", media_kind="video"),
_input("audio_url", "string", media_kind="audio"),
])
it = schema_to_inputs.build_input_types(model)
assert it["required"]["image_url"][0] == "IMAGE"
assert it["optional"]["video_url"][0] == "VIDEO"
assert it["optional"]["audio_url"][0] == "AUDIO"
def test_custom_size_companions(schema_to_inputs):
model = _model([
_input(
"image_size", "enum",
enum=["square", "landscape_4_3", "custom_size"],
default="landscape_4_3", has_custom_size=True,
)
])
it = schema_to_inputs.build_input_types(model)
assert "width" in it["optional"] and "height" in it["optional"]
assert it["optional"]["width"][0] == "INT"
def test_dict_default_maps_to_custom_size(schema_to_inputs):
model = _model([
_input(
"image_size", "enum",
enum=["square", "custom_size"],
default={"width": 2048, "height": 1536},
has_custom_size=True,
)
])
it = schema_to_inputs.build_input_types(model)
assert it["optional"]["image_size"][1]["default"] == "custom_size"
assert it["optional"]["width"][1]["default"] == 2048
assert it["optional"]["height"][1]["default"] == 1536
def test_multi_select_enum_is_comma_string(schema_to_inputs):
model = _model([
_input("stems", "enum", enum=["vocals", "drums", "bass"],
default=["vocals", "drums"], is_list=True)
])
it = schema_to_inputs.build_input_types(model)
typ, opts = it["optional"]["stems"]
assert typ == "STRING"
assert opts["default"] == "vocals, drums"
assert "vocals, drums, bass" in opts["tooltip"]
def test_json_field_is_multiline_string(schema_to_inputs):
model = _model([_input("loras", "json")])
it = schema_to_inputs.build_input_types(model)
typ, opts = it["optional"]["loras"]
assert typ == "STRING"
assert opts["multiline"] is True
def test_force_rerun_always_present(schema_to_inputs):
it = schema_to_inputs.build_input_types(_model([]))
typ, opts = it["optional"]["force_rerun"]
assert typ == "BOOLEAN"
assert opts["default"] is False
def test_every_input_has_tooltip_when_description_given(schema_to_inputs):
model = _model([_input("prompt", "string", required=True, description="What to draw")])
it = schema_to_inputs.build_input_types(model)
assert it["required"]["prompt"][1]["tooltip"] == "What to draw"