Files
ngzz-mc/internal/netserver/netserver_test.go

127 lines
3.5 KiB
Go
Raw Normal View History

package netserver
import (
"context"
"net"
"testing"
"time"
"mc/internal/logx"
"mc/internal/netproto"
)
// testHandler 记录回调的测试处理器。
type testHandler struct {
logins chan netproto.Handshake
moves chan netproto.PlayerMove
connects chan uint64
}
func newTestHandler() *testHandler {
return &testHandler{
logins: make(chan netproto.Handshake, 8),
moves: make(chan netproto.PlayerMove, 8),
connects: make(chan uint64, 8),
}
}
func (h *testHandler) OnConnect(c *Conn) { h.connects <- c.ID() }
func (h *testHandler) OnHandshake(c *Conn, hs netproto.Handshake) uint8 {
h.logins <- hs
return 0
}
func (h *testHandler) OnMove(c *Conn, m netproto.PlayerMove) { h.moves <- m }
func (h *testHandler) OnDisconnect(c *Conn) {}
// startServer 启动测试服务器。
func startServer(t *testing.T) (*Server, *testHandler, context.CancelFunc) {
t.Helper()
log, err := logx.New("", logx.LevelDebug) // 仅 stderr(避免临时文件句柄)
if err != nil {
t.Fatalf("创建日志失败: %v", err)
}
h := newTestHandler()
s := New("127.0.0.1:0", log, h)
ctx, cancel := context.WithCancel(context.Background())
if err := s.Listen(ctx); err != nil {
t.Fatalf("监听失败: %v", err)
}
t.Cleanup(func() {
cancel()
time.Sleep(20 * time.Millisecond)
})
return s, h, cancel
}
// dial 建立客户端连接。
func dial(t *testing.T, addr string) net.Conn {
t.Helper()
c, err := net.DialTimeout("tcp", addr, 3*time.Second)
if err != nil {
t.Fatalf("拨号失败: %v", err)
}
t.Cleanup(func() { _ = c.Close() })
return c
}
// TestHandshakeFlow 握手 → 登录响应 → 心跳回显全链路(网络协议.md §4)。
func TestHandshakeFlow(t *testing.T) {
s, h, _ := startServer(t)
nc := dial(t, s.Addr())
// 发送握手
hs := netproto.Handshake{ProtocolVersion: 1, ClientID: "测试客户端"}
_, err := nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgHandshake, Payload: hs.Encode()}))
if err != nil {
t.Fatalf("发送握手失败: %v", err)
}
select {
case got := <-h.logins:
if got != hs {
t.Fatalf("握手内容不一致: %+v", got)
}
case <-time.After(3 * time.Second):
t.Fatal("未收到握手回调")
}
// 读登录响应帧
_ = nc.SetReadDeadline(time.Now().Add(3 * time.Second))
buf := make([]byte, 256)
n, err := nc.Read(buf)
if err != nil {
t.Fatalf("读登录响应失败: %v", err)
}
f, _, err := netproto.DecodeFrame(buf[:n])
if err != nil || f.MsgID != netproto.MsgLoginResponse {
t.Fatalf("登录响应异常: %+v err=%v", f, err)
}
if len(f.Payload) != 1 || f.Payload[0] != 0 {
t.Fatalf("登录结果应为 0(成功),实际 %v", f.Payload)
}
// 心跳回显
_, _ = nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgHeartbeat, Payload: []byte{7}}))
n, _ = nc.Read(buf)
f, _, _ = netproto.DecodeFrame(buf[:n])
if f.MsgID != netproto.MsgHeartbeat || len(f.Payload) != 1 || f.Payload[0] != 7 {
t.Fatalf("心跳回显异常: %+v", f)
}
}
// TestMoveBroadcast 移动消息分发。
func TestMoveBroadcast(t *testing.T) {
s, h, _ := startServer(t)
nc := dial(t, s.Addr())
m := netproto.PlayerMove{X: 1.5, Y: 64, Z: -2, Yaw: 0.5, Pitch: 0, OnGround: true}
_, _ = nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgPlayerMove, Payload: m.Encode()}))
select {
case got := <-h.moves:
if got != m {
t.Fatalf("移动数据不一致: %+v", got)
}
case <-time.After(3 * time.Second):
t.Fatal("未收到移动回调")
}
_ = s.Count() // 连接计数可用
}