feat(ml): auto-commit con 6 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Tests de estructura, conexiones y validacion para comfyui_build_ipadapter_workflow (pura)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
import pytest
|
||||
|
||||
from ml.comfyui_build_ipadapter_workflow import comfyui_build_ipadapter_workflow
|
||||
from _comfyui_wf_assert import assert_api_format, class_types
|
||||
|
||||
|
||||
def _node(wf, class_type):
|
||||
return next((n for n in wf.values() if n["class_type"] == class_type), None)
|
||||
|
||||
|
||||
def _node_id(wf, class_type):
|
||||
return next((nid for nid, n in wf.items() if n["class_type"] == class_type), None)
|
||||
|
||||
|
||||
def test_style_mode_nodos_y_conexiones():
|
||||
wf = comfyui_build_ipadapter_workflow(
|
||||
"a castle, oil painting", "ref.png",
|
||||
base_checkpoint="dreamshaper_8.safetensors", mode="style", weight=0.75,
|
||||
)
|
||||
assert_api_format(wf)
|
||||
cts = class_types(wf)
|
||||
assert "LoadImage" in cts
|
||||
assert "IPAdapterUnifiedLoader" in cts
|
||||
assert "IPAdapter" in cts
|
||||
|
||||
ckpt = _node_id(wf, "CheckpointLoaderSimple")
|
||||
load_id = _node_id(wf, "LoadImage")
|
||||
loader_id = _node_id(wf, "IPAdapterUnifiedLoader")
|
||||
apply_node = _node(wf, "IPAdapter")
|
||||
apply_id = _node_id(wf, "IPAdapter")
|
||||
|
||||
# loader toma el MODEL del checkpoint
|
||||
assert _node(wf, "IPAdapterUnifiedLoader")["inputs"]["model"] == [ckpt, 0]
|
||||
# el nodo IPAdapter cablea model/ipadapter del loader y la imagen del LoadImage
|
||||
assert apply_node["inputs"]["model"] == [loader_id, 0]
|
||||
assert apply_node["inputs"]["ipadapter"] == [loader_id, 1]
|
||||
assert apply_node["inputs"]["image"] == [load_id, 0]
|
||||
assert apply_node["inputs"]["weight"] == 0.75
|
||||
# KSampler repuntado a la salida MODEL del IPAdapter
|
||||
assert _node(wf, "KSampler")["inputs"]["model"] == [apply_id, 0]
|
||||
# defaults de modo style
|
||||
assert _node(wf, "IPAdapterUnifiedLoader")["inputs"]["preset"] == "STANDARD (medium strength)"
|
||||
assert apply_node["inputs"]["weight_type"] == "standard"
|
||||
|
||||
|
||||
def test_faceid_mode_nodos_y_conexiones():
|
||||
wf = comfyui_build_ipadapter_workflow(
|
||||
"a knight portrait", "face.png",
|
||||
base_checkpoint="dreamshaper_8.safetensors", mode="faceid",
|
||||
weight=0.9, lora_strength=0.7,
|
||||
)
|
||||
assert_api_format(wf)
|
||||
cts = class_types(wf)
|
||||
assert "IPAdapterUnifiedLoaderFaceID" in cts
|
||||
assert "IPAdapterFaceID" in cts
|
||||
# no debe haber rama style
|
||||
assert "IPAdapterUnifiedLoader" not in cts
|
||||
assert "IPAdapter" not in cts
|
||||
|
||||
loader = _node(wf, "IPAdapterUnifiedLoaderFaceID")
|
||||
apply_node = _node(wf, "IPAdapterFaceID")
|
||||
loader_id = _node_id(wf, "IPAdapterUnifiedLoaderFaceID")
|
||||
load_id = _node_id(wf, "LoadImage")
|
||||
apply_id = _node_id(wf, "IPAdapterFaceID")
|
||||
|
||||
assert loader["inputs"]["preset"] == "FACEID PLUS V2"
|
||||
assert loader["inputs"]["lora_strength"] == 0.7
|
||||
assert loader["inputs"]["provider"] == "CPU"
|
||||
assert apply_node["inputs"]["model"] == [loader_id, 0]
|
||||
assert apply_node["inputs"]["ipadapter"] == [loader_id, 1]
|
||||
assert apply_node["inputs"]["image"] == [load_id, 0]
|
||||
assert apply_node["inputs"]["weight"] == 0.9
|
||||
assert apply_node["inputs"]["weight_type"] == "linear"
|
||||
assert _node(wf, "KSampler")["inputs"]["model"] == [apply_id, 0]
|
||||
|
||||
|
||||
def test_mode_invalido_lanza_valueerror():
|
||||
with pytest.raises(ValueError):
|
||||
comfyui_build_ipadapter_workflow(
|
||||
"x", "ref.png", base_checkpoint="ck.safetensors", mode="bogus")
|
||||
|
||||
|
||||
def test_ref_image_vacia_lanza_valueerror():
|
||||
with pytest.raises(ValueError):
|
||||
comfyui_build_ipadapter_workflow(
|
||||
"x", "", base_checkpoint="ck.safetensors", mode="style")
|
||||
|
||||
|
||||
def test_preset_y_weight_type_override():
|
||||
wf = comfyui_build_ipadapter_workflow(
|
||||
"x", "ref.png", base_checkpoint="ck.safetensors", mode="style",
|
||||
preset="PLUS (high strength)", weight_type="style transfer",
|
||||
)
|
||||
assert _node(wf, "IPAdapterUnifiedLoader")["inputs"]["preset"] == "PLUS (high strength)"
|
||||
assert _node(wf, "IPAdapter")["inputs"]["weight_type"] == "style transfer"
|
||||
|
||||
|
||||
def test_determinista():
|
||||
kw = dict(base_checkpoint="ck.safetensors", mode="faceid", seed=42)
|
||||
a = comfyui_build_ipadapter_workflow("x", "ref.png", **kw)
|
||||
b = comfyui_build_ipadapter_workflow("x", "ref.png", **kw)
|
||||
assert a == b
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Tests de estructura, orden y pureza para comfyui_inject_multi_lora (funcion pura)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
import pytest
|
||||
|
||||
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
||||
from ml.comfyui_inject_multi_lora import comfyui_inject_multi_lora
|
||||
from _comfyui_wf_assert import assert_api_format, class_types
|
||||
|
||||
|
||||
def _lora_nodes(wf):
|
||||
"""Mapa lora_name -> (node_id, inputs) de los LoraLoader del workflow."""
|
||||
return {
|
||||
n["inputs"]["lora_name"]: (nid, n["inputs"])
|
||||
for nid, n in wf.items()
|
||||
if n["class_type"] == "LoraLoader"
|
||||
}
|
||||
|
||||
|
||||
def test_encadena_n_loras():
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
inj = comfyui_inject_multi_lora(
|
||||
base,
|
||||
[
|
||||
{"name": "a.safetensors", "strength_model": 0.9},
|
||||
{"name": "b.safetensors", "strength_model": 0.5},
|
||||
{"name": "c.safetensors", "strength_model": 0.3},
|
||||
],
|
||||
)
|
||||
assert_api_format(inj)
|
||||
loras = _lora_nodes(inj)
|
||||
assert set(loras) == {"a.safetensors", "b.safetensors", "c.safetensors"}
|
||||
|
||||
|
||||
def test_orden_y_cadena_validos():
|
||||
# Cadena esperada: checkpoint -> a -> b -> KSampler/CLIPTextEncode.
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
inj = comfyui_inject_multi_lora(
|
||||
base,
|
||||
[{"name": "a.safetensors"}, {"name": "b.safetensors"}],
|
||||
)
|
||||
loras = _lora_nodes(inj)
|
||||
a_id, a_in = loras["a.safetensors"]
|
||||
b_id, b_in = loras["b.safetensors"]
|
||||
ckpt = next(nid for nid, n in inj.items() if n["class_type"] == "CheckpointLoaderSimple")
|
||||
|
||||
# 'a' (primer elemento) toma el MODEL del checkpoint.
|
||||
assert a_in["model"] == [ckpt, 0]
|
||||
# 'b' (segundo elemento) toma el MODEL de 'a' (salida slot 0).
|
||||
assert b_in["model"] == [a_id, 0]
|
||||
# El KSampler queda al final de la cadena: toma el MODEL de 'b'.
|
||||
ks = next(n for n in inj.values() if n["class_type"] == "KSampler")
|
||||
assert ks["inputs"]["model"] == [b_id, 0]
|
||||
# El CLIP tambien se encadena hasta 'b' (salida slot 1).
|
||||
cte = next(n for n in inj.values() if n["class_type"] == "CLIPTextEncode")
|
||||
assert cte["inputs"]["clip"] == [b_id, 1]
|
||||
|
||||
|
||||
def test_respeta_pesos_por_posicion():
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
inj = comfyui_inject_multi_lora(
|
||||
base,
|
||||
[
|
||||
{"name": "a.safetensors", "strength_model": 0.9, "strength_clip": 0.8},
|
||||
{"name": "b.safetensors", "strength_model": 0.4},
|
||||
],
|
||||
)
|
||||
loras = _lora_nodes(inj)
|
||||
assert loras["a.safetensors"][1]["strength_model"] == 0.9
|
||||
assert loras["a.safetensors"][1]["strength_clip"] == 0.8
|
||||
assert loras["b.safetensors"][1]["strength_model"] == 0.4
|
||||
# default strength_clip = 1.0 cuando no se especifica
|
||||
assert loras["b.safetensors"][1]["strength_clip"] == 1.0
|
||||
|
||||
|
||||
def test_no_muta_la_entrada():
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
claves_antes = set(base)
|
||||
_ = comfyui_inject_multi_lora(base, [{"name": "a.safetensors"}, {"name": "b.safetensors"}])
|
||||
assert "LoraLoader" not in class_types(base)
|
||||
assert set(base) == claves_antes
|
||||
|
||||
|
||||
def test_lista_vacia_devuelve_copia_sin_loras():
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
inj = comfyui_inject_multi_lora(base, [])
|
||||
assert "LoraLoader" not in class_types(inj)
|
||||
assert class_types(inj) == class_types(base)
|
||||
|
||||
|
||||
def test_lora_sin_name_lanza_valueerror():
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
with pytest.raises(ValueError):
|
||||
comfyui_inject_multi_lora(base, [{"strength_model": 0.5}])
|
||||
with pytest.raises(ValueError):
|
||||
comfyui_inject_multi_lora(base, ["a.safetensors"]) # no es dict
|
||||
|
||||
|
||||
def test_determinista():
|
||||
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||
spec = [{"name": "a.safetensors"}, {"name": "b.safetensors"}]
|
||||
assert comfyui_inject_multi_lora(base, spec) == comfyui_inject_multi_lora(base, spec)
|
||||
Reference in New Issue
Block a user