Release standalone FromListIdx node pack

This commit is contained in:
Hopping Mad Games
2026-08-26 14:20:13 -06:00
commit 3ceab56427
14 changed files with 1521 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location(
"etk_from_list_index_test_package",
ROOT / "__init__.py",
submodule_search_locations=[str(ROOT)],
)
PACKAGE = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = PACKAGE
SPEC.loader.exec_module(PACKAGE)
EXPECTED_IDS = {
"ImageFromListIdx",
"LatentFromListIdx",
"ConditioningFromListIdx",
"AudioFromListIdx",
"DictFromListIdx",
"StrFromListIdx",
"BytesFromListIdx",
"IntFromListIdx",
"FloatFromListIdx",
}
def test_exact_workflow_ids_are_registered():
assert set(PACKAGE.NODE_CLASS_MAPPINGS) == EXPECTED_IDS
assert PACKAGE.NODE_DISPLAY_NAME_MAPPINGS == {
"AudioFromListIdx": "Audio From List Index"
}
def test_etk_schema_and_selection_contracts():
image = PACKAGE.ImageFromListIdx
latent = PACKAGE.LatentFromListIdx
conditioning = PACKAGE.ConditioningFromListIdx
assert image.CATEGORY == "ETK/Image"
assert image.INPUT_TYPES()["required"]["images"] == ("LIST", {"default": []})
assert image().get_image(["zero", "one"], 1) == ("one",)
assert latent().get_latent(["zero", "one"], -1) == ("one",)
assert conditioning().get_conditioning(["zero"], 0) == ("zero",)
with pytest.raises(IndexError, match="index 2 out of range for list length 1"):
conditioning().get_conditioning(["zero"], 2)
def test_swain_schema_and_selection_contracts():
assert PACKAGE.DictFromListIdx.INPUT_TYPES() == {
"required": {"list": ("LIST", {"default": []})},
"optional": {"int": ("INT", {"default": 0, "min": -1000, "max": 1000})},
}
assert PACKAGE.DictFromListIdx().handler(list=[{"a": 1}], int=0) == ({"a": 1},)
assert PACKAGE.StrFromListIdx().handler(list=["a", "b"], int=1) == (
"b",
["a", "b"],
)
assert PACKAGE.BytesFromListIdx().handler(list=[[b"value"]], int=0) == (b"value",)
assert PACKAGE.IntFromListIdx().handler(list=["7"], int=0) == (7, ["7"])
assert PACKAGE.FloatFromListIdx().handler(list=["1.25"], int=0) == (
1.25,
["1.25"],
)
def test_audio_normalization_matches_comfy_audio_shape():
torch = pytest.importorskip("torch")
source = {"waveform": torch.tensor([0.0, 0.5]), "sample_rate": 24000, "tag": "x"}
selected = PACKAGE.AudioFromListIdx().get_audio([source], 0)[0]
assert selected["waveform"].shape == (1, 1, 2)
assert selected["sample_rate"] == 24000
assert selected["tag"] == "x"
@pytest.mark.parametrize(
"node",
[
PACKAGE.DictFromListIdx,
PACKAGE.StrFromListIdx,
PACKAGE.BytesFromListIdx,
PACKAGE.IntFromListIdx,
PACKAGE.FloatFromListIdx,
],
)
def test_swain_nodes_reject_missing_and_out_of_range_values(node):
with pytest.raises(ValueError, match="Must provide an input"):
node().handler(list=None, int=0)
with pytest.raises(ValueError, match="index must be in the range"):
node().handler(list=["only"], int=1)
+58
View File
@@ -0,0 +1,58 @@
from pathlib import Path
import pytest
from tools import publish_registry_release
def test_publish_stops_before_registry_when_manager_gate_fails(monkeypatch, tmp_path):
calls = []
def fail_gate(source):
raise RuntimeError("public clone failed")
monkeypatch.setattr(publish_registry_release, "verify_manager_install", fail_gate)
monkeypatch.setattr(
publish_registry_release.subprocess,
"run",
lambda *args, **kwargs: calls.append((args, kwargs)),
)
with pytest.raises(RuntimeError, match="public clone failed"):
publish_registry_release.publish_release(
tmp_path,
comfy="comfy",
token="private-token",
changelog="release",
)
assert calls == []
def test_publish_keeps_token_out_of_arguments_and_environment(monkeypatch, tmp_path):
calls = []
monkeypatch.setattr(
publish_registry_release,
"verify_manager_install",
lambda source: ("1.0.3", "https://example.test/owner/repo", "abc123"),
)
def record_run(command, **kwargs):
calls.append((command, kwargs))
monkeypatch.setattr(publish_registry_release.subprocess, "run", record_run)
publish_registry_release.publish_release(
Path(tmp_path),
comfy="comfy",
token="private-token",
changelog="release notes",
)
assert [command for command, _ in calls] == [
["comfy", "node", "validate"],
["comfy", "node", "publish"],
]
publish_kwargs = calls[1][1]
assert publish_kwargs["input"] == "private-token\n"
assert "private-token" not in repr(calls[1][0])
assert "private-token" not in repr(publish_kwargs["env"])
assert publish_kwargs["env"]["COMFY_NODE_CHANGELOG"] == "release notes"
+55
View File
@@ -0,0 +1,55 @@
import importlib.util
from pathlib import Path
import pytest
SCRIPT = Path(__file__).parents[1] / "tools" / "verify_manager_install.py"
SPEC = importlib.util.spec_from_file_location("verify_manager_install", SCRIPT)
release_gate = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
SPEC.loader.exec_module(release_gate)
def test_manager_clone_command_matches_manager_install_shape(tmp_path):
destination = tmp_path / "custom_nodes" / "etk-from-list-index"
assert release_gate.manager_clone_command("https://example.test/owner/repo", destination) == [
"git",
"clone",
"-v",
"--recursive",
"--progress",
"--",
"https://example.test/owner/repo",
str(destination),
]
def test_anonymous_environment_removes_ambient_git_credentials(tmp_path):
env = release_gate.anonymous_git_environment(
{
"PATH": "/usr/bin",
"GIT_ASKPASS": "credential-helper",
"GIT_CONFIG_GLOBAL": "/secret/config",
"GIT_SSH_COMMAND": "ssh -i /secret/key",
},
tmp_path,
)
assert env["GIT_ASKPASS"] == "/bin/false"
assert env["GIT_CONFIG_GLOBAL"] == "/dev/null"
assert env["GIT_TERMINAL_PROMPT"] == "0"
assert env["HOME"] == str(tmp_path)
assert "GIT_SSH_COMMAND" not in env
@pytest.mark.parametrize(
"url",
[
"",
"ssh://git@example.test/owner/repo",
"https://user:password@example.test/owner/repo",
"https://example.test/owner/repo?token=secret",
],
)
def test_public_repository_url_rejects_nonanonymous_forms(url):
with pytest.raises(release_gate.VerificationError):
release_gate.validate_public_repository_url(url)