mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: add PacketID and core packet tests, expand stringstack coverage
- Add network/packetid_test.go with tests for PacketID type, constants, String() method, ranges, uniqueness, and sequential verification - Add network/mhfpacket/msg_sys_core_test.go with round-trip tests for MsgSysAck (including large payloads), MsgSysNop, and MsgSysEnd - Expand common/stringstack/stringstack_test.go with Lock/Unlock tests achieving 100% coverage
This commit is contained in:
310
network/mhfpacket/msg_sys_core_test.go
Normal file
310
network/mhfpacket/msg_sys_core_test.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
func TestMsgSysAckRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
isBufferResponse bool
|
||||
errorCode uint8
|
||||
ackData []byte
|
||||
}{
|
||||
{
|
||||
name: "simple non-buffer response",
|
||||
ackHandle: 1,
|
||||
isBufferResponse: false,
|
||||
errorCode: 0,
|
||||
ackData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
name: "buffer response with small data",
|
||||
ackHandle: 0x12345678,
|
||||
isBufferResponse: true,
|
||||
errorCode: 0,
|
||||
ackData: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
|
||||
},
|
||||
{
|
||||
name: "error response",
|
||||
ackHandle: 100,
|
||||
isBufferResponse: false,
|
||||
errorCode: 1,
|
||||
ackData: []byte{0xDE, 0xAD, 0xBE, 0xEF},
|
||||
},
|
||||
{
|
||||
name: "empty buffer response",
|
||||
ackHandle: 999,
|
||||
isBufferResponse: true,
|
||||
errorCode: 0,
|
||||
ackData: []byte{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
original := &MsgSysAck{
|
||||
AckHandle: tt.ackHandle,
|
||||
IsBufferResponse: tt.isBufferResponse,
|
||||
ErrorCode: tt.errorCode,
|
||||
AckData: tt.ackData,
|
||||
}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
// Build
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := original.Build(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
|
||||
// Parse
|
||||
bf.Seek(0, io.SeekStart)
|
||||
parsed := &MsgSysAck{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
// Compare
|
||||
if parsed.AckHandle != original.AckHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", parsed.AckHandle, original.AckHandle)
|
||||
}
|
||||
if parsed.IsBufferResponse != original.IsBufferResponse {
|
||||
t.Errorf("IsBufferResponse = %v, want %v", parsed.IsBufferResponse, original.IsBufferResponse)
|
||||
}
|
||||
if parsed.ErrorCode != original.ErrorCode {
|
||||
t.Errorf("ErrorCode = %d, want %d", parsed.ErrorCode, original.ErrorCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysAckLargePayload(t *testing.T) {
|
||||
// Test with payload larger than 0xFFFF to trigger extended size field
|
||||
largeData := make([]byte, 0x10000) // 65536 bytes
|
||||
for i := range largeData {
|
||||
largeData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
original := &MsgSysAck{
|
||||
AckHandle: 1,
|
||||
IsBufferResponse: true,
|
||||
ErrorCode: 0,
|
||||
AckData: largeData,
|
||||
}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
// Build
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := original.Build(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
|
||||
// Parse
|
||||
bf.Seek(0, io.SeekStart)
|
||||
parsed := &MsgSysAck{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if len(parsed.AckData) != len(largeData) {
|
||||
t.Errorf("AckData len = %d, want %d", len(parsed.AckData), len(largeData))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysAckOpcode(t *testing.T) {
|
||||
pkt := &MsgSysAck{}
|
||||
if pkt.Opcode() != network.MSG_SYS_ACK {
|
||||
t.Errorf("Opcode() = %s, want MSG_SYS_ACK", pkt.Opcode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysNopRoundTrip(t *testing.T) {
|
||||
original := &MsgSysNop{}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
// Build
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := original.Build(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
|
||||
// Should write no data
|
||||
if len(bf.Data()) != 0 {
|
||||
t.Errorf("MsgSysNop.Build() wrote %d bytes, want 0", len(bf.Data()))
|
||||
}
|
||||
|
||||
// Parse (from empty buffer)
|
||||
parsed := &MsgSysNop{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysNopOpcode(t *testing.T) {
|
||||
pkt := &MsgSysNop{}
|
||||
if pkt.Opcode() != network.MSG_SYS_NOP {
|
||||
t.Errorf("Opcode() = %s, want MSG_SYS_NOP", pkt.Opcode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysEndRoundTrip(t *testing.T) {
|
||||
original := &MsgSysEnd{}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
// Build
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := original.Build(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
|
||||
// Should write no data
|
||||
if len(bf.Data()) != 0 {
|
||||
t.Errorf("MsgSysEnd.Build() wrote %d bytes, want 0", len(bf.Data()))
|
||||
}
|
||||
|
||||
// Parse (from empty buffer)
|
||||
parsed := &MsgSysEnd{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysEndOpcode(t *testing.T) {
|
||||
pkt := &MsgSysEnd{}
|
||||
if pkt.Opcode() != network.MSG_SYS_END {
|
||||
t.Errorf("Opcode() = %s, want MSG_SYS_END", pkt.Opcode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysAckNonBufferResponse(t *testing.T) {
|
||||
// Non-buffer response should always read/write 4 bytes of data
|
||||
original := &MsgSysAck{
|
||||
AckHandle: 1,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0,
|
||||
AckData: []byte{0xAA, 0xBB, 0xCC, 0xDD},
|
||||
}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := original.Build(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
|
||||
bf.Seek(0, io.SeekStart)
|
||||
parsed := &MsgSysAck{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
// Non-buffer response should have exactly 4 bytes of data
|
||||
if len(parsed.AckData) != 4 {
|
||||
t.Errorf("Non-buffer AckData len = %d, want 4", len(parsed.AckData))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysAckNonBufferShortData(t *testing.T) {
|
||||
// Non-buffer response with short data should pad to 4 bytes
|
||||
original := &MsgSysAck{
|
||||
AckHandle: 1,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0,
|
||||
AckData: []byte{0x01}, // Only 1 byte
|
||||
}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
err := original.Build(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
|
||||
bf.Seek(0, io.SeekStart)
|
||||
parsed := &MsgSysAck{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
// Should still read 4 bytes
|
||||
if len(parsed.AckData) != 4 {
|
||||
t.Errorf("AckData len = %d, want 4", len(parsed.AckData))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysAckBuildFormat(t *testing.T) {
|
||||
pkt := &MsgSysAck{
|
||||
AckHandle: 0x12345678,
|
||||
IsBufferResponse: true,
|
||||
ErrorCode: 0x55,
|
||||
AckData: []byte{0xAA, 0xBB},
|
||||
}
|
||||
ctx := &clientctx.ClientContext{}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
pkt.Build(bf, ctx)
|
||||
|
||||
data := bf.Data()
|
||||
|
||||
// Check AckHandle (big-endian)
|
||||
if data[0] != 0x12 || data[1] != 0x34 || data[2] != 0x56 || data[3] != 0x78 {
|
||||
t.Errorf("AckHandle bytes = %X, want 12345678", data[:4])
|
||||
}
|
||||
|
||||
// Check IsBufferResponse (1 = true)
|
||||
if data[4] != 1 {
|
||||
t.Errorf("IsBufferResponse byte = %d, want 1", data[4])
|
||||
}
|
||||
|
||||
// Check ErrorCode
|
||||
if data[5] != 0x55 {
|
||||
t.Errorf("ErrorCode byte = %X, want 55", data[5])
|
||||
}
|
||||
|
||||
// Check payload size (2 bytes, big-endian)
|
||||
if data[6] != 0x00 || data[7] != 0x02 {
|
||||
t.Errorf("PayloadSize bytes = %X %X, want 00 02", data[6], data[7])
|
||||
}
|
||||
|
||||
// Check actual data
|
||||
if data[8] != 0xAA || data[9] != 0xBB {
|
||||
t.Errorf("AckData bytes = %X %X, want AA BB", data[8], data[9])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorePacketsFromOpcode(t *testing.T) {
|
||||
coreOpcodes := []network.PacketID{
|
||||
network.MSG_SYS_NOP,
|
||||
network.MSG_SYS_END,
|
||||
network.MSG_SYS_ACK,
|
||||
network.MSG_SYS_PING,
|
||||
}
|
||||
|
||||
for _, opcode := range coreOpcodes {
|
||||
t.Run(opcode.String(), func(t *testing.T) {
|
||||
pkt := FromOpcode(opcode)
|
||||
if pkt == nil {
|
||||
t.Fatalf("FromOpcode(%s) returned nil", opcode)
|
||||
}
|
||||
if pkt.Opcode() != opcode {
|
||||
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
211
network/packetid_test.go
Normal file
211
network/packetid_test.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPacketIDType(t *testing.T) {
|
||||
// PacketID is based on uint16
|
||||
var p PacketID = 0xFFFF
|
||||
if uint16(p) != 0xFFFF {
|
||||
t.Errorf("PacketID max value = %d, want %d", uint16(p), 0xFFFF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketIDConstants(t *testing.T) {
|
||||
// Test critical packet IDs are correct
|
||||
tests := []struct {
|
||||
name string
|
||||
id PacketID
|
||||
expect uint16
|
||||
}{
|
||||
{"MSG_HEAD", MSG_HEAD, 0},
|
||||
{"MSG_SYS_END", MSG_SYS_END, 0x10},
|
||||
{"MSG_SYS_NOP", MSG_SYS_NOP, 0x11},
|
||||
{"MSG_SYS_ACK", MSG_SYS_ACK, 0x12},
|
||||
{"MSG_SYS_LOGIN", MSG_SYS_LOGIN, 0x14},
|
||||
{"MSG_SYS_LOGOUT", MSG_SYS_LOGOUT, 0x15},
|
||||
{"MSG_SYS_PING", MSG_SYS_PING, 0x17},
|
||||
{"MSG_SYS_TIME", MSG_SYS_TIME, 0x1A},
|
||||
{"MSG_SYS_CREATE_STAGE", MSG_SYS_CREATE_STAGE, 0x20},
|
||||
{"MSG_SYS_ENTER_STAGE", MSG_SYS_ENTER_STAGE, 0x22},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if uint16(tt.id) != tt.expect {
|
||||
t.Errorf("%s = 0x%X, want 0x%X", tt.name, uint16(tt.id), tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketIDString(t *testing.T) {
|
||||
// Test that String() method works for known packet IDs
|
||||
tests := []struct {
|
||||
id PacketID
|
||||
contains string
|
||||
}{
|
||||
{MSG_HEAD, "MSG_HEAD"},
|
||||
{MSG_SYS_PING, "MSG_SYS_PING"},
|
||||
{MSG_SYS_END, "MSG_SYS_END"},
|
||||
{MSG_SYS_NOP, "MSG_SYS_NOP"},
|
||||
{MSG_SYS_ACK, "MSG_SYS_ACK"},
|
||||
{MSG_SYS_LOGIN, "MSG_SYS_LOGIN"},
|
||||
{MSG_SYS_LOGOUT, "MSG_SYS_LOGOUT"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.contains, func(t *testing.T) {
|
||||
got := tt.id.String()
|
||||
if got != tt.contains {
|
||||
t.Errorf("String() = %q, want %q", got, tt.contains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketIDUnknown(t *testing.T) {
|
||||
// Unknown packet ID should still have a valid string representation
|
||||
unknown := PacketID(0xFFFF)
|
||||
str := unknown.String()
|
||||
if str == "" {
|
||||
t.Error("String() for unknown PacketID should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketIDZero(t *testing.T) {
|
||||
// MSG_HEAD should be 0
|
||||
if MSG_HEAD != 0 {
|
||||
t.Errorf("MSG_HEAD = %d, want 0", MSG_HEAD)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemPacketIDRange(t *testing.T) {
|
||||
// System packets should be in a specific range
|
||||
systemPackets := []PacketID{
|
||||
MSG_SYS_reserve01,
|
||||
MSG_SYS_reserve02,
|
||||
MSG_SYS_reserve03,
|
||||
MSG_SYS_ADD_OBJECT,
|
||||
MSG_SYS_DEL_OBJECT,
|
||||
MSG_SYS_END,
|
||||
MSG_SYS_NOP,
|
||||
MSG_SYS_ACK,
|
||||
MSG_SYS_LOGIN,
|
||||
MSG_SYS_LOGOUT,
|
||||
MSG_SYS_PING,
|
||||
MSG_SYS_TIME,
|
||||
}
|
||||
|
||||
for _, pkt := range systemPackets {
|
||||
// System packets should have IDs > 0 (MSG_HEAD is 0)
|
||||
if pkt < MSG_SYS_reserve01 {
|
||||
t.Errorf("System packet %s has ID %d, should be >= MSG_SYS_reserve01", pkt, pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMHFPacketIDRange(t *testing.T) {
|
||||
// MHF packets start at MSG_MHF_SAVEDATA (0x60)
|
||||
mhfPackets := []PacketID{
|
||||
MSG_MHF_SAVEDATA,
|
||||
MSG_MHF_LOADDATA,
|
||||
MSG_MHF_ENUMERATE_QUEST,
|
||||
MSG_MHF_ACQUIRE_TITLE,
|
||||
MSG_MHF_ACQUIRE_DIST_ITEM,
|
||||
MSG_MHF_ACQUIRE_MONTHLY_ITEM,
|
||||
}
|
||||
|
||||
for _, pkt := range mhfPackets {
|
||||
// MHF packets should be >= MSG_MHF_SAVEDATA
|
||||
if pkt < MSG_MHF_SAVEDATA {
|
||||
t.Errorf("MHF packet %s has ID %d, should be >= MSG_MHF_SAVEDATA (%d)", pkt, pkt, MSG_MHF_SAVEDATA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagePacketIDsSequential(t *testing.T) {
|
||||
// Stage-related packets should be sequential
|
||||
stagePackets := []PacketID{
|
||||
MSG_SYS_CREATE_STAGE,
|
||||
MSG_SYS_STAGE_DESTRUCT,
|
||||
MSG_SYS_ENTER_STAGE,
|
||||
MSG_SYS_BACK_STAGE,
|
||||
MSG_SYS_MOVE_STAGE,
|
||||
MSG_SYS_LEAVE_STAGE,
|
||||
MSG_SYS_LOCK_STAGE,
|
||||
MSG_SYS_UNLOCK_STAGE,
|
||||
}
|
||||
|
||||
for i := 1; i < len(stagePackets); i++ {
|
||||
if stagePackets[i] != stagePackets[i-1]+1 {
|
||||
t.Errorf("Stage packets not sequential: %s (%d) should follow %s (%d)",
|
||||
stagePackets[i], stagePackets[i], stagePackets[i-1], stagePackets[i-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketIDUniqueness(t *testing.T) {
|
||||
// Sample of important packet IDs should be unique
|
||||
packets := []PacketID{
|
||||
MSG_HEAD,
|
||||
MSG_SYS_END,
|
||||
MSG_SYS_NOP,
|
||||
MSG_SYS_ACK,
|
||||
MSG_SYS_LOGIN,
|
||||
MSG_SYS_LOGOUT,
|
||||
MSG_SYS_PING,
|
||||
MSG_SYS_TIME,
|
||||
MSG_SYS_CREATE_STAGE,
|
||||
MSG_SYS_ENTER_STAGE,
|
||||
MSG_MHF_SAVEDATA,
|
||||
MSG_MHF_LOADDATA,
|
||||
}
|
||||
|
||||
seen := make(map[PacketID]bool)
|
||||
for _, pkt := range packets {
|
||||
if seen[pkt] {
|
||||
t.Errorf("Duplicate PacketID: %s (%d)", pkt, pkt)
|
||||
}
|
||||
seen[pkt] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquirePacketIDs(t *testing.T) {
|
||||
// Verify acquire-related packet IDs exist and are correct type
|
||||
acquirePackets := []PacketID{
|
||||
MSG_MHF_ACQUIRE_DIST_ITEM,
|
||||
MSG_MHF_ACQUIRE_TITLE,
|
||||
MSG_MHF_ACQUIRE_ITEM,
|
||||
MSG_MHF_ACQUIRE_MONTHLY_ITEM,
|
||||
MSG_MHF_ACQUIRE_CAFE_ITEM,
|
||||
MSG_MHF_ACQUIRE_GUILD_TRESURE,
|
||||
}
|
||||
|
||||
for _, pkt := range acquirePackets {
|
||||
str := pkt.String()
|
||||
if str == "" {
|
||||
t.Errorf("PacketID %d should have a string representation", pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildPacketIDs(t *testing.T) {
|
||||
// Verify guild-related packet IDs
|
||||
guildPackets := []PacketID{
|
||||
MSG_MHF_CREATE_GUILD,
|
||||
MSG_MHF_OPERATE_GUILD,
|
||||
MSG_MHF_OPERATE_GUILD_MEMBER,
|
||||
MSG_MHF_INFO_GUILD,
|
||||
MSG_MHF_ENUMERATE_GUILD,
|
||||
MSG_MHF_UPDATE_GUILD,
|
||||
}
|
||||
|
||||
for _, pkt := range guildPackets {
|
||||
// All guild packets should be MHF packets
|
||||
if pkt < MSG_MHF_SAVEDATA {
|
||||
t.Errorf("Guild packet %s should be an MHF packet (>= 0x60)", pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user