"""Tests for agent_add existing-update review behavior.""" from __future__ import annotations import sys from pathlib import Path from typing import Any from unittest.mock import MagicMock import pytest SRC_DIR = Path(__file__).resolve().parents[1] if str(SRC_DIR) not in sys.path: sys.path.insert(0, str(SRC_DIR)) import agent_add # noqa: E402 from ctx.core.wiki.wiki_packs import load_merged_wiki_pages, write_wiki_base_pack # noqa: E402 class _Decision: allow = True warnings: tuple[Any, ...] = () def _agent_text( *, description: str = "Agent that reviews code changes with clear findings.", model: str = "inherit", body: str | None = None, ) -> str: body = body or ( "This agent reviews code and reports concrete risks.\n\n" "## Review Process\n\n" "Read the diff, identify regressions, and return prioritized findings." ) return "\n".join( [ "---", "name: reviewer-agent", f"description: {description}", f"model: {model}", "---", "# reviewer-agent", "", body, ] ) def _setup_paths(tmp_path: Path) -> tuple[Path, Path, Path]: wiki = tmp_path / "wiki" agents_dir = tmp_path / "agents" source = tmp_path / "reviewer-agent.md" (wiki / "entities" / "agents").mkdir(parents=True) agents_dir.mkdir(parents=True) return wiki, agents_dir, source def _patch_side_effects(monkeypatch: Any) -> MagicMock: check = MagicMock(return_value=_Decision()) monkeypatch.setattr(agent_add, "check_intake", check) monkeypatch.setattr(agent_add, "record_embedding", MagicMock()) monkeypatch.setattr(agent_add, "update_index", MagicMock()) monkeypatch.setattr(agent_add, "append_log", MagicMock()) monkeypatch.setattr(agent_add, "ensure_wiki", MagicMock()) return check def _symlink_to(target: Path, link: Path, *, target_is_directory: bool) -> None: try: link.symlink_to(target, target_is_directory=target_is_directory) except (OSError, NotImplementedError) as exc: pytest.skip(f"symlinks unavailable in this environment: {exc}") def test_add_agent_rejects_symlinked_source_before_intake( tmp_path: Path, monkeypatch: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) target = tmp_path / "real-agent.md" target.write_text(_agent_text(), encoding="utf-8") _symlink_to(target, source, target_is_directory=False) check = _patch_side_effects(monkeypatch) with pytest.raises(ValueError, match="symlinked path"): agent_add.add_agent( source_path=source, name="reviewer-agent", wiki_path=wiki, agents_dir=agents_dir, ) check.assert_not_called() assert not (agents_dir / "reviewer-agent.md").exists() def test_install_agent_rejects_symlinked_destination(tmp_path: Path) -> None: source = tmp_path / "agent.md" outside = tmp_path / "outside.md" agents_dir = tmp_path / "agents" source.write_text("# agent\n", encoding="utf-8") outside.write_text("outside\n", encoding="utf-8") agents_dir.mkdir() _symlink_to(outside, agents_dir / "agent.md", target_is_directory=False) with pytest.raises(ValueError, match="symlinked destination file"): agent_add.install_agent(source, agents_dir, "agent") assert outside.read_text(encoding="utf-8") == "outside\n" def test_existing_agent_review_skips_without_mutating_files( tmp_path: Path, monkeypatch: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) installed = agents_dir / "reviewer-agent.md" existing_text = _agent_text( description="Detailed agent with a conservative review process.", model="sonnet", ) installed.write_text(existing_text, encoding="utf-8") entity = wiki / "entities" / "agents" / "reviewer-agent.md" entity.write_text( agent_add.generate_agent_page("reviewer-agent", installed), encoding="utf-8", ) entity_text = entity.read_text(encoding="utf-8") source.write_text( _agent_text(description="Short agent.", model="haiku"), encoding="utf-8", ) check = _patch_side_effects(monkeypatch) result = agent_add.add_agent( source_path=source, name="reviewer-agent", wiki_path=wiki, agents_dir=agents_dir, review_existing=True, ) assert result["skipped"] is True assert result["update_required"] is True assert "Existing agent already exists: reviewer-agent" in result["update_review"] assert "Changed frontmatter fields:" in result["update_review"] assert "model" in result["update_review"] assert installed.read_text(encoding="utf-8") == existing_text assert entity.read_text(encoding="utf-8") == entity_text check.assert_not_called() def test_existing_agent_update_existing_applies_change( tmp_path: Path, monkeypatch: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) installed = agents_dir / "reviewer-agent.md" installed.write_text(_agent_text(), encoding="utf-8") entity = wiki / "entities" / "agents" / "reviewer-agent.md" entity.write_text("# existing entity\n", encoding="utf-8") updated_text = _agent_text( description="Updated agent with stronger review coverage.", model="opus", ) source.write_text(updated_text, encoding="utf-8") _patch_side_effects(monkeypatch) result = agent_add.add_agent( source_path=source, name="reviewer-agent", wiki_path=wiki, agents_dir=agents_dir, review_existing=True, update_existing=True, ) assert result["skipped"] is False assert result["update_required"] is False assert result["is_new_page"] is False assert installed.read_text(encoding="utf-8") == updated_text assert "Updated agent with stronger review coverage" in entity.read_text( encoding="utf-8" ) def test_new_agent_add_writes_converted_agent_mirror( tmp_path: Path, monkeypatch: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) source_text = _agent_text(description="Installable mirrored agent.") source.write_text(source_text, encoding="utf-8") _patch_side_effects(monkeypatch) result = agent_add.add_agent( source_path=source, name="reviewer-agent", wiki_path=wiki, agents_dir=agents_dir, ) mirror = wiki / "converted-agents" / "reviewer-agent.md" assert result["is_new_page"] is True assert mirror.read_text(encoding="utf-8") == source_text def test_bom_prefixed_agent_frontmatter_is_accepted( tmp_path: Path, monkeypatch: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) source.write_text("\ufeff" + _agent_text(), encoding="utf-8") _patch_side_effects(monkeypatch) result = agent_add.add_agent( source_path=source, name="reviewer-agent", wiki_path=wiki, agents_dir=agents_dir, ) assert result["is_new_page"] is True assert (wiki / "entities" / "agents" / "reviewer-agent.md").exists() def test_existing_agent_update_refreshes_converted_agent_mirror( tmp_path: Path, monkeypatch: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) installed = agents_dir / "reviewer-agent.md" installed.write_text(_agent_text(), encoding="utf-8") packs_dir = wiki / "wiki-packs" write_wiki_base_pack( pack_dir=packs_dir / "base-export-1", pack_id="base-export-1", base_export_id="wiki-export-1", pages={ "entities/agents/reviewer-agent.md": ( "# reviewer-agent\n\nExisting packed agent page.\n" ) }, ) mirror = wiki / "converted-agents" / "reviewer-agent.md" mirror.parent.mkdir(parents=True) mirror.write_text("old mirror\n", encoding="utf-8") updated_text = _agent_text(description="Updated mirrored agent.") source.write_text(updated_text, encoding="utf-8") _patch_side_effects(monkeypatch) result = agent_add.add_agent( source_path=source, name="reviewer-agent", wiki_path=wiki, agents_dir=agents_dir, review_existing=True, update_existing=True, ) assert result["is_new_page"] is False assert mirror.read_text(encoding="utf-8") == updated_text entity = wiki / "entities" / "agents" / "reviewer-agent.md" merged = load_merged_wiki_pages(packs_dir) assert not entity.exists() assert "Updated mirrored agent." in merged["entities/agents/reviewer-agent.md"] def test_main_existing_agent_prints_update_review( tmp_path: Path, monkeypatch: Any, capsys: Any, ) -> None: wiki, agents_dir, source = _setup_paths(tmp_path) installed = agents_dir / "reviewer-agent.md" installed.write_text(_agent_text(), encoding="utf-8") source.write_text( _agent_text(description="Replacement agent."), encoding="utf-8", ) _patch_side_effects(monkeypatch) monkeypatch.setattr(sys, "argv", [ "agent_add.py", "--agent-path", str(source), "--name", "reviewer-agent", "--wiki", str(wiki), "--agents-dir", str(agents_dir), ]) agent_add.main() out = capsys.readouterr().out assert "Existing agent already exists: reviewer-agent" in out assert "Use the explicit update flag" in out assert "Replacement agent." not in installed.read_text(encoding="utf-8")