Files
ComfyAssets-ComfyUI_PromptM…/tests/test_inspection_parity_facts.py
T
Vito 1ff2ddce05 fix(inspector): fall back to the positive's category and tags for recipe-linked images (#267)
* fix(inspector): fall back to the positive's category and tags for recipe-linked images

Maintainer ruling D39 (2026-10-21): a recipe-linked image shows the recipe's
own category and tags when set, otherwise the linked positive prompt's, each
field on its own and without merging tag sets. Set category (#259) and the
Images category and Tags filters work on the positive, so the panel now agrees
with them.

The inspection library replaces metadata_scope with category_scope and
tags_scope so the category chip's hidden scope prefix (D21) and the tag chips'
filter-button rule (#260 D2) follow the value actually shown. History's media
inspector uses the same shared rule. The concept fixture's positive_links
workaround is gone: the comparison now matches the concept without it.

* test(inspector): assert the D39 fallback tag chips in their saved order

* fix(inspector): treat Unicode whitespace as a blank History recipe category

* test(inspector): cover mixed-scope panels, invalid library scopes and the skipped positive tags query
2026-10-21 04:18:28 -07:00

114 lines
4.0 KiB
Python

"""Shared parity vectors: the workspace's JS facts match ``vectors.json``.
Each ``inspection`` vector's file goes through the real current-file route
pipeline (``inspect_current_file`` then ``encode_inspection_response``). The
exact response text is handed to Node, where the workspace's own
``projectInspectionPayload`` validates it and ``extractGenerationFacts`` reads
it. Python and JS therefore assert the same files with the same expectations.
"""
import json
import os
import shutil
import subprocess # nosec B404
import tempfile
import unittest
from pathlib import Path
from py.api.inspection_service import inspect_current_file
from utils.inspection_response import encode_inspection_response
try:
from tests import inspection_parity as parity
from tests.open_handles import assert_closed
except ImportError: # pragma: no cover - discover -s tests puts tests/ first
import inspection_parity as parity
from open_handles import assert_closed
HARNESS = (
Path(__file__).resolve().parent / "js" / "support" / "inspection-parity-facts.mjs"
)
LIBRARY = {
"status": "none",
"scope": "saved-library",
"positive_id": None,
"pair_id": None,
"title": None,
"category": None,
"tags": [],
"category_scope": None,
"tags_scope": None,
}
NODE_TIMEOUT_SECONDS = 120
def _encoded(name: str, path: Path) -> str:
observed = inspect_current_file(
path, source_label="parity", saved=None, library=LIBRARY
)
body = encode_inspection_response(
image={"root_index": 0, "path": f"{name}.png", "filename": f"{name}.png"},
root_scope="a" * 64,
file=observed["file"],
prompts=observed["prompts"],
resolution=observed["resolution"],
inspection=observed["inspection"],
library=observed["library"],
)
return body.decode("utf-8")
def _responses(vectors, directory: Path) -> list:
responses = []
for vector in vectors:
name = vector["fixture"]
if name in parity.TEST_TIME:
path = parity.fixture_path(name, directory)
parity.write_png(path, parity.build(name))
else:
path = parity.fixture_path(name)
responses.append({"name": vector["name"], "body": _encoded(name, path)})
return responses
class TestParityFacts(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.addCleanup(assert_closed, self, temporary.name)
self.directory = Path(temporary.name)
def test_vectors_json_facts_match_workspace_js(self):
node = shutil.which("node")
if node is None: # pragma: no cover - CI runners and dev hosts ship Node
if os.environ.get("CI"):
self.fail("Node.js is required in CI for the facts vectors")
self.skipTest("Node.js is not installed")
vectors = [v for v in parity.load_vectors() if v["profile"] == "inspection"]
self.assertTrue(vectors)
payload = json.dumps(_responses(vectors, self.directory))
# Fixed local Node harness, no shell.
completed = subprocess.run( # nosec B603
[node, str(HARNESS)],
input=payload.encode("utf-8"),
capture_output=True,
timeout=NODE_TIMEOUT_SECONDS,
check=False,
)
self.assertEqual(completed.returncode, 0, completed.stderr.decode("utf-8"))
observed = json.loads(completed.stdout)
for vector in vectors:
with self.subTest(vector=vector["name"]):
self.assertEqual(observed[vector["name"]], vector["expect"]["facts"])
def test_default_vectors_carry_no_facts(self):
# Facts are a workspace (inspection) concern; default vectors pin the
# ingestion resolver only.
for vector in parity.load_vectors():
if vector["profile"] == "default":
self.assertIsNone(vector["expect"]["facts"], vector["name"])
if __name__ == "__main__":
unittest.main()