mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: improve test coverage for mhfpacket, channelserver, and server packages
Add comprehensive tests for: - Pure time functions in channelserver (sys_time_test.go) - Stage-related packet parsing (msg_sys_stage_test.go) - Acquire packet family parsing (msg_mhf_acquire_test.go) - Extended mhfpacket tests for login, logout, and stage packets - Entrance server makeHeader structure and checksum tests - SignV2 server request/response JSON structure tests
This commit is contained in:
@@ -461,3 +461,383 @@ func TestMHFSaveLoad(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysCreateStageParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantHandle uint32
|
||||
wantUnk0 uint8
|
||||
wantPlayers uint8
|
||||
wantStageID string
|
||||
}{
|
||||
{
|
||||
name: "simple stage",
|
||||
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x05}, []byte("test")...),
|
||||
wantHandle: 1,
|
||||
wantUnk0: 2,
|
||||
wantPlayers: 4,
|
||||
wantStageID: "test",
|
||||
},
|
||||
{
|
||||
name: "empty stage ID",
|
||||
data: []byte{0x12, 0x34, 0x56, 0x78, 0x01, 0x02, 0x00},
|
||||
wantHandle: 0x12345678,
|
||||
wantUnk0: 1,
|
||||
wantPlayers: 2,
|
||||
wantStageID: "",
|
||||
},
|
||||
{
|
||||
name: "with null terminator",
|
||||
data: append([]byte{0x00, 0x00, 0x00, 0x0A, 0x01, 0x01, 0x08}, append([]byte("stage01"), 0x00)...),
|
||||
wantHandle: 10,
|
||||
wantUnk0: 1,
|
||||
wantPlayers: 1,
|
||||
wantStageID: "stage01",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(tt.data)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysCreateStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.wantHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
|
||||
}
|
||||
if pkt.Unk0 != tt.wantUnk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
|
||||
}
|
||||
if pkt.PlayerCount != tt.wantPlayers {
|
||||
t.Errorf("PlayerCount = %d, want %d", pkt.PlayerCount, tt.wantPlayers)
|
||||
}
|
||||
if pkt.StageID != tt.wantStageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysEnterStageParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantHandle uint32
|
||||
wantUnkBool uint8
|
||||
wantStageID string
|
||||
}{
|
||||
{
|
||||
name: "enter mezeporta",
|
||||
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x0F}, []byte("sl1Ns200p0a0u0")...),
|
||||
wantHandle: 1,
|
||||
wantUnkBool: 0,
|
||||
wantStageID: "sl1Ns200p0a0u0",
|
||||
},
|
||||
{
|
||||
name: "with unk bool set",
|
||||
data: append([]byte{0xAB, 0xCD, 0xEF, 0x12, 0x01, 0x05}, []byte("room1")...),
|
||||
wantHandle: 0xABCDEF12,
|
||||
wantUnkBool: 1,
|
||||
wantStageID: "room1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(tt.data)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysEnterStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.wantHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
|
||||
}
|
||||
if pkt.UnkBool != tt.wantUnkBool {
|
||||
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.wantUnkBool)
|
||||
}
|
||||
if pkt.StageID != tt.wantStageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysMoveStageParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantHandle uint32
|
||||
wantUnkBool uint8
|
||||
wantStageID string
|
||||
}{
|
||||
{
|
||||
name: "move to quest stage",
|
||||
data: append([]byte{0x00, 0x00, 0x12, 0x34, 0x00, 0x06}, []byte("quest1")...),
|
||||
wantHandle: 0x1234,
|
||||
wantUnkBool: 0,
|
||||
wantStageID: "quest1",
|
||||
},
|
||||
{
|
||||
name: "with null in string",
|
||||
data: append([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x08}, append([]byte("stage"), []byte{0x00, 0x00, 0x00}...)...),
|
||||
wantHandle: 0xFFFFFFFF,
|
||||
wantUnkBool: 1,
|
||||
wantStageID: "stage",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(tt.data)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysMoveStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.wantHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
|
||||
}
|
||||
if pkt.UnkBool != tt.wantUnkBool {
|
||||
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.wantUnkBool)
|
||||
}
|
||||
if pkt.StageID != tt.wantStageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysLockStageParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantHandle uint32
|
||||
wantUnk0 uint8
|
||||
wantUnk1 uint8
|
||||
wantStageID string
|
||||
}{
|
||||
{
|
||||
name: "lock stage",
|
||||
data: append([]byte{0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x06}, []byte("room01")...),
|
||||
wantHandle: 5,
|
||||
wantUnk0: 1,
|
||||
wantUnk1: 1,
|
||||
wantStageID: "room01",
|
||||
},
|
||||
{
|
||||
name: "different unk values",
|
||||
data: append([]byte{0x12, 0x34, 0x56, 0x78, 0x02, 0x03, 0x04}, []byte("test")...),
|
||||
wantHandle: 0x12345678,
|
||||
wantUnk0: 2,
|
||||
wantUnk1: 3,
|
||||
wantStageID: "test",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(tt.data)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysLockStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.wantHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
|
||||
}
|
||||
if pkt.Unk0 != tt.wantUnk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
|
||||
}
|
||||
if pkt.Unk1 != tt.wantUnk1 {
|
||||
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.wantUnk1)
|
||||
}
|
||||
if pkt.StageID != tt.wantStageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysUnlockStageRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
unk0 uint16
|
||||
}{
|
||||
{"zero value", 0},
|
||||
{"typical value", 1},
|
||||
{"max value", 0xFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
original := &MsgSysUnlockStage{Unk0: tt.unk0}
|
||||
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 := &MsgSysUnlockStage{}
|
||||
err = parsed.Parse(bf, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
// Compare
|
||||
if parsed.Unk0 != original.Unk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", parsed.Unk0, original.Unk0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysBackStageParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantHandle uint32
|
||||
}{
|
||||
{"simple handle", []byte{0x00, 0x00, 0x00, 0x01}, 1},
|
||||
{"large handle", []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0xDEADBEEF},
|
||||
{"zero handle", []byte{0x00, 0x00, 0x00, 0x00}, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(tt.data)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysBackStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.wantHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysLogoutParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantUnk0 uint8
|
||||
}{
|
||||
{"typical logout", []byte{0x01}, 1},
|
||||
{"zero value", []byte{0x00}, 0},
|
||||
{"max value", []byte{0xFF}, 255},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(tt.data)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysLogout{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.Unk0 != tt.wantUnk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysLoginParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
charID0 uint32
|
||||
loginTokenNumber uint32
|
||||
hardcodedZero0 uint16
|
||||
requestVersion uint16
|
||||
charID1 uint32
|
||||
hardcodedZero1 uint16
|
||||
tokenStrLen uint16
|
||||
tokenString string
|
||||
}{
|
||||
{
|
||||
name: "typical login",
|
||||
ackHandle: 1,
|
||||
charID0: 12345,
|
||||
loginTokenNumber: 67890,
|
||||
hardcodedZero0: 0,
|
||||
requestVersion: 1,
|
||||
charID1: 12345,
|
||||
hardcodedZero1: 0,
|
||||
tokenStrLen: 0x11,
|
||||
tokenString: "abc123token",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.ackHandle)
|
||||
bf.WriteUint32(tt.charID0)
|
||||
bf.WriteUint32(tt.loginTokenNumber)
|
||||
bf.WriteUint16(tt.hardcodedZero0)
|
||||
bf.WriteUint16(tt.requestVersion)
|
||||
bf.WriteUint32(tt.charID1)
|
||||
bf.WriteUint16(tt.hardcodedZero1)
|
||||
bf.WriteUint16(tt.tokenStrLen)
|
||||
bf.WriteBytes(append([]byte(tt.tokenString), 0x00)) // null terminated
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysLogin{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.ackHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
|
||||
}
|
||||
if pkt.CharID0 != tt.charID0 {
|
||||
t.Errorf("CharID0 = %d, want %d", pkt.CharID0, tt.charID0)
|
||||
}
|
||||
if pkt.LoginTokenNumber != tt.loginTokenNumber {
|
||||
t.Errorf("LoginTokenNumber = %d, want %d", pkt.LoginTokenNumber, tt.loginTokenNumber)
|
||||
}
|
||||
if pkt.RequestVersion != tt.requestVersion {
|
||||
t.Errorf("RequestVersion = %d, want %d", pkt.RequestVersion, tt.requestVersion)
|
||||
}
|
||||
if pkt.LoginTokenString != tt.tokenString {
|
||||
t.Errorf("LoginTokenString = %q, want %q", pkt.LoginTokenString, tt.tokenString)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
263
network/mhfpacket/msg_mhf_acquire_test.go
Normal file
263
network/mhfpacket/msg_mhf_acquire_test.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
func TestAcquirePacketOpcodes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkt MHFPacket
|
||||
expect network.PacketID
|
||||
}{
|
||||
{"MsgMhfAcquireGuildTresure", &MsgMhfAcquireGuildTresure{}, network.MSG_MHF_ACQUIRE_GUILD_TRESURE},
|
||||
{"MsgMhfAcquireTitle", &MsgMhfAcquireTitle{}, network.MSG_MHF_ACQUIRE_TITLE},
|
||||
{"MsgMhfAcquireDistItem", &MsgMhfAcquireDistItem{}, network.MSG_MHF_ACQUIRE_DIST_ITEM},
|
||||
{"MsgMhfAcquireMonthlyItem", &MsgMhfAcquireMonthlyItem{}, network.MSG_MHF_ACQUIRE_MONTHLY_ITEM},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.pkt.Opcode(); got != tt.expect {
|
||||
t.Errorf("Opcode() = %v, want %v", got, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgMhfAcquireGuildTresureParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
huntID uint32
|
||||
unk uint8
|
||||
}{
|
||||
{"basic acquisition", 1, 12345, 0},
|
||||
{"large hunt ID", 0xABCDEF12, 0xFFFFFFFF, 1},
|
||||
{"zero values", 0, 0, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.ackHandle)
|
||||
bf.WriteUint32(tt.huntID)
|
||||
bf.WriteUint8(tt.unk)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgMhfAcquireGuildTresure{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.ackHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
|
||||
}
|
||||
if pkt.HuntID != tt.huntID {
|
||||
t.Errorf("HuntID = %d, want %d", pkt.HuntID, tt.huntID)
|
||||
}
|
||||
if pkt.Unk != tt.unk {
|
||||
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgMhfAcquireTitleParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
unk0 uint16
|
||||
unk1 uint16
|
||||
titleID uint16
|
||||
}{
|
||||
{"acquire title 1", 1, 0, 0, 1},
|
||||
{"acquire title 100", 0x12345678, 10, 20, 100},
|
||||
{"max title ID", 0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.ackHandle)
|
||||
bf.WriteUint16(tt.unk0)
|
||||
bf.WriteUint16(tt.unk1)
|
||||
bf.WriteUint16(tt.titleID)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgMhfAcquireTitle{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.ackHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
|
||||
}
|
||||
if pkt.Unk0 != tt.unk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
|
||||
}
|
||||
if pkt.Unk1 != tt.unk1 {
|
||||
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
|
||||
}
|
||||
if pkt.TitleID != tt.titleID {
|
||||
t.Errorf("TitleID = %d, want %d", pkt.TitleID, tt.titleID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgMhfAcquireDistItemParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
distributionType uint8
|
||||
distributionID uint32
|
||||
}{
|
||||
{"type 0", 1, 0, 12345},
|
||||
{"type 1", 0xABCD, 1, 67890},
|
||||
{"max values", 0xFFFFFFFF, 0xFF, 0xFFFFFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.ackHandle)
|
||||
bf.WriteUint8(tt.distributionType)
|
||||
bf.WriteUint32(tt.distributionID)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgMhfAcquireDistItem{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.ackHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
|
||||
}
|
||||
if pkt.DistributionType != tt.distributionType {
|
||||
t.Errorf("DistributionType = %d, want %d", pkt.DistributionType, tt.distributionType)
|
||||
}
|
||||
if pkt.DistributionID != tt.distributionID {
|
||||
t.Errorf("DistributionID = %d, want %d", pkt.DistributionID, tt.distributionID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgMhfAcquireMonthlyItemParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
unk0 uint16
|
||||
unk1 uint16
|
||||
unk2 uint32
|
||||
unk3 uint32
|
||||
}{
|
||||
{"basic", 1, 0, 0, 0, 0},
|
||||
{"with values", 100, 10, 20, 30, 40},
|
||||
{"max values", 0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.ackHandle)
|
||||
bf.WriteUint16(tt.unk0)
|
||||
bf.WriteUint16(tt.unk1)
|
||||
bf.WriteUint32(tt.unk2)
|
||||
bf.WriteUint32(tt.unk3)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgMhfAcquireMonthlyItem{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.ackHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
|
||||
}
|
||||
if pkt.Unk0 != tt.unk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
|
||||
}
|
||||
if pkt.Unk1 != tt.unk1 {
|
||||
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
|
||||
}
|
||||
if pkt.Unk2 != tt.unk2 {
|
||||
t.Errorf("Unk2 = %d, want %d", pkt.Unk2, tt.unk2)
|
||||
}
|
||||
if pkt.Unk3 != tt.unk3 {
|
||||
t.Errorf("Unk3 = %d, want %d", pkt.Unk3, tt.unk3)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquirePacketsFromOpcode(t *testing.T) {
|
||||
acquireOpcodes := []network.PacketID{
|
||||
network.MSG_MHF_ACQUIRE_GUILD_TRESURE,
|
||||
network.MSG_MHF_ACQUIRE_TITLE,
|
||||
network.MSG_MHF_ACQUIRE_DIST_ITEM,
|
||||
network.MSG_MHF_ACQUIRE_MONTHLY_ITEM,
|
||||
}
|
||||
|
||||
for _, opcode := range acquireOpcodes {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquirePacketEdgeCases(t *testing.T) {
|
||||
t.Run("guild tresure with max hunt ID", func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint32(0xFFFFFFFF)
|
||||
bf.WriteUint8(255)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgMhfAcquireGuildTresure{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.HuntID != 0xFFFFFFFF {
|
||||
t.Errorf("HuntID = %d, want %d", pkt.HuntID, 0xFFFFFFFF)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dist item with all types", func(t *testing.T) {
|
||||
for i := uint8(0); i < 5; i++ {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint8(i)
|
||||
bf.WriteUint32(12345)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgMhfAcquireDistItem{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v for type %d", err, i)
|
||||
}
|
||||
|
||||
if pkt.DistributionType != i {
|
||||
t.Errorf("DistributionType = %d, want %d", pkt.DistributionType, i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
338
network/mhfpacket/msg_sys_stage_test.go
Normal file
338
network/mhfpacket/msg_sys_stage_test.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
func TestStagePacketOpcodes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkt MHFPacket
|
||||
expect network.PacketID
|
||||
}{
|
||||
{"MsgSysCreateStage", &MsgSysCreateStage{}, network.MSG_SYS_CREATE_STAGE},
|
||||
{"MsgSysEnterStage", &MsgSysEnterStage{}, network.MSG_SYS_ENTER_STAGE},
|
||||
{"MsgSysMoveStage", &MsgSysMoveStage{}, network.MSG_SYS_MOVE_STAGE},
|
||||
{"MsgSysBackStage", &MsgSysBackStage{}, network.MSG_SYS_BACK_STAGE},
|
||||
{"MsgSysLockStage", &MsgSysLockStage{}, network.MSG_SYS_LOCK_STAGE},
|
||||
{"MsgSysUnlockStage", &MsgSysUnlockStage{}, network.MSG_SYS_UNLOCK_STAGE},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.pkt.Opcode(); got != tt.expect {
|
||||
t.Errorf("Opcode() = %v, want %v", got, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysCreateStageFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ackHandle uint32
|
||||
unk0 uint8
|
||||
playerCount uint8
|
||||
stageID string
|
||||
}{
|
||||
{"empty stage", 1, 1, 4, ""},
|
||||
{"mezeporta", 0x12345678, 2, 8, "sl1Ns200p0a0u0"},
|
||||
{"quest room", 100, 1, 4, "q1234"},
|
||||
{"max players", 0xFFFFFFFF, 2, 16, "max_stage"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.ackHandle)
|
||||
bf.WriteUint8(tt.unk0)
|
||||
bf.WriteUint8(tt.playerCount)
|
||||
stageIDBytes := []byte(tt.stageID)
|
||||
bf.WriteUint8(uint8(len(stageIDBytes)))
|
||||
bf.WriteBytes(stageIDBytes)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysCreateStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.ackHandle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
|
||||
}
|
||||
if pkt.Unk0 != tt.unk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
|
||||
}
|
||||
if pkt.PlayerCount != tt.playerCount {
|
||||
t.Errorf("PlayerCount = %d, want %d", pkt.PlayerCount, tt.playerCount)
|
||||
}
|
||||
if pkt.StageID != tt.stageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysEnterStageFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handle uint32
|
||||
unkBool uint8
|
||||
stageID string
|
||||
}{
|
||||
{"enter town", 1, 0, "town01"},
|
||||
{"force enter", 2, 1, "quest_stage"},
|
||||
{"rasta bar", 999, 0, "sl1Ns211p0a0u0"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.handle)
|
||||
bf.WriteUint8(tt.unkBool)
|
||||
stageIDBytes := []byte(tt.stageID)
|
||||
bf.WriteUint8(uint8(len(stageIDBytes)))
|
||||
bf.WriteBytes(stageIDBytes)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysEnterStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.handle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
|
||||
}
|
||||
if pkt.UnkBool != tt.unkBool {
|
||||
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.unkBool)
|
||||
}
|
||||
if pkt.StageID != tt.stageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysMoveStageFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handle uint32
|
||||
unkBool uint8
|
||||
stageID string
|
||||
}{
|
||||
{"move to area", 1, 0, "area01"},
|
||||
{"move to quest", 0xABCD, 1, "quest12345"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.handle)
|
||||
bf.WriteUint8(tt.unkBool)
|
||||
stageIDBytes := []byte(tt.stageID)
|
||||
bf.WriteUint8(uint8(len(stageIDBytes)))
|
||||
bf.WriteBytes(stageIDBytes)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysMoveStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.handle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
|
||||
}
|
||||
if pkt.UnkBool != tt.unkBool {
|
||||
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.unkBool)
|
||||
}
|
||||
if pkt.StageID != tt.stageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysLockStageFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handle uint32
|
||||
unk0 uint8
|
||||
unk1 uint8
|
||||
stageID string
|
||||
}{
|
||||
{"lock room", 1, 1, 1, "room01"},
|
||||
{"private party", 0x1234, 1, 1, "party_stage"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.handle)
|
||||
bf.WriteUint8(tt.unk0)
|
||||
bf.WriteUint8(tt.unk1)
|
||||
stageIDBytes := []byte(tt.stageID)
|
||||
bf.WriteUint8(uint8(len(stageIDBytes)))
|
||||
bf.WriteBytes(stageIDBytes)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysLockStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.handle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
|
||||
}
|
||||
if pkt.Unk0 != tt.unk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
|
||||
}
|
||||
if pkt.Unk1 != tt.unk1 {
|
||||
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
|
||||
}
|
||||
if pkt.StageID != tt.stageID {
|
||||
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysUnlockStageFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
unk0 uint16
|
||||
}{
|
||||
{"zero", 0},
|
||||
{"typical", 1},
|
||||
{"max", 0xFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(tt.unk0)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysUnlockStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.Unk0 != tt.unk0 {
|
||||
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgSysBackStageFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handle uint32
|
||||
}{
|
||||
{"small handle", 1},
|
||||
{"large handle", 0xDEADBEEF},
|
||||
{"zero", 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(tt.handle)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysBackStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.AckHandle != tt.handle {
|
||||
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageIDEdgeCases(t *testing.T) {
|
||||
t.Run("long stage ID", func(t *testing.T) {
|
||||
// Stage ID with max length (255 bytes)
|
||||
longID := make([]byte, 200)
|
||||
for i := range longID {
|
||||
longID[i] = 'a' + byte(i%26)
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint8(1)
|
||||
bf.WriteUint8(4)
|
||||
bf.WriteUint8(uint8(len(longID)))
|
||||
bf.WriteBytes(longID)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysCreateStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if pkt.StageID != string(longID) {
|
||||
t.Errorf("StageID length = %d, want %d", len(pkt.StageID), len(longID))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("stage ID with null terminator", func(t *testing.T) {
|
||||
// String terminated with null byte
|
||||
stageID := "test\x00extra"
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(uint8(len(stageID)))
|
||||
bf.WriteBytes([]byte(stageID))
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
pkt := &MsgSysEnterStage{}
|
||||
err := pkt.Parse(bf, &clientctx.ClientContext{})
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
// Should truncate at null
|
||||
if pkt.StageID != "test" {
|
||||
t.Errorf("StageID = %q, want %q (should truncate at null)", pkt.StageID, "test")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStagePacketFromOpcode(t *testing.T) {
|
||||
stageOpcodes := []network.PacketID{
|
||||
network.MSG_SYS_CREATE_STAGE,
|
||||
network.MSG_SYS_ENTER_STAGE,
|
||||
network.MSG_SYS_BACK_STAGE,
|
||||
network.MSG_SYS_MOVE_STAGE,
|
||||
network.MSG_SYS_LOCK_STAGE,
|
||||
network.MSG_SYS_UNLOCK_STAGE,
|
||||
}
|
||||
|
||||
for _, opcode := range stageOpcodes {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
167
server/channelserver/sys_time_test.go
Normal file
167
server/channelserver/sys_time_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimeAdjusted(t *testing.T) {
|
||||
result := TimeAdjusted()
|
||||
|
||||
// Should return a time in UTC+9 timezone
|
||||
_, offset := result.Zone()
|
||||
expectedOffset := 9 * 60 * 60 // 9 hours in seconds
|
||||
if offset != expectedOffset {
|
||||
t.Errorf("TimeAdjusted() zone offset = %d, want %d (UTC+9)", offset, expectedOffset)
|
||||
}
|
||||
|
||||
// The time should be close to current time (within a few seconds)
|
||||
now := time.Now()
|
||||
diff := result.Sub(now.In(time.FixedZone("UTC+9", 9*60*60)))
|
||||
if diff < -time.Second || diff > time.Second {
|
||||
t.Errorf("TimeAdjusted() time differs from expected by %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeMidnight(t *testing.T) {
|
||||
midnight := TimeMidnight()
|
||||
|
||||
// Should be at midnight (hour=0, minute=0, second=0, nanosecond=0)
|
||||
if midnight.Hour() != 0 {
|
||||
t.Errorf("TimeMidnight() hour = %d, want 0", midnight.Hour())
|
||||
}
|
||||
if midnight.Minute() != 0 {
|
||||
t.Errorf("TimeMidnight() minute = %d, want 0", midnight.Minute())
|
||||
}
|
||||
if midnight.Second() != 0 {
|
||||
t.Errorf("TimeMidnight() second = %d, want 0", midnight.Second())
|
||||
}
|
||||
if midnight.Nanosecond() != 0 {
|
||||
t.Errorf("TimeMidnight() nanosecond = %d, want 0", midnight.Nanosecond())
|
||||
}
|
||||
|
||||
// Should be in UTC+9 timezone
|
||||
_, offset := midnight.Zone()
|
||||
expectedOffset := 9 * 60 * 60
|
||||
if offset != expectedOffset {
|
||||
t.Errorf("TimeMidnight() zone offset = %d, want %d (UTC+9)", offset, expectedOffset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeWeekStart(t *testing.T) {
|
||||
weekStart := TimeWeekStart()
|
||||
|
||||
// Should be on Monday (weekday = 1)
|
||||
if weekStart.Weekday() != time.Monday {
|
||||
t.Errorf("TimeWeekStart() weekday = %v, want Monday", weekStart.Weekday())
|
||||
}
|
||||
|
||||
// Should be at midnight
|
||||
if weekStart.Hour() != 0 || weekStart.Minute() != 0 || weekStart.Second() != 0 {
|
||||
t.Errorf("TimeWeekStart() should be at midnight, got %02d:%02d:%02d",
|
||||
weekStart.Hour(), weekStart.Minute(), weekStart.Second())
|
||||
}
|
||||
|
||||
// Should be in UTC+9 timezone
|
||||
_, offset := weekStart.Zone()
|
||||
expectedOffset := 9 * 60 * 60
|
||||
if offset != expectedOffset {
|
||||
t.Errorf("TimeWeekStart() zone offset = %d, want %d (UTC+9)", offset, expectedOffset)
|
||||
}
|
||||
|
||||
// Week start should be before or equal to current midnight
|
||||
midnight := TimeMidnight()
|
||||
if weekStart.After(midnight) {
|
||||
t.Errorf("TimeWeekStart() %v should be <= current midnight %v", weekStart, midnight)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeWeekNext(t *testing.T) {
|
||||
weekStart := TimeWeekStart()
|
||||
weekNext := TimeWeekNext()
|
||||
|
||||
// TimeWeekNext should be exactly 7 days after TimeWeekStart
|
||||
expectedNext := weekStart.Add(time.Hour * 24 * 7)
|
||||
if !weekNext.Equal(expectedNext) {
|
||||
t.Errorf("TimeWeekNext() = %v, want %v (7 days after WeekStart)", weekNext, expectedNext)
|
||||
}
|
||||
|
||||
// Should also be on Monday
|
||||
if weekNext.Weekday() != time.Monday {
|
||||
t.Errorf("TimeWeekNext() weekday = %v, want Monday", weekNext.Weekday())
|
||||
}
|
||||
|
||||
// Should be at midnight
|
||||
if weekNext.Hour() != 0 || weekNext.Minute() != 0 || weekNext.Second() != 0 {
|
||||
t.Errorf("TimeWeekNext() should be at midnight, got %02d:%02d:%02d",
|
||||
weekNext.Hour(), weekNext.Minute(), weekNext.Second())
|
||||
}
|
||||
|
||||
// Should be in the future relative to week start
|
||||
if !weekNext.After(weekStart) {
|
||||
t.Errorf("TimeWeekNext() %v should be after TimeWeekStart() %v", weekNext, weekStart)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeWeekStartSundayEdge(t *testing.T) {
|
||||
// When today is Sunday, the calculation should go back to last Monday
|
||||
// This is tested indirectly by verifying the weekday is always Monday
|
||||
weekStart := TimeWeekStart()
|
||||
|
||||
// Regardless of what day it is now, week start should be Monday
|
||||
if weekStart.Weekday() != time.Monday {
|
||||
t.Errorf("TimeWeekStart() on any day should return Monday, got %v", weekStart.Weekday())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeMidnightSameDay(t *testing.T) {
|
||||
adjusted := TimeAdjusted()
|
||||
midnight := TimeMidnight()
|
||||
|
||||
// Midnight should be on the same day (year, month, day)
|
||||
if midnight.Year() != adjusted.Year() ||
|
||||
midnight.Month() != adjusted.Month() ||
|
||||
midnight.Day() != adjusted.Day() {
|
||||
t.Errorf("TimeMidnight() date = %v, want same day as TimeAdjusted() %v",
|
||||
midnight.Format("2006-01-02"), adjusted.Format("2006-01-02"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeWeekDuration(t *testing.T) {
|
||||
weekStart := TimeWeekStart()
|
||||
weekNext := TimeWeekNext()
|
||||
|
||||
// Duration between week boundaries should be exactly 7 days
|
||||
duration := weekNext.Sub(weekStart)
|
||||
expectedDuration := time.Hour * 24 * 7
|
||||
|
||||
if duration != expectedDuration {
|
||||
t.Errorf("Duration between WeekStart and WeekNext = %v, want %v", duration, expectedDuration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeZoneConsistency(t *testing.T) {
|
||||
adjusted := TimeAdjusted()
|
||||
midnight := TimeMidnight()
|
||||
weekStart := TimeWeekStart()
|
||||
weekNext := TimeWeekNext()
|
||||
|
||||
// All times should be in the same timezone (UTC+9)
|
||||
times := []struct {
|
||||
name string
|
||||
time time.Time
|
||||
}{
|
||||
{"TimeAdjusted", adjusted},
|
||||
{"TimeMidnight", midnight},
|
||||
{"TimeWeekStart", weekStart},
|
||||
{"TimeWeekNext", weekNext},
|
||||
}
|
||||
|
||||
expectedOffset := 9 * 60 * 60
|
||||
for _, tt := range times {
|
||||
_, offset := tt.time.Zone()
|
||||
if offset != expectedOffset {
|
||||
t.Errorf("%s() zone offset = %d, want %d (UTC+9)", tt.name, offset, expectedOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,3 +203,199 @@ func TestMakeHeaderDataIntegrity(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMakeHeaderStructure verifies the internal structure of makeHeader output
|
||||
func TestMakeHeaderStructure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
respType string
|
||||
entryCount uint16
|
||||
key byte
|
||||
}{
|
||||
{"SV2 response", []byte{0x01, 0x02, 0x03, 0x04}, "SV2", 5, 0x00},
|
||||
{"SVR response", []byte{0xAA, 0xBB}, "SVR", 10, 0x10},
|
||||
{"USR response", []byte{0x00}, "USR", 1, 0xFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := makeHeader(tt.data, tt.respType, tt.entryCount, tt.key)
|
||||
|
||||
// Result should not be empty
|
||||
if len(result) == 0 {
|
||||
t.Fatal("makeHeader returned empty result")
|
||||
}
|
||||
|
||||
// First byte should be the key
|
||||
if result[0] != tt.key {
|
||||
t.Errorf("first byte = 0x%X, want 0x%X", result[0], tt.key)
|
||||
}
|
||||
|
||||
// Decrypt the rest
|
||||
encrypted := result[1:]
|
||||
decrypted := DecryptBin8(encrypted, tt.key)
|
||||
|
||||
// First 3 bytes should be respType
|
||||
if len(decrypted) < 3 {
|
||||
t.Fatal("decrypted data too short for respType")
|
||||
}
|
||||
if string(decrypted[:3]) != tt.respType {
|
||||
t.Errorf("respType = %s, want %s", string(decrypted[:3]), tt.respType)
|
||||
}
|
||||
|
||||
// Next 2 bytes should be entry count (big endian)
|
||||
if len(decrypted) < 5 {
|
||||
t.Fatal("decrypted data too short for entry count")
|
||||
}
|
||||
gotCount := uint16(decrypted[3])<<8 | uint16(decrypted[4])
|
||||
if gotCount != tt.entryCount {
|
||||
t.Errorf("entryCount = %d, want %d", gotCount, tt.entryCount)
|
||||
}
|
||||
|
||||
// Next 2 bytes should be data length (big endian)
|
||||
if len(decrypted) < 7 {
|
||||
t.Fatal("decrypted data too short for data length")
|
||||
}
|
||||
gotLen := uint16(decrypted[5])<<8 | uint16(decrypted[6])
|
||||
if gotLen != uint16(len(tt.data)) {
|
||||
t.Errorf("dataLen = %d, want %d", gotLen, len(tt.data))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMakeHeaderChecksum verifies that checksum is correctly calculated
|
||||
func TestMakeHeaderChecksum(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||
key := byte(0x00)
|
||||
|
||||
result := makeHeader(data, "SV2", 1, key)
|
||||
|
||||
// Decrypt
|
||||
decrypted := DecryptBin8(result[1:], key)
|
||||
|
||||
// After respType(3) + entryCount(2) + dataLen(2) = 7 bytes
|
||||
// Next 4 bytes should be checksum
|
||||
if len(decrypted) < 11 {
|
||||
t.Fatal("decrypted data too short for checksum")
|
||||
}
|
||||
|
||||
expectedChecksum := CalcSum32(data)
|
||||
gotChecksum := uint32(decrypted[7])<<24 | uint32(decrypted[8])<<16 | uint32(decrypted[9])<<8 | uint32(decrypted[10])
|
||||
|
||||
if gotChecksum != expectedChecksum {
|
||||
t.Errorf("checksum = 0x%X, want 0x%X", gotChecksum, expectedChecksum)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMakeHeaderDataPreservation verifies original data is preserved in output
|
||||
func TestMakeHeaderDataPreservation(t *testing.T) {
|
||||
originalData := []byte{0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE}
|
||||
key := byte(0x00)
|
||||
|
||||
result := makeHeader(originalData, "SV2", 1, key)
|
||||
|
||||
// Decrypt
|
||||
decrypted := DecryptBin8(result[1:], key)
|
||||
|
||||
// Header: respType(3) + entryCount(2) + dataLen(2) + checksum(4) = 11 bytes
|
||||
// Data starts at offset 11
|
||||
if len(decrypted) < 11+len(originalData) {
|
||||
t.Fatalf("decrypted data too short: got %d, want at least %d", len(decrypted), 11+len(originalData))
|
||||
}
|
||||
|
||||
recoveredData := decrypted[11 : 11+len(originalData)]
|
||||
if !bytes.Equal(recoveredData, originalData) {
|
||||
t.Errorf("recovered data = %X, want %X", recoveredData, originalData)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMakeHeaderEmptyDataNoChecksum verifies empty data doesn't include checksum
|
||||
func TestMakeHeaderEmptyDataNoChecksum(t *testing.T) {
|
||||
result := makeHeader([]byte{}, "SV2", 0, 0x00)
|
||||
|
||||
// Decrypt
|
||||
decrypted := DecryptBin8(result[1:], 0x00)
|
||||
|
||||
// Header without data: respType(3) + entryCount(2) + dataLen(2) = 7 bytes
|
||||
// No checksum for empty data
|
||||
if len(decrypted) != 7 {
|
||||
t.Errorf("decrypted length = %d, want 7 (no checksum for empty data)", len(decrypted))
|
||||
}
|
||||
|
||||
// Verify data length is 0
|
||||
gotLen := uint16(decrypted[5])<<8 | uint16(decrypted[6])
|
||||
if gotLen != 0 {
|
||||
t.Errorf("dataLen = %d, want 0", gotLen)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMakeHeaderKeyVariation verifies different keys produce different output
|
||||
func TestMakeHeaderKeyVariation(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03}
|
||||
|
||||
result1 := makeHeader(data, "SV2", 1, 0x00)
|
||||
result2 := makeHeader(data, "SV2", 1, 0x55)
|
||||
result3 := makeHeader(data, "SV2", 1, 0xAA)
|
||||
|
||||
// All results should have different first bytes (the key)
|
||||
if result1[0] == result2[0] || result2[0] == result3[0] {
|
||||
t.Error("different keys should produce different first bytes")
|
||||
}
|
||||
|
||||
// Encrypted portions should also differ
|
||||
if bytes.Equal(result1[1:], result2[1:]) {
|
||||
t.Error("different keys should produce different encrypted data")
|
||||
}
|
||||
if bytes.Equal(result2[1:], result3[1:]) {
|
||||
t.Error("different keys should produce different encrypted data")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalcSum32EdgeCases tests edge cases for the checksum function
|
||||
func TestCalcSum32EdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"single byte", []byte{0x00}},
|
||||
{"all zeros", make([]byte, 10)},
|
||||
{"all ones", bytes.Repeat([]byte{0xFF}, 10)},
|
||||
{"alternating", []byte{0xAA, 0x55, 0xAA, 0x55}},
|
||||
{"sequential", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Should not panic
|
||||
result := CalcSum32(tt.data)
|
||||
|
||||
// Result should be deterministic
|
||||
result2 := CalcSum32(tt.data)
|
||||
if result != result2 {
|
||||
t.Errorf("CalcSum32 not deterministic: got %X and %X", result, result2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalcSum32Uniqueness verifies different inputs produce different checksums
|
||||
func TestCalcSum32Uniqueness(t *testing.T) {
|
||||
inputs := [][]byte{
|
||||
{0x01},
|
||||
{0x02},
|
||||
{0x01, 0x02},
|
||||
{0x02, 0x01},
|
||||
{0x01, 0x02, 0x03},
|
||||
}
|
||||
|
||||
checksums := make(map[uint32]int)
|
||||
for i, input := range inputs {
|
||||
sum := CalcSum32(input)
|
||||
if prevIdx, exists := checksums[sum]; exists {
|
||||
t.Errorf("collision: input %d and %d both produce checksum 0x%X", prevIdx, i, sum)
|
||||
}
|
||||
checksums[sum] = i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,3 +347,374 @@ func TestServerConfig(t *testing.T) {
|
||||
t.Error("Config.Logger should be nil when not set")
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Tests that require database operations are skipped when no DB is available.
|
||||
// The following tests validate the structure and JSON handling of endpoints.
|
||||
|
||||
// TestLoginRequestStructure tests that login request JSON structure is correct
|
||||
func TestLoginRequestStructure(t *testing.T) {
|
||||
// Test JSON marshaling/unmarshaling of request structure
|
||||
reqData := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}{
|
||||
Username: "testuser",
|
||||
Password: "testpass",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Username != reqData.Username {
|
||||
t.Errorf("Username = %s, want %s", decoded.Username, reqData.Username)
|
||||
}
|
||||
if decoded.Password != reqData.Password {
|
||||
t.Errorf("Password = %s, want %s", decoded.Password, reqData.Password)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterRequestStructure tests that register request JSON structure is correct
|
||||
func TestRegisterRequestStructure(t *testing.T) {
|
||||
reqData := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}{
|
||||
Username: "newuser",
|
||||
Password: "newpass",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Username != reqData.Username {
|
||||
t.Errorf("Username = %s, want %s", decoded.Username, reqData.Username)
|
||||
}
|
||||
if decoded.Password != reqData.Password {
|
||||
t.Errorf("Password = %s, want %s", decoded.Password, reqData.Password)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateCharacterRequestStructure tests that create character request JSON structure is correct
|
||||
func TestCreateCharacterRequestStructure(t *testing.T) {
|
||||
reqData := struct {
|
||||
Token string `json:"token"`
|
||||
}{
|
||||
Token: "test-token-12345",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Token != reqData.Token {
|
||||
t.Errorf("Token = %s, want %s", decoded.Token, reqData.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteCharacterRequestStructure tests that delete character request JSON structure is correct
|
||||
func TestDeleteCharacterRequestStructure(t *testing.T) {
|
||||
reqData := struct {
|
||||
Token string `json:"token"`
|
||||
CharID int `json:"id"`
|
||||
}{
|
||||
Token: "test-token",
|
||||
CharID: 12345,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Token string `json:"token"`
|
||||
CharID int `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Token != reqData.Token {
|
||||
t.Errorf("Token = %s, want %s", decoded.Token, reqData.Token)
|
||||
}
|
||||
if decoded.CharID != reqData.CharID {
|
||||
t.Errorf("CharID = %d, want %d", decoded.CharID, reqData.CharID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoginResponseStructure tests the login response JSON structure
|
||||
func TestLoginResponseStructure(t *testing.T) {
|
||||
respData := struct {
|
||||
Token string `json:"token"`
|
||||
Characters []Character `json:"characters"`
|
||||
}{
|
||||
Token: "login-token-abc123",
|
||||
Characters: []Character{
|
||||
{ID: 1, Name: "Hunter1", IsFemale: false, Weapon: 3, HR: 100, GR: 10},
|
||||
{ID: 2, Name: "Hunter2", IsFemale: true, Weapon: 7, HR: 200, GR: 20},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Token string `json:"token"`
|
||||
Characters []Character `json:"characters"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Token != respData.Token {
|
||||
t.Errorf("Token = %s, want %s", decoded.Token, respData.Token)
|
||||
}
|
||||
if len(decoded.Characters) != len(respData.Characters) {
|
||||
t.Errorf("Characters count = %d, want %d", len(decoded.Characters), len(respData.Characters))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterResponseStructure tests the register response JSON structure
|
||||
func TestRegisterResponseStructure(t *testing.T) {
|
||||
respData := struct {
|
||||
Token string `json:"token"`
|
||||
}{
|
||||
Token: "register-token-xyz789",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Token != respData.Token {
|
||||
t.Errorf("Token = %s, want %s", decoded.Token, respData.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateCharacterResponseStructure tests the create character response JSON structure
|
||||
func TestCreateCharacterResponseStructure(t *testing.T) {
|
||||
respData := struct {
|
||||
CharID int `json:"id"`
|
||||
}{
|
||||
CharID: 42,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
CharID int `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.CharID != respData.CharID {
|
||||
t.Errorf("CharID = %d, want %d", decoded.CharID, respData.CharID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLauncherContentType tests that Launcher sets correct content type
|
||||
func TestLauncherContentType(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
// Note: The handler sets header after WriteHeader, so we check response body is JSON
|
||||
resp := w.Result()
|
||||
var data map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
t.Errorf("Launcher() response is not valid JSON: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLauncherMessageDates tests that launcher message dates are valid timestamps
|
||||
func TestLauncherMessageDates(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
var data struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
json.NewDecoder(w.Result().Body).Decode(&data)
|
||||
|
||||
// All dates should be positive unix timestamps
|
||||
for _, msg := range data.Important {
|
||||
if msg.Date <= 0 {
|
||||
t.Errorf("Important message date should be positive, got %d", msg.Date)
|
||||
}
|
||||
}
|
||||
for _, msg := range data.Normal {
|
||||
if msg.Date <= 0 {
|
||||
t.Errorf("Normal message date should be positive, got %d", msg.Date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLauncherMessageLinks tests that launcher message links are valid URLs
|
||||
func TestLauncherMessageLinks(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.Launcher(w, req)
|
||||
|
||||
var data struct {
|
||||
Important []LauncherMessage `json:"important"`
|
||||
Normal []LauncherMessage `json:"normal"`
|
||||
}
|
||||
json.NewDecoder(w.Result().Body).Decode(&data)
|
||||
|
||||
// All links should start with http:// or https://
|
||||
for _, msg := range data.Important {
|
||||
if len(msg.Link) < 7 || (msg.Link[:7] != "http://" && msg.Link[:8] != "https://") {
|
||||
t.Errorf("Important message link should be a URL, got %q", msg.Link)
|
||||
}
|
||||
}
|
||||
for _, msg := range data.Normal {
|
||||
if len(msg.Link) < 7 || (msg.Link[:7] != "http://" && msg.Link[:8] != "https://") {
|
||||
t.Errorf("Normal message link should be a URL, got %q", msg.Link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCharacterStructJSONMarshal tests Character struct marshals correctly
|
||||
func TestCharacterStructJSONMarshal(t *testing.T) {
|
||||
char := Character{
|
||||
ID: 42,
|
||||
Name: "TestHunter",
|
||||
IsFemale: true,
|
||||
Weapon: 7,
|
||||
HR: 999,
|
||||
GR: 100,
|
||||
LastLogin: 1609459200,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(char)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded Character
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.ID != char.ID {
|
||||
t.Errorf("ID = %d, want %d", decoded.ID, char.ID)
|
||||
}
|
||||
if decoded.Name != char.Name {
|
||||
t.Errorf("Name = %s, want %s", decoded.Name, char.Name)
|
||||
}
|
||||
if decoded.IsFemale != char.IsFemale {
|
||||
t.Errorf("IsFemale = %v, want %v", decoded.IsFemale, char.IsFemale)
|
||||
}
|
||||
if decoded.Weapon != char.Weapon {
|
||||
t.Errorf("Weapon = %d, want %d", decoded.Weapon, char.Weapon)
|
||||
}
|
||||
if decoded.HR != char.HR {
|
||||
t.Errorf("HR = %d, want %d", decoded.HR, char.HR)
|
||||
}
|
||||
if decoded.GR != char.GR {
|
||||
t.Errorf("GR = %d, want %d", decoded.GR, char.GR)
|
||||
}
|
||||
if decoded.LastLogin != char.LastLogin {
|
||||
t.Errorf("LastLogin = %d, want %d", decoded.LastLogin, char.LastLogin)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLauncherMessageJSONMarshal tests LauncherMessage struct marshals correctly
|
||||
func TestLauncherMessageJSONMarshal(t *testing.T) {
|
||||
msg := LauncherMessage{
|
||||
Message: "Test Announcement",
|
||||
Date: 1609459200,
|
||||
Link: "https://example.com/news",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
|
||||
var decoded LauncherMessage
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
|
||||
if decoded.Message != msg.Message {
|
||||
t.Errorf("Message = %s, want %s", decoded.Message, msg.Message)
|
||||
}
|
||||
if decoded.Date != msg.Date {
|
||||
t.Errorf("Date = %d, want %d", decoded.Date, msg.Date)
|
||||
}
|
||||
if decoded.Link != msg.Link {
|
||||
t.Errorf("Link = %s, want %s", decoded.Link, msg.Link)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEndpointHTTPMethods tests that endpoints respond to correct HTTP methods
|
||||
func TestEndpointHTTPMethods(t *testing.T) {
|
||||
s := mockServer()
|
||||
|
||||
// Launcher should respond to GET
|
||||
t.Run("Launcher GET", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/launcher", nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.Launcher(w, req)
|
||||
if w.Result().StatusCode != http.StatusOK {
|
||||
t.Errorf("Launcher() GET status = %d, want %d", w.Result().StatusCode, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
// Note: Login, Register, CreateCharacter, DeleteCharacter require database
|
||||
// and cannot be tested without mocking the database connection
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user