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:
Houmgaor
2026-02-01 23:36:18 +01:00
parent db3e0bccc7
commit 0e2502c9dc
3 changed files with 633 additions and 0 deletions

View 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
View 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)
}
}
}