test_agent_profile.py 5.39 KB
"""U-004 tests: migrated agent personas compile into valid-shaped subagent
definitions and deploy correctly. No real `claude` calls here -- see
src/test_agent_e2e.py (RUN_REAL_AGENT_E2E=1) for the actual A-2 validation.

Run: python3 -m unittest src.test_agent_profile -v
"""

import os
import tempfile
import unittest

from src import agent_profile, schema


class RenderAgentDefinitionTest(unittest.TestCase):
    def test_all_three_org_nodes_render(self):
        for org_node in agent_profile.ORG_NODES:
            with self.subTest(org_node=org_node):
                content = agent_profile.render_agent_definition(org_node)
                self.assertTrue(content.startswith("---\n"))
                self.assertIn("name: " + org_node, content)
                self.assertIn("memory: project", content)
                self.assertIn("model:", content)
                self.assertIn("skills:", content)
                self.assertIn("tools:", content)

    def test_validator_has_disallowed_tools_from_agents_md_contract(self):
        content = agent_profile.render_agent_definition("validator")
        self.assertIn("disallowedTools:", content)
        self.assertIn("- Edit", content)
        self.assertIn("- Write", content)

    def test_validator_disallowed_tools_has_no_scoped_bash_pattern(self):
        # 2026-07-31: confirmed for real that any "Bash(...)" entry in a
        # subagent's disallowedTools silently disables Bash *entirely*, not
        # just that pattern -- must never reappear in the actual frontmatter
        # (see agent_profile.py's FRONTMATTER["validator"] comment for the
        # full story; the migrated AGENTS.md body is allowed to *mention*
        # "Bash(...)" in prose explaining why it's no longer used there).
        content = agent_profile.render_agent_definition("validator")
        frontmatter = content.split("---", 2)[1]
        self.assertNotIn("Bash(", frontmatter)
        self.assertIn("- Bash", frontmatter)

    def test_planner_and_coder_have_no_disallowed_tools(self):
        for org_node in ("planner", "coder"):
            content = agent_profile.render_agent_definition(org_node)
            self.assertNotIn("disallowedTools:", content)

    def test_validator_body_includes_both_soul_and_agents_md(self):
        content = agent_profile.render_agent_definition("validator")
        self.assertIn("QA Agent", content)  # from SOUL.md
        self.assertIn("AGENTS.md — QA Profile 运行契约", content)  # from AGENTS.md

    def test_no_leaked_feishu_secret_or_personal_path(self):
        for org_node in agent_profile.ORG_NODES:
            content = agent_profile.render_agent_definition(org_node)
            self.assertNotIn("MCYAbbfuEaY03ks8F45cs53qnfd", content)
            self.assertNotIn("/Users/yibo/Projects/lemonAI/graphagent", content)
            # the original told Coder to itself run this; the migrated version is
            # allowed to *mention* the flag only as a prohibition, not an instruction
            self.assertNotIn("优先使用 `claude --dangerously-skip-permissions`", content)
            self.assertNotIn("优先使用 claude --dangerously-skip-permissions", content)


class DeployAgentDefinitionsTest(unittest.TestCase):
    def test_writes_three_files_into_target_claude_agents(self):
        with tempfile.TemporaryDirectory() as tmp:
            agent_profile.deploy_agent_definitions(tmp)
            out_dir = os.path.join(tmp, ".claude", "agents")
            for org_node in agent_profile.ORG_NODES:
                path = os.path.join(out_dir, org_node + ".md")
                self.assertTrue(os.path.isfile(path))
                with open(path, encoding="utf-8") as f:
                    self.assertTrue(f.read().startswith("---\n"))


class RecordAgentProfilesTest(unittest.TestCase):
    def setUp(self):
        self.conn = schema.connect(":memory:")
        schema.create_schema(self.conn)

    def tearDown(self):
        self.conn.close()

    def test_inserts_one_row_per_org_node(self):
        agent_profile.record_agent_profiles(self.conn, "deadbeef")
        rows = self.conn.execute(
            "SELECT org_node, migrated_from_profile, current_git_commit_sha FROM agent_profile ORDER BY org_node"
        ).fetchall()
        self.assertEqual(len(rows), 3)
        by_node = {r[0]: r for r in rows}
        self.assertEqual(by_node["planner"][1], "planning")
        self.assertEqual(by_node["coder"][1], "code")
        self.assertEqual(by_node["validator"][1], "qa")
        for row in rows:
            self.assertEqual(row[2], "deadbeef")

    def test_validator_has_agents_file_path_others_do_not(self):
        agent_profile.record_agent_profiles(self.conn, "deadbeef")
        rows = dict(
            self.conn.execute("SELECT org_node, agents_file_path FROM agent_profile").fetchall()
        )
        self.assertIsNone(rows["planner"])
        self.assertIsNone(rows["coder"])
        self.assertIsNotNone(rows["validator"])

    def test_rerun_upserts_instead_of_duplicating(self):
        agent_profile.record_agent_profiles(self.conn, "sha-1")
        agent_profile.record_agent_profiles(self.conn, "sha-2")
        count = self.conn.execute("SELECT COUNT(*) FROM agent_profile").fetchone()[0]
        self.assertEqual(count, 3)
        sha = self.conn.execute(
            "SELECT current_git_commit_sha FROM agent_profile WHERE org_node = 'planner'"
        ).fetchone()[0]
        self.assertEqual(sha, "sha-2")


if __name__ == "__main__":
    unittest.main()