Files
JasonHoku-ComfyUI-Ultimate-…/tests/test_upscale_validation.py
T
Jason HokuandClaude Fable 5.1 e37e8e861c feat: Dashboard Upscale phase 2 (inline editing, cancel) + embed a ComfyUI nodes workflow in every image (Roadmap 93, 71)
Item 71: comfy_nodes_workflow.py is an exact Python port of the Revise view's "copy as nodes" graph
builder (pinned to a golden file generated from the JS, now in resources/logic_comfynodes.js) plus
lossless PNG tEXt / WebP EXIF writers in the format ComfyUI's frontend reads on drop. The Builder's new
Run Setting "Embed ComfyUI Nodes Workflow" (session_settings.pack_nodes_metadata) stamps every saved
image at every save site (generation, remote VAE, inline/deferred upscales, dashboard upscales, worker
images via a master pass after collection); export_favorites falls back to the Python builder.

Item 93: the upscale modal gets step/pipeline reordering, inline validation (mirrored server-side,
400 on bad configs), a preset bar (Update / Save as / Revert / Delete, read-modify-write), "Run once
(unsaved)", an output summary, separate Cancel and Close, a running lock and reattach after closing.
Cancel now lands within one sampler step through a per-job unet function wrapper on a cloned model
(never the process-wide interrupt flag), keeps completed outputs, and sends the final progress event.
Also fixed: the dashboard received the session's OLDEST items as "new" after an upscale (slice at the
wrong end), the poll targeted the wrong button, jobs were never pruned, two jobs could rewrite one
session's manifest (409 guard).

Known quirk recorded, not changed: a final pipeline step with several ratio/denoise values saves only
the last combination; the summary and a warning now say so.

Tests: 101 python, 59 node, 9 Playwright.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 19:01:49 -08:00

48 lines
1.8 KiB
Python

"""Server-side upscale config validation: shared fixture with tests/js/upscale_edit.test.js."""
import json
import os
import sys
import pytest
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _ROOT)
from tests.upscale_runner_loader import load_runner # noqa: E402
with open(os.path.join(_ROOT, "tests", "fixtures", "upscale_validation_cases.json"), encoding="utf-8") as _f:
CASES = json.load(_f)
ur = load_runner()
@pytest.mark.parametrize("case", CASES, ids=[c["name"] for c in CASES])
def test_validation_cases(case):
errors, _warnings = ur._validate_upscale(case["cfg"])
assert (not errors) == case["expect_ok"], errors
assert sorted({e["field"] for e in errors}) == sorted(set(case["error_fields"]))
# the string form used by the route
msgs = ur.validate_upscale_config(case["cfg"])
assert (not msgs) == case["expect_ok"]
def test_error_paths_point_at_the_step():
cfg = {"pipelines": [{"steps": [{}]}, {"steps": [{}, {"hires_denoise": "2"}]}]}
errors, _ = ur._validate_upscale(cfg)
assert [e["path"] for e in errors] == ["p1.s1"]
assert ur.validate_upscale_config(cfg)[0].startswith("p1.s1:")
def test_warnings_not_errors():
cfg = {"pipelines": [{"steps": [{"mode": "florence2_hires", "hires_denoise": "0.4,0.5",
"florence2": {"text_input": "face"}}]}],
"hires_prompt_adjust": True, "hires_prompt_text": ""}
errors, warnings = ur._validate_upscale(cfg)
assert errors == []
assert {w["field"] for w in warnings} == {"hires_denoise", "hires_prompt_text"}
def test_garbage_config_does_not_raise():
for bad in (None, [], "x", {"pipelines": "nope"}, {"pipelines": [None, 3, {"steps": "x"}]}):
assert ur.validate_upscale_config(bad)