Files
kunail0804-ComfyUI-Scene-Co…/tests/analyzer/test_response_parser.py
T
5948b20741 feat(analyzer): parse and validate Analyzer responses (#17) (#61)
Add compiler/analyzer/response_parser.py: parse_scene_response(text, logger=None)
-> CompilerResult, per MASTER_SPEC §12.2, §12.8.

- Strictly json.loads the model response; JSON is never repaired heuristically
  (§12.9), so malformed or markdown-fenced output fails here (feeding the retry
  path in #18).
- Validates the parsed document against the Scene schema; any failure (malformed
  JSON, non-object, missing field, wrong type) is reported as SC0011 and stops
  the compilation -- invalid responses are never silently accepted.
- Handles both object and string concept forms via the Scene model; confidence
  stays metadata-only.
- Stamps a default schema_version onto compiler-owned metadata (the model emits
  `"metadata": {}`) before validation.

Tests: valid response -> Scene, metadata stamping, object/string concepts +
confidence, and rejection (SC0011) of malformed JSON, non-object JSON, missing
fields, wrong types, and markdown-fenced (unrepaired) output.

Co-authored-by: kunail0804 <kunail0804@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:54:39 +02:00

111 lines
3.4 KiB
Python

"""Tests for Analyzer response parsing and validation (issue #17, §12.2, §12.8)."""
from __future__ import annotations
import json
from compiler.analyzer.response_parser import parse_scene_response
from schemas.models import Scene
def scene_response(**overrides) -> str:
scene = {
"characters": [
{
"id": 0,
"identity": ["female"],
"appearance": ["blonde hair"],
"clothing": [],
"accessories": [],
"pose": [],
"expression": [],
"actions": ["walking"],
}
],
"interactions": [],
"objects": [],
"environment": ["rain"],
"camera": [],
"lighting": [],
"metadata": {},
}
scene.update(overrides)
return json.dumps(scene)
def codes(messages) -> list[str]:
return [m.code for m in messages]
# --- valid responses -------------------------------------------------------
def test_valid_response_parses_to_scene() -> None:
result = parse_scene_response(scene_response())
assert result.success
assert isinstance(result.data, Scene)
assert result.data.characters[0].identity[0].name == "female"
assert result.data.environment[0].name == "rain"
def test_empty_metadata_is_stamped_with_schema_version() -> None:
# §12.2 shows the Analyzer emitting `"metadata": {}`; the parser stamps the
# compiler-owned schema_version so the document validates.
result = parse_scene_response(scene_response())
assert result.success
assert result.data.metadata.schema_version
def test_object_and_string_concept_forms() -> None:
scene = json.loads(scene_response())
scene["characters"][0]["appearance"] = [
"blonde hair",
{"name": "blue eyes", "metadata": {"confidence": 0.9}},
]
result = parse_scene_response(json.dumps(scene))
assert result.success
appearance = result.data.characters[0].appearance
assert appearance[0].name == "blonde hair"
assert appearance[1].name == "blue eyes"
assert appearance[1].metadata["confidence"] # confidence stays metadata-only
# --- invalid responses -> SC0011 -------------------------------------------
def test_malformed_json_is_rejected() -> None:
result = parse_scene_response("{ not valid json")
assert not result.success
assert result.data is None
assert "SC0011" in codes(result.errors)
def test_non_object_json_is_rejected() -> None:
result = parse_scene_response("[1, 2, 3]")
assert not result.success
assert "SC0011" in codes(result.errors)
def test_missing_required_field_is_rejected() -> None:
scene = json.loads(scene_response())
del scene["characters"]
result = parse_scene_response(json.dumps(scene))
assert not result.success
assert "SC0011" in codes(result.errors)
def test_wrong_type_is_rejected() -> None:
scene = json.loads(scene_response())
scene["characters"] = "not a list"
result = parse_scene_response(json.dumps(scene))
assert not result.success
assert "SC0011" in codes(result.errors)
def test_markdown_fenced_json_is_not_repaired() -> None:
# No heuristic repair: fenced output must fail rather than be stripped.
fenced = "```json\n" + scene_response() + "\n```"
result = parse_scene_response(fenced)
assert not result.success
assert "SC0011" in codes(result.errors)