#!/usr/bin/env python3 """ Aetheel — Full Feature Test Script (cross-platform) ==================================================== Runs all tests and smoke checks for every Aetheel feature. Works on Windows, Linux, and macOS. Usage: uv run python test_all.py # or python test_all.py """ import importlib import json import os import shutil import subprocess import sys import tempfile PASS = 0 FAIL = 0 SKIP = 0 def _pass(msg): global PASS PASS += 1 print(f" ✅ {msg}") def _fail(msg): global FAIL FAIL += 1 print(f" ❌ {msg}") def _skip(msg): global SKIP SKIP += 1 print(f" ⚠️ {msg}") def section(title): print(f"\n━━━ {title} ━━━") def run_cmd(args, cwd=None, timeout=120): """Run a command and return (returncode, stdout).""" try: result = subprocess.run( args, capture_output=True, text=True, cwd=cwd, timeout=timeout, ) return result.returncode, result.stdout + result.stderr except FileNotFoundError: return -1, f"Command not found: {args[0]}" except subprocess.TimeoutExpired: return -1, "Timeout" # ========================================================================= section("1. Environment Check") # ========================================================================= # Python version py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" if sys.version_info >= (3, 12): _pass(f"Python {py_ver} (>= 3.12 required)") else: _fail(f"Python {py_ver} — need >= 3.12") # uv if shutil.which("uv"): rc, out = run_cmd(["uv", "--version"]) _pass(f"uv found: {out.strip()}") else: _skip("uv not found — using pip directly") # Working directory if os.path.isfile("pyproject.toml"): _pass("Running from Aetheel directory") else: _fail("pyproject.toml not found — run from the Aetheel/ directory") sys.exit(1) # ========================================================================= section("2. Dependency Check") # ========================================================================= required = { "pytest": "pytest", "pytest_asyncio": "pytest-asyncio", "hypothesis": "hypothesis", "click": "click", "aiohttp": "aiohttp", "apscheduler": "apscheduler", } for mod_name, pkg_name in required.items(): try: importlib.import_module(mod_name) _pass(f"{pkg_name} installed") except ImportError: _fail(f"{pkg_name} NOT installed — run: uv sync --extra test") # ========================================================================= section("3. Pytest — Unit Tests") # ========================================================================= print(" Running full test suite...") rc, out = run_cmd( [sys.executable, "-m", "pytest", "tests/", "-v", "--tb=short", "--ignore=tests/test_scheduler.py"], timeout=120, ) # Print last 20 lines lines = out.strip().splitlines() for line in lines[-20:]: print(f" {line}") if rc == 0: _pass("Core test suite passed") else: _fail("Core test suite had failures") # Scheduler tests print("\n Attempting scheduler tests...") rc2, out2 = run_cmd( [sys.executable, "-m", "pytest", "tests/test_scheduler.py", "-v", "--tb=short"], timeout=30, ) if rc2 == 0: _pass("Scheduler tests passed") else: _skip("Scheduler tests skipped (apscheduler import issue)") # ========================================================================= section("4. Config System") # ========================================================================= try: from config import load_config, AetheelConfig, MCPConfig, MCPServerConfig, write_mcp_config from config import HeartbeatConfig, WebChatConfig, HooksConfig, WebhookConfig cfg = load_config() assert isinstance(cfg, AetheelConfig) assert cfg.claude.no_tools is False assert len(cfg.claude.allowed_tools) > 0 assert hasattr(cfg, "heartbeat") assert hasattr(cfg, "webchat") assert hasattr(cfg, "mcp") assert hasattr(cfg, "hooks") assert hasattr(cfg, "webhooks") _pass("Config loads with all sections (heartbeat, webchat, mcp, hooks, webhooks)") except Exception as e: _fail(f"Config system: {e}") # MCP config writer try: with tempfile.TemporaryDirectory() as tmpdir: mcp = MCPConfig(servers={ "test": MCPServerConfig(command="echo", args=["hi"], env={"K": "V"}) }) write_mcp_config(mcp, tmpdir, use_claude=True) with open(os.path.join(tmpdir, ".mcp.json")) as f: data = json.load(f) assert "mcpServers" in data and "test" in data["mcpServers"] write_mcp_config(mcp, tmpdir, use_claude=False) with open(os.path.join(tmpdir, "opencode.json")) as f: data = json.load(f) assert "mcp" in data _pass("MCP config writer (Claude + OpenCode formats)") except Exception as e: _fail(f"MCP config writer: {e}") # ========================================================================= section("5. System Prompt") # ========================================================================= try: from agent.opencode_runtime import build_aetheel_system_prompt prompt = build_aetheel_system_prompt() for s in ["Your Tools", "Self-Modification", "Subagents & Teams"]: assert s in prompt, f"Missing: {s}" _pass("System prompt contains all new sections") except Exception as e: _fail(f"System prompt: {e}") # ========================================================================= section("6. CLI") # ========================================================================= rc, out = run_cmd([sys.executable, "cli.py", "--help"]) if rc == 0: _pass("CLI --help works") else: _fail("CLI --help failed") for cmd in ["start", "chat", "status", "doctor"]: rc, _ = run_cmd([sys.executable, "cli.py", cmd, "--help"]) if rc == 0: _pass(f"CLI command '{cmd}' exists") else: _fail(f"CLI command '{cmd}' missing") for grp in ["cron", "config", "memory"]: rc, _ = run_cmd([sys.executable, "cli.py", grp, "--help"]) if rc == 0: _pass(f"CLI group '{grp}' exists") else: _fail(f"CLI group '{grp}' missing") # ========================================================================= section("7. Heartbeat Parser") # ========================================================================= try: from heartbeat.heartbeat import HeartbeatRunner tests = [ ("Every 30 minutes", "*/30 * * * *"), ("Every hour", "0 * * * *"), ("Every 2 hours", "0 */2 * * *"), ("Every morning (9:00 AM)", "0 9 * * *"), ("Every evening (6:00 PM)", "0 18 * * *"), ] for header, expected in tests: result = HeartbeatRunner._parse_schedule_header(header) assert result == expected, f"{header}: got {result}" _pass(f"Schedule parser: all {len(tests)} patterns correct") except Exception as e: _fail(f"Heartbeat parser: {e}") # ========================================================================= section("8. Hook System") # ========================================================================= try: from hooks.hooks import HookManager, HookEvent mgr = HookManager() received = [] mgr.register("gateway:startup", lambda e: received.append(e.event_key)) mgr.trigger(HookEvent(type="gateway", action="startup")) assert received == ["gateway:startup"] _pass("Programmatic hook register + trigger") mgr2 = HookManager() mgr2.register("test:event", lambda e: e.messages.append("hello")) event = HookEvent(type="test", action="event") msgs = mgr2.trigger(event) assert "hello" in msgs _pass("Hook message passing") # File-based hook discovery with tempfile.TemporaryDirectory() as tmpdir: hook_dir = os.path.join(tmpdir, "hooks", "test-hook") os.makedirs(hook_dir) with open(os.path.join(hook_dir, "HOOK.md"), "w") as f: f.write("---\nname: test-hook\nevents: [gateway:startup]\n---\n# Test\n") with open(os.path.join(hook_dir, "handler.py"), "w") as f: f.write("def handle(event):\n event.messages.append('file-hook-fired')\n") mgr3 = HookManager(workspace_dir=tmpdir) hooks = mgr3.discover() assert len(hooks) == 1 event3 = HookEvent(type="gateway", action="startup") msgs3 = mgr3.trigger(event3) assert "file-hook-fired" in msgs3 _pass("File-based hook discovery + execution") except Exception as e: _fail(f"Hook system: {e}") # ========================================================================= section("9. Webhook Receiver") # ========================================================================= try: from webhooks.receiver import WebhookReceiver, WebhookConfig as WHConfig config = WHConfig(enabled=True, port=0, host="127.0.0.1", token="test") receiver = WebhookReceiver( ai_handler_fn=lambda msg: "ok", send_fn=lambda *a: None, config=config, ) # Check routes exist route_info = [str(r) for r in receiver._app.router.routes()] has_wake = any("wake" in r for r in route_info) has_agent = any("agent" in r for r in route_info) has_health = any("health" in r for r in route_info) assert has_wake and has_agent and has_health _pass("Webhook routes registered (wake, agent, health)") # Auth check assert receiver._check_auth(type("R", (), {"headers": {"Authorization": "Bearer test"}, "query": {}})()) assert not receiver._check_auth(type("R", (), {"headers": {}, "query": {}})()) _pass("Webhook bearer token auth works") except Exception as e: _fail(f"Webhook receiver: {e}") # ========================================================================= section("10. SubagentBus") # ========================================================================= try: from agent.subagent import SubagentBus, SubagentManager bus = SubagentBus() received = [] bus.subscribe("ch1", lambda msg, sender: received.append((msg, sender))) bus.publish("ch1", "hello", "agent-1") assert received == [("hello", "agent-1")] _pass("SubagentBus pub/sub") mgr = SubagentManager(runtime_factory=lambda: None, send_fn=lambda *a: None) assert isinstance(mgr.bus, SubagentBus) _pass("SubagentManager.bus property") except Exception as e: _fail(f"SubagentBus: {e}") # ========================================================================= section("11. WebChat Adapter") # ========================================================================= try: from adapters.webchat_adapter import WebChatAdapter from adapters.base import BaseAdapter adapter = WebChatAdapter(host="127.0.0.1", port=9999) assert isinstance(adapter, BaseAdapter) assert adapter.source_name == "webchat" _pass("WebChatAdapter extends BaseAdapter, source_name='webchat'") assert os.path.isfile(os.path.join("static", "chat.html")) _pass("static/chat.html exists") except Exception as e: _fail(f"WebChat adapter: {e}") # ========================================================================= section("12. Claude Runtime Config") # ========================================================================= try: from agent.claude_runtime import ClaudeCodeConfig cfg = ClaudeCodeConfig() assert cfg.no_tools is False assert len(cfg.allowed_tools) >= 15 assert "Bash" in cfg.allowed_tools assert "TeamCreate" in cfg.allowed_tools assert "SendMessage" in cfg.allowed_tools _pass(f"ClaudeCodeConfig: no_tools=False, {len(cfg.allowed_tools)} tools, Team tools included") except Exception as e: _fail(f"Claude runtime config: {e}") # ========================================================================= section("13. Module Imports") # ========================================================================= modules = [ "config", "adapters.base", "adapters.webchat_adapter", "agent.opencode_runtime", "agent.claude_runtime", "agent.subagent", "heartbeat.heartbeat", "hooks.hooks", "webhooks.receiver", "skills.skills", "cli", ] for mod in modules: try: importlib.import_module(mod) _pass(f"import {mod}") except Exception as e: _fail(f"import {mod}: {e}") # ========================================================================= section("RESULTS") # ========================================================================= total = PASS + FAIL + SKIP print(f"\nTotal: {total} checks") print(f" Passed: {PASS}") print(f" Failed: {FAIL}") print(f" Skipped: {SKIP}") print() if FAIL == 0: print("All checks passed! 🎉") sys.exit(0) else: print(f"{FAIL} check(s) failed.") sys.exit(1)