From 29e5e65e5fea8a6ca267b78a0bb395575a64b459 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 14:47:35 +0200 Subject: [PATCH] Add node runtime contract smoke --- tools/prompt_smoke.py | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index fe5dfbb..ef1f12a 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -5998,6 +5998,69 @@ def smoke_formatter_metadata_fixtures() -> None: _expect("coworking lounge" in caption_text, "Generated training caption lost scene metadata") +def _expect_comfy_input_spec(node_name: str, group: str, input_name: str, spec: Any) -> None: + _expect(isinstance(input_name, str) and input_name, f"{node_name}.{group} has invalid input name {input_name!r}") + if group == "hidden": + _expect( + isinstance(spec, (str, tuple)), + f"{node_name}.{input_name} hidden input should be a ComfyUI hidden token or tuple", + ) + return + + _expect(isinstance(spec, tuple) and spec, f"{node_name}.{input_name} visible input has invalid spec") + type_spec = spec[0] + _expect( + isinstance(type_spec, str) or isinstance(type_spec, list), + f"{node_name}.{input_name} input type should be a socket type or choices list", + ) + if isinstance(type_spec, list): + _expect(type_spec, f"{node_name}.{input_name} choice list should not be empty") + _expect(len(spec) >= 2 and isinstance(spec[1], dict), f"{node_name}.{input_name} visible input options should be a dict") + _expect("tooltip" in spec[1], f"{node_name}.{input_name} visible input is missing tooltip") + + +def smoke_node_runtime_contracts() -> None: + node_names = sorted(sxcp_nodes.NODE_CLASS_MAPPINGS) + _expect(node_names, "Node registry is empty") + _expect( + set(sxcp_nodes.NODE_CLASS_MAPPINGS) == set(sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS), + "Node class and display registries are out of sync", + ) + _expect(len(node_names) >= 50, "Node registry unexpectedly small") + + for node_name in node_names: + node_class = sxcp_nodes.NODE_CLASS_MAPPINGS[node_name] + display_name = sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS.get(node_name) + _expect(isinstance(display_name, str) and display_name, f"{node_name} has no display name") + + input_types = node_class.INPUT_TYPES() + _expect(isinstance(input_types, dict), f"{node_name}.INPUT_TYPES should return a dict") + _expect("required" in input_types, f"{node_name}.INPUT_TYPES lost required group") + for group, inputs in input_types.items(): + _expect(group in {"required", "optional", "hidden"}, f"{node_name}.INPUT_TYPES has unknown group {group!r}") + _expect(isinstance(inputs, dict), f"{node_name}.{group} inputs should be a dict") + for input_name, spec in inputs.items(): + _expect_comfy_input_spec(node_name, group, input_name, spec) + + return_types = getattr(node_class, "RETURN_TYPES", None) + _expect(isinstance(return_types, tuple) and return_types, f"{node_name}.RETURN_TYPES should be a non-empty tuple") + for output_type in return_types: + _expect(isinstance(output_type, str) and output_type, f"{node_name}.RETURN_TYPES contains an invalid output type") + + return_names = getattr(node_class, "RETURN_NAMES", None) + if return_names is not None: + _expect(isinstance(return_names, tuple), f"{node_name}.RETURN_NAMES should be a tuple when present") + _expect(len(return_names) == len(return_types), f"{node_name}.RETURN_NAMES length does not match RETURN_TYPES") + for output_name in return_names: + _expect(isinstance(output_name, str) and output_name, f"{node_name}.RETURN_NAMES contains an invalid name") + + function_name = getattr(node_class, "FUNCTION", None) + _expect(isinstance(function_name, str) and function_name, f"{node_name}.FUNCTION should name the executor method") + _expect(callable(getattr(node_class(), function_name, None)), f"{node_name}.FUNCTION target is not callable") + category = getattr(node_class, "CATEGORY", None) + _expect(isinstance(category, str) and category, f"{node_name}.CATEGORY should be a non-empty string") + + def smoke_node_utility_registration() -> None: required_nodes = [ "SxCPGlobalSeed", @@ -7100,6 +7163,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [ ("fallback_role_graph_routes", smoke_fallback_role_graph_routes), ("expression_disabled", smoke_no_expression_fallback), ("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures), + ("node_runtime_contracts", smoke_node_runtime_contracts), ("node_utility_registration", smoke_node_utility_registration), ("server_route_payload_policy", smoke_server_route_payload_policy), ("seed_config_policy", smoke_seed_config_policy),