mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test(channelserver): add comprehensive handler-level tests for handlers_house
Cover all 14 handler functions in handlers_house.go with 25 new tests: - 7 unit tests for guard paths (payload size limits, box index bounds, no-op handlers) that run without a database - 18 integration tests against real PostgreSQL covering interior updates, house state/password, house enumeration by char ID and name, house loading with access control, mission data CRUD, title acquisition with dedup, warehouse operations (box names, usage limits, rename guards), item storage round-trips, and deco myset defaults Introduces readAck() helper to parse MsgSysAck wire format from the sendPackets channel, and setupHouseTest() for DB + session scaffolding with user_binary row initialization.
This commit is contained in:
@@ -1,12 +1,74 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/common/mhfitem"
|
||||
"erupe-ce/common/token"
|
||||
"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 = _config.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
|
||||
@@ -26,6 +88,610 @@ func createTestEquipment(itemIDs []uint16, warehouseIDs []uint32) []mhfitem.MHFE
|
||||
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 = _config.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 {
|
||||
|
||||
Reference in New Issue
Block a user