package peer import ( "net" "os" "path/filepath" "testing" "time" "omnisocketgo/cmd/internal/protocol" "omnisocketgo/cmd/internal/server" "omnisocketgo/cmd/internal/transport" ) // TestUDPDialAndSendText 验证 UDP 客户端可以成功连接、注册并发送文本消息。 func TestUDPDialAndSendText(t *testing.T) { hubAddr := startUDPTestHub(t) clientA, err := DialUDP(hubAddr.String(), "peer-a") if err != nil { t.Fatalf("DialUDP(peer-a) error = %v", err) } defer clientA.Close() clientB, err := DialUDP(hubAddr.String(), "peer-b") if err != nil { t.Fatalf("DialUDP(peer-b) error = %v", err) } defer clientB.Close() // 等待注册被处理 time.Sleep(50 * time.Millisecond) // peer-a 发送文本给 peer-b if err := clientA.SendText("peer-b", "hello from udp"); err != nil { t.Fatalf("SendText() error = %v", err) } // peer-b 接收 msg := receiveUDPClientMessage(t, clientB) if msg.Type != protocol.MessageTypeText { t.Fatalf("message type = %s, want text", msg.Type) } if string(msg.Body) != "hello from udp" { t.Fatalf("message body = %q, want %q", string(msg.Body), "hello from udp") } } // TestUDPClientID 验证 ID() 返回正确的 peer 标识。 func TestUDPClientID(t *testing.T) { hubAddr := startUDPTestHub(t) client, err := DialUDP(hubAddr.String(), "my-peer-id") if err != nil { t.Fatalf("DialUDP() error = %v", err) } defer client.Close() if got := client.ID(); got != "my-peer-id" { t.Fatalf("ID() = %q, want %q", got, "my-peer-id") } } // TestUDPClientPersistMessage 验证 UDP 客户端可以将消息持久化到磁盘。 func TestUDPClientPersistMessage(t *testing.T) { hubAddr := startUDPTestHub(t) client, err := DialUDP(hubAddr.String(), "peer-persist") if err != nil { t.Fatalf("DialUDP() error = %v", err) } defer client.Close() inboxDir := t.TempDir() // 持久化文本消息 textMsg := protocol.Message{ Type: protocol.MessageTypeText, ID: 1, From: "sender", To: "peer-persist", Body: []byte("persisted text"), } path, err := client.PersistMessage(textMsg, inboxDir) if err != nil { t.Fatalf("PersistMessage(text) error = %v", err) } if !filepath.IsAbs(path) && path == "" { t.Fatalf("PersistMessage(text) returned empty path") } // 持久化文件消息 fileMsg := protocol.Message{ Type: protocol.MessageTypeFile, ID: 2, From: "sender", To: "peer-persist", FileName: "test.bin", Body: []byte{0x01, 0x02, 0x03}, } filePath, err := client.PersistMessage(fileMsg, inboxDir) if err != nil { t.Fatalf("PersistMessage(file) error = %v", err) } content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("ReadFile(%s) error = %v", filePath, err) } if len(content) != 3 || content[0] != 0x01 { t.Fatalf("file content mismatch: got %v", content) } } // TestUDPClientSendFile 验证 UDP 客户端可以发送文件消息。 func TestUDPClientSendFile(t *testing.T) { hubAddr := startUDPTestHub(t) clientA, err := DialUDP(hubAddr.String(), "peer-a") if err != nil { t.Fatalf("DialUDP(peer-a) error = %v", err) } defer clientA.Close() clientB, err := DialUDP(hubAddr.String(), "peer-b") if err != nil { t.Fatalf("DialUDP(peer-b) error = %v", err) } defer clientB.Close() time.Sleep(50 * time.Millisecond) fileBody := []byte{0xDE, 0xAD, 0xBE, 0xEF} if err := clientA.SendFile("peer-b", "test.bin", fileBody); err != nil { t.Fatalf("SendFile() error = %v", err) } msg := receiveUDPClientMessage(t, clientB) if msg.Type != protocol.MessageTypeFile { t.Fatalf("message type = %s, want file", msg.Type) } if msg.FileName != "test.bin" { t.Fatalf("file name = %q, want %q", msg.FileName, "test.bin") } if len(msg.Body) != 4 { t.Fatalf("body length = %d, want 4", len(msg.Body)) } } // startUDPTestHub 创建并启动一个测试用 UDPHub。 func startUDPTestHub(t *testing.T) *net.UDPAddr { t.Helper() addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") if err != nil { t.Fatalf("ResolveUDPAddr() error = %v", err) } conn, err := net.ListenUDP("udp", addr) if err != nil { t.Fatalf("ListenUDP() error = %v", err) } hub, err := server.NewUDPHub(conn) if err != nil { _ = conn.Close() t.Fatalf("NewUDPHub() error = %v", err) } go func() { _ = hub.Serve() }() t.Cleanup(func() { _ = hub.Close() }) return conn.LocalAddr().(*net.UDPAddr) } // receiveUDPClientMessage 从 UDP 客户端接收一条消息,带超时。 func receiveUDPClientMessage(t *testing.T, client *UDPClient) protocol.Message { t.Helper() type result struct { msg protocol.Message err error } ch := make(chan result, 1) go func() { msg, err := client.Receive() ch <- result{msg: msg, err: err} }() select { case r := <-ch: if r.err != nil { t.Fatalf("Receive() error = %v", r.err) } return r.msg case <-time.After(2 * time.Second): t.Fatal("Receive() timed out after 2s") return protocol.Message{} } } // Ensure transport package is used (needed for WithTXTimestampDebugLogger option). var _ transport.TXTimestampDebugLogger = nil