mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
1601 lines
42 KiB
Go
1601 lines
42 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"erupe-ce/common/byteframe"
|
|
"erupe-ce/common/mhfitem"
|
|
"erupe-ce/common/token"
|
|
cfg "erupe-ce/config"
|
|
"erupe-ce/network/mhfpacket"
|
|
"testing"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
// ackResponse holds parsed fields from a queued MsgSysAck packet.
|
|
type ackResponse struct {
|
|
AckHandle uint32
|
|
IsBufferResponse bool
|
|
ErrorCode uint8
|
|
PayloadSize uint
|
|
Payload []byte
|
|
}
|
|
|
|
// readAck drains one packet from the session's sendPackets channel and
|
|
// parses the MsgSysAck wire format that QueueSendMHF produces.
|
|
func readAck(t *testing.T, session *Session) ackResponse {
|
|
t.Helper()
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
bf := byteframe.NewByteFrameFromBytes(p.data)
|
|
_ = bf.ReadUint16() // opcode
|
|
ack := ackResponse{}
|
|
ack.AckHandle = bf.ReadUint32()
|
|
ack.IsBufferResponse = bf.ReadBool()
|
|
ack.ErrorCode = bf.ReadUint8()
|
|
size := uint(bf.ReadUint16())
|
|
if size == 0xFFFF {
|
|
size = uint(bf.ReadUint32())
|
|
}
|
|
ack.PayloadSize = size
|
|
if ack.IsBufferResponse {
|
|
ack.Payload = bf.ReadBytes(size)
|
|
} else {
|
|
ack.Payload = bf.ReadBytes(4)
|
|
}
|
|
return ack
|
|
default:
|
|
t.Fatal("No response packet queued")
|
|
return ackResponse{}
|
|
}
|
|
}
|
|
|
|
// setupHouseTest creates DB, server, session, and a character with user_binary row.
|
|
func setupHouseTest(t *testing.T) (*sqlx.DB, *Server, *Session, uint32) {
|
|
t.Helper()
|
|
db := SetupTestDB(t)
|
|
server := createMockServer()
|
|
server.erupeConfig.RealClientMode = cfg.ZZ
|
|
SetTestDB(server, db)
|
|
|
|
userID := CreateTestUser(t, db, "house_test_user")
|
|
charID := CreateTestCharacter(t, db, userID, "HousePlayer")
|
|
|
|
_, err := db.Exec(`INSERT INTO user_binary (id) VALUES ($1) ON CONFLICT DO NOTHING`, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create user_binary row: %v", err)
|
|
}
|
|
|
|
session := createMockSession(charID, server)
|
|
return db, server, session, charID
|
|
}
|
|
|
|
// createTestEquipment creates properly initialized test equipment
|
|
func createTestEquipment(itemIDs []uint16, warehouseIDs []uint32) []mhfitem.MHFEquipment {
|
|
var equip []mhfitem.MHFEquipment
|
|
for i, itemID := range itemIDs {
|
|
e := mhfitem.MHFEquipment{
|
|
ItemID: itemID,
|
|
WarehouseID: warehouseIDs[i],
|
|
Decorations: make([]mhfitem.MHFItem, 3),
|
|
Sigils: make([]mhfitem.MHFSigil, 3),
|
|
}
|
|
// Initialize Sigils Effects arrays
|
|
for j := 0; j < 3; j++ {
|
|
e.Sigils[j].Effects = make([]mhfitem.MHFSigilEffect, 3)
|
|
}
|
|
equip = append(equip, e)
|
|
}
|
|
return equip
|
|
}
|
|
|
|
// =============================================================================
|
|
// Unit Tests — guard paths, no database
|
|
// =============================================================================
|
|
|
|
func TestUpdateInterior_PayloadTooLarge(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateInterior{
|
|
AckHandle: 1,
|
|
InteriorData: make([]byte, 65), // > 64 triggers guard
|
|
}
|
|
handleMsgMhfUpdateInterior(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Errorf("expected success ACK (guard returns succeed), got error code %d", ack.ErrorCode)
|
|
}
|
|
}
|
|
|
|
func TestUpdateMyhouseInfo_PayloadTooLarge(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateMyhouseInfo{
|
|
AckHandle: 2,
|
|
Data: make([]byte, 513), // > 512 triggers guard
|
|
}
|
|
handleMsgMhfUpdateMyhouseInfo(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Errorf("expected success ACK on oversized payload, got error code %d", ack.ErrorCode)
|
|
}
|
|
}
|
|
|
|
func TestSaveDecoMyset_PayloadTooShort(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfSaveDecoMyset{
|
|
AckHandle: 3,
|
|
RawDataPayload: []byte{0x00, 0x01}, // < 3 bytes
|
|
}
|
|
handleMsgMhfSaveDecoMyset(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Errorf("expected success ACK on short payload, got error code %d", ack.ErrorCode)
|
|
}
|
|
}
|
|
|
|
func TestUpdateWarehouse_BoxIndexTooHigh(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateWarehouse{
|
|
AckHandle: 4,
|
|
BoxIndex: 11, // > 10 triggers fail
|
|
}
|
|
handleMsgMhfUpdateWarehouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 1 {
|
|
t.Errorf("expected fail ACK for out-of-bounds box index, got error code %d", ack.ErrorCode)
|
|
}
|
|
}
|
|
|
|
func TestEnumerateHouse_Method5_EmptyResult(t *testing.T) {
|
|
server := createMockServer()
|
|
server.erupeConfig.RealClientMode = cfg.ZZ
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{
|
|
AckHandle: 5,
|
|
Method: 5, // Recent visitors — always returns empty
|
|
}
|
|
handleMsgMhfEnumerateHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
if !ack.IsBufferResponse {
|
|
t.Fatal("expected buffer response")
|
|
}
|
|
// First 2 bytes = count, should be 0
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
count := bf.ReadUint16()
|
|
if count != 0 {
|
|
t.Errorf("expected 0 houses for method 5, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestResetTitle_NoOp(t *testing.T) {
|
|
// handleMsgMhfResetTitle is an empty function — just verify no panic
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleMsgMhfResetTitle panicked: %v", r)
|
|
}
|
|
}()
|
|
handleMsgMhfResetTitle(nil, nil)
|
|
}
|
|
|
|
func TestOperateWarehouse_RenameBoxIndexTooHigh(t *testing.T) {
|
|
// Operation 2 = Rename. BoxIndex > 9 should skip the rename.
|
|
// This needs a DB for initializeWarehouse, so the full test is the
|
|
// integration test TestOperateWarehouse_Op2_RenameBoxIndexTooHigh below.
|
|
}
|
|
|
|
// =============================================================================
|
|
// Integration Tests — real PostgreSQL via SetupTestDB
|
|
// =============================================================================
|
|
|
|
func TestUpdateInterior_SavesData(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
interiorData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}
|
|
pkt := &mhfpacket.MsgMhfUpdateInterior{
|
|
AckHandle: 10,
|
|
InteriorData: interiorData,
|
|
}
|
|
handleMsgMhfUpdateInterior(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
|
|
// Verify data was persisted
|
|
_, _, furniture, _, _, _, _, err := session.server.houseRepo.GetHouseContents(charID)
|
|
if err != nil {
|
|
t.Fatalf("GetHouseContents failed: %v", err)
|
|
}
|
|
if len(furniture) < len(interiorData) {
|
|
t.Fatalf("furniture data too short: got %d bytes", len(furniture))
|
|
}
|
|
for i, b := range interiorData {
|
|
if furniture[i] != b {
|
|
t.Errorf("furniture[%d] = %#x, want %#x", i, furniture[i], b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateHouse_SetsStateAndPassword(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateHouse{
|
|
AckHandle: 11,
|
|
State: 3,
|
|
Password: "secret",
|
|
}
|
|
handleMsgMhfUpdateHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
|
|
state, password, err := session.server.houseRepo.GetHouseAccess(charID)
|
|
if err != nil {
|
|
t.Fatalf("GetHouseAccess failed: %v", err)
|
|
}
|
|
if state != 3 {
|
|
t.Errorf("state = %d, want 3", state)
|
|
}
|
|
if password != "secret" {
|
|
t.Errorf("password = %q, want %q", password, "secret")
|
|
}
|
|
}
|
|
|
|
func TestEnumerateHouse_Method4_ByCharID(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{
|
|
AckHandle: 12,
|
|
Method: 4,
|
|
CharID: charID,
|
|
}
|
|
handleMsgMhfEnumerateHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
count := bf.ReadUint16()
|
|
if count != 1 {
|
|
t.Errorf("expected 1 house for charID lookup, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestEnumerateHouse_Method3_ByName(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{
|
|
AckHandle: 13,
|
|
Method: 3,
|
|
Name: "HousePlayer",
|
|
}
|
|
handleMsgMhfEnumerateHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
count := bf.ReadUint16()
|
|
if count < 1 {
|
|
t.Errorf("expected at least 1 house for name search, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_OwnHouse_Destination9(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
// Set some interior data first
|
|
interior := make([]byte, 20)
|
|
interior[0] = 0xAB
|
|
_ = session.server.houseRepo.UpdateInterior(charID, interior)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{
|
|
AckHandle: 14,
|
|
CharID: charID,
|
|
Destination: 9, // Own house — bypasses access control
|
|
}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success loading own house, got error code %d", ack.ErrorCode)
|
|
}
|
|
if !ack.IsBufferResponse {
|
|
t.Fatal("expected buffer response")
|
|
}
|
|
if len(ack.Payload) == 0 {
|
|
t.Error("expected non-empty house data")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_WrongPassword_Fails(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
// Set a password on the house
|
|
_ = session.server.houseRepo.UpdateHouseState(charID, 2, "correct")
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{
|
|
AckHandle: 15,
|
|
CharID: charID,
|
|
Destination: 3, // Others house
|
|
CheckPass: true,
|
|
Password: "wrong",
|
|
}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 1 {
|
|
t.Errorf("expected fail ACK for wrong password, got error code %d", ack.ErrorCode)
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_CorrectPassword_Succeeds(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
_ = session.server.houseRepo.UpdateHouseState(charID, 2, "correct")
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{
|
|
AckHandle: 16,
|
|
CharID: charID,
|
|
Destination: 3,
|
|
CheckPass: true,
|
|
Password: "correct",
|
|
}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Errorf("expected success for correct password, got error code %d", ack.ErrorCode)
|
|
}
|
|
if !ack.IsBufferResponse {
|
|
t.Fatal("expected buffer response for house data")
|
|
}
|
|
}
|
|
|
|
func TestGetMyhouseInfo_NoData(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfGetMyhouseInfo{AckHandle: 17}
|
|
handleMsgMhfGetMyhouseInfo(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
// When no mission data exists, handler returns 9-byte default
|
|
if len(ack.Payload) != 9 {
|
|
t.Errorf("expected 9-byte default payload, got %d bytes", len(ack.Payload))
|
|
}
|
|
}
|
|
|
|
func TestGetMyhouseInfo_WithData(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
missionData := make([]byte, 50)
|
|
missionData[0] = 0xDE
|
|
missionData[1] = 0xAD
|
|
_ = session.server.houseRepo.UpdateMission(charID, missionData)
|
|
|
|
pkt := &mhfpacket.MsgMhfGetMyhouseInfo{AckHandle: 18}
|
|
handleMsgMhfGetMyhouseInfo(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
if len(ack.Payload) != 50 {
|
|
t.Fatalf("expected 50-byte payload, got %d bytes", len(ack.Payload))
|
|
}
|
|
if ack.Payload[0] != 0xDE || ack.Payload[1] != 0xAD {
|
|
t.Errorf("payload mismatch: got %#x %#x, want 0xDE 0xAD", ack.Payload[0], ack.Payload[1])
|
|
}
|
|
}
|
|
|
|
func TestUpdateMyhouseInfo_SavesData(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
missionData := make([]byte, 100)
|
|
missionData[0] = 0xCA
|
|
missionData[1] = 0xFE
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateMyhouseInfo{
|
|
AckHandle: 19,
|
|
Data: missionData,
|
|
}
|
|
handleMsgMhfUpdateMyhouseInfo(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
|
|
// Verify via repository
|
|
data, err := session.server.houseRepo.GetMission(charID)
|
|
if err != nil {
|
|
t.Fatalf("GetMission failed: %v", err)
|
|
}
|
|
if len(data) != 100 {
|
|
t.Fatalf("mission data length = %d, want 100", len(data))
|
|
}
|
|
if data[0] != 0xCA || data[1] != 0xFE {
|
|
t.Errorf("mission data mismatch: got %#x %#x, want 0xCA 0xFE", data[0], data[1])
|
|
}
|
|
}
|
|
|
|
func TestEnumerateTitle_Empty(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateTitle{AckHandle: 20}
|
|
handleMsgMhfEnumerateTitle(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
count := bf.ReadUint16()
|
|
if count != 0 {
|
|
t.Errorf("expected 0 titles, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestAcquireTitle_AndEnumerate(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
// Acquire two titles
|
|
acquirePkt := &mhfpacket.MsgMhfAcquireTitle{
|
|
AckHandle: 21,
|
|
TitleIDs: []uint16{100, 200},
|
|
}
|
|
handleMsgMhfAcquireTitle(session, acquirePkt)
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("acquire failed: error code %d", ack.ErrorCode)
|
|
}
|
|
|
|
// Enumerate
|
|
enumPkt := &mhfpacket.MsgMhfEnumerateTitle{AckHandle: 22}
|
|
handleMsgMhfEnumerateTitle(session, enumPkt)
|
|
ack = readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("enumerate failed: error code %d", ack.ErrorCode)
|
|
}
|
|
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
count := bf.ReadUint16()
|
|
if count != 2 {
|
|
t.Errorf("expected 2 titles, got %d", count)
|
|
}
|
|
|
|
// Read title IDs
|
|
_ = bf.ReadUint16() // unk
|
|
ids := make(map[uint16]bool)
|
|
for i := 0; i < int(count); i++ {
|
|
id := bf.ReadUint16()
|
|
ids[id] = true
|
|
_ = bf.ReadUint16() // unk
|
|
_ = bf.ReadUint32() // acquired timestamp
|
|
_ = bf.ReadUint32() // updated timestamp
|
|
}
|
|
if !ids[100] || !ids[200] {
|
|
t.Errorf("expected title IDs 100 and 200, got %v", ids)
|
|
}
|
|
}
|
|
|
|
func TestAcquireTitle_Duplicate(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
// Acquire title 300
|
|
pkt1 := &mhfpacket.MsgMhfAcquireTitle{AckHandle: 23, TitleIDs: []uint16{300}}
|
|
handleMsgMhfAcquireTitle(session, pkt1)
|
|
_ = readAck(t, session)
|
|
|
|
// Acquire same title again
|
|
pkt2 := &mhfpacket.MsgMhfAcquireTitle{AckHandle: 24, TitleIDs: []uint16{300}}
|
|
handleMsgMhfAcquireTitle(session, pkt2)
|
|
_ = readAck(t, session)
|
|
|
|
// Should still have exactly 1 title (upsert)
|
|
titles, err := session.server.houseRepo.GetTitles(charID)
|
|
if err != nil {
|
|
t.Fatalf("GetTitles failed: %v", err)
|
|
}
|
|
if len(titles) != 1 {
|
|
t.Errorf("expected 1 title after duplicate acquire, got %d", len(titles))
|
|
}
|
|
}
|
|
|
|
func TestOperateWarehouse_Op0_GetBoxNames(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
// Initialize warehouse and rename a box
|
|
_ = session.server.houseRepo.InitializeWarehouse(charID)
|
|
_ = session.server.houseRepo.RenameWarehouseBox(charID, 0, 0, "MyItems")
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{
|
|
AckHandle: 25,
|
|
Operation: 0,
|
|
}
|
|
handleMsgMhfOperateWarehouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
if !ack.IsBufferResponse {
|
|
t.Fatal("expected buffer response")
|
|
}
|
|
// Response format: op(1) + renewal(4) + usages(2) + count(1) + entries
|
|
if len(ack.Payload) < 8 {
|
|
t.Fatalf("payload too short: %d bytes", len(ack.Payload))
|
|
}
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
op := bf.ReadUint8()
|
|
if op != 0 {
|
|
t.Errorf("op = %d, want 0", op)
|
|
}
|
|
}
|
|
|
|
func TestOperateWarehouse_Op3_GetUsageLimit(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{
|
|
AckHandle: 26,
|
|
Operation: 3,
|
|
}
|
|
handleMsgMhfOperateWarehouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
// Response: op(1) + renewal_time(4) + usages(2) = 7 bytes
|
|
bf := byteframe.NewByteFrameFromBytes(ack.Payload)
|
|
op := bf.ReadUint8()
|
|
if op != 3 {
|
|
t.Errorf("op = %d, want 3", op)
|
|
}
|
|
renewalTime := bf.ReadUint32()
|
|
usages := bf.ReadUint16()
|
|
if renewalTime != 0 {
|
|
t.Errorf("renewal time = %d, want 0", renewalTime)
|
|
}
|
|
if usages != 10000 {
|
|
t.Errorf("usages = %d, want 10000", usages)
|
|
}
|
|
}
|
|
|
|
func TestOperateWarehouse_Op2_RenameBoxIndexTooHigh(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{
|
|
AckHandle: 27,
|
|
Operation: 2,
|
|
BoxIndex: 10, // > 9, rename should be skipped
|
|
Name: "ShouldNotRename",
|
|
}
|
|
handleMsgMhfOperateWarehouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success ACK even with skipped rename, got error code %d", ack.ErrorCode)
|
|
}
|
|
}
|
|
|
|
func TestEnumerateWarehouse_EmptyBox(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateWarehouse{
|
|
AckHandle: 28,
|
|
BoxType: 0, // Items
|
|
BoxIndex: 0,
|
|
}
|
|
handleMsgMhfEnumerateWarehouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
if !ack.IsBufferResponse {
|
|
t.Fatal("expected buffer response")
|
|
}
|
|
// Empty box returns serialized empty list: count(2) + unk(2) = 4 bytes minimum
|
|
if len(ack.Payload) < 4 {
|
|
t.Errorf("expected at least 4-byte payload for empty box, got %d", len(ack.Payload))
|
|
}
|
|
}
|
|
|
|
func TestUpdateWarehouse_Items(t *testing.T) {
|
|
_, _, session, charID := setupHouseTest(t)
|
|
|
|
items := []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 42}, Quantity: 10, WarehouseID: token.RNG.Uint32()},
|
|
{Item: mhfitem.MHFItem{ItemID: 99}, Quantity: 5, WarehouseID: token.RNG.Uint32()},
|
|
}
|
|
pkt := &mhfpacket.MsgMhfUpdateWarehouse{
|
|
AckHandle: 29,
|
|
BoxType: 0,
|
|
BoxIndex: 0,
|
|
UpdatedItems: items,
|
|
}
|
|
handleMsgMhfUpdateWarehouse(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
|
|
// Read back via enumerate
|
|
session2 := createMockSession(charID, session.server)
|
|
enumPkt := &mhfpacket.MsgMhfEnumerateWarehouse{
|
|
AckHandle: 30,
|
|
BoxType: 0,
|
|
BoxIndex: 0,
|
|
}
|
|
handleMsgMhfEnumerateWarehouse(session2, enumPkt)
|
|
|
|
ack2 := readAck(t, session2)
|
|
if ack2.ErrorCode != 0 {
|
|
t.Fatalf("enumerate failed: error code %d", ack2.ErrorCode)
|
|
}
|
|
// Parse the serialized items
|
|
bf := byteframe.NewByteFrameFromBytes(ack2.Payload)
|
|
count := bf.ReadUint16()
|
|
if count != 2 {
|
|
t.Errorf("expected 2 items in warehouse, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestLoadDecoMyset_Default(t *testing.T) {
|
|
_, _, session, _ := setupHouseTest(t)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadDecoMyset{AckHandle: 31}
|
|
handleMsgMhfLoadDecoMyset(session, pkt)
|
|
|
|
ack := readAck(t, session)
|
|
if ack.ErrorCode != 0 {
|
|
t.Fatalf("expected success, got error code %d", ack.ErrorCode)
|
|
}
|
|
if !ack.IsBufferResponse {
|
|
t.Fatal("expected buffer response")
|
|
}
|
|
// G10+ mode returns {0x01, 0x00}
|
|
if len(ack.Payload) < 2 {
|
|
t.Fatalf("expected at least 2-byte payload, got %d", len(ack.Payload))
|
|
}
|
|
if ack.Payload[0] != 0x01 || ack.Payload[1] != 0x00 {
|
|
t.Errorf("expected default {0x01, 0x00}, got {%#x, %#x}", ack.Payload[0], ack.Payload[1])
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Existing pure-logic tests and benchmarks (unchanged)
|
|
// =============================================================================
|
|
|
|
// TestWarehouseItemSerialization verifies warehouse item serialization
|
|
func TestWarehouseItemSerialization(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
items []mhfitem.MHFItemStack
|
|
}{
|
|
{
|
|
name: "empty_warehouse",
|
|
items: []mhfitem.MHFItemStack{},
|
|
},
|
|
{
|
|
name: "single_item",
|
|
items: []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple_items",
|
|
items: []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
{Item: mhfitem.MHFItem{ItemID: 2}, Quantity: 20},
|
|
{Item: mhfitem.MHFItem{ItemID: 3}, Quantity: 30},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Serialize
|
|
serialized := mhfitem.SerializeWarehouseItems(tt.items)
|
|
|
|
// Basic validation
|
|
if serialized == nil {
|
|
t.Error("serialization returned nil")
|
|
}
|
|
|
|
// Verify we can work with the serialized data
|
|
if serialized == nil {
|
|
t.Error("invalid serialized length")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseEquipmentSerialization verifies warehouse equipment serialization
|
|
func TestWarehouseEquipmentSerialization(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
equipment []mhfitem.MHFEquipment
|
|
}{
|
|
{
|
|
name: "empty_equipment",
|
|
equipment: []mhfitem.MHFEquipment{},
|
|
},
|
|
{
|
|
name: "single_equipment",
|
|
equipment: createTestEquipment([]uint16{100}, []uint32{1}),
|
|
},
|
|
{
|
|
name: "multiple_equipment",
|
|
equipment: createTestEquipment([]uint16{100, 101, 102}, []uint32{1, 2, 3}),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Serialize
|
|
serialized := mhfitem.SerializeWarehouseEquipment(tt.equipment, cfg.ZZ)
|
|
|
|
// Basic validation
|
|
if serialized == nil {
|
|
t.Error("serialization returned nil")
|
|
}
|
|
|
|
// Verify we can work with the serialized data
|
|
if serialized == nil {
|
|
t.Error("invalid serialized length")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseItemDiff verifies the item diff calculation
|
|
func TestWarehouseItemDiff(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
oldItems []mhfitem.MHFItemStack
|
|
newItems []mhfitem.MHFItemStack
|
|
wantDiff bool
|
|
}{
|
|
{
|
|
name: "no_changes",
|
|
oldItems: []mhfitem.MHFItemStack{{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10}},
|
|
newItems: []mhfitem.MHFItemStack{{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10}},
|
|
wantDiff: false,
|
|
},
|
|
{
|
|
name: "quantity_changed",
|
|
oldItems: []mhfitem.MHFItemStack{{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10}},
|
|
newItems: []mhfitem.MHFItemStack{{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 15}},
|
|
wantDiff: true,
|
|
},
|
|
{
|
|
name: "item_added",
|
|
oldItems: []mhfitem.MHFItemStack{{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10}},
|
|
newItems: []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
{Item: mhfitem.MHFItem{ItemID: 2}, Quantity: 5},
|
|
},
|
|
wantDiff: true,
|
|
},
|
|
{
|
|
name: "item_removed",
|
|
oldItems: []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
{Item: mhfitem.MHFItem{ItemID: 2}, Quantity: 5},
|
|
},
|
|
newItems: []mhfitem.MHFItemStack{{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10}},
|
|
wantDiff: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
diff := mhfitem.DiffItemStacks(tt.oldItems, tt.newItems)
|
|
|
|
// Verify that diff returns a valid result (not nil)
|
|
if diff == nil {
|
|
t.Error("diff should not be nil")
|
|
}
|
|
|
|
// The diff function returns items where Quantity > 0
|
|
// So with no changes (all same quantity), diff should have same items
|
|
if tt.name == "no_changes" {
|
|
if len(diff) == 0 {
|
|
t.Error("no_changes should return items")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseEquipmentMerge verifies equipment merging logic
|
|
func TestWarehouseEquipmentMerge(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
oldEquip []mhfitem.MHFEquipment
|
|
newEquip []mhfitem.MHFEquipment
|
|
wantMerged int
|
|
}{
|
|
{
|
|
name: "merge_empty",
|
|
oldEquip: []mhfitem.MHFEquipment{},
|
|
newEquip: []mhfitem.MHFEquipment{},
|
|
wantMerged: 0,
|
|
},
|
|
{
|
|
name: "add_new_equipment",
|
|
oldEquip: []mhfitem.MHFEquipment{
|
|
{ItemID: 100, WarehouseID: 1},
|
|
},
|
|
newEquip: []mhfitem.MHFEquipment{
|
|
{ItemID: 101, WarehouseID: 0}, // New item, no warehouse ID yet
|
|
},
|
|
wantMerged: 2, // Old + new
|
|
},
|
|
{
|
|
name: "update_existing_equipment",
|
|
oldEquip: []mhfitem.MHFEquipment{
|
|
{ItemID: 100, WarehouseID: 1},
|
|
},
|
|
newEquip: []mhfitem.MHFEquipment{
|
|
{ItemID: 101, WarehouseID: 1}, // Update existing
|
|
},
|
|
wantMerged: 1, // Updated in place
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Simulate the merge logic from handleMsgMhfUpdateWarehouse
|
|
var finalEquip []mhfitem.MHFEquipment
|
|
oEquips := tt.oldEquip
|
|
|
|
for _, uEquip := range tt.newEquip {
|
|
exists := false
|
|
for i := range oEquips {
|
|
if oEquips[i].WarehouseID == uEquip.WarehouseID && uEquip.WarehouseID != 0 {
|
|
exists = true
|
|
oEquips[i].ItemID = uEquip.ItemID
|
|
break
|
|
}
|
|
}
|
|
if !exists {
|
|
// Generate new warehouse ID
|
|
uEquip.WarehouseID = token.RNG.Uint32()
|
|
finalEquip = append(finalEquip, uEquip)
|
|
}
|
|
}
|
|
|
|
for _, oEquip := range oEquips {
|
|
if oEquip.ItemID > 0 {
|
|
finalEquip = append(finalEquip, oEquip)
|
|
}
|
|
}
|
|
|
|
// Verify merge result count
|
|
if len(finalEquip) != tt.wantMerged {
|
|
t.Errorf("expected %d merged equipment, got %d", tt.wantMerged, len(finalEquip))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseIDGeneration verifies warehouse ID uniqueness
|
|
func TestWarehouseIDGeneration(t *testing.T) {
|
|
// Generate multiple warehouse IDs and verify they're unique
|
|
idCount := 100
|
|
ids := make(map[uint32]bool)
|
|
|
|
for i := 0; i < idCount; i++ {
|
|
id := token.RNG.Uint32()
|
|
if id == 0 {
|
|
t.Error("generated warehouse ID is 0 (invalid)")
|
|
}
|
|
if ids[id] {
|
|
// While collisions are possible with random IDs,
|
|
// they should be extremely rare
|
|
t.Logf("Warning: duplicate warehouse ID generated: %d", id)
|
|
}
|
|
ids[id] = true
|
|
}
|
|
|
|
if len(ids) < idCount*90/100 {
|
|
t.Errorf("too many duplicate IDs: got %d unique out of %d", len(ids), idCount)
|
|
}
|
|
}
|
|
|
|
// TestWarehouseItemRemoval verifies item removal logic
|
|
func TestWarehouseItemRemoval(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
items []mhfitem.MHFItemStack
|
|
removeID uint16
|
|
wantRemain int
|
|
}{
|
|
{
|
|
name: "remove_existing",
|
|
items: []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
{Item: mhfitem.MHFItem{ItemID: 2}, Quantity: 20},
|
|
},
|
|
removeID: 1,
|
|
wantRemain: 1,
|
|
},
|
|
{
|
|
name: "remove_non_existing",
|
|
items: []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
},
|
|
removeID: 999,
|
|
wantRemain: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var remaining []mhfitem.MHFItemStack
|
|
for _, item := range tt.items {
|
|
if item.Item.ItemID != tt.removeID {
|
|
remaining = append(remaining, item)
|
|
}
|
|
}
|
|
|
|
if len(remaining) != tt.wantRemain {
|
|
t.Errorf("expected %d remaining items, got %d", tt.wantRemain, len(remaining))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseEquipmentRemoval verifies equipment removal logic
|
|
func TestWarehouseEquipmentRemoval(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
equipment []mhfitem.MHFEquipment
|
|
setZeroID uint32
|
|
wantActive int
|
|
}{
|
|
{
|
|
name: "remove_by_setting_zero",
|
|
equipment: []mhfitem.MHFEquipment{
|
|
{ItemID: 100, WarehouseID: 1},
|
|
{ItemID: 101, WarehouseID: 2},
|
|
},
|
|
setZeroID: 1,
|
|
wantActive: 1,
|
|
},
|
|
{
|
|
name: "all_active",
|
|
equipment: []mhfitem.MHFEquipment{
|
|
{ItemID: 100, WarehouseID: 1},
|
|
{ItemID: 101, WarehouseID: 2},
|
|
},
|
|
setZeroID: 999,
|
|
wantActive: 2,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Simulate removal by setting ItemID to 0
|
|
equipment := make([]mhfitem.MHFEquipment, len(tt.equipment))
|
|
copy(equipment, tt.equipment)
|
|
|
|
for i := range equipment {
|
|
if equipment[i].WarehouseID == tt.setZeroID {
|
|
equipment[i].ItemID = 0
|
|
}
|
|
}
|
|
|
|
// Count active equipment (ItemID > 0)
|
|
activeCount := 0
|
|
for _, eq := range equipment {
|
|
if eq.ItemID > 0 {
|
|
activeCount++
|
|
}
|
|
}
|
|
|
|
if activeCount != tt.wantActive {
|
|
t.Errorf("expected %d active equipment, got %d", tt.wantActive, activeCount)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseBoxIndexValidation verifies box index bounds
|
|
func TestWarehouseBoxIndexValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
boxIndex uint8
|
|
isValid bool
|
|
}{
|
|
{
|
|
name: "box_0",
|
|
boxIndex: 0,
|
|
isValid: true,
|
|
},
|
|
{
|
|
name: "box_1",
|
|
boxIndex: 1,
|
|
isValid: true,
|
|
},
|
|
{
|
|
name: "box_9",
|
|
boxIndex: 9,
|
|
isValid: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Verify box index is within reasonable bounds
|
|
if tt.isValid && tt.boxIndex > 100 {
|
|
t.Error("box index unreasonably high")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWarehouseErrorRecovery verifies error handling doesn't corrupt state
|
|
func TestWarehouseErrorRecovery(t *testing.T) {
|
|
t.Run("database_error_handling", func(t *testing.T) {
|
|
// After our fix, database errors should:
|
|
// 1. Be logged with s.logger.Error()
|
|
// 2. Send doAckSimpleFail()
|
|
// 3. Return immediately
|
|
// 4. NOT send doAckSimpleSucceed() (the bug we fixed)
|
|
|
|
// This test documents the expected behavior
|
|
})
|
|
|
|
t.Run("serialization_error_handling", func(t *testing.T) {
|
|
// Test that serialization errors are handled gracefully
|
|
emptyItems := []mhfitem.MHFItemStack{}
|
|
serialized := mhfitem.SerializeWarehouseItems(emptyItems)
|
|
|
|
// Should handle empty gracefully
|
|
if serialized == nil {
|
|
t.Error("serialization of empty items should not return nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
// BenchmarkWarehouseSerialization benchmarks warehouse serialization performance
|
|
func BenchmarkWarehouseSerialization(b *testing.B) {
|
|
items := []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1}, Quantity: 10},
|
|
{Item: mhfitem.MHFItem{ItemID: 2}, Quantity: 20},
|
|
{Item: mhfitem.MHFItem{ItemID: 3}, Quantity: 30},
|
|
{Item: mhfitem.MHFItem{ItemID: 4}, Quantity: 40},
|
|
{Item: mhfitem.MHFItem{ItemID: 5}, Quantity: 50},
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = mhfitem.SerializeWarehouseItems(items)
|
|
}
|
|
}
|
|
|
|
// BenchmarkWarehouseEquipmentMerge benchmarks equipment merge performance
|
|
func BenchmarkWarehouseEquipmentMerge(b *testing.B) {
|
|
oldEquip := make([]mhfitem.MHFEquipment, 50)
|
|
for i := range oldEquip {
|
|
oldEquip[i] = mhfitem.MHFEquipment{
|
|
ItemID: uint16(100 + i),
|
|
WarehouseID: uint32(i + 1),
|
|
}
|
|
}
|
|
|
|
newEquip := make([]mhfitem.MHFEquipment, 10)
|
|
for i := range newEquip {
|
|
newEquip[i] = mhfitem.MHFEquipment{
|
|
ItemID: uint16(200 + i),
|
|
WarehouseID: uint32(i + 1),
|
|
}
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
var finalEquip []mhfitem.MHFEquipment
|
|
oEquips := oldEquip
|
|
|
|
for _, uEquip := range newEquip {
|
|
exists := false
|
|
for j := range oEquips {
|
|
if oEquips[j].WarehouseID == uEquip.WarehouseID {
|
|
exists = true
|
|
oEquips[j].ItemID = uEquip.ItemID
|
|
break
|
|
}
|
|
}
|
|
if !exists {
|
|
finalEquip = append(finalEquip, uEquip)
|
|
}
|
|
}
|
|
|
|
for _, oEquip := range oEquips {
|
|
if oEquip.ItemID > 0 {
|
|
finalEquip = append(finalEquip, oEquip)
|
|
}
|
|
}
|
|
_ = finalEquip // Use finalEquip to avoid unused variable warning
|
|
}
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateHouse_Method3_SearchByName(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.erupeConfig.RealClientMode = cfg.ZZ
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 3, Name: "TestHouse"}
|
|
handleMsgMhfEnumerateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateHouse_Method4_ByCharID(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.erupeConfig.RealClientMode = cfg.ZZ
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 4, CharID: 200}
|
|
handleMsgMhfEnumerateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateHouse_Method5_RecentVisitors(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 5}
|
|
handleMsgMhfEnumerateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateHouse_Method1_Friends(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
charRepo := newMockCharacterRepo()
|
|
charRepo.strings["friends"] = ""
|
|
srv.charRepo = charRepo
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 1}
|
|
handleMsgMhfEnumerateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateHouse_Method2_GuildMembers(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
guild := &Guild{ID: 1}
|
|
srv.guildRepo = &mockGuildRepo{guild: guild}
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 2}
|
|
handleMsgMhfEnumerateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateHouse_Method2_NoGuild(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
srv.guildRepo = &mockGuildRepo{getErr: errNotFound}
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateHouse{AckHandle: 1, Method: 2}
|
|
handleMsgMhfEnumerateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfSaveDecoMyset_ShortPayload(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.charRepo = newMockCharacterRepo()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfSaveDecoMyset{AckHandle: 1, RawDataPayload: []byte{0x00, 0x01}}
|
|
handleMsgMhfSaveDecoMyset(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfSaveDecoMyset_WithData(t *testing.T) {
|
|
srv := createMockServer()
|
|
charRepo := newMockCharacterRepo()
|
|
// Pre-populate with version byte + 0 sets
|
|
charRepo.columns["decomyset"] = []byte{0x01, 0x00}
|
|
srv.charRepo = charRepo
|
|
srv.erupeConfig.RealClientMode = cfg.ZZ
|
|
|
|
s := createMockSession(100, srv)
|
|
|
|
// Build payload: version byte + 1 set with index 0 + 76 bytes of data
|
|
payload := make([]byte, 3+2+76)
|
|
payload[0] = 0x01 // version
|
|
payload[1] = 0x01 // count
|
|
payload[2] = 0x00 // padding
|
|
|
|
pkt := &mhfpacket.MsgMhfSaveDecoMyset{AckHandle: 1, RawDataPayload: payload}
|
|
handleMsgMhfSaveDecoMyset(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfInfoTournament_Type2(t *testing.T) {
|
|
srv := createMockServer()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfInfoTournament{AckHandle: 1, QueryType: 2}
|
|
handleMsgMhfInfoTournament(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfUpdateInterior_Normal(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateInterior{AckHandle: 1, InteriorData: make([]byte, 20)}
|
|
handleMsgMhfUpdateInterior(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfUpdateInterior_TooLarge(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateInterior{AckHandle: 1, InteriorData: make([]byte, 100)}
|
|
handleMsgMhfUpdateInterior(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfUpdateMyhouseInfo_Normal(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateMyhouseInfo{AckHandle: 1, Data: make([]byte, 9)}
|
|
handleMsgMhfUpdateMyhouseInfo(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfUpdateMyhouseInfo_TooLarge(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateMyhouseInfo{AckHandle: 1, Data: make([]byte, 600)}
|
|
handleMsgMhfUpdateMyhouseInfo(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfGetMyhouseInfo(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfGetMyhouseInfo{AckHandle: 1}
|
|
handleMsgMhfGetMyhouseInfo(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateTitle(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateTitle{AckHandle: 1}
|
|
handleMsgMhfEnumerateTitle(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfAcquireTitle(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfAcquireTitle{AckHandle: 1, TitleIDs: []uint16{1, 2, 3}}
|
|
handleMsgMhfAcquireTitle(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfUpdateHouse(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateHouse{AckHandle: 1, State: 2, Password: "1234"}
|
|
handleMsgMhfUpdateHouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfOperateWarehouse_Op0(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 0}
|
|
handleMsgMhfOperateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfOperateWarehouse_Op1(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 1}
|
|
handleMsgMhfOperateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfOperateWarehouse_Op2_Rename(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 2, BoxType: 0, BoxIndex: 1, Name: "MyBox"}
|
|
handleMsgMhfOperateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfOperateWarehouse_Op3(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 3}
|
|
handleMsgMhfOperateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfOperateWarehouse_Op4(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfOperateWarehouse{AckHandle: 1, Operation: 4}
|
|
handleMsgMhfOperateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
// --- handleMsgMhfLoadHouse tests ---
|
|
|
|
func TestLoadHouse_OwnHouse(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
server.charRepo = newMockCharacterRepo()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 1, Destination: 9}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_OthersHouse(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
server.charRepo = newMockCharacterRepo()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 2, Destination: 3}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_Bookshelf(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
server.charRepo = newMockCharacterRepo()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 1, Destination: 4}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_Gallery(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 1, Destination: 5}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_Tore(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 1, Destination: 8}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_Garden(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
server.goocooRepo = newMockGoocooRepo()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 1, Destination: 10}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestLoadHouse_UnknownDestination(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadHouse{AckHandle: 100, CharID: 1, Destination: 99}
|
|
handleMsgMhfLoadHouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
// --- handleMsgMhfLoadDecoMyset tests ---
|
|
|
|
func TestLoadDecoMyset(t *testing.T) {
|
|
server := createMockServer()
|
|
charMock := newMockCharacterRepo()
|
|
server.charRepo = charMock
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfLoadDecoMyset{AckHandle: 100}
|
|
handleMsgMhfLoadDecoMyset(session, pkt)
|
|
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Response should have default data")
|
|
}
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
// --- handleMsgMhfUpdateWarehouse tests ---
|
|
|
|
func TestUpdateWarehouse_EmptyItems(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateWarehouse{
|
|
AckHandle: 100,
|
|
BoxType: 0,
|
|
BoxIndex: 0,
|
|
UpdatedItems: []mhfitem.MHFItemStack{},
|
|
}
|
|
handleMsgMhfUpdateWarehouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestUpdateWarehouse_EmptyEquipment(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateWarehouse{
|
|
AckHandle: 100,
|
|
BoxType: 1,
|
|
BoxIndex: 0,
|
|
UpdatedEquipment: []mhfitem.MHFEquipment{},
|
|
}
|
|
handleMsgMhfUpdateWarehouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestUpdateWarehouse_InvalidIndex(t *testing.T) {
|
|
server := createMockServer()
|
|
server.houseRepo = newMockHouseRepoForItems()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfUpdateWarehouse{
|
|
AckHandle: 100,
|
|
BoxType: 0,
|
|
BoxIndex: 15, // > 10
|
|
}
|
|
handleMsgMhfUpdateWarehouse(session, pkt)
|
|
|
|
select {
|
|
case <-session.sendPackets:
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateWarehouse_Items(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateWarehouse{AckHandle: 1, BoxType: 0, BoxIndex: 0}
|
|
handleMsgMhfEnumerateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|
|
|
|
func TestHandleMsgMhfEnumerateWarehouse_Equipment(t *testing.T) {
|
|
srv := createMockServer()
|
|
srv.houseRepo = newMockHouseRepoForItems()
|
|
s := createMockSession(100, srv)
|
|
|
|
pkt := &mhfpacket.MsgMhfEnumerateWarehouse{AckHandle: 1, BoxType: 1, BoxIndex: 0}
|
|
handleMsgMhfEnumerateWarehouse(s, pkt)
|
|
<-s.sendPackets
|
|
}
|