generated from coulomb/repo-seed
acceptance matrix and workflow generation
This commit is contained in:
217
tests/test_workflow.py
Normal file
217
tests/test_workflow.py
Normal file
@@ -0,0 +1,217 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from infospace_bench import InfospaceError, add_artifact, create_infospace, load_infospace
|
||||
from infospace_bench.workflow import (
|
||||
load_workflows,
|
||||
plan_workflow,
|
||||
run_workflow,
|
||||
)
|
||||
|
||||
|
||||
SOURCE = """# Chapter One
|
||||
|
||||
Division of labour increases output by specializing tasks.
|
||||
"""
|
||||
|
||||
|
||||
SUMMARY_TEMPLATE = """# {{ input.title }} Summary
|
||||
|
||||
Lens: {{ macros.discipline }}
|
||||
|
||||
Source:
|
||||
{{ input.content }}
|
||||
"""
|
||||
|
||||
|
||||
ASSISTED_TEMPLATE = """Review {{ input.title }} through {{ macros.discipline }}.
|
||||
|
||||
{{ input.content }}
|
||||
"""
|
||||
|
||||
|
||||
def cli_env() -> dict[str, str]:
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = "src:/home/worsch/markitect-tool/src"
|
||||
return env
|
||||
|
||||
|
||||
def make_workflow_infospace(tmp_path: Path) -> Path:
|
||||
infospace = create_infospace(tmp_path, "pilot", name="Pilot")
|
||||
source = tmp_path / "chapter.md"
|
||||
source.write_text(SOURCE, encoding="utf-8")
|
||||
add_artifact(infospace.root, source, kind="source", title="Chapter One")
|
||||
|
||||
template_dir = infospace.root / "workflows" / "templates"
|
||||
template_dir.mkdir(parents=True, exist_ok=True)
|
||||
(template_dir / "summary.md").write_text(SUMMARY_TEMPLATE, encoding="utf-8")
|
||||
(template_dir / "review.md").write_text(ASSISTED_TEMPLATE, encoding="utf-8")
|
||||
|
||||
config_path = infospace.root / "infospace.yaml"
|
||||
config = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
config["workflows"] = [
|
||||
{
|
||||
"id": "source-summary",
|
||||
"description": "Render deterministic summaries for source artifacts.",
|
||||
"inputs": {"source": {"kind": "source"}},
|
||||
"static_macros": {"discipline": "Viable System Model"},
|
||||
"stages": [
|
||||
{
|
||||
"id": "render-summary",
|
||||
"kind": "template",
|
||||
"input": "source",
|
||||
"template": "workflows/templates/summary.md",
|
||||
"output": {
|
||||
"path": "artifacts/generated/{{ input.slug }}-summary.md",
|
||||
"artifact_id": "generated/{{ input.slug }}-summary.md",
|
||||
"kind": "generated",
|
||||
"title": "{{ input.title }} Summary",
|
||||
},
|
||||
}
|
||||
],
|
||||
"expected_evaluations": ["metrics"],
|
||||
},
|
||||
{
|
||||
"id": "assisted-review",
|
||||
"description": "Plan an assisted review without binding to a provider.",
|
||||
"inputs": {"source": {"kind": "source"}},
|
||||
"static_macros": {"discipline": "Viable System Model"},
|
||||
"stages": [
|
||||
{
|
||||
"id": "draft-review",
|
||||
"kind": "assisted",
|
||||
"input": "source",
|
||||
"template": "workflows/templates/review.md",
|
||||
"output": {
|
||||
"path": "artifacts/generated/{{ input.slug }}-review.md",
|
||||
"artifact_id": "generated/{{ input.slug }}-review.md",
|
||||
"kind": "generated",
|
||||
"title": "{{ input.title }} Review",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
config_path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8")
|
||||
return infospace.root
|
||||
|
||||
|
||||
def test_load_workflow_definitions_from_infospace_yaml(tmp_path: Path) -> None:
|
||||
root = make_workflow_infospace(tmp_path)
|
||||
|
||||
workflows = load_workflows(root)
|
||||
|
||||
assert [workflow.id for workflow in workflows] == [
|
||||
"source-summary",
|
||||
"assisted-review",
|
||||
]
|
||||
assert workflows[0].inputs["source"].kind == "source"
|
||||
assert workflows[0].stages[0].kind == "template"
|
||||
assert workflows[0].expected_evaluations == ["metrics"]
|
||||
|
||||
|
||||
def test_plan_workflow_resolves_inputs_outputs_and_assisted_requests(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = make_workflow_infospace(tmp_path)
|
||||
|
||||
plan = plan_workflow(root, "source-summary")
|
||||
assisted_plan = plan_workflow(root, "assisted-review")
|
||||
|
||||
assert plan.dry_run is True
|
||||
assert plan.status == "planned"
|
||||
assert plan.inputs[0].artifact_id == "source/chapter.md"
|
||||
assert plan.outputs[0].artifact_id == "generated/chapter-summary.md"
|
||||
assert plan.outputs[0].path == "artifacts/generated/chapter-summary.md"
|
||||
assert not (root / "artifacts" / "generated" / "chapter-summary.md").exists()
|
||||
assert assisted_plan.assisted_requests[0].stage_id == "draft-review"
|
||||
assert "Chapter One" in assisted_plan.assisted_requests[0].prompt
|
||||
assert assisted_plan.assisted_requests[0].provider_hint is None
|
||||
|
||||
|
||||
def test_run_workflow_writes_generated_artifact_manifest_and_run_record(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = make_workflow_infospace(tmp_path)
|
||||
|
||||
result = run_workflow(root, "source-summary")
|
||||
output_path = root / "artifacts" / "generated" / "chapter-summary.md"
|
||||
run_record = Path(result.run_record_path)
|
||||
loaded = load_infospace(root)
|
||||
generated = next(item for item in loaded.artifacts if item.kind == "generated")
|
||||
|
||||
assert result.status == "completed"
|
||||
assert output_path.is_file()
|
||||
assert "Lens: Viable System Model" in output_path.read_text(encoding="utf-8")
|
||||
assert generated.id == "generated/chapter-summary.md"
|
||||
assert generated.provenance["workflow_id"] == "source-summary"
|
||||
assert generated.provenance["input_artifact_id"] == "source/chapter.md"
|
||||
assert run_record.is_file()
|
||||
assert yaml.safe_load(run_record.read_text(encoding="utf-8"))["status"] == "completed"
|
||||
|
||||
|
||||
def test_assisted_stage_requires_explicit_adapter_for_run(tmp_path: Path) -> None:
|
||||
root = make_workflow_infospace(tmp_path)
|
||||
|
||||
with pytest.raises(InfospaceError) as raised:
|
||||
run_workflow(root, "assisted-review")
|
||||
|
||||
assert raised.value.code == "assisted_stage_requires_adapter"
|
||||
assert raised.value.detail["stage_id"] == "draft-review"
|
||||
|
||||
|
||||
def test_cli_workflow_inspect_plan_and_run(tmp_path: Path) -> None:
|
||||
root = make_workflow_infospace(tmp_path)
|
||||
|
||||
inspected = subprocess.run(
|
||||
[sys.executable, "-m", "infospace_bench", "workflow", "inspect", str(root)],
|
||||
check=False,
|
||||
env=cli_env(),
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
planned = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"infospace_bench",
|
||||
"workflow",
|
||||
"plan",
|
||||
str(root),
|
||||
"source-summary",
|
||||
],
|
||||
check=False,
|
||||
env=cli_env(),
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
run = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"infospace_bench",
|
||||
"workflow",
|
||||
"run",
|
||||
str(root),
|
||||
"source-summary",
|
||||
],
|
||||
check=False,
|
||||
env=cli_env(),
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
assert inspected.returncode == 0, inspected.stderr
|
||||
assert planned.returncode == 0, planned.stderr
|
||||
assert run.returncode == 0, run.stderr
|
||||
assert json.loads(inspected.stdout)["workflows"][0]["id"] == "source-summary"
|
||||
assert json.loads(planned.stdout)["status"] == "planned"
|
||||
assert json.loads(run.stdout)["outputs"][0]["artifact_id"] == (
|
||||
"generated/chapter-summary.md"
|
||||
)
|
||||
Reference in New Issue
Block a user