263 lines
8.0 KiB
Python
Executable File
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())
|