From e9680d3563fe3667d04ae2aa8d3e451e4f07bd76 Mon Sep 17 00:00:00 2001 From: Crespin Alexandre <114501859+kunail0804@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:45:32 +0200 Subject: [PATCH] test(integration): add full-pipeline integration tests (#27) (#71) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests/integration/test_pipeline.py exercising the no-LLM pipeline end to end (MASTER_SPEC §26.2): Scene JSON -> Validator -> Resolver -> Category Splitter -> Prompt Builder against the shipped Knowledge Base. The Analyzer is bypassed with fixed Scene JSON inputs, so no test needs a running Ollama. - Covers the reference scenes: single character, two characters, character interaction, indoor, and outdoor. - Asserts the expected per-category prompt strings, no errors, and no unknown-concept warnings for core concepts. - Asserts deterministic outputs across repeated runs, and that expansion (school_uniform -> blazer + pleated_skirt) flows end to end. Co-authored-by: kunail0804 Co-authored-by: Claude Opus 4.8 --- tests/integration/test_pipeline.py | 180 +++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tests/integration/test_pipeline.py diff --git a/tests/integration/test_pipeline.py b/tests/integration/test_pipeline.py new file mode 100644 index 0000000..b549c33 --- /dev/null +++ b/tests/integration/test_pipeline.py @@ -0,0 +1,180 @@ +"""End-to-end compiler pipeline tests (issue #27, MASTER_SPEC §26.2). + +Exercises Scene JSON -> Validator -> Resolver -> Category Splitter -> Prompt +Builder against the shipped Knowledge Base. The Analyzer is bypassed (fixed Scene +JSON inputs), so no test requires a running Ollama. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from compiler.builder.prompt_builder import build_prompts +from compiler.common.config import Config +from compiler.common.knowledge_base import load_knowledge_base +from compiler.resolver.illustrious_resolver import resolve_scene +from compiler.splitter.category_splitter import split_into_categories +from compiler.validator.scene_validator import validate_scene + +KB_DIR = Path(__file__).resolve().parent.parent.parent / "knowledge_base" + + +@pytest.fixture(scope="module") +def knowledge_base(): + return load_knowledge_base(KB_DIR) + + +def character(id_: int, **fields) -> dict: + base = { + "id": id_, + "identity": [], + "appearance": [], + "clothing": [], + "accessories": [], + "pose": [], + "expression": [], + "actions": [], + } + base.update(fields) + return base + + +def scene(characters, interactions=(), **sections) -> dict: + return { + "characters": list(characters), + "interactions": list(interactions), + "objects": list(sections.get("objects", [])), + "environment": list(sections.get("environment", [])), + "camera": list(sections.get("camera", [])), + "lighting": list(sections.get("lighting", [])), + "metadata": {"schema_version": "1.0"}, + } + + +class Compiled: + """The outputs of a full compilation run.""" + + def __init__(self, outputs: dict[str, str], warning_codes: list[str]) -> None: + self.outputs = outputs + self.warning_codes = warning_codes + + +def compile_scene(scene_json: dict, knowledge_base, config: Config) -> Compiled: + """Run the full no-LLM pipeline and return the prompt outputs + warnings.""" + validated = validate_scene(scene_json, config) + assert validated.success, [m.code for m in validated.errors] + + resolved = resolve_scene(validated.data, knowledge_base, config) + assert resolved.success, [m.code for m in resolved.errors] + + categorized = split_into_categories(resolved.data) + assert categorized.success, [m.code for m in categorized.errors] + + built = build_prompts(categorized.data, config) + outputs = {output.name: output.value for output in built.data} + warning_codes = [m.code for m in (*validated.warnings, *resolved.warnings)] + return Compiled(outputs, warning_codes) + + +# --- reference scenes ------------------------------------------------------ + + +def test_single_character(knowledge_base) -> None: + doc = scene( + [ + character( + 0, + identity=["girl"], + appearance=["blonde hair", "blue eyes"], + clothing=["dress"], + expression=["smile"], + ) + ], + environment=["classroom"], + lighting=["sunset"], + ) + result = compile_scene(doc, knowledge_base, Config()) + assert result.outputs["character"] == "1girl" + assert result.outputs["hair"] == "blonde hair" + assert result.outputs["eyes"] == "blue eyes" + assert result.outputs["clothing"] == "dress" + assert result.outputs["expression"] == "smile" + assert result.outputs["environment"] == "classroom" + assert result.outputs["lighting"] == "sunset" + assert result.outputs["negative"] == "" + assert "SC0001" not in result.warning_codes # all core concepts are known + + +def test_two_characters(knowledge_base) -> None: + doc = scene( + [ + character(0, identity=["girl"], appearance=["long hair"]), + character(1, identity=["man"], appearance=["short hair"]), + ] + ) + result = compile_scene(doc, knowledge_base, Config()) + assert result.outputs["character"] == "1girl,1boy" + assert result.outputs["hair"] == "long hair,short hair" + + +def test_character_interaction(knowledge_base) -> None: + doc = scene( + [character(0, identity=["girl"]), character(1, identity=["boy"])], + interactions=[{"participants": [0, 1], "concept": "hug"}], + ) + result = compile_scene(doc, knowledge_base, Config()) + assert result.outputs["character"] == "1girl,1boy" + assert result.outputs["interaction"] == "hug" + + +def test_indoor_scene(knowledge_base) -> None: + doc = scene( + [character(0, identity=["girl"], pose=["sitting"])], + objects=["chair", "table"], + environment=["bedroom"], + ) + result = compile_scene(doc, knowledge_base, Config()) + assert result.outputs["environment"] == "bedroom" + assert result.outputs["objects"] == "chair,table" + assert result.outputs["pose"] == "sitting" + + +def test_outdoor_scene(knowledge_base) -> None: + doc = scene( + [character(0, identity=["girl"], actions=["walking"])], + environment=["forest"], + camera=["wide shot"], + ) + result = compile_scene(doc, knowledge_base, Config()) + assert result.outputs["environment"] == "forest" + assert result.outputs["action"] == "walking" + assert result.outputs["camera"] == "wide shot" + + +# --- determinism ----------------------------------------------------------- + + +def test_pipeline_is_deterministic(knowledge_base) -> None: + doc = scene( + [ + character( + 0, + identity=["girl"], + appearance=["blonde hair"], + clothing=["school uniform"], + ) + ], + environment=["classroom"], + ) + first = compile_scene(doc, knowledge_base, Config()) + second = compile_scene(doc, knowledge_base, Config()) + assert first.outputs == second.outputs + + +def test_expansion_flows_end_to_end(knowledge_base) -> None: + # school_uniform expands to blazer + pleated_skirt in the shipped KB. + doc = scene([character(0, identity=["girl"], clothing=["school uniform"])]) + result = compile_scene(doc, knowledge_base, Config()) + assert result.outputs["clothing"] == "school uniform,blazer,pleated skirt"