Files
gh-human-frontier-labs-inc-…/tests/test_integration.py
2025-11-29 18:47:40 +08:00

347 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Integration tests for Tailscale SSH Sync Agent.
Tests complete workflows from query to result.
"""
import sys
from pathlib import Path
# Add scripts to path
sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts'))
from sshsync_wrapper import get_host_status, list_hosts, get_groups
from tailscale_manager import get_tailscale_status, get_network_summary
from load_balancer import format_load_report, MachineMetrics
from utils.helpers import (
format_bytes, format_duration, format_percentage,
calculate_load_score, classify_load_status, classify_latency
)
def test_host_status_basic():
"""Test get_host_status() without errors."""
print("\n✓ Testing get_host_status()...")
try:
result = get_host_status()
# Validations
assert 'hosts' in result, "Missing 'hosts' in result"
assert isinstance(result.get('hosts', []), list), "'hosts' must be list"
# Should have basic counts even if no hosts configured
assert 'total_count' in result, "Missing 'total_count'"
assert 'online_count' in result, "Missing 'online_count'"
assert 'offline_count' in result, "Missing 'offline_count'"
print(f" ✓ Found {result.get('total_count', 0)} hosts")
print(f" ✓ Online: {result.get('online_count', 0)}")
print(f" ✓ Offline: {result.get('offline_count', 0)}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
import traceback
traceback.print_exc()
return False
def test_list_hosts():
"""Test list_hosts() function."""
print("\n✓ Testing list_hosts()...")
try:
result = list_hosts(with_status=False)
assert 'hosts' in result, "Missing 'hosts' in result"
assert 'count' in result, "Missing 'count' in result"
assert isinstance(result['hosts'], list), "'hosts' must be list"
print(f" ✓ List hosts working")
print(f" ✓ Found {result['count']} configured hosts")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_get_groups():
"""Test get_groups() function."""
print("\n✓ Testing get_groups()...")
try:
groups = get_groups()
assert isinstance(groups, dict), "Groups must be dict"
print(f" ✓ Groups config loaded")
print(f" ✓ Found {len(groups)} groups")
for group, hosts in list(groups.items())[:3]: # Show first 3
print(f" - {group}: {len(hosts)} hosts")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_tailscale_status():
"""Test Tailscale status check."""
print("\n✓ Testing get_tailscale_status()...")
try:
status = get_tailscale_status()
assert isinstance(status, dict), "Status must be dict"
assert 'connected' in status, "Missing 'connected' field"
if status.get('connected'):
print(f" ✓ Tailscale connected")
print(f" ✓ Peers: {status.get('total_count', 0)} total, {status.get('online_count', 0)} online")
else:
print(f" Tailscale not connected: {status.get('error', 'Unknown')}")
print(f" (This is OK if Tailscale is not installed/configured)")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_network_summary():
"""Test network summary generation."""
print("\n✓ Testing get_network_summary()...")
try:
summary = get_network_summary()
assert isinstance(summary, str), "Summary must be string"
assert len(summary) > 0, "Summary cannot be empty"
print(f" ✓ Network summary generated:")
for line in summary.split('\n'):
print(f" {line}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_format_helpers():
"""Test formatting helper functions."""
print("\n✓ Testing format helpers...")
try:
# Test format_bytes
assert format_bytes(1024) == "1.0 KB", "format_bytes failed for 1024"
assert format_bytes(12582912) == "12.0 MB", "format_bytes failed for 12MB"
# Test format_duration
assert format_duration(65) == "1m 5s", "format_duration failed for 65s"
assert format_duration(3665) == "1h 1m", "format_duration failed for 1h+"
# Test format_percentage
assert format_percentage(45.567) == "45.6%", "format_percentage failed"
print(f" ✓ format_bytes(12582912) = {format_bytes(12582912)}")
print(f" ✓ format_duration(3665) = {format_duration(3665)}")
print(f" ✓ format_percentage(45.567) = {format_percentage(45.567)}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_load_score_calculation():
"""Test load score calculation."""
print("\n✓ Testing calculate_load_score()...")
try:
# Test various scenarios
score1 = calculate_load_score(45, 60, 40)
assert 0 <= score1 <= 1, "Score must be 0-1"
assert abs(score1 - 0.49) < 0.01, f"Expected ~0.49, got {score1}"
score2 = calculate_load_score(20, 35, 30)
assert score2 < score1, "Lower usage should have lower score"
score3 = calculate_load_score(85, 70, 65)
assert score3 > score1, "Higher usage should have higher score"
print(f" ✓ Low load (20%, 35%, 30%): {score2:.2f}")
print(f" ✓ Med load (45%, 60%, 40%): {score1:.2f}")
print(f" ✓ High load (85%, 70%, 65%): {score3:.2f}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_load_classification():
"""Test load status classification."""
print("\n✓ Testing classify_load_status()...")
try:
assert classify_load_status(0.28) == "low", "0.28 should be 'low'"
assert classify_load_status(0.55) == "moderate", "0.55 should be 'moderate'"
assert classify_load_status(0.82) == "high", "0.82 should be 'high'"
print(f" ✓ Score 0.28 = {classify_load_status(0.28)}")
print(f" ✓ Score 0.55 = {classify_load_status(0.55)}")
print(f" ✓ Score 0.82 = {classify_load_status(0.82)}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_latency_classification():
"""Test network latency classification."""
print("\n✓ Testing classify_latency()...")
try:
status1, desc1 = classify_latency(25)
assert status1 == "excellent", "25ms should be 'excellent'"
status2, desc2 = classify_latency(75)
assert status2 == "good", "75ms should be 'good'"
status3, desc3 = classify_latency(150)
assert status3 == "fair", "150ms should be 'fair'"
status4, desc4 = classify_latency(250)
assert status4 == "poor", "250ms should be 'poor'"
print(f" ✓ 25ms: {status1} - {desc1}")
print(f" ✓ 75ms: {status2} - {desc2}")
print(f" ✓ 150ms: {status3} - {desc3}")
print(f" ✓ 250ms: {status4} - {desc4}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_load_report_formatting():
"""Test load report formatting."""
print("\n✓ Testing format_load_report()...")
try:
metrics = MachineMetrics(
host='web-01',
cpu_pct=45.0,
mem_pct=60.0,
disk_pct=40.0,
load_score=0.49,
status='moderate'
)
report = format_load_report(metrics)
assert 'web-01' in report, "Report must include hostname"
assert '0.49' in report, "Report must include load score"
assert 'moderate' in report, "Report must include status"
print(f" ✓ Report generated:")
for line in report.split('\n'):
print(f" {line}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def test_dry_run_execution():
"""Test dry-run mode for operations."""
print("\n✓ Testing dry-run execution...")
try:
from sshsync_wrapper import execute_on_all
result = execute_on_all("uptime", dry_run=True)
assert result.get('dry_run') == True, "Must indicate dry-run mode"
assert 'command' in result, "Must include command"
assert 'message' in result, "Must include message"
print(f" ✓ Dry-run mode working")
print(f" ✓ Command: {result.get('command')}")
print(f" ✓ Message: {result.get('message')}")
return True
except Exception as e:
print(f" ✗ FAILED: {e}")
return False
def main():
"""Run all integration tests."""
print("=" * 70)
print("INTEGRATION TESTS - Tailscale SSH Sync Agent")
print("=" * 70)
tests = [
("Host status check", test_host_status_basic),
("List hosts", test_list_hosts),
("Get groups", test_get_groups),
("Tailscale status", test_tailscale_status),
("Network summary", test_network_summary),
("Format helpers", test_format_helpers),
("Load score calculation", test_load_score_calculation),
("Load classification", test_load_classification),
("Latency classification", test_latency_classification),
("Load report formatting", test_load_report_formatting),
("Dry-run execution", test_dry_run_execution),
]
results = []
for test_name, test_func in tests:
passed = test_func()
results.append((test_name, passed))
# Summary
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
for test_name, passed in results:
status = "✅ PASS" if passed else "❌ FAIL"
print(f"{status}: {test_name}")
passed_count = sum(1 for _, p in results if p)
total_count = len(results)
print(f"\nResults: {passed_count}/{total_count} passed")
if passed_count == total_count:
print("\n🎉 All tests passed!")
else:
print(f"\n⚠️ {total_count - passed_count} test(s) failed")
return passed_count == total_count
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)