495 lines
13 KiB
Go
495 lines
13 KiB
Go
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"},
|
|
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"},
|
|
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)
|
|
|
|
// 查询不存在的任务
|
|
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 或 confirmed
|
|
if status != types.P2PStatusPending && status != types.P2PStatusConfirmed {
|
|
t.Errorf("Status should be pending or confirmed: got %s", status)
|
|
}
|
|
}
|
|
|
|
func TestListTasks(t *testing.T) {
|
|
ep := types.AgentEndpoint{
|
|
AgentID: "agent-001",
|
|
Endpoint: "http://localhost:8080",
|
|
}
|
|
|
|
proto := NewP2PProtocol(ep, nil)
|
|
|
|
// 初始应该为空
|
|
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(),
|
|
}
|
|
|
|
_, err := proto.HandleMessage(context.Background(), msg)
|
|
if err == nil {
|
|
t.Error("Unknown message type should return error")
|
|
}
|
|
}
|