blob: ce9e2b6e8c831161f3e113c4affbbbb2f7ac1c6a [file] [log] [blame] [raw]
"""
SSHOUT实际连接集成测试
测试真实的SSH连接和消息处理功能
"""
import unittest
import asyncio
import os
import sys
from unittest.mock import patch, AsyncMock
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.path.join(project_root, 'src'))
from claude_agent.sshout.integration import SSHOUTIntegration, SSHOUTConnection
from claude_agent.utils.config import get_config_manager
class TestSSHOUTRealConnection(unittest.IsolatedAsyncioTestCase):
"""SSHOUT实际连接测试"""
def setUp(self):
"""设置测试环境"""
self.mock_agent = AsyncMock()
# 确保使用默认配置
self.integration = SSHOUTIntegration(self.mock_agent, config_name="default")
def test_config_loading(self):
"""测试配置加载"""
config = self.integration.config_manager.get_sshout_config()
# 验证配置结构
self.assertIn('server', config)
self.assertIn('ssh_key', config)
self.assertIn('message', config)
# 验证服务器配置
server_config = config['server']
self.assertEqual(server_config['hostname'], 'tianjin1.rivoreo.one')
self.assertEqual(server_config['port'], 22333)
self.assertEqual(server_config['username'], 'sshout')
# 验证SSH密钥路径
ssh_config = config['ssh_key']
self.assertIn('private_key_path', ssh_config)
key_path = ssh_config['private_key_path']
self.assertTrue(os.path.exists(key_path), f"SSH密钥文件不存在: {key_path}")
def test_config_validation(self):
"""测试配置验证"""
# 正常情况应该不抛出异常
try:
self.integration._validate_config()
except Exception as e:
self.fail(f"配置验证失败: {e}")
@patch('claude_agent.sshout.integration.SSHOUTConnection')
async def test_connection_attempt(self, mock_connection_class):
"""测试连接尝试"""
# 设置mock连接
mock_connection = AsyncMock()
mock_connection.connect.return_value = True
mock_connection_class.return_value = mock_connection
# 尝试连接
success = await self.integration.connect_to_sshout()
# 验证结果
self.assertTrue(success)
mock_connection_class.assert_called_once()
mock_connection.connect.assert_called_once()
# 验证连接参数
call_args = mock_connection_class.call_args
self.assertEqual(call_args[1]['hostname'], 'tianjin1.rivoreo.one')
self.assertEqual(call_args[1]['port'], 22333)
self.assertEqual(call_args[1]['username'], 'sshout')
async def test_mention_detection_with_config(self):
"""测试使用配置的@Claude检测"""
# 创建测试连接
connection = SSHOUTConnection("test", 22, "test", "test")
# 获取配置的提及模式
mention_patterns = self.integration.config_manager.get('sshout.mention_patterns', [])
self.assertGreater(len(mention_patterns), 0)
# 测试配置中的每个模式
for pattern in mention_patterns[:3]: # 只测试前3个模式
with self.subTest(pattern=pattern):
# 构造测试消息
test_message = f"Hello {pattern} how are you?"
result = connection._is_claude_mention(test_message)
self.assertTrue(result, f"应该检测到模式: {pattern}")
async def test_message_length_limit_from_config(self):
"""测试从配置获取消息长度限制"""
max_length = self.integration.config_manager.get('sshout.message.max_reply_length', 200)
# 创建超长回复
long_response = "这是一个很长的回复。" * 50
# 清理回复
cleaned = self.integration._clean_response_for_sshout(long_response)
# 验证长度限制
self.assertLessEqual(len(cleaned), max_length + 3) # +3 for "..."
def test_ssh_key_permissions(self):
"""测试SSH密钥文件权限"""
config = self.integration.config_manager.get_sshout_config()
key_path = config['ssh_key']['private_key_path']
if os.path.exists(key_path):
# 检查文件权限 (应该是只有所有者可读)
stat_info = os.stat(key_path)
permissions = stat_info.st_mode & 0o777
# 私钥文件应该是 600 (只有所有者可读写) 或更严格
self.assertLessEqual(permissions, 0o600,
f"SSH私钥文件权限过于宽松: {oct(permissions)}")
class TestSSHOUTConnectionReal(unittest.IsolatedAsyncioTestCase):
"""SSHOUT连接真实性测试(需要实际网络)"""
def setUp(self):
"""设置测试环境"""
self.config_manager = get_config_manager("default")
self.sshout_config = self.config_manager.get_sshout_config()
@unittest.skipUnless(os.getenv('TEST_REAL_SSHOUT') == '1',
"需要设置 TEST_REAL_SSHOUT=1 环境变量来运行实际连接测试")
async def test_real_ssh_connection(self):
"""测试实际的SSH连接(可选测试)"""
server_config = self.sshout_config['server']
ssh_config = self.sshout_config['ssh_key']
connection = SSHOUTConnection(
hostname=server_config['hostname'],
port=server_config['port'],
username=server_config['username'],
key_path=ssh_config['private_key_path']
)
try:
# 尝试连接(有超时保护)
success = await asyncio.wait_for(
connection.connect(),
timeout=ssh_config.get('timeout', 10)
)
if success:
print("✅ SSHOUT实际连接测试成功")
# 发送测试消息
test_message = f"[测试] Claude Agent 连接测试 - {asyncio.get_event_loop().time()}"
send_success = await connection.send_message(test_message)
if send_success:
print("✅ SSHOUT消息发送测试成功")
else:
print("❌ SSHOUT消息发送失败")
# 清理连接
await connection.disconnect()
else:
print("❌ SSHOUT实际连接失败")
except asyncio.TimeoutError:
print("⏰ SSHOUT连接超时")
self.skipTest("SSHOUT连接超时,跳过实际连接测试")
except Exception as e:
print(f"💥 SSHOUT连接异常: {e}")
# 不将异常视为测试失败,因为可能是网络或服务器问题
self.skipTest(f"SSHOUT连接异常,跳过测试: {e}")
async def test_network_connectivity(self):
"""测试网络连通性"""
import socket
server_config = self.sshout_config['server']
hostname = server_config['hostname']
port = server_config['port']
try:
# 创建socket连接测试
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
result = sock.connect_ex((hostname, port))
sock.close()
if result == 0:
print(f"✅ 网络连通性测试通过: {hostname}:{port}")
else:
print(f"❌ 网络连通性测试失败: {hostname}:{port}")
self.skipTest(f"无法连接到 {hostname}:{port}")
except Exception as e:
print(f"💥 网络连通性测试异常: {e}")
self.skipTest(f"网络连通性测试异常: {e}")
def test_config_completeness(self):
"""测试配置完整性"""
# 验证所有必需的配置项
required_paths = [
'server.hostname',
'server.port',
'server.username',
'ssh_key.private_key_path',
'message.max_history',
'message.context_count',
'message.max_reply_length',
'mention_patterns'
]
for path in required_paths:
with self.subTest(config_path=path):
value = self.config_manager.get(f'sshout.{path}')
self.assertIsNotNone(value, f"配置项缺失: sshout.{path}")
def test_mention_patterns_validity(self):
"""测试提及模式的有效性"""
patterns = self.config_manager.get('sshout.mention_patterns', [])
self.assertIsInstance(patterns, list)
self.assertGreater(len(patterns), 0, "提及模式列表不能为空")
for pattern in patterns:
with self.subTest(pattern=pattern):
self.assertIsInstance(pattern, str)
self.assertGreater(len(pattern), 0)
# 验证模式包含 Claude
self.assertIn('claude', pattern.lower())
if __name__ == '__main__':
# 运行测试
print("🧪 开始SSHOUT集成测试...")
print("💡 提示:设置环境变量 TEST_REAL_SSHOUT=1 来运行实际连接测试")
unittest.main(verbosity=2)