package mhfitem import ( "bytes" "erupe-ce/common/byteframe" "erupe-ce/common/token" cfg "erupe-ce/config" "testing" ) func TestReadWarehouseItem(t *testing.T) { bf := byteframe.NewByteFrame() bf.WriteUint32(12345) // WarehouseID bf.WriteUint16(100) // ItemID bf.WriteUint16(5) // Quantity bf.WriteUint32(999999) // Unk0 _, _ = bf.Seek(0, 0) item := ReadWarehouseItem(bf) if item.WarehouseID != 12345 { t.Errorf("WarehouseID = %d, want 12345", item.WarehouseID) } if item.Item.ItemID != 100 { t.Errorf("ItemID = %d, want 100", item.Item.ItemID) } if item.Quantity != 5 { t.Errorf("Quantity = %d, want 5", item.Quantity) } if item.Unk0 != 999999 { t.Errorf("Unk0 = %d, want 999999", item.Unk0) } } func TestReadWarehouseItem_ZeroWarehouseID(t *testing.T) { // When WarehouseID is 0, it should be replaced with a random value bf := byteframe.NewByteFrame() bf.WriteUint32(0) // WarehouseID = 0 bf.WriteUint16(100) // ItemID bf.WriteUint16(5) // Quantity bf.WriteUint32(0) // Unk0 _, _ = bf.Seek(0, 0) item := ReadWarehouseItem(bf) if item.WarehouseID == 0 { t.Error("WarehouseID should be replaced with random value when input is 0") } } func TestMHFItemStack_ToBytes(t *testing.T) { item := MHFItemStack{ WarehouseID: 12345, Item: MHFItem{ItemID: 100}, Quantity: 5, Unk0: 999999, } data := item.ToBytes() if len(data) != 12 { // 4 + 2 + 2 + 4 t.Errorf("ToBytes() length = %d, want 12", len(data)) } // Read it back bf := byteframe.NewByteFrameFromBytes(data) readItem := ReadWarehouseItem(bf) if readItem.WarehouseID != item.WarehouseID { t.Errorf("WarehouseID = %d, want %d", readItem.WarehouseID, item.WarehouseID) } if readItem.Item.ItemID != item.Item.ItemID { t.Errorf("ItemID = %d, want %d", readItem.Item.ItemID, item.Item.ItemID) } if readItem.Quantity != item.Quantity { t.Errorf("Quantity = %d, want %d", readItem.Quantity, item.Quantity) } if readItem.Unk0 != item.Unk0 { t.Errorf("Unk0 = %d, want %d", readItem.Unk0, item.Unk0) } } func TestSerializeWarehouseItems(t *testing.T) { items := []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 5, Unk0: 0}, {WarehouseID: 2, Item: MHFItem{ItemID: 200}, Quantity: 10, Unk0: 0}, } data := SerializeWarehouseItems(items) bf := byteframe.NewByteFrameFromBytes(data) count := bf.ReadUint16() if count != 2 { t.Errorf("count = %d, want 2", count) } bf.ReadUint16() // Skip unused for i := 0; i < 2; i++ { item := ReadWarehouseItem(bf) if item.WarehouseID != items[i].WarehouseID { t.Errorf("item[%d] WarehouseID = %d, want %d", i, item.WarehouseID, items[i].WarehouseID) } if item.Item.ItemID != items[i].Item.ItemID { t.Errorf("item[%d] ItemID = %d, want %d", i, item.Item.ItemID, items[i].Item.ItemID) } } } func TestSerializeWarehouseItems_Empty(t *testing.T) { items := []MHFItemStack{} data := SerializeWarehouseItems(items) bf := byteframe.NewByteFrameFromBytes(data) count := bf.ReadUint16() if count != 0 { t.Errorf("count = %d, want 0", count) } } func TestDiffItemStacks(t *testing.T) { tests := []struct { name string old []MHFItemStack update []MHFItemStack wantLen int checkFn func(t *testing.T, result []MHFItemStack) }{ { name: "update existing quantity", old: []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 5}, }, update: []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 10}, }, wantLen: 1, checkFn: func(t *testing.T, result []MHFItemStack) { if result[0].Quantity != 10 { t.Errorf("Quantity = %d, want 10", result[0].Quantity) } }, }, { name: "add new item", old: []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 5}, }, update: []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 5}, {WarehouseID: 0, Item: MHFItem{ItemID: 200}, Quantity: 3}, // WarehouseID 0 = new }, wantLen: 2, checkFn: func(t *testing.T, result []MHFItemStack) { hasNewItem := false for _, item := range result { if item.Item.ItemID == 200 { hasNewItem = true if item.WarehouseID == 0 { t.Error("New item should have generated WarehouseID") } } } if !hasNewItem { t.Error("New item should be in result") } }, }, { name: "remove item (quantity 0)", old: []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 5}, {WarehouseID: 2, Item: MHFItem{ItemID: 200}, Quantity: 10}, }, update: []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 0}, // Removed }, wantLen: 1, checkFn: func(t *testing.T, result []MHFItemStack) { for _, item := range result { if item.WarehouseID == 1 { t.Error("Item with quantity 0 should be removed") } } }, }, { name: "empty old, add new", old: []MHFItemStack{}, update: []MHFItemStack{{WarehouseID: 0, Item: MHFItem{ItemID: 100}, Quantity: 5}}, wantLen: 1, checkFn: func(t *testing.T, result []MHFItemStack) { if len(result) != 1 || result[0].Item.ItemID != 100 { t.Error("Should add new item to empty list") } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := DiffItemStacks(tt.old, tt.update) if len(result) != tt.wantLen { t.Errorf("DiffItemStacks() length = %d, want %d", len(result), tt.wantLen) } if tt.checkFn != nil { tt.checkFn(t, result) } }) } } func TestReadWarehouseEquipment(t *testing.T) { mode := cfg.Z1 bf := byteframe.NewByteFrame() bf.WriteUint32(12345) // WarehouseID bf.WriteUint8(1) // ItemType bf.WriteUint8(2) // Unk0 bf.WriteUint16(100) // ItemID bf.WriteUint16(5) // Level // Write 3 decorations bf.WriteUint16(201) bf.WriteUint16(202) bf.WriteUint16(203) // Write 3 sigils (G1+) for i := 0; i < 3; i++ { // 3 effects per sigil for j := 0; j < 3; j++ { bf.WriteUint16(uint16(300 + i*10 + j)) // Effect ID } for j := 0; j < 3; j++ { bf.WriteUint16(uint16(1 + j)) // Effect Level } bf.WriteUint8(10) bf.WriteUint8(11) bf.WriteUint8(12) bf.WriteUint8(13) } // Unk1 (Z1+) bf.WriteUint16(9999) _, _ = bf.Seek(0, 0) equipment := ReadWarehouseEquipment(bf, mode) if equipment.WarehouseID != 12345 { t.Errorf("WarehouseID = %d, want 12345", equipment.WarehouseID) } if equipment.ItemType != 1 { t.Errorf("ItemType = %d, want 1", equipment.ItemType) } if equipment.ItemID != 100 { t.Errorf("ItemID = %d, want 100", equipment.ItemID) } if equipment.Level != 5 { t.Errorf("Level = %d, want 5", equipment.Level) } if equipment.Decorations[0].ItemID != 201 { t.Errorf("Decoration[0] = %d, want 201", equipment.Decorations[0].ItemID) } if equipment.Sigils[0].Effects[0].ID != 300 { t.Errorf("Sigil[0].Effect[0].ID = %d, want 300", equipment.Sigils[0].Effects[0].ID) } if equipment.Unk1 != 9999 { t.Errorf("Unk1 = %d, want 9999", equipment.Unk1) } } func TestReadWarehouseEquipment_ZeroWarehouseID(t *testing.T) { mode := cfg.Z1 bf := byteframe.NewByteFrame() bf.WriteUint32(0) // WarehouseID = 0 bf.WriteUint8(1) bf.WriteUint8(2) bf.WriteUint16(100) bf.WriteUint16(5) // Write decorations for i := 0; i < 3; i++ { bf.WriteUint16(0) } // Write sigils for i := 0; i < 3; i++ { for j := 0; j < 6; j++ { bf.WriteUint16(0) } bf.WriteUint8(0) bf.WriteUint8(0) bf.WriteUint8(0) bf.WriteUint8(0) } bf.WriteUint16(0) _, _ = bf.Seek(0, 0) equipment := ReadWarehouseEquipment(bf, mode) if equipment.WarehouseID == 0 { t.Error("WarehouseID should be replaced with random value when input is 0") } } func TestMHFEquipment_ToBytes(t *testing.T) { mode := cfg.Z1 equipment := MHFEquipment{ WarehouseID: 12345, ItemType: 1, Unk0: 2, ItemID: 100, Level: 5, Decorations: []MHFItem{{ItemID: 201}, {ItemID: 202}, {ItemID: 203}}, Sigils: make([]MHFSigil, 3), Unk1: 9999, } for i := 0; i < 3; i++ { equipment.Sigils[i].Effects = make([]MHFSigilEffect, 3) } data := equipment.ToBytes(mode) bf := byteframe.NewByteFrameFromBytes(data) readEquipment := ReadWarehouseEquipment(bf, mode) if readEquipment.WarehouseID != equipment.WarehouseID { t.Errorf("WarehouseID = %d, want %d", readEquipment.WarehouseID, equipment.WarehouseID) } if readEquipment.ItemID != equipment.ItemID { t.Errorf("ItemID = %d, want %d", readEquipment.ItemID, equipment.ItemID) } if readEquipment.Level != equipment.Level { t.Errorf("Level = %d, want %d", readEquipment.Level, equipment.Level) } if readEquipment.Unk1 != equipment.Unk1 { t.Errorf("Unk1 = %d, want %d", readEquipment.Unk1, equipment.Unk1) } } func TestSerializeWarehouseEquipment(t *testing.T) { mode := cfg.Z1 equipment := []MHFEquipment{ { WarehouseID: 1, ItemType: 1, ItemID: 100, Level: 5, Decorations: []MHFItem{{ItemID: 0}, {ItemID: 0}, {ItemID: 0}}, Sigils: make([]MHFSigil, 3), }, { WarehouseID: 2, ItemType: 2, ItemID: 200, Level: 10, Decorations: []MHFItem{{ItemID: 0}, {ItemID: 0}, {ItemID: 0}}, Sigils: make([]MHFSigil, 3), }, } for i := range equipment { for j := 0; j < 3; j++ { equipment[i].Sigils[j].Effects = make([]MHFSigilEffect, 3) } } data := SerializeWarehouseEquipment(equipment, mode) bf := byteframe.NewByteFrameFromBytes(data) count := bf.ReadUint16() if count != 2 { t.Errorf("count = %d, want 2", count) } } func TestMHFEquipment_RoundTrip(t *testing.T) { mode := cfg.Z1 original := MHFEquipment{ WarehouseID: 99999, ItemType: 5, Unk0: 10, ItemID: 500, Level: 25, Decorations: []MHFItem{{ItemID: 1}, {ItemID: 2}, {ItemID: 3}}, Sigils: make([]MHFSigil, 3), Unk1: 12345, } for i := 0; i < 3; i++ { original.Sigils[i].Effects = []MHFSigilEffect{ {ID: uint16(100 + i), Level: 1}, {ID: uint16(200 + i), Level: 2}, {ID: uint16(300 + i), Level: 3}, } } // Write to bytes data := original.ToBytes(mode) // Read back bf := byteframe.NewByteFrameFromBytes(data) recovered := ReadWarehouseEquipment(bf, mode) // Compare if recovered.WarehouseID != original.WarehouseID { t.Errorf("WarehouseID = %d, want %d", recovered.WarehouseID, original.WarehouseID) } if recovered.ItemType != original.ItemType { t.Errorf("ItemType = %d, want %d", recovered.ItemType, original.ItemType) } if recovered.ItemID != original.ItemID { t.Errorf("ItemID = %d, want %d", recovered.ItemID, original.ItemID) } if recovered.Level != original.Level { t.Errorf("Level = %d, want %d", recovered.Level, original.Level) } for i := 0; i < 3; i++ { if recovered.Decorations[i].ItemID != original.Decorations[i].ItemID { t.Errorf("Decoration[%d] = %d, want %d", i, recovered.Decorations[i].ItemID, original.Decorations[i].ItemID) } } } func BenchmarkReadWarehouseItem(b *testing.B) { bf := byteframe.NewByteFrame() bf.WriteUint32(12345) bf.WriteUint16(100) bf.WriteUint16(5) bf.WriteUint32(0) data := bf.Data() b.ResetTimer() for i := 0; i < b.N; i++ { bf := byteframe.NewByteFrameFromBytes(data) _ = ReadWarehouseItem(bf) } } func BenchmarkDiffItemStacks(b *testing.B) { old := []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 5}, {WarehouseID: 2, Item: MHFItem{ItemID: 200}, Quantity: 10}, {WarehouseID: 3, Item: MHFItem{ItemID: 300}, Quantity: 15}, } update := []MHFItemStack{ {WarehouseID: 1, Item: MHFItem{ItemID: 100}, Quantity: 8}, {WarehouseID: 0, Item: MHFItem{ItemID: 400}, Quantity: 3}, } b.ResetTimer() for i := 0; i < b.N; i++ { _ = DiffItemStacks(old, update) } } func BenchmarkSerializeWarehouseItems(b *testing.B) { items := make([]MHFItemStack, 100) for i := range items { items[i] = MHFItemStack{ WarehouseID: uint32(i), Item: MHFItem{ItemID: uint16(i)}, Quantity: uint16(i % 99), } } b.ResetTimer() for i := 0; i < b.N; i++ { _ = SerializeWarehouseItems(items) } } func TestMHFItemStack_ToBytes_RoundTrip(t *testing.T) { original := MHFItemStack{ WarehouseID: 12345, Item: MHFItem{ItemID: 999}, Quantity: 42, Unk0: 777, } data := original.ToBytes() bf := byteframe.NewByteFrameFromBytes(data) recovered := ReadWarehouseItem(bf) if !bytes.Equal(original.ToBytes(), recovered.ToBytes()) { t.Error("Round-trip serialization failed") } } func TestDiffItemStacks_PreserveOldWarehouseID(t *testing.T) { // Verify that when updating existing items, the old WarehouseID is preserved old := []MHFItemStack{ {WarehouseID: 555, Item: MHFItem{ItemID: 100}, Quantity: 5}, } update := []MHFItemStack{ {WarehouseID: 555, Item: MHFItem{ItemID: 100}, Quantity: 10}, } result := DiffItemStacks(old, update) if len(result) != 1 { t.Fatalf("Expected 1 item, got %d", len(result)) } if result[0].WarehouseID != 555 { t.Errorf("WarehouseID = %d, want 555", result[0].WarehouseID) } if result[0].Quantity != 10 { t.Errorf("Quantity = %d, want 10", result[0].Quantity) } } func TestDiffItemStacks_GeneratesNewWarehouseID(t *testing.T) { // Verify that new items get a generated WarehouseID old := []MHFItemStack{} update := []MHFItemStack{ {WarehouseID: 0, Item: MHFItem{ItemID: 100}, Quantity: 5}, } // Reset RNG for consistent test token.RNG = token.NewSafeRand() result := DiffItemStacks(old, update) if len(result) != 1 { t.Fatalf("Expected 1 item, got %d", len(result)) } if result[0].WarehouseID == 0 { t.Error("New item should have generated WarehouseID, got 0") } }