"""Tests for idempotent agent-docs generation (WP-0007 T01/T02).""" from __future__ import annotations from pathlib import Path import pytest from click.testing import CliRunner from kaizen_agentic.agent_docs import ( SECTION_FOOTER, SECTION_HEADING, render_installed_agents_section, upsert_installed_agents_section, ) from kaizen_agentic.cli import cli from kaizen_agentic.registry import AgentRegistry AGENTS_DIR = Path(__file__).parent.parent / "agents" @pytest.fixture def runner() -> CliRunner: return CliRunner() def _section() -> str: agents = AgentRegistry(AGENTS_DIR).list_agents()[:4] return render_installed_agents_section(agents) class TestUpsertIdempotency: def test_upsert_is_idempotent(self): section = _section() base = f"# Project\n\nintro\n\n{section}\n## Keep Me\nbody\n" once = upsert_installed_agents_section(base, section) twice = upsert_installed_agents_section(once, section) assert once == twice assert once.count(SECTION_HEADING) == 1 assert once.count(SECTION_FOOTER) == 1 # A following top-level section must survive the replace assert once.count("## Keep Me") == 1 def test_subheadings_do_not_truncate_section(self): section = _section() # The block contains '### Category' subheadings; the replace must not # stop at the first one (the original regex bug). merged = upsert_installed_agents_section("# P\n\n", section) assert merged.count(SECTION_FOOTER) == 1 assert "### " in merged # categories rendered def test_append_when_absent(self): section = _section() merged = upsert_installed_agents_section("# Project\n\nbody\n", section) assert SECTION_HEADING in merged assert merged.count(SECTION_FOOTER) == 1 class TestDocsGenerateCli: def test_generate_then_check_clean(self, runner: CliRunner, tmp_path: Path): # A project with two installed agents proj = tmp_path / "proj" (proj / "agents").mkdir(parents=True) for name, cat in (("alpha", "testing"), ("beta", "code-quality")): (proj / "agents" / f"agent-{name}.md").write_text( f"---\nname: {name}\ndescription: d{name}\ncategory: {cat}\n---\nx\n" ) gen = runner.invoke(cli, ["docs", "generate", "--target", str(proj)]) assert gen.exit_code == 0 claude = (proj / "CLAUDE.md").read_text() assert claude.count(SECTION_HEADING) == 1 assert "**alpha**" in claude and "**beta**" in claude # Second generate is a no-op again = runner.invoke(cli, ["docs", "generate", "--target", str(proj)]) assert "already up to date" in again.output # --check passes on a synced repo check = runner.invoke( cli, ["docs", "generate", "--check", "--target", str(proj)] ) assert check.exit_code == 0 assert "up to date" in check.output def test_check_fails_when_stale(self, runner: CliRunner, tmp_path: Path): proj = tmp_path / "proj" (proj / "agents").mkdir(parents=True) (proj / "agents" / "agent-alpha.md").write_text( "---\nname: alpha\ndescription: d\ncategory: testing\n---\nx\n" ) (proj / "CLAUDE.md").write_text("# Proj\n\nno agents section yet\n") check = runner.invoke( cli, ["docs", "generate", "--check", "--target", str(proj)] ) assert check.exit_code == 1 assert "out of date" in check.output