Files
basicmachines-co-basic-memory/tests/scripts/test_generate_pr_infographic.py
T
Paul Hernandez 93494b8c13 ci(ci): gate bossbot on passing tests (#937)
Signed-off-by: phernandez <paul@basicmachines.co>
2026-06-09 20:47:34 -05:00

361 lines
12 KiB
Python

from pathlib import Path
import pytest
from click import unstyle
from typer.testing import CliRunner
from scripts import generate_infographic, generate_pr_infographic
def test_infographic_scripts_are_uv_typer_entrypoints() -> None:
for module in (generate_infographic, generate_pr_infographic):
source = module.__file__
assert source is not None
text = Path(source).read_text(encoding="utf-8")
assert text.startswith("#!/usr/bin/env -S uv run --script\n")
assert "# /// script" in text
assert "typer" in text
assert hasattr(module, "app")
def test_generate_pr_infographic_cli_help_exposes_useful_options() -> None:
result = CliRunner().invoke(generate_pr_infographic.app, ["--help"])
help_text = unstyle(result.output)
assert result.exit_code == 0
assert "--pr-number" in help_text
assert "--pr-body-file" in help_text
assert "--output" in help_text
assert "--theme" in help_text
assert "--provenance-output" in help_text
assert "--print-prompt" in help_text
assert "--dry-run" in help_text
def test_extract_bossbot_summary_from_pr_body() -> None:
body = "\n".join(
[
"Before",
"<!-- BM_BOSSBOT_SUMMARY:start -->",
"Reviewed SHA: abc123",
"Verdict: approve",
"<!-- BM_BOSSBOT_SUMMARY:end -->",
"After",
]
)
summary = generate_pr_infographic.extract_bossbot_summary(body)
assert summary == "Reviewed SHA: abc123\nVerdict: approve"
def test_extract_bossbot_summary_requires_managed_block() -> None:
with pytest.raises(ValueError, match="BM Bossbot summary block"):
generate_pr_infographic.extract_bossbot_summary("No managed summary")
def test_extract_infographic_theme_from_pr_body() -> None:
body = "\n".join(
[
"Before",
"<!-- BM_INFOGRAPHIC_THEME:start -->",
"Italian movie poster with a release-route map",
"<!-- BM_INFOGRAPHIC_THEME:end -->",
"After",
]
)
theme = generate_pr_infographic.extract_infographic_theme(body)
assert theme == "Italian movie poster with a release-route map"
def test_extract_infographic_theme_is_optional() -> None:
assert generate_pr_infographic.extract_infographic_theme("No theme") is None
def test_select_image_theme_reports_source() -> None:
body = "\n".join(
[
"<!-- BM_INFOGRAPHIC_THEME:start -->",
"paintings: Rembrandt-inspired merge gate",
"<!-- BM_INFOGRAPHIC_THEME:end -->",
]
)
from_body = generate_pr_infographic.select_image_theme(
pr_number=42,
summary="Summary: Adds a merge gate.",
pr_body=body,
theme_override=None,
)
from_cli = generate_pr_infographic.select_image_theme(
pr_number=42,
summary="Summary: Adds a merge gate.",
pr_body=body,
theme_override="80's action movies",
)
from_auto = generate_pr_infographic.select_image_theme(
pr_number=42,
summary="Summary: Adds a merge gate.",
pr_body="No theme",
theme_override=None,
)
assert from_body.theme == "paintings: Rembrandt-inspired merge gate"
assert from_body.source == generate_pr_infographic.ThemeSource.PR_BODY
assert from_cli.theme == "80's action movies"
assert from_cli.source == generate_pr_infographic.ThemeSource.CLI
assert from_auto.theme in generate_pr_infographic.BM_IMAGE_THEME_POOL
assert from_auto.source == generate_pr_infographic.ThemeSource.AUTO
def test_build_infographic_prompt_uses_summary_without_making_gate_claims() -> None:
prompt = generate_pr_infographic.build_infographic_prompt(
pr_number=42,
summary="Verdict: approve\nSummary: Adds a merge gate.",
theme="WWII propaganda posters with home-front logistics routes",
theme_source=generate_pr_infographic.ThemeSource.CLI,
)
assert "PR #42" in prompt
assert "Adds a merge gate" in prompt
assert "WWII propaganda posters" in prompt
assert "User-supplied visual direction" in prompt
assert "style inspiration only" in prompt
assert "polished landscape WebP editorial image" in prompt
assert "image-first composition" in prompt
assert "scene" in prompt
assert "poster" in prompt
assert "painting" in prompt
assert "classic photograph" in prompt
assert "symbolic tableau" in prompt
assert "before/after value story" in prompt
assert "Do not render an infographic" in prompt
assert "dashboard" in prompt
assert "flowchart" in prompt
assert "copyrighted characters" in prompt
assert "restrained" not in prompt
assert "non-gating" in prompt
assert "BM Bossbot Approval" in prompt
def test_build_infographic_provenance_block_includes_image_choices_without_prompt() -> None:
block = generate_pr_infographic.build_infographic_provenance_block(
pr_number=42,
output_path=Path("docs/assets/infographics/pr-42.webp"),
model="gpt-image-2",
size="1536x1024",
quality="high",
theme="classic black-and-white photography",
theme_source=generate_pr_infographic.ThemeSource.CLI,
)
assert generate_pr_infographic.PROVENANCE_START in block
assert generate_pr_infographic.PROVENANCE_END in block
assert "BM Bossbot image choices" in block
assert "Generated asset: `docs/assets/infographics/pr-42.webp`" in block
assert "Image model: `gpt-image-2`" in block
assert "Size: `1536x1024`" in block
assert "Quality: `high`" in block
assert "Image mode: `editorial-image`" in block
assert "Theme source: `cli`" in block
assert "classic black-and-white photography" in block
assert "Image prompt sent to" not in block
assert "Images API revised prompt" not in block
def test_upsert_managed_block_appends_and_replaces() -> None:
first = "\n".join(
[
generate_pr_infographic.PROVENANCE_START,
"first",
generate_pr_infographic.PROVENANCE_END,
]
)
second = "\n".join(
[
generate_pr_infographic.PROVENANCE_START,
"second",
generate_pr_infographic.PROVENANCE_END,
]
)
appended = generate_pr_infographic.upsert_managed_block(
"Existing body",
block=first,
start=generate_pr_infographic.PROVENANCE_START,
end=generate_pr_infographic.PROVENANCE_END,
)
replaced = generate_pr_infographic.upsert_managed_block(
appended,
block=second,
start=generate_pr_infographic.PROVENANCE_START,
end=generate_pr_infographic.PROVENANCE_END,
)
assert appended == f"Existing body\n\n{first}\n"
assert "first" not in replaced
assert "second" in replaced
assert replaced.count(generate_pr_infographic.PROVENANCE_START) == 1
def test_build_infographic_prompt_uses_auto_theme_as_visual_direction() -> None:
theme = generate_pr_infographic.select_image_theme(
pr_number=42,
summary="Verdict: approve\nSummary: Adds a merge gate.",
pr_body="No theme",
theme_override=None,
)
prompt = generate_pr_infographic.build_infographic_prompt(
pr_number=42,
summary="Verdict: approve\nSummary: Adds a merge gate.",
theme=theme.theme,
theme_source=theme.source,
)
assert "Selected BM visual direction" in prompt
assert theme.theme in prompt
assert "Use image-first composition" in prompt
assert "movie poster" in prompt
assert "painting" in prompt
assert "classic photograph" in prompt
assert "scene" in prompt
assert "poster" in prompt
assert "cover image" in prompt
assert "symbolic tableau" in prompt
assert "Use at most a short title" in prompt
assert "Do not render an infographic" in prompt
assert "dashboard" in prompt
assert "flowchart" in prompt
assert "bullet-list panel" in prompt
@pytest.mark.parametrize("flag", ["--print-prompt", "--dry-run"])
def test_generate_pr_infographic_can_print_prompt_without_image_call(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
flag: str,
) -> None:
body_file = tmp_path / "pr-body.md"
body_file.write_text(
"\n".join(
[
"<!-- BM_BOSSBOT_SUMMARY:start -->",
"Verdict: approve",
"Summary: Adds a merge gate.",
"<!-- BM_BOSSBOT_SUMMARY:end -->",
"<!-- BM_INFOGRAPHIC_THEME:start -->",
"space exploration and astronomy",
"<!-- BM_INFOGRAPHIC_THEME:end -->",
]
),
encoding="utf-8",
)
def fail_generate_image_result(**_: object) -> generate_infographic.GeneratedImage:
raise AssertionError("print-prompt mode must not call image generation")
monkeypatch.setattr(
generate_pr_infographic, "generate_image_result", fail_generate_image_result
)
output = tmp_path / "docs/assets/infographics/pr-42.webp"
result = CliRunner().invoke(
generate_pr_infographic.app,
[
"--pr-number",
"42",
"--pr-body-file",
str(body_file),
"--output",
str(output),
flag,
],
)
assert result.exit_code == 0, result.output
assert (
"Create a polished landscape WebP editorial image for Basic Memory PR #42"
in result.output
)
assert "Adds a merge gate" in result.output
assert "space exploration and astronomy" in result.output
assert "image-first composition" in result.output
assert "Do not render an infographic" in result.output
assert "BM Bossbot Approval" in result.output
assert not output.exists()
def test_generate_pr_infographic_writes_provenance_after_image_generation(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
body_file = tmp_path / "pr-body.md"
body_file.write_text(
"\n".join(
[
"<!-- BM_BOSSBOT_SUMMARY:start -->",
"Verdict: approve",
"Summary: Adds a merge gate.",
"<!-- BM_BOSSBOT_SUMMARY:end -->",
"<!-- BM_INFOGRAPHIC_THEME:start -->",
"paintings: Rembrandt-inspired merge gate",
"<!-- BM_INFOGRAPHIC_THEME:end -->",
]
),
encoding="utf-8",
)
def fake_generate_image_result(**kwargs: object) -> generate_infographic.GeneratedImage:
output_path = kwargs["output_path"]
assert isinstance(output_path, Path)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_bytes(b"fake-webp")
return generate_infographic.GeneratedImage(
path=output_path,
revised_prompt="A Rembrandt-inspired painting of a robot guarding a merge gate.",
)
monkeypatch.setattr(
generate_pr_infographic, "generate_image_result", fake_generate_image_result
)
output = tmp_path / "docs/assets/infographics/pr-42.webp"
provenance = tmp_path / "provenance.md"
result = CliRunner().invoke(
generate_pr_infographic.app,
[
"--pr-number",
"42",
"--pr-body-file",
str(body_file),
"--output",
str(output),
"--provenance-output",
str(provenance),
],
)
assert result.exit_code == 0, result.output
assert output.exists()
text = provenance.read_text(encoding="utf-8")
assert "Generated asset:" in text
assert "Image mode: `editorial-image`" in text
assert "Theme source: `pr-body`" in text
assert "paintings: Rembrandt-inspired merge gate" in text
assert "Image prompt sent to" not in text
assert "Images API revised prompt" not in text
assert "robot guarding a merge gate" not in text
assert "Adds a merge gate" not in text
def test_validate_output_path_must_stay_under_docs_assets_infographics(tmp_path: Path) -> None:
good = tmp_path / "docs/assets/infographics/pr-42.webp"
bad = tmp_path / "docs/assets/pr-42.webp"
assert generate_infographic.validate_output_path(good, repo_root=tmp_path) == good
with pytest.raises(ValueError, match="docs/assets/infographics"):
generate_infographic.validate_output_path(bad, repo_root=tmp_path)