From 7b26922339bda460b7ebf5ed01e5ca78cd2d5fbc Mon Sep 17 00:00:00 2001 From: Alexander Payne Date: Thu, 26 Feb 2026 13:08:48 -0500 Subject: [PATCH] test: Phase 5 Hands tests Add comprehensive tests for new Hands: TestScoutHand: - Directory structure, TOML validity, SYSTEM.md - Registry loading TestScribeHand: - Same validation pattern TestLedgerHand: - Same validation pattern TestWeaverHand: - Same validation pattern TestPhase5Schedules: - Scout: hourly (0 * * * *) - Scribe: daily 9am (0 9 * * *) - Ledger: every 6 hours (0 */6 * * *) - Weaver: Sunday 10am (0 10 * * 0) TestPhase5ApprovalGates: - All 4 Hands have approval gates TestAllHandsLoad: - All 6 Hands load together 25 tests total, all passing. --- tests/test_hands_phase5.py | 339 +++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 tests/test_hands_phase5.py diff --git a/tests/test_hands_phase5.py b/tests/test_hands_phase5.py new file mode 100644 index 0000000..929310d --- /dev/null +++ b/tests/test_hands_phase5.py @@ -0,0 +1,339 @@ +"""Tests for Phase 5 Additional Hands (Scout, Scribe, Ledger, Weaver). + +Validates the new Hands load correctly and have proper configuration. +""" + +from __future__ import annotations + +import pytest +from pathlib import Path + +from hands import HandRegistry +from hands.models import HandStatus + + +@pytest.fixture +def hands_dir(): + """Return the actual hands directory.""" + return Path("hands") + + +@pytest.mark.asyncio +class TestScoutHand: + """Scout Hand validation tests.""" + + async def test_scout_hand_exists(self, hands_dir): + """Scout hand directory should exist.""" + scout_dir = hands_dir / "scout" + assert scout_dir.exists() + assert scout_dir.is_dir() + + async def test_scout_hand_toml_valid(self, hands_dir): + """Scout HAND.toml should be valid.""" + toml_path = hands_dir / "scout" / "HAND.toml" + assert toml_path.exists() + + import tomllib + with open(toml_path, "rb") as f: + config = tomllib.load(f) + + assert config["hand"]["name"] == "scout" + assert config["hand"]["schedule"] == "0 * * * *" # Hourly + assert config["hand"]["enabled"] is True + + async def test_scout_system_md_exists(self, hands_dir): + """Scout SYSTEM.md should exist.""" + system_path = hands_dir / "scout" / "SYSTEM.md" + assert system_path.exists() + + content = system_path.read_text() + assert "Scout" in content + assert "OSINT" in content + + async def test_scout_loads_in_registry(self, hands_dir): + """Scout should load in HandRegistry.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + + hands = await registry.load_all() + + assert "scout" in hands + hand = hands["scout"] + + assert hand.name == "scout" + assert "OSINT" in hand.description or "intelligence" in hand.description.lower() + assert hand.schedule is not None + assert hand.schedule.cron == "0 * * * *" + + +@pytest.mark.asyncio +class TestScribeHand: + """Scribe Hand validation tests.""" + + async def test_scribe_hand_exists(self, hands_dir): + """Scribe hand directory should exist.""" + scribe_dir = hands_dir / "scribe" + assert scribe_dir.exists() + + async def test_scribe_hand_toml_valid(self, hands_dir): + """Scribe HAND.toml should be valid.""" + toml_path = hands_dir / "scribe" / "HAND.toml" + assert toml_path.exists() + + import tomllib + with open(toml_path, "rb") as f: + config = tomllib.load(f) + + assert config["hand"]["name"] == "scribe" + assert config["hand"]["schedule"] == "0 9 * * *" # Daily 9am + assert config["hand"]["enabled"] is True + + async def test_scribe_system_md_exists(self, hands_dir): + """Scribe SYSTEM.md should exist.""" + system_path = hands_dir / "scribe" / "SYSTEM.md" + assert system_path.exists() + + content = system_path.read_text() + assert "Scribe" in content + assert "content" in content.lower() + + async def test_scribe_loads_in_registry(self, hands_dir): + """Scribe should load in HandRegistry.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + + hands = await registry.load_all() + + assert "scribe" in hands + hand = hands["scribe"] + + assert hand.name == "scribe" + assert hand.schedule.cron == "0 9 * * *" + + +@pytest.mark.asyncio +class TestLedgerHand: + """Ledger Hand validation tests.""" + + async def test_ledger_hand_exists(self, hands_dir): + """Ledger hand directory should exist.""" + ledger_dir = hands_dir / "ledger" + assert ledger_dir.exists() + + async def test_ledger_hand_toml_valid(self, hands_dir): + """Ledger HAND.toml should be valid.""" + toml_path = hands_dir / "ledger" / "HAND.toml" + assert toml_path.exists() + + import tomllib + with open(toml_path, "rb") as f: + config = tomllib.load(f) + + assert config["hand"]["name"] == "ledger" + assert config["hand"]["schedule"] == "0 */6 * * *" # Every 6 hours + assert config["hand"]["enabled"] is True + + async def test_ledger_system_md_exists(self, hands_dir): + """Ledger SYSTEM.md should exist.""" + system_path = hands_dir / "ledger" / "SYSTEM.md" + assert system_path.exists() + + content = system_path.read_text() + assert "Ledger" in content + assert "treasury" in content.lower() + + async def test_ledger_loads_in_registry(self, hands_dir): + """Ledger should load in HandRegistry.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + + hands = await registry.load_all() + + assert "ledger" in hands + hand = hands["ledger"] + + assert hand.name == "ledger" + assert "treasury" in hand.description.lower() or "bitcoin" in hand.description.lower() + assert hand.schedule.cron == "0 */6 * * *" + + +@pytest.mark.asyncio +class TestWeaverHand: + """Weaver Hand validation tests.""" + + async def test_weaver_hand_exists(self, hands_dir): + """Weaver hand directory should exist.""" + weaver_dir = hands_dir / "weaver" + assert weaver_dir.exists() + + async def test_weaver_hand_toml_valid(self, hands_dir): + """Weaver HAND.toml should be valid.""" + toml_path = hands_dir / "weaver" / "HAND.toml" + assert toml_path.exists() + + import tomllib + with open(toml_path, "rb") as f: + config = tomllib.load(f) + + assert config["hand"]["name"] == "weaver" + assert config["hand"]["schedule"] == "0 10 * * 0" # Sunday 10am + assert config["hand"]["enabled"] is True + + async def test_weaver_system_md_exists(self, hands_dir): + """Weaver SYSTEM.md should exist.""" + system_path = hands_dir / "weaver" / "SYSTEM.md" + assert system_path.exists() + + content = system_path.read_text() + assert "Weaver" in content + assert "creative" in content.lower() + + async def test_weaver_loads_in_registry(self, hands_dir): + """Weaver should load in HandRegistry.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + + hands = await registry.load_all() + + assert "weaver" in hands + hand = hands["weaver"] + + assert hand.name == "weaver" + assert hand.schedule.cron == "0 10 * * 0" + + +@pytest.mark.asyncio +class TestPhase5Schedules: + """Validate all Phase 5 Hand schedules.""" + + async def test_scout_runs_hourly(self, hands_dir): + """Scout should run every hour.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("scout") + assert hand.schedule.cron == "0 * * * *" + + async def test_scribe_runs_daily(self, hands_dir): + """Scribe should run daily at 9am.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("scribe") + assert hand.schedule.cron == "0 9 * * *" + + async def test_ledger_runs_6_hours(self, hands_dir): + """Ledger should run every 6 hours.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("ledger") + assert hand.schedule.cron == "0 */6 * * *" + + async def test_weaver_runs_weekly(self, hands_dir): + """Weaver should run weekly on Sunday.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("weaver") + assert hand.schedule.cron == "0 10 * * 0" + + +@pytest.mark.asyncio +class TestPhase5ApprovalGates: + """Validate Phase 5 Hands have approval gates.""" + + async def test_scout_has_approval_gates(self, hands_dir): + """Scout should have approval gates.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("scout") + assert len(hand.approval_gates) >= 1 + + async def test_scribe_has_approval_gates(self, hands_dir): + """Scribe should have approval gates.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("scribe") + assert len(hand.approval_gates) >= 1 + + async def test_ledger_has_approval_gates(self, hands_dir): + """Ledger should have approval gates.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("ledger") + assert len(hand.approval_gates) >= 1 + + async def test_weaver_has_approval_gates(self, hands_dir): + """Weaver should have approval gates.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + await registry.load_all() + + hand = registry.get_hand("weaver") + assert len(hand.approval_gates) >= 1 + + +@pytest.mark.asyncio +class TestAllHandsLoad: + """Verify all 6 Hands load together.""" + + async def test_all_hands_present(self, hands_dir): + """All 6 Hands should load without errors.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmpdir: + db_path = Path(tmpdir) / "test.db" + registry = HandRegistry(hands_dir=hands_dir, db_path=db_path) + + hands = await registry.load_all() + + # All 6 Hands should be present + expected = {"oracle", "sentinel", "scout", "scribe", "ledger", "weaver"} + assert expected.issubset(set(hands.keys()))