# SimpleSyrup - workflow-focused ComfyUI extensions for image generation # Copyright (C) 2026 Artificial Sweetener and contributors # SPDX-License-Identifier: AGPL-3.0-or-later """Tests for the Grounded SAM Model Info node declaration.""" from __future__ import annotations from typing import Any, cast import pytest from simple_syrup.nodes.grounded_sam_model_info import GroundedSAMModelInfo def test_model_info_node_contract_constants() -> None: """Model info node constants match the public ComfyUI contract.""" assert GroundedSAMModelInfo.RETURN_TYPES == ("STRING",) assert GroundedSAMModelInfo.RETURN_NAMES == ("model_info",) assert GroundedSAMModelInfo.FUNCTION == "describe" assert GroundedSAMModelInfo.CATEGORY == "SimpleSyrup/Masking" def test_model_info_node_declares_expected_inputs( monkeypatch: pytest.MonkeyPatch, ) -> None: """Model info node exposes model selectors.""" class FakeChoices: """Return the known downloadable selections for declaration tests.""" def sam_choices(self) -> list[str]: """Return the expected SAM choice.""" return ["sam_hq_vit_b (379MB)"] def grounding_dino_choices(self) -> list[str]: """Return the expected GroundingDINO choice.""" return ["GroundingDINO_SwinT_OGC (694MB)"] monkeypatch.setattr(GroundedSAMModelInfo, "_choices", cast(Any, FakeChoices())) input_types: dict[str, dict[str, tuple[Any, ...]]] = ( GroundedSAMModelInfo.INPUT_TYPES() ) required = input_types["required"] assert set(required) == {"sam_model", "grounding_dino_model"} assert "sam_hq_vit_b (379MB)" in required["sam_model"][0] assert "GroundingDINO_SwinT_OGC (694MB)" in required["grounding_dino_model"][0] def test_model_info_node_uses_settings_aware_choices() -> None: """Model metadata selectors follow the downloadable-models preference.""" class FakeChoices: """Return the local-only choices supplied by settings policy.""" def sam_choices(self) -> list[str]: """Return the available SAM choices.""" return ["local-sam"] def grounding_dino_choices(self) -> list[str]: """Return the available GroundingDINO choices.""" return ["local-dino"] def reject_sentinel(self, selection: str) -> None: """Accept the deterministic test selections.""" del selection original = GroundedSAMModelInfo._choices GroundedSAMModelInfo._choices = cast(Any, FakeChoices()) try: required = GroundedSAMModelInfo.INPUT_TYPES()["required"] finally: GroundedSAMModelInfo._choices = original assert required["sam_model"][0] == ["local-sam"] assert required["grounding_dino_model"][0] == ["local-dino"] def test_model_info_node_delegates_to_metadata_provider() -> None: """Node execution delegates metadata creation to its metadata provider.""" class FakeMetadata: """Metadata double for model info.""" def describe_selection(self, sam_model: str, grounding_dino_model: str) -> str: """Return deterministic metadata.""" return f"{sam_model}|{grounding_dino_model}" class FakeChoices: """Accept all model selections while exercising metadata delegation.""" def reject_sentinel(self, selection: str) -> None: """Accept the deterministic test selections.""" del selection node = GroundedSAMModelInfo() original = GroundedSAMModelInfo._metadata original_choices = GroundedSAMModelInfo._choices GroundedSAMModelInfo._metadata = FakeMetadata() # type: ignore[assignment] GroundedSAMModelInfo._choices = cast(Any, FakeChoices()) try: result = node.describe("sam", "dino") finally: GroundedSAMModelInfo._metadata = original GroundedSAMModelInfo._choices = original_choices assert result == ("sam|dino",)