Files
synapse/internal/agent/p2p/protocol_test.go
2026-06-29 00:48:45 +08:00

509 lines
14 KiB
Go
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.

package p2p
import (
"context"
"encoding/json"
"testing"
"time"
"synapse/pkg/types"
)
// =============================================================================
// Mock Task Handler
// =============================================================================
type mockTaskHandler struct {
canHandle bool
missingFields []types.MissingField
result any
err error
}
func (m *mockTaskHandler) ExecuteTask(ctx context.Context, taskType string, payload map[string]interface{}) (any, error) {
return m.result, m.err
}
func (m *mockTaskHandler) ValidateTask(taskType string, payload map[string]interface{}) ([]types.MissingField, bool) {
return m.missingFields, m.canHandle
}
func (m *mockTaskHandler) GetCapabilities() []string {
return []string{"text_generation", "image_generation"}
}
// =============================================================================
// Mock Callback Sender
// =============================================================================
type mockCallbackSender struct {
lastMsg *types.UnifiedSynapseMessage
err error
}
func (m *mockCallbackSender) SendCallback(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
m.lastMsg = msg
return m.err
}
// =============================================================================
// P2PProtocol 测试
// =============================================================================
func TestNewP2PProtocol(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
if proto == nil {
t.Error("Protocol should not be nil")
}
if proto.AgentEndpoint().AgentID != "agent-001" {
t.Error("Agent ID mismatch")
}
}
func TestP2PProtocolWithCustomConfig(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
config := &P2PConfig{
MaxRetries: 10,
RetryBaseDelay: 5 * time.Second,
TaskTimeout: 10 * time.Minute,
HeartbeatInterval: 60 * time.Second,
}
proto := NewP2PProtocol(ep, config)
if proto.config.MaxRetries != 10 {
t.Errorf("MaxRetries mismatch: got %d, want 10", proto.config.MaxRetries)
}
}
func TestDefaultP2PConfig(t *testing.T) {
config := DefaultP2PConfig()
if config.MaxRetries != 5 {
t.Errorf("Default MaxRetries should be 5")
}
if config.TaskTimeout != 5*time.Minute {
t.Errorf("Default TaskTimeout should be 5 minutes")
}
}
// =============================================================================
// 消息处理测试
// =============================================================================
func TestHandleTaskRequest(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
proto.SetTaskHandler(&mockTaskHandler{
canHandle: true,
result: "test result",
})
// 构造任务请求消息
taskReq := types.TaskRequestPayload{}
taskReq.Task.ID = "task-001"
taskReq.Task.Type = "text_generation"
taskReq.Task.Priority = types.PriorityNormal
taskReq.Task.Payload = map[string]interface{}{"prompt": "test"}
payloadBytes, _ := json.Marshal(taskReq)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
resp, err := proto.HandleMessage(context.Background(), msg)
if err != nil {
t.Errorf("HandleMessage failed: %v", err)
}
if resp == nil {
t.Error("Response should not be nil")
}
}
func TestHandleTaskReject(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
proto.SetTaskHandler(&mockTaskHandler{
canHandle: false,
missingFields: nil,
})
// 构造任务请求消息
taskReq := types.TaskRequestPayload{}
taskReq.Task.ID = "task-001"
taskReq.Task.Type = "unsupported_type"
taskReq.Task.Priority = types.PriorityNormal
payloadBytes, _ := json.Marshal(taskReq)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
resp, err := proto.HandleMessage(context.Background(), msg)
if err != nil {
t.Errorf("HandleMessage should not return error for rejection: %v", err)
}
if resp == nil {
t.Error("Response should not be nil")
}
// 应该返回拒绝消息
if resp.Type != string(types.P2PTaskReject) {
t.Errorf("Expected reject response, got: %s", resp.Type)
}
}
func TestHandleHeartbeat(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PHeartbeat),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: json.RawMessage("{}"),
Timestamp: types.NowTimestamp(),
}
resp, err := proto.HandleMessage(context.Background(), msg)
if err != nil {
t.Errorf("HandleHeartbeat failed: %v", err)
}
if resp == nil {
t.Error("Heartbeat response should not be nil")
}
}
func TestHandleAck(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PAck),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
ReplyTo: "msg-001",
Payload: json.RawMessage("{}"),
Timestamp: types.NowTimestamp(),
}
resp, err := proto.HandleMessage(context.Background(), msg)
if err != nil {
t.Errorf("HandleAck failed: %v", err)
}
// ACK 不需要响应
if resp != nil {
t.Error("ACK should not produce a response")
}
}
// =============================================================================
// 消息验证测试
// =============================================================================
func TestValidateMessage(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
// 有效消息
validMsg := &types.UnifiedSynapseMessage{
ID: "msg-001",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: json.RawMessage("{}"),
Timestamp: types.NowTimestamp(),
}
if err := proto.validateMessage(validMsg); err != nil {
t.Errorf("Valid message should pass validation: %v", err)
}
// 缺少 ID 的消息
noIDMsg := &types.UnifiedSynapseMessage{
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: json.RawMessage("{}"),
Timestamp: types.NowTimestamp(),
}
if err := proto.validateMessage(noIDMsg); err == nil {
t.Error("Message without ID should fail validation")
}
// 缺少 Sender AgentID 的消息
noSenderMsg := &types.UnifiedSynapseMessage{
ID: "msg-001",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{Endpoint: "http://localhost:8081"},
Payload: json.RawMessage("{}"),
Timestamp: types.NowTimestamp(),
}
if err := proto.validateMessage(noSenderMsg); err == nil {
t.Error("Message without sender AgentID should fail validation")
}
}
// =============================================================================
// 状态管理测试
// =============================================================================
func TestGetTaskStatus(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
proto.SetTaskHandler(&mockTaskHandler{
canHandle: true,
result: "test result",
})
// 查询不存在的任务
status, ok := proto.GetTaskStatus("nonexistent")
if ok {
t.Error("Nonexistent task should not be found")
}
// 先通过任务请求添加任务状态
taskReq := types.TaskRequestPayload{}
taskReq.Task.ID = "task-001"
taskReq.Task.Type = "text_generation"
taskReq.Task.Priority = types.PriorityNormal
taskReq.Task.Payload = map[string]interface{}{"prompt": "test"}
payloadBytes, _ := json.Marshal(taskReq)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
proto.HandleMessage(context.Background(), msg)
// 然后查询状态
status, ok = proto.GetTaskStatus("task-001")
if !ok {
t.Error("Task should be found")
}
// 状态应该是 pending 或 executing(异步执行可能已经开始)
if status != types.P2PStatusPending && status != types.P2PStatusExecuting && status != types.P2PStatusCompleted {
t.Errorf("Status should be pending/executing/completed: got %s", status)
}
}
func TestListTasks(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
proto.SetTaskHandler(&mockTaskHandler{
canHandle: true,
result: "test result",
})
// 初始应该为空
tasks := proto.ListTasks()
if len(tasks) != 0 {
t.Error("Initial task list should be empty")
}
// 添加任务(通过消息处理)
taskReq := types.TaskRequestPayload{}
taskReq.Task.ID = "task-001"
taskReq.Task.Type = "text_generation"
taskReq.Task.Priority = types.PriorityNormal
payloadBytes, _ := json.Marshal(taskReq)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
proto.HandleMessage(context.Background(), msg)
tasks = proto.ListTasks()
if len(tasks) < 1 {
t.Errorf("Task list should have at least 1 item: got %d", len(tasks))
}
}
// =============================================================================
// 回调发送测试
// =============================================================================
func TestSendStatusUpdate(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
mockSender := &mockCallbackSender{}
proto.SetCallbackSender(mockSender)
// 发送状态更新
proto.sendStatusUpdate(context.Background(), "task-001", types.P2PStatusExecuting, 50, "Processing")
if mockSender.lastMsg == nil {
t.Error("Callback should have been sent")
}
if mockSender.lastMsg.Type != string(types.P2PTaskStatusUpdate) {
t.Errorf("Callback type mismatch: got %s", mockSender.lastMsg.Type)
}
}
func TestSendTaskComplete(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
mockSender := &mockCallbackSender{}
proto.SetCallbackSender(mockSender)
// 发送完成通知
proto.sendTaskComplete(context.Background(), "task-001", "test result")
if mockSender.lastMsg == nil {
t.Error("Callback should have been sent")
}
if mockSender.lastMsg.Type != string(types.P2PTaskComplete) {
t.Errorf("Callback type mismatch: got %s", mockSender.lastMsg.Type)
}
}
func TestSendTaskFailed(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
mockSender := &mockCallbackSender{}
proto.SetCallbackSender(mockSender)
// 发送失败通知
proto.sendTaskFailed(context.Background(), "task-001", "execution_failed", "test error", true, nil)
if mockSender.lastMsg == nil {
t.Error("Callback should have been sent")
}
if mockSender.lastMsg.Type != string(types.P2PTaskFailed) {
t.Errorf("Callback type mismatch: got %s", mockSender.lastMsg.Type)
}
}
// =============================================================================
// 错误处理测试
// =============================================================================
func TestHandleInvalidPayload(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskRequest),
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: json.RawMessage("invalid json"),
Timestamp: types.NowTimestamp(),
}
resp, err := proto.HandleMessage(context.Background(), msg)
if err != nil {
t.Errorf("Should handle invalid payload gracefully: %v", err)
}
// 应该返回 NACK
if resp == nil {
t.Error("Should return NACK for invalid payload")
}
}
func TestHandleUnknownMessageType(t *testing.T) {
ep := types.AgentEndpoint{
AgentID: "agent-001",
Endpoint: "http://localhost:8080",
}
proto := NewP2PProtocol(ep, nil)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: "p2p:unknown_type",
Sender: types.AgentEndpoint{AgentID: "agent-002", Endpoint: "http://localhost:8081"},
Payload: json.RawMessage("{}"),
Timestamp: types.NowTimestamp(),
}
resp, err := proto.HandleMessage(context.Background(), msg)
if err != nil {
t.Errorf("Unknown message type should return NACK response, not error: %v", err)
}
if resp == nil {
t.Error("Unknown message type should return NACK response")
}
if resp.Type != string(types.P2PNack) {
t.Errorf("Expected NACK response, got: %s", resp.Type)
}
}