Files
lda-wf/tests/examples/test_lda_report_workflow_example.py
T

330 lines
11 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
import pytest
from examples.lda_report_workflow.build_workflow import (
build_workflow,
workflow_plan_payload,
)
from examples.lda_report_workflow.document_source import (
ListDocumentsInput,
ReadDocumentsInput,
_list_documents,
_read_documents,
)
from examples.lda_report_workflow.issue_board_source import (
CreateIssuesInput,
ResetIssueBoardInput,
_create_issues,
_reset_issue_board,
)
from examples.lda_report_workflow.report_source import (
AnalyzeDocumentsInput,
BuildReportInput,
CreateIssueDraftsInput,
FinaliseReportInput,
ReadinessReport,
RecordRevisionRequestInput,
_analyze_documents,
_build_report,
_create_issue_drafts,
_finalise_report,
_record_revision_request,
)
from wf_api.models import RawWorkflowPlan
from wf_config import load_workflow_config
from wf_server.config import build_workflow_server_from_workflow_config
EXAMPLE_DIR = Path(__file__).resolve().parents[2] / "examples" / "lda_report_workflow"
def test_lda_docs_lists_known_documents() -> None:
result = _list_documents(ListDocumentsInput())
names = {document.name for document in result.documents}
assert "project-brief.md" in names
assert "architecture-notes.md" in names
assert len(result.documents) == 5
def test_lda_docs_reads_selected_documents() -> None:
result = _read_documents(
ReadDocumentsInput(names=["project-brief.md", "roadmap.md"])
)
assert [document.name for document in result.documents] == [
"project-brief.md",
"roadmap.md",
]
assert "workflow substrate" in result.documents[0].text
def test_lda_docs_rejects_path_traversal() -> None:
with pytest.raises(ValueError, match="known document"):
_read_documents(ReadDocumentsInput(names=["../README.md"]))
def test_lda_report_source_builds_report_and_issue_drafts() -> None:
docs = _read_documents(
ReadDocumentsInput(names=["project-brief.md", "risk-register.md", "roadmap.md"])
)
analysis = _analyze_documents(AnalyzeDocumentsInput(documents=docs.documents))
report = _build_report(BuildReportInput(analysis=analysis.analysis))
issue_drafts = _create_issue_drafts(CreateIssueDraftsInput(report=report.report))
assert report.report.title == "lda.chat Thesis And Project Readiness Report"
assert "workflow substrate" in report.report.summary
assert issue_drafts.issues
assert issue_drafts.issues[0].id
assert issue_drafts.issues[0].title
def test_lda_report_source_finalises_approved_report() -> None:
docs = _read_documents(ReadDocumentsInput(names=["project-brief.md", "roadmap.md"]))
analysis = _analyze_documents(AnalyzeDocumentsInput(documents=docs.documents))
report = _build_report(BuildReportInput(analysis=analysis.analysis))
issue_drafts = _create_issue_drafts(CreateIssueDraftsInput(report=report.report))
final = _finalise_report(
FinaliseReportInput(
report=report.report,
created_issues=[],
approved=True,
selected_issue_ids=[issue_drafts.issues[0].id],
comment="Looks good.",
)
)
assert final.approved is True
assert final.markdown.startswith("# lda.chat Thesis And Project Readiness Report")
assert "Looks good." in final.markdown
def test_lda_report_source_records_revision_request() -> None:
result = _record_revision_request(
RecordRevisionRequestInput(comment="Needs revision")
)
assert result.approved is False
assert "Needs revision" in result.markdown
def test_issue_board_creates_selected_issues(tmp_path: Path) -> None:
board_path = tmp_path / "issue-board.json"
drafts = _create_issue_drafts(
CreateIssueDraftsInput(
report=ReadinessReport(
title="Test",
summary="Summary",
achievements=[],
risks=["Risk one", "Risk two"],
next_actions=[],
)
)
)
result = _create_issues(
CreateIssuesInput(
board_path=str(board_path),
issues=drafts.issues,
selected_issue_ids=[drafts.issues[0].id],
)
)
assert len(result.created_issues) == 1
assert result.created_issues[0].title == drafts.issues[0].title
assert board_path.exists()
def test_issue_board_reset_removes_existing_file(tmp_path: Path) -> None:
board_path = tmp_path / "issue-board.json"
board_path.write_text("[]", encoding="utf-8")
result = _reset_issue_board(ResetIssueBoardInput(board_path=str(board_path)))
assert result.reset is True
assert not board_path.exists()
def test_issue_board_rejects_paths_outside_example_or_temp() -> None:
unsafe_path = Path.home() / "lda-chat-unsafe-issue-board.json"
with pytest.raises(ValueError, match="board_path must stay"):
_reset_issue_board(ResetIssueBoardInput(board_path=str(unsafe_path)))
@pytest.mark.asyncio
async def test_lda_report_workflow_config_loads_sources(tmp_path: Path) -> None:
config = load_workflow_config(EXAMPLE_DIR / "wf.config.json")
config.server.store.root = tmp_path / "store"
server = build_workflow_server_from_workflow_config(config)
listed = await server.api.list_capabilities(source_id="local.lda_report")
names = {capability["name"] for capability in listed["capabilities"]}
assert "local.lda_report.build_report" in names
assert "local.lda_report.finalise_report" in names
def test_lda_report_workflow_builder_generates_committed_raw_plan() -> None:
workflow = build_workflow()
payload = workflow_plan_payload()
committed = json.loads(
(EXAMPLE_DIR / "workflow.plan.json").read_text(encoding="utf-8")
)
validated = RawWorkflowPlan.model_validate(payload)
assert workflow.name == "lda_report_case_study"
assert validated.name == "lda_report_case_study"
assert validated.start == "reset_board"
assert any(node.id == "review_issues" for node in validated.nodes)
assert payload == committed
@pytest.mark.asyncio
async def test_lda_report_workflow_artifact_interrupt_resume_path(
tmp_path: Path,
) -> None:
config = load_workflow_config(EXAMPLE_DIR / "wf.config.json")
config.server.store.root = tmp_path / "store"
server = build_workflow_server_from_workflow_config(config)
plan = json.loads((EXAMPLE_DIR / "workflow.plan.json").read_text(encoding="utf-8"))
await server.api.create_artifact_from_plan(
artifact_id="lda_report_case_study",
version=1,
title="lda.chat Report Case Study",
plan=plan,
outcomes=["completed", "cancelled"],
source_bindings={
"local.lda_docs": "local.lda_docs",
"local.lda_report": "local.lda_report",
"local.issue_board": "local.issue_board",
},
)
await server.api.save_deployment(
{
"id": "lda_report_case_study.default",
"artifact_id": "lda_report_case_study",
"artifact_version": 1,
"bindings": {
"local.lda_docs": "local.lda_docs",
"local.lda_report": "local.lda_report",
"local.issue_board": "local.issue_board",
},
}
)
run_input = json.loads((EXAMPLE_DIR / "run-input.json").read_text(encoding="utf-8"))
board_path = tmp_path / "issue-board.json"
board_path.write_text(
json.dumps(
[
{
"id": "ISSUE-099",
"title": "Stale demo issue",
"url": "local://issue-board/ISSUE-099",
}
]
),
encoding="utf-8",
)
run_input["board_path"] = str(board_path)
started = await server.api.run_deployment(
deployment_id="lda_report_case_study.default",
workflow_input=run_input,
)
assert started["status"] == "interrupted"
assert started["interrupt"]["kind"] == "issue_review"
assert started["interrupt"]["typed"] is True
assert set(started["interrupt"]["request_schema"]["required"]) == {
"report_markdown",
"proposed_issues",
}
assert set(started["interrupt"]["resume_schema"]["required"]) == {
"approved",
"selected_issue_ids",
}
proposed_ids = [
issue["id"] for issue in started["interrupt"]["payload"]["proposed_issues"]
]
assert proposed_ids
resumed = await server.api.resume_run(
run_id=started["run_id"],
resume_payload={
"approved": True,
"selected_issue_ids": proposed_ids[:2],
"comment": "Create selected issues before the defense.",
},
resume_outcome="submitted",
)
assert resumed["status"] == "completed"
assert resumed["outcome"] == "completed"
assert resumed["output"]["approved"] is True
assert resumed["output"]["created_issues"]
assert resumed["output"]["created_issues"][0]["id"] == "ISSUE-001"
assert resumed["output"]["markdown"].startswith(
"# lda.chat Thesis And Project Readiness Report"
)
@pytest.mark.asyncio
async def test_lda_report_workflow_cancelled_resume_path(tmp_path: Path) -> None:
config = load_workflow_config(EXAMPLE_DIR / "wf.config.json")
config.server.store.root = tmp_path / "store"
server = build_workflow_server_from_workflow_config(config)
plan = json.loads((EXAMPLE_DIR / "workflow.plan.json").read_text(encoding="utf-8"))
await server.api.create_artifact_from_plan(
artifact_id="lda_report_cancel_case",
version=1,
title="lda.chat Report Cancel Case",
plan=plan,
outcomes=["completed", "cancelled"],
source_bindings={
"local.lda_docs": "local.lda_docs",
"local.lda_report": "local.lda_report",
"local.issue_board": "local.issue_board",
},
)
await server.api.save_deployment(
{
"id": "lda_report_cancel_case.default",
"artifact_id": "lda_report_cancel_case",
"artifact_version": 1,
"bindings": {
"local.lda_docs": "local.lda_docs",
"local.lda_report": "local.lda_report",
"local.issue_board": "local.issue_board",
},
}
)
run_input = json.loads((EXAMPLE_DIR / "run-input.json").read_text(encoding="utf-8"))
run_input["board_path"] = str(tmp_path / "issue-board.json")
started = await server.api.run_deployment(
deployment_id="lda_report_cancel_case.default",
workflow_input=run_input,
)
resumed = await server.api.resume_run(
run_id=started["run_id"],
resume_payload={
"approved": False,
"selected_issue_ids": [],
"comment": "Revise risk wording.",
},
resume_outcome="cancelled",
)
assert resumed["status"] == "completed"
assert resumed["outcome"] == "cancelled"
assert resumed["output"]["approved"] is False
assert "Revision Requested" in resumed["output"]["markdown"]