Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:47:40 +08:00
commit 14c678ceac
22 changed files with 7501 additions and 0 deletions

346
tests/test_integration.py Normal file
View File

@@ -0,0 +1,346 @@
#!/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)