215 lines
6.9 KiB
Python
215 lines
6.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Integration Test - Archon Architecture
|
||
|
|
|
||
|
|
End-to-end test verifying the full three-layer flow:
|
||
|
|
1. Thin Hermes Profile (Layer 1)
|
||
|
|
2. Claw Code Runtime (Layer 2)
|
||
|
|
3. Gemma via Ollama (Layer 3)
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import yaml
|
||
|
|
import json
|
||
|
|
import unittest
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Dict, Any
|
||
|
|
|
||
|
|
# Add archon-poc to path
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
||
|
|
|
||
|
|
from ollama_client import OllamaClient, OllamaError
|
||
|
|
from runtime.harness import ClawHarness
|
||
|
|
from runtime.tool_registry import ToolRegistry
|
||
|
|
|
||
|
|
|
||
|
|
class TestArchonArchitecture(unittest.TestCase):
|
||
|
|
"""Integration tests for Archon three-layer architecture."""
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def setUpClass(cls):
|
||
|
|
"""Set up test fixtures."""
|
||
|
|
cls.base_path = Path(__file__).parent
|
||
|
|
|
||
|
|
# === Layer 1: Profile Tests ===
|
||
|
|
|
||
|
|
def test_01_profile_exists(self):
|
||
|
|
"""Verify profile.yaml exists and is valid."""
|
||
|
|
profile_path = self.base_path / "profile.yaml"
|
||
|
|
self.assertTrue(profile_path.exists(), "profile.yaml must exist")
|
||
|
|
|
||
|
|
with open(profile_path) as f:
|
||
|
|
profile = yaml.safe_load(f)
|
||
|
|
|
||
|
|
self.assertIn("name", profile)
|
||
|
|
self.assertIn("model", profile)
|
||
|
|
self.assertEqual(profile["model"], "gemma3:4b")
|
||
|
|
|
||
|
|
def test_02_profile_is_thin(self):
|
||
|
|
"""Verify profile is thin (< 50 lines, no logic)."""
|
||
|
|
profile_path = self.base_path / "profile.yaml"
|
||
|
|
lines = profile_path.read_text().splitlines()
|
||
|
|
|
||
|
|
self.assertLess(len(lines), 50, "Profile must be < 50 lines")
|
||
|
|
|
||
|
|
# Check for routing section (should exist)
|
||
|
|
content = "\n".join(lines)
|
||
|
|
self.assertIn("routing:", content)
|
||
|
|
|
||
|
|
# === Layer 2: Runtime Tests ===
|
||
|
|
|
||
|
|
def test_03_harness_exists(self):
|
||
|
|
"""Verify harness.py exists and imports."""
|
||
|
|
harness_path = self.base_path / "runtime" / "harness.py"
|
||
|
|
self.assertTrue(harness_path.exists())
|
||
|
|
|
||
|
|
# Already imported in setUp, if we got here it works
|
||
|
|
harness = ClawHarness()
|
||
|
|
self.assertIsNotNone(harness)
|
||
|
|
|
||
|
|
def test_04_tool_registry(self):
|
||
|
|
"""Verify tool registry works."""
|
||
|
|
registry = ToolRegistry()
|
||
|
|
|
||
|
|
# Test tool listing
|
||
|
|
tools = registry.list_tools()
|
||
|
|
self.assertGreater(len(tools), 0, "Must have at least one tool")
|
||
|
|
|
||
|
|
# Test tool parsing
|
||
|
|
parsed = registry.parse_tool_call("/time")
|
||
|
|
self.assertIsNotNone(parsed)
|
||
|
|
self.assertEqual(parsed["name"], "time")
|
||
|
|
|
||
|
|
# Test tool execution
|
||
|
|
result = registry.execute(parsed)
|
||
|
|
self.assertIn("Tool: time", result)
|
||
|
|
|
||
|
|
def test_05_harness_message_processing(self):
|
||
|
|
"""Test harness can process messages."""
|
||
|
|
harness = ClawHarness()
|
||
|
|
|
||
|
|
# Test tool invocation path
|
||
|
|
result = harness.process_message("/status")
|
||
|
|
self.assertEqual(result["status"], "success")
|
||
|
|
self.assertEqual(result["layer"], "claw_runtime")
|
||
|
|
|
||
|
|
# === Layer 3: Ollama Tests ===
|
||
|
|
|
||
|
|
def test_06_ollama_connection(self):
|
||
|
|
"""Verify Ollama is reachable."""
|
||
|
|
client = OllamaClient()
|
||
|
|
self.assertTrue(client.health_check(), "Ollama must be running")
|
||
|
|
|
||
|
|
def test_07_ollama_models(self):
|
||
|
|
"""Verify gemma model is available."""
|
||
|
|
client = OllamaClient()
|
||
|
|
models = client.list_models()
|
||
|
|
|
||
|
|
# Check for gemma3:4b (available) or gemma4:4b (target)
|
||
|
|
gemma_models = [m for m in models if "gemma" in m.lower()]
|
||
|
|
self.assertGreater(len(gemma_models), 0, "Need at least one Gemma model")
|
||
|
|
|
||
|
|
def test_08_ollama_generation(self):
|
||
|
|
"""Test basic generation through Ollama."""
|
||
|
|
client = OllamaClient()
|
||
|
|
|
||
|
|
response = client.generate(
|
||
|
|
prompt="Say 'Archon test passed' and nothing else.",
|
||
|
|
system="You are a test assistant. Be brief."
|
||
|
|
)
|
||
|
|
|
||
|
|
self.assertIn("response", response)
|
||
|
|
self.assertIsInstance(response["response"], str)
|
||
|
|
self.assertGreater(len(response["response"]), 0)
|
||
|
|
|
||
|
|
# === End-to-End Tests ===
|
||
|
|
|
||
|
|
def test_09_full_flow_tool_path(self):
|
||
|
|
"""Test full flow: Profile -> Runtime -> Tool -> Response."""
|
||
|
|
harness = ClawHarness()
|
||
|
|
|
||
|
|
# Simulate what the thin profile would do - just route to harness
|
||
|
|
result = harness.process_message("/echo Integration test")
|
||
|
|
|
||
|
|
self.assertEqual(result["status"], "success")
|
||
|
|
self.assertIn("content", result)
|
||
|
|
self.assertIn("Integration test", result["content"])
|
||
|
|
self.assertEqual(result["layer"], "claw_runtime")
|
||
|
|
self.assertEqual(result["tag"], "#archon-poc")
|
||
|
|
|
||
|
|
def test_10_full_flow_intelligence_path(self):
|
||
|
|
"""Test full flow: Profile -> Runtime -> Ollama -> Response."""
|
||
|
|
harness = ClawHarness()
|
||
|
|
|
||
|
|
# This routes through Ollama
|
||
|
|
result = harness.process_message("Say 'Archon intelligence layer active'")
|
||
|
|
|
||
|
|
self.assertEqual(result["status"], "success")
|
||
|
|
self.assertIn("content", result)
|
||
|
|
self.assertIn("layer", result)
|
||
|
|
self.assertIn("metadata", result)
|
||
|
|
|
||
|
|
def test_11_conversation_history(self):
|
||
|
|
"""Test conversation history tracking."""
|
||
|
|
harness = ClawHarness()
|
||
|
|
|
||
|
|
# Send multiple messages
|
||
|
|
harness.process_message("Message 1")
|
||
|
|
harness.process_message("Message 2")
|
||
|
|
|
||
|
|
# Check history
|
||
|
|
self.assertEqual(len(harness.conversation_history), 4) # 2 user + 2 assistant
|
||
|
|
|
||
|
|
# === Architecture Compliance Tests ===
|
||
|
|
|
||
|
|
def test_12_all_files_exist(self):
|
||
|
|
"""Verify all required files exist."""
|
||
|
|
required_files = [
|
||
|
|
"README.md",
|
||
|
|
"profile.yaml",
|
||
|
|
"runtime/harness.py",
|
||
|
|
"runtime/tool_registry.py",
|
||
|
|
"ollama_client.py",
|
||
|
|
"test_integration.py"
|
||
|
|
]
|
||
|
|
|
||
|
|
for file_path in required_files:
|
||
|
|
full_path = self.base_path / file_path
|
||
|
|
self.assertTrue(
|
||
|
|
full_path.exists(),
|
||
|
|
f"Required file missing: {file_path}"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def run_tests():
|
||
|
|
"""Run all integration tests."""
|
||
|
|
print("=" * 60)
|
||
|
|
print("ARCHON ARCHITECTURE - INTEGRATION TEST SUITE")
|
||
|
|
print("=" * 60)
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Create test suite
|
||
|
|
loader = unittest.TestLoader()
|
||
|
|
suite = loader.loadTestsFromTestCase(TestArchonArchitecture)
|
||
|
|
|
||
|
|
# Run with verbose output
|
||
|
|
runner = unittest.TextTestRunner(verbosity=2)
|
||
|
|
result = runner.run(suite)
|
||
|
|
|
||
|
|
print()
|
||
|
|
print("=" * 60)
|
||
|
|
if result.wasSuccessful():
|
||
|
|
print("✓ ALL TESTS PASSED")
|
||
|
|
print("Archon Architecture POC is working correctly!")
|
||
|
|
print("=" * 60)
|
||
|
|
return 0
|
||
|
|
else:
|
||
|
|
print("✗ SOME TESTS FAILED")
|
||
|
|
print("See output above for details")
|
||
|
|
print("=" * 60)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(run_tests())
|