Files
luzia/tests/test_integrations.py
admin ec33ac1936 Refactor cockpit to use DockerTmuxController pattern
Based on claude-code-tools TmuxCLIController, this refactor:

- Added DockerTmuxController class for robust tmux session management
- Implements send_keys() with configurable delay_enter
- Implements capture_pane() for output retrieval
- Implements wait_for_prompt() for pattern-based completion detection
- Implements wait_for_idle() for content-hash-based idle detection
- Implements wait_for_shell_prompt() for shell prompt detection

Also includes workflow improvements:
- Pre-task git snapshot before agent execution
- Post-task commit protocol in agent guidelines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 10:42:16 -03:00

512 lines
16 KiB
Python

#!/usr/bin/env python3
"""
Integration tests for Luzia orchestrator components
"""
import sys
import json
import os
import tempfile
import shutil
from pathlib import Path
from datetime import datetime
# Add lib to path
sys.path.insert(0, '/opt/server-agents/orchestrator/lib')
# Test results tracking
RESULTS = {'passed': 0, 'failed': 0, 'errors': []}
def test(name):
"""Decorator for test functions"""
def decorator(func):
def wrapper():
try:
func()
RESULTS['passed'] += 1
print(f"{name}")
return True
except AssertionError as e:
RESULTS['failed'] += 1
RESULTS['errors'].append(f"{name}: {e}")
print(f"{name}: {e}")
return False
except Exception as e:
RESULTS['failed'] += 1
RESULTS['errors'].append(f"{name}: {type(e).__name__}: {e}")
print(f"{name}: {type(e).__name__}: {e}")
return False
wrapper.__name__ = func.__name__
return wrapper
return decorator
# =============================================================================
# Chat Memory Lookup Tests
# =============================================================================
print("\n### Chat Memory Lookup Tests ###")
@test("ChatMemoryLookup imports")
def test_chat_memory_import():
from chat_memory_lookup import ChatMemoryLookup
assert ChatMemoryLookup is not None
@test("ChatMemoryLookup initializes")
def test_chat_memory_init():
from chat_memory_lookup import ChatMemoryLookup
lookup = ChatMemoryLookup(timeout_ms=150)
assert lookup.timeout_ms == 150
@test("ChatMemoryLookup.memory_statistics returns data")
def test_chat_memory_stats():
from chat_memory_lookup import ChatMemoryLookup
lookup = ChatMemoryLookup()
stats = lookup.memory_statistics()
assert 'available' in stats
assert stats['available'] == True
assert 'entities' in stats
assert stats['entities'] > 0
@test("ChatMemoryLookup.list_all_projects returns projects")
def test_chat_memory_projects():
from chat_memory_lookup import ChatMemoryLookup
lookup = ChatMemoryLookup()
result = lookup.list_all_projects()
assert 'projects' in result
assert 'count' in result
assert result['count'] > 0
assert len(result['projects']) > 0
@test("ChatMemoryLookup.search_entities works")
def test_chat_memory_search():
from chat_memory_lookup import ChatMemoryLookup
lookup = ChatMemoryLookup()
result = lookup.search_entities('admin', limit=5)
assert 'entities' in result
assert 'count' in result
test_chat_memory_import()
test_chat_memory_init()
test_chat_memory_stats()
test_chat_memory_projects()
test_chat_memory_search()
# =============================================================================
# Chat Intent Parser Tests
# =============================================================================
print("\n### Chat Intent Parser Tests ###")
@test("ChatIntentParser imports")
def test_intent_import():
from chat_intent_parser import ChatIntentParser
assert ChatIntentParser is not None
@test("ChatIntentParser.parse returns intent structure")
def test_intent_parse():
from chat_intent_parser import ChatIntentParser
parser = ChatIntentParser()
result = parser.parse("list projects")
assert 'intent' in result
assert 'keywords' in result
assert 'scope' in result
@test("ChatIntentParser detects project_info intent")
def test_intent_project():
from chat_intent_parser import ChatIntentParser
parser = ChatIntentParser()
result = parser.parse("list projects")
assert result['intent'] == 'project_info'
assert 'projects' in result['keywords']
@test("ChatIntentParser detects system_status intent")
def test_intent_status():
from chat_intent_parser import ChatIntentParser
parser = ChatIntentParser()
result = parser.parse("system status")
assert result['intent'] == 'system_status'
@test("ChatIntentParser.extract_search_term works")
def test_intent_search_term():
from chat_intent_parser import ChatIntentParser
parser = ChatIntentParser()
term = parser.extract_search_term("search for authentication")
assert term is not None
assert len(term) > 0
test_intent_import()
test_intent_parse()
test_intent_project()
test_intent_status()
test_intent_search_term()
# =============================================================================
# Chat Orchestrator Tests
# =============================================================================
print("\n### Chat Orchestrator Tests ###")
@test("ChatOrchestrator imports")
def test_orchestrator_import():
from chat_orchestrator import ChatOrchestrator
assert ChatOrchestrator is not None
@test("ChatOrchestrator initializes")
def test_orchestrator_init():
from chat_orchestrator import ChatOrchestrator
orch = ChatOrchestrator(timeout_ms=500)
assert orch.timeout_ms == 500
@test("ChatOrchestrator.process_query returns response")
def test_orchestrator_query():
from chat_orchestrator import ChatOrchestrator
orch = ChatOrchestrator()
result = orch.process_query("help")
assert 'response' in result
assert 'status' in result
assert result['status'] == 'success'
@test("ChatOrchestrator handles system status query")
def test_orchestrator_status():
from chat_orchestrator import ChatOrchestrator
orch = ChatOrchestrator()
result = orch.process_query("system status")
assert 'response' in result
assert 'execution_time_ms' in result
@test("ChatOrchestrator handles project query")
def test_orchestrator_projects():
from chat_orchestrator import ChatOrchestrator
orch = ChatOrchestrator()
result = orch.process_query("list projects")
assert 'response' in result
assert 'Projects' in result['response'] or 'project' in result['response'].lower()
test_orchestrator_import()
test_orchestrator_init()
test_orchestrator_query()
test_orchestrator_status()
test_orchestrator_projects()
# =============================================================================
# Chat Response Formatter Tests
# =============================================================================
print("\n### Chat Response Formatter Tests ###")
@test("ChatResponseFormatter imports")
def test_formatter_import():
from chat_response_formatter import ChatResponseFormatter
assert ChatResponseFormatter is not None
@test("ChatResponseFormatter.format_help returns markdown")
def test_formatter_help():
from chat_response_formatter import ChatResponseFormatter
formatter = ChatResponseFormatter()
help_text = formatter.format_help()
assert '# ' in help_text # Has markdown header
assert len(help_text) > 100
@test("ChatResponseFormatter.format_response_time works")
def test_formatter_time():
from chat_response_formatter import ChatResponseFormatter
formatter = ChatResponseFormatter()
instant = formatter.format_response_time(5)
assert 'instant' in instant
fast = formatter.format_response_time(150)
assert 'fast' in fast.lower() or 'ms' in fast
@test("ChatResponseFormatter.format_project_list works")
def test_formatter_projects():
from chat_response_formatter import ChatResponseFormatter
formatter = ChatResponseFormatter()
data = {'projects': [{'name': 'test', 'type': 'project'}], 'count': 1}
result = formatter.format_project_list(data)
assert 'test' in result
assert 'Project' in result or '1' in result
test_formatter_import()
test_formatter_help()
test_formatter_time()
test_formatter_projects()
# =============================================================================
# Chat Bash Executor Tests
# =============================================================================
print("\n### Chat Bash Executor Tests ###")
@test("ChatBashExecutor imports")
def test_bash_import():
from chat_bash_executor import ChatBashExecutor
assert ChatBashExecutor is not None
@test("ChatBashExecutor.execute runs uptime")
def test_bash_uptime():
from chat_bash_executor import ChatBashExecutor
executor = ChatBashExecutor()
result = executor.execute('uptime')
assert 'success' in result
assert result['success'] == True
assert 'output' in result
@test("ChatBashExecutor.execute runs disk")
def test_bash_disk():
from chat_bash_executor import ChatBashExecutor
executor = ChatBashExecutor()
result = executor.execute('disk')
assert result['success'] == True
@test("ChatBashExecutor rejects unknown commands")
def test_bash_reject():
from chat_bash_executor import ChatBashExecutor
executor = ChatBashExecutor()
result = executor.execute('unknown_dangerous_cmd')
# Unknown commands return error without success key
assert 'error' in result
assert 'not allowed' in result['error'].lower()
test_bash_import()
test_bash_uptime()
test_bash_disk()
test_bash_reject()
# =============================================================================
# Task Watchdog Tests
# =============================================================================
print("\n### Task Watchdog Tests ###")
@test("TaskWatchdog imports")
def test_watchdog_import():
from task_watchdog import TaskWatchdog
assert TaskWatchdog is not None
@test("TaskWatchdog initializes")
def test_watchdog_init():
from task_watchdog import TaskWatchdog
watchdog = TaskWatchdog()
assert watchdog.HEARTBEAT_TIMEOUT_SECONDS == 300
assert watchdog.LOCK_TIMEOUT_SECONDS == 3600
@test("TaskWatchdog.check_heartbeats runs")
def test_watchdog_heartbeats():
from task_watchdog import TaskWatchdog
watchdog = TaskWatchdog()
stuck = watchdog.check_heartbeats()
assert isinstance(stuck, list)
@test("TaskWatchdog.get_project_queue_status returns dict")
def test_watchdog_queue_status():
from task_watchdog import TaskWatchdog
watchdog = TaskWatchdog()
status = watchdog.get_project_queue_status()
assert isinstance(status, dict)
@test("TaskWatchdog.is_project_blocked returns tuple")
def test_watchdog_blocked():
from task_watchdog import TaskWatchdog
watchdog = TaskWatchdog()
blocked, reason = watchdog.is_project_blocked('test_nonexistent')
assert isinstance(blocked, bool)
@test("TaskWatchdog.run_check returns summary")
def test_watchdog_check():
from task_watchdog import TaskWatchdog
watchdog = TaskWatchdog()
summary = watchdog.run_check()
assert 'timestamp' in summary
assert 'stuck_tasks' in summary
assert 'project_status' in summary
test_watchdog_import()
test_watchdog_init()
test_watchdog_heartbeats()
test_watchdog_queue_status()
test_watchdog_blocked()
test_watchdog_check()
# =============================================================================
# Task Completion Tests
# =============================================================================
print("\n### Task Completion Tests ###")
@test("TaskCompletion imports")
def test_completion_import():
from task_completion import TaskCompletion, complete_task, fail_task
assert TaskCompletion is not None
assert complete_task is not None
assert fail_task is not None
@test("TaskCompletion initializes")
def test_completion_init():
from task_completion import TaskCompletion
handler = TaskCompletion()
assert handler.COMPLETED_DIR.exists() or True # May not exist yet
@test("TaskCompletion.complete_task handles missing task")
def test_completion_missing():
from task_completion import TaskCompletion
handler = TaskCompletion()
result = handler.complete_task('nonexistent-task-12345')
assert result['success'] == False
assert 'not found' in result.get('error', '').lower()
@test("TaskCompletion.fail_task handles missing task")
def test_fail_missing():
from task_completion import TaskCompletion
handler = TaskCompletion()
result = handler.fail_task('nonexistent-task-12345', 'test error')
assert result['success'] == False
@test("TaskCompletion.set_awaiting_human handles missing task")
def test_awaiting_missing():
from task_completion import TaskCompletion
handler = TaskCompletion()
result = handler.set_awaiting_human('nonexistent-task-12345', 'question?')
assert result['success'] == False
test_completion_import()
test_completion_init()
test_completion_missing()
test_fail_missing()
test_awaiting_missing()
# =============================================================================
# Cockpit Tests
# =============================================================================
print("\n### Cockpit Tests ###")
@test("Cockpit module imports")
def test_cockpit_import():
from cockpit import cockpit_status, cockpit_start, cockpit_stop, cockpit_send
assert cockpit_status is not None
assert cockpit_start is not None
assert cockpit_stop is not None
@test("cockpit_status returns state")
def test_cockpit_status():
from cockpit import cockpit_status
result = cockpit_status('admin')
assert isinstance(result, dict)
# Should have status info even if not running
@test("container_exists helper works")
def test_cockpit_container_exists():
from cockpit import container_exists
# Test with a non-existent container
result = container_exists('nonexistent-container-12345')
assert isinstance(result, bool)
assert result == False
@test("get_container_name generates correct name")
def test_cockpit_container_name():
from cockpit import get_container_name
name = get_container_name('testproject')
assert 'testproject' in name
assert 'cockpit' in name.lower()
test_cockpit_import()
test_cockpit_status()
test_cockpit_container_exists()
test_cockpit_container_name()
# =============================================================================
# KG Lookup Tests
# =============================================================================
print("\n### KG Lookup Tests ###")
@test("ChatKGLookup imports")
def test_kg_import():
from chat_kg_lookup import ChatKGLookup
assert ChatKGLookup is not None
@test("ChatKGLookup.get_kg_statistics returns stats")
def test_kg_stats():
from chat_kg_lookup import ChatKGLookup
lookup = ChatKGLookup()
stats = lookup.get_kg_statistics()
assert isinstance(stats, dict)
@test("ChatKGLookup.search_all_domains works")
def test_kg_search():
from chat_kg_lookup import ChatKGLookup
lookup = ChatKGLookup()
results = lookup.search_all_domains('admin', limit=5)
assert isinstance(results, dict)
test_kg_import()
test_kg_stats()
test_kg_search()
# =============================================================================
# CLI Integration Tests
# =============================================================================
print("\n### CLI Integration Tests ###")
import subprocess
@test("luzia --help works")
def test_cli_help():
result = subprocess.run(['luzia', '--help'], capture_output=True, text=True, timeout=10)
assert result.returncode == 0
assert 'luzia' in result.stdout.lower() or 'usage' in result.stdout.lower()
@test("luzia chat help works")
def test_cli_chat_help():
result = subprocess.run(['luzia', 'chat', 'help'], capture_output=True, text=True, timeout=10)
assert result.returncode == 0
assert 'Chat' in result.stdout or 'chat' in result.stdout.lower()
@test("luzia watchdog status works")
def test_cli_watchdog():
result = subprocess.run(['luzia', 'watchdog', 'status'], capture_output=True, text=True, timeout=10)
assert result.returncode == 0
assert 'PROJECT' in result.stdout or 'Queue' in result.stdout
@test("luzia cockpit status works")
def test_cli_cockpit():
result = subprocess.run(['luzia', 'cockpit', 'status'], capture_output=True, text=True, timeout=10)
assert result.returncode == 0
@test("luzia list works")
def test_cli_list():
result = subprocess.run(['luzia', 'list'], capture_output=True, text=True, timeout=10)
assert result.returncode == 0
test_cli_help()
test_cli_chat_help()
test_cli_watchdog()
test_cli_cockpit()
test_cli_list()
# =============================================================================
# Summary
# =============================================================================
print("\n" + "=" * 60)
print(f"RESULTS: {RESULTS['passed']} passed, {RESULTS['failed']} failed")
print("=" * 60)
if RESULTS['errors']:
print("\nFailed tests:")
for err in RESULTS['errors']:
print(f" - {err}")
sys.exit(0 if RESULTS['failed'] == 0 else 1)