Files
GaCy-GO/internal/gameserver/handlers_equipment_test.go

176 lines
5.8 KiB
Go

package gameserver
import (
"encoding/json"
"os"
"testing"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
func TestLightconeSwapAndUnequipPersistAssignments(t *testing.T) {
t.Chdir(t.TempDir())
writeEquipmentFixture(t, `{"lightcones":[{"item_id":23001,"internal_uid":1,"equip_avatar":1201},{"item_id":23002,"internal_uid":2,"equip_avatar":1202}],"relics":[]}`)
player := &common.FreesrData{
Avatars: map[uint32]*common.AvatarJson{
1201: {AvatarID: 1201},
1202: {AvatarID: 1202},
},
Lightcones: []common.Lightcone{
{ItemID: 23001, InternalUID: 1, EquipAvatar: 1201},
{ItemID: 23002, InternalUID: 2, EquipAvatar: 1202},
},
}
session := &Session{json: player}
update, err := setLightconeEquipper(session, 1201, 3002)
if err != nil {
t.Fatalf("equip Light Cone: %v", err)
}
if player.Lightcones[0].EquipAvatar != 1202 || player.Lightcones[1].EquipAvatar != 1201 {
t.Fatalf("Light Cone swap failed: %+v", player.Lightcones)
}
if len(update.EquipmentList) != 2 || len(update.AvatarSync.AvatarList) != 2 {
t.Fatalf("swap did not sync both Light Cones and avatars: %+v", update)
}
assertSavedEquipmentOwners(t, "lightcones", map[uint32]uint32{1: 1202, 2: 1201})
update, err = setLightconeEquipper(session, 1201, 0)
if err != nil {
t.Fatalf("unequip Light Cone: %v", err)
}
if player.Lightcones[1].EquipAvatar != 0 || len(update.EquipmentList) != 1 {
t.Fatalf("Light Cone unequip failed: items=%+v update=%+v", player.Lightcones, update)
}
assertSavedEquipmentOwners(t, "lightcones", map[uint32]uint32{1: 1202, 2: 0})
}
func TestRelicSwapUnequipAndSlotValidationPersistAssignments(t *testing.T) {
t.Chdir(t.TempDir())
writeEquipmentFixture(t, `{"lightcones":[],"relics":[{"relic_id":31012,"internal_uid":11,"equip_avatar":1201},{"relic_id":32012,"internal_uid":12,"equip_avatar":1202}]}`)
player := &common.FreesrData{
Avatars: map[uint32]*common.AvatarJson{
1201: {AvatarID: 1201},
1202: {AvatarID: 1202},
},
Relics: []common.Relic{
{RelicID: 31012, InternalUID: 11, EquipAvatar: 1201},
{RelicID: 32012, InternalUID: 12, EquipAvatar: 1202},
},
}
session := &Session{json: player}
wrongSlot, err := equipRelic(session, &goproto.DressRelicAvatarCsReq{
AvatarId: 1201,
SwitchList: []*goproto.DressRelicParam{{RelicType: 1, RelicUniqueId: 12}},
})
if err != nil || wrongSlot != nil {
t.Fatalf("mismatched Relic slot should be ignored: update=%+v err=%v", wrongSlot, err)
}
if player.Relics[0].EquipAvatar != 1201 || player.Relics[1].EquipAvatar != 1202 {
t.Fatalf("mismatched Relic slot changed ownership: %+v", player.Relics)
}
update, err := equipRelic(session, &goproto.DressRelicAvatarCsReq{
AvatarId: 1201,
SwitchList: []*goproto.DressRelicParam{{RelicType: 2, RelicUniqueId: 12}},
})
if err != nil {
t.Fatalf("equip Relic: %v", err)
}
if player.Relics[0].EquipAvatar != 1202 || player.Relics[1].EquipAvatar != 1201 {
t.Fatalf("Relic swap failed: %+v", player.Relics)
}
if len(update.RelicList) != 2 || len(update.AvatarSync.AvatarList) != 2 {
t.Fatalf("swap did not sync both Relics and avatars: %+v", update)
}
assertSavedEquipmentOwners(t, "relics", map[uint32]uint32{11: 1202, 12: 1201})
update, err = unequipRelic(session, &goproto.TakeOffRelicCsReq{
AvatarId: 1201,
RelicTypeList: []uint32{2},
})
if err != nil {
t.Fatalf("unequip Relic: %v", err)
}
if player.Relics[1].EquipAvatar != 0 || len(update.RelicList) != 1 {
t.Fatalf("Relic unequip failed: items=%+v update=%+v", player.Relics, update)
}
assertSavedEquipmentOwners(t, "relics", map[uint32]uint32{11: 1202, 12: 0})
}
func TestEquipmentPickerAuxiliaryRequestsHaveTypedReplies(t *testing.T) {
for _, name := range []string{
"RelicPresetGetPlanCsReq",
"RelicSmartWearGetPlanCsReq",
"RelicSmartWearGetPinRelicCsReq",
"GetBigDataRecommendCsReq",
} {
t.Run(name, func(t *testing.T) {
id, ok := goproto.CmdIDByName[name]
if !ok {
t.Fatalf("protocol table is missing %s", name)
}
if typedHandlers[uint16(id)] == nil {
t.Fatalf("%s is not registered as a typed handler", name)
}
})
}
avatarID := uint32(1201)
recommendation := emptyEquipmentRecommendation(avatarID, goproto.BigDataRecommendType_BIG_DATA_RECOMMEND_TYPE_AVATAR_RELIC)
responses := []proto.Message{
emptyRelicPresetPlans(avatarID),
emptyRelicSmartWearPlans(avatarID),
emptyPinnedRelics(avatarID),
&recommendation,
}
for _, response := range responses {
encoded, err := proto.Marshal(response)
if err != nil {
t.Fatalf("marshal %T: %v", response, err)
}
if len(encoded) == 0 {
t.Fatalf("%T encoded as an empty response", response)
}
}
if recommendation.EquipAvatar != avatarID || recommendation.BigDataRecommendType != goproto.BigDataRecommendType_BIG_DATA_RECOMMEND_TYPE_AVATAR_RELIC {
t.Fatalf("recommendation response lost request context: %+v", recommendation)
}
}
func writeEquipmentFixture(t *testing.T, contents string) {
t.Helper()
if err := os.WriteFile("freesr-data.json", []byte(contents), 0600); err != nil {
t.Fatalf("write fixture: %v", err)
}
}
func assertSavedEquipmentOwners(t *testing.T, collection string, expected map[uint32]uint32) {
t.Helper()
contents, err := os.ReadFile("freesr-data.json")
if err != nil {
t.Fatalf("read persisted equipment: %v", err)
}
var root map[string][]struct {
InternalUID uint32 `json:"internal_uid"`
EquipAvatar uint32 `json:"equip_avatar"`
}
if err := json.Unmarshal(contents, &root); err != nil {
t.Fatalf("decode persisted equipment: %v", err)
}
owners := make(map[uint32]uint32, len(root[collection]))
for _, entry := range root[collection] {
owners[entry.InternalUID] = entry.EquipAvatar
}
for uid, want := range expected {
if got, ok := owners[uid]; !ok || got != want {
t.Errorf("%s UID %d owner = %d (present %v), want %d", collection, uid, got, ok, want)
}
}
}