.agents/tests/test_identity.py

263 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Signet Identity Functional Test
Tests that all harnesses are correctly configured to share identity.
Verifies config files, symlinks, and (optionally) live harness responses.
Run: python ~/.agents/tests/test_identity.py [--live]
"""
import subprocess
import sys
import re
import os
import signal
from pathlib import Path
EXPECTED_NAME = "Mr. Claude"
EXPECTED_OPERATOR = "Nicholai"
LIVE_MODE = "--live" in sys.argv
def test_agents_md_content():
"""Test AGENTS.md contains correct identity."""
print("1. Testing AGENTS.md content...")
agents_md = Path.home() / ".agents/AGENTS.md"
if not agents_md.exists():
print(f" ✗ AGENTS.md not found: {agents_md}")
return False
content = agents_md.read_text()
has_name = EXPECTED_NAME in content
has_operator = EXPECTED_OPERATOR in content
if has_name and has_operator:
print(f" ✓ AGENTS.md contains '{EXPECTED_NAME}' and '{EXPECTED_OPERATOR}'")
return True
else:
print(f" ✗ Identity not found in AGENTS.md")
return False
def test_claude_code_config():
"""Test Claude Code config is generated from AGENTS.md."""
print("\n2. Testing Claude Code config...")
agents_md = Path.home() / ".agents/AGENTS.md"
claude_md = Path.home() / ".claude/CLAUDE.md"
if not claude_md.exists():
print(f" ✗ Claude Code config missing: {claude_md}")
return False
agents_content = agents_md.read_text()
claude_content = claude_md.read_text()
# Check if AGENTS.md content is present in generated file
# (generated files have a header, so we check for content inclusion)
if agents_content in claude_content:
print(f" ✓ Claude Code config contains AGENTS.md")
return True
elif EXPECTED_NAME in claude_content and EXPECTED_OPERATOR in claude_content:
print(f" ~ Claude Code config has identity (different format)")
return True
else:
print(f" ✗ Claude Code config missing identity")
return False
def test_opencode_config():
"""Test OpenCode config is generated from AGENTS.md."""
print("\n3. Testing OpenCode config...")
agents_md = Path.home() / ".agents/AGENTS.md"
opencode_md = Path.home() / ".config/opencode/AGENTS.md"
if not opencode_md.exists():
print(f" ✗ OpenCode config missing: {opencode_md}")
return False
agents_content = agents_md.read_text()
opencode_content = opencode_md.read_text()
# Check if AGENTS.md content is present in generated file
if agents_content in opencode_content:
print(f" ✓ OpenCode config contains AGENTS.md")
return True
elif EXPECTED_NAME in opencode_content and EXPECTED_OPERATOR in opencode_content:
print(f" ~ OpenCode config has identity (different format)")
return True
else:
print(f" ✗ OpenCode config missing identity")
return False
def test_openclaw_config():
"""Test OpenClaw reads AGENTS.md from workspace."""
print("\n4. Testing OpenClaw config...")
agents_md = Path.home() / ".agents/AGENTS.md"
# OpenClaw uses workspace context, which includes AGENTS.md
# Just verify the file exists and is readable
if agents_md.exists() and agents_md.is_file():
content = agents_md.read_text()
if EXPECTED_NAME in content and EXPECTED_OPERATOR in content:
print(f" ✓ OpenClaw workspace has AGENTS.md")
return True
print(f" ✗ OpenClaw workspace missing AGENTS.md")
return False
def test_live_claude_code():
"""Test Claude Code responds with correct identity (requires API)."""
print("\n5. Testing Claude Code identity (live)...")
if not LIVE_MODE:
print(" ⊘ Skipped (use --live to enable)")
return True
try:
proc = subprocess.Popen(
["claude", "--print", "--dangerously-skip-permissions",
"-p", "What is your name and who is your operator? Answer in one sentence."],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
start_new_session=True
)
try:
stdout, stderr = proc.communicate(timeout=300)
output = stdout + stderr
except subprocess.TimeoutExpired:
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
proc.wait()
print(" ✗ Claude Code timed out (5m)")
return False
output_lower = output.lower()
has_name = "claude" in output_lower or "mr. claude" in output_lower
has_operator = "nicholai" in output_lower
if has_name and has_operator:
print(f" ✓ Claude Code identifies correctly")
return True
else:
print(f" ✗ Claude Code identity not confirmed")
return False
except FileNotFoundError:
print(" ⊘ Claude Code not installed (skipped)")
return True
except Exception as e:
print(f" ✗ Error: {e}")
return False
def test_live_opencode():
"""Test OpenCode responds with correct identity (requires API)."""
print("\n6. Testing OpenCode identity (live)...")
if not LIVE_MODE:
print(" ⊘ Skipped (use --live to enable)")
return True
try:
proc = subprocess.Popen(
["opencode", "run",
"What is your name and who is your operator? Answer in one sentence."],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
start_new_session=True
)
try:
stdout, stderr = proc.communicate(timeout=300)
output = stdout + stderr
except subprocess.TimeoutExpired:
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
proc.wait()
print(" ✗ OpenCode timed out (5m)")
return False
# Strip ANSI codes
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
clean_output = ansi_escape.sub('', output)
output_lower = clean_output.lower()
has_name = "claude" in output_lower or "mr. claude" in output_lower
has_operator = "nicholai" in output_lower
if has_name and has_operator:
print(f" ✓ OpenCode identifies correctly")
return True
else:
print(f" ✗ OpenCode identity not confirmed")
return False
except FileNotFoundError:
print(" ⊘ OpenCode not installed (skipped)")
return True
except Exception as e:
print(f" ✗ Error: {e}")
return False
def main():
print("=" * 60)
print("SIGNET IDENTITY TEST")
print("=" * 60)
if LIVE_MODE:
print("Mode: LIVE (includes API calls, may take several minutes)")
else:
print("Mode: FAST (config/symlink verification only)")
print("Use --live to include API response tests")
print()
tests = [
("AGENTS.md content", test_agents_md_content),
("Claude Code config", test_claude_code_config),
("OpenCode config", test_opencode_config),
("OpenClaw config", test_openclaw_config),
]
if LIVE_MODE:
tests.extend([
("Claude Code live", test_live_claude_code),
("OpenCode live", test_live_opencode),
])
results = []
for name, test_fn in tests:
try:
results.append(test_fn())
except Exception as e:
print(f" ✗ Error: {e}")
results.append(False)
print()
print("=" * 60)
passed = sum(results)
total = len(results)
if all(results):
print(f"RESULT: ALL TESTS PASSED ({passed}/{total})")
print("=" * 60)
return 0
else:
print(f"RESULT: {passed}/{total} TESTS PASSED")
print("=" * 60)
return 1
if __name__ == "__main__":
sys.exit(main())