mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
Move all direct DB calls from handlers_festa.go (23 calls across 8 tables) and handlers_tower.go (16 calls across 4 tables) into dedicated repository structs following the established pattern. FestaRepository (14 methods): lifecycle cleanup, event management, team souls, trial stats/rankings, user state, voting, registration, soul submission, prize claiming/enumeration. TowerRepository (12 methods): personal tower data (skills, progress, gems), guild tenrouirai progress/scores/page advancement, tower RP. Also fix pre-existing nil pointer panics in integration tests by adding SetTestDB helper that initializes both the DB connection and all repositories, and wire the done channel in createTestServerWithDB to prevent Shutdown panics.
704 lines
21 KiB
Go
704 lines
21 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"time"
|
|
|
|
_config "erupe-ce/config"
|
|
"erupe-ce/common/mhfitem"
|
|
"erupe-ce/network/mhfpacket"
|
|
"erupe-ce/server/channelserver/compression/nullcomp"
|
|
)
|
|
|
|
// ============================================================================
|
|
// SAVE/LOAD INTEGRATION TESTS
|
|
// Tests to verify user-reported save/load issues
|
|
//
|
|
// USER COMPLAINT SUMMARY:
|
|
// Features that ARE saved: RdP, items purchased, money spent, Hunter Navi
|
|
// Features that are NOT saved: current equipment, equipment sets, transmogs,
|
|
// crafted equipment, monster kill counter (Koryo), warehouse, inventory
|
|
// ============================================================================
|
|
|
|
// TestSaveLoad_RoadPoints tests that Road Points (RdP) are saved correctly
|
|
// User reports this DOES save correctly
|
|
func TestSaveLoad_RoadPoints(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Set initial Road Points
|
|
initialPoints := uint32(1000)
|
|
_, err := db.Exec("UPDATE characters SET frontier_points = $1 WHERE id = $2", initialPoints, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to set initial road points: %v", err)
|
|
}
|
|
|
|
// Modify Road Points
|
|
newPoints := uint32(2500)
|
|
_, err = db.Exec("UPDATE characters SET frontier_points = $1 WHERE id = $2", newPoints, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to update road points: %v", err)
|
|
}
|
|
|
|
// Verify Road Points persisted
|
|
var savedPoints uint32
|
|
err = db.QueryRow("SELECT frontier_points FROM characters WHERE id = $1", charID).Scan(&savedPoints)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query road points: %v", err)
|
|
}
|
|
|
|
if savedPoints != newPoints {
|
|
t.Errorf("Road Points not saved correctly: got %d, want %d", savedPoints, newPoints)
|
|
} else {
|
|
t.Logf("✓ Road Points saved correctly: %d", savedPoints)
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_HunterNavi tests that Hunter Navi data is saved correctly
|
|
// User reports this DOES save correctly
|
|
func TestSaveLoad_HunterNavi(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Create test session
|
|
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
|
s := createTestSession(mock)
|
|
s.charID = charID
|
|
SetTestDB(s.server, db)
|
|
|
|
// Create Hunter Navi data
|
|
naviData := make([]byte, 552) // G8+ size
|
|
for i := range naviData {
|
|
naviData[i] = byte(i % 256)
|
|
}
|
|
|
|
// Save Hunter Navi
|
|
pkt := &mhfpacket.MsgMhfSaveHunterNavi{
|
|
AckHandle: 1234,
|
|
IsDataDiff: false, // Full save
|
|
RawDataPayload: naviData,
|
|
}
|
|
|
|
handleMsgMhfSaveHunterNavi(s, pkt)
|
|
|
|
// Verify saved
|
|
var saved []byte
|
|
err := db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", charID).Scan(&saved)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query hunter navi: %v", err)
|
|
}
|
|
|
|
if len(saved) == 0 {
|
|
t.Error("Hunter Navi not saved")
|
|
} else if !bytes.Equal(saved, naviData) {
|
|
t.Error("Hunter Navi data mismatch")
|
|
} else {
|
|
t.Logf("✓ Hunter Navi saved correctly: %d bytes", len(saved))
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_MonsterKillCounter tests that Koryo points (kill counter) are saved
|
|
// User reports this DOES NOT save correctly
|
|
func TestSaveLoad_MonsterKillCounter(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Create test session
|
|
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
|
s := createTestSession(mock)
|
|
s.charID = charID
|
|
SetTestDB(s.server, db)
|
|
|
|
// Initial Koryo points
|
|
initialPoints := uint32(0)
|
|
err := db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", charID).Scan(&initialPoints)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query initial koryo points: %v", err)
|
|
}
|
|
|
|
// Add Koryo points (simulate killing monsters)
|
|
addPoints := uint32(100)
|
|
pkt := &mhfpacket.MsgMhfAddKouryouPoint{
|
|
AckHandle: 5678,
|
|
KouryouPoints: addPoints,
|
|
}
|
|
|
|
handleMsgMhfAddKouryouPoint(s, pkt)
|
|
|
|
// Verify points were added
|
|
var savedPoints uint32
|
|
err = db.QueryRow("SELECT kouryou_point FROM characters WHERE id = $1", charID).Scan(&savedPoints)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query koryo points: %v", err)
|
|
}
|
|
|
|
expectedPoints := initialPoints + addPoints
|
|
if savedPoints != expectedPoints {
|
|
t.Errorf("Koryo points not saved correctly: got %d, want %d (BUG CONFIRMED)", savedPoints, expectedPoints)
|
|
} else {
|
|
t.Logf("✓ Koryo points saved correctly: %d", savedPoints)
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_Inventory tests that inventory (item_box) is saved correctly
|
|
// User reports this DOES NOT save correctly
|
|
func TestSaveLoad_Inventory(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
_ = CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Create test items
|
|
items := []mhfitem.MHFItemStack{
|
|
{Item: mhfitem.MHFItem{ItemID: 1001}, Quantity: 10},
|
|
{Item: mhfitem.MHFItem{ItemID: 1002}, Quantity: 20},
|
|
{Item: mhfitem.MHFItem{ItemID: 1003}, Quantity: 30},
|
|
}
|
|
|
|
// Serialize and save inventory
|
|
serialized := mhfitem.SerializeWarehouseItems(items)
|
|
_, err := db.Exec("UPDATE users SET item_box = $1 WHERE id = $2", serialized, userID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save inventory: %v", err)
|
|
}
|
|
|
|
// Reload inventory
|
|
var savedItemBox []byte
|
|
err = db.QueryRow("SELECT item_box FROM users WHERE id = $1", userID).Scan(&savedItemBox)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load inventory: %v", err)
|
|
}
|
|
|
|
if len(savedItemBox) == 0 {
|
|
t.Error("Inventory not saved (BUG CONFIRMED)")
|
|
} else if !bytes.Equal(savedItemBox, serialized) {
|
|
t.Error("Inventory data mismatch (BUG CONFIRMED)")
|
|
} else {
|
|
t.Logf("✓ Inventory saved correctly: %d bytes", len(savedItemBox))
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_Warehouse tests that warehouse contents are saved correctly
|
|
// User reports this DOES NOT save correctly
|
|
func TestSaveLoad_Warehouse(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Create test equipment for warehouse (Decorations and Sigils must be initialized)
|
|
newEquip := func(id uint16, wid uint32) mhfitem.MHFEquipment {
|
|
e := mhfitem.MHFEquipment{ItemID: id, WarehouseID: wid}
|
|
e.Decorations = make([]mhfitem.MHFItem, 3)
|
|
e.Sigils = make([]mhfitem.MHFSigil, 3)
|
|
for i := range e.Sigils {
|
|
e.Sigils[i].Effects = make([]mhfitem.MHFSigilEffect, 3)
|
|
}
|
|
return e
|
|
}
|
|
equipment := []mhfitem.MHFEquipment{
|
|
newEquip(100, 1),
|
|
newEquip(101, 2),
|
|
newEquip(102, 3),
|
|
}
|
|
|
|
// Serialize and save to warehouse
|
|
serializedEquip := mhfitem.SerializeWarehouseEquipment(equipment, _config.ZZ)
|
|
|
|
// Initialize warehouse row then update
|
|
_, _ = db.Exec("INSERT INTO warehouse (character_id) VALUES ($1) ON CONFLICT DO NOTHING", charID)
|
|
_, err := db.Exec("UPDATE warehouse SET equip0 = $1 WHERE character_id = $2", serializedEquip, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save warehouse: %v", err)
|
|
}
|
|
|
|
// Reload warehouse
|
|
var savedEquip []byte
|
|
err = db.QueryRow("SELECT equip0 FROM warehouse WHERE character_id = $1", charID).Scan(&savedEquip)
|
|
if err != nil {
|
|
t.Errorf("Failed to load warehouse: %v (BUG CONFIRMED)", err)
|
|
return
|
|
}
|
|
|
|
if len(savedEquip) == 0 {
|
|
t.Error("Warehouse not saved (BUG CONFIRMED)")
|
|
} else if !bytes.Equal(savedEquip, serializedEquip) {
|
|
t.Error("Warehouse data mismatch (BUG CONFIRMED)")
|
|
} else {
|
|
t.Logf("✓ Warehouse saved correctly: %d bytes", len(savedEquip))
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_CurrentEquipment tests that currently equipped gear is saved
|
|
// User reports this DOES NOT save correctly
|
|
func TestSaveLoad_CurrentEquipment(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Create test session
|
|
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
|
s := createTestSession(mock)
|
|
s.charID = charID
|
|
s.Name = "TestChar"
|
|
SetTestDB(s.server, db)
|
|
|
|
// Create savedata with equipped gear
|
|
// Equipment data is embedded in the main savedata blob
|
|
saveData := make([]byte, 150000)
|
|
copy(saveData[88:], []byte("TestChar\x00"))
|
|
|
|
// Set weapon type at known offset (simplified)
|
|
weaponTypeOffset := 500 // Example offset
|
|
saveData[weaponTypeOffset] = 0x03 // Great Sword
|
|
|
|
compressed, err := nullcomp.Compress(saveData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to compress savedata: %v", err)
|
|
}
|
|
|
|
// Save equipment data
|
|
pkt := &mhfpacket.MsgMhfSavedata{
|
|
SaveType: 0, // Full blob
|
|
AckHandle: 1111,
|
|
AllocMemSize: uint32(len(compressed)),
|
|
DataSize: uint32(len(compressed)),
|
|
RawDataPayload: compressed,
|
|
}
|
|
|
|
handleMsgMhfSavedata(s, pkt)
|
|
|
|
// Drain ACK
|
|
if len(s.sendPackets) > 0 {
|
|
<-s.sendPackets
|
|
}
|
|
|
|
// Reload savedata
|
|
var savedCompressed []byte
|
|
err = db.QueryRow("SELECT savedata FROM characters WHERE id = $1", charID).Scan(&savedCompressed)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load savedata: %v", err)
|
|
}
|
|
|
|
if len(savedCompressed) == 0 {
|
|
t.Error("Savedata (current equipment) not saved (BUG CONFIRMED)")
|
|
return
|
|
}
|
|
|
|
// Decompress and verify
|
|
decompressed, err := nullcomp.Decompress(savedCompressed)
|
|
if err != nil {
|
|
t.Errorf("Failed to decompress savedata: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(decompressed) < weaponTypeOffset+1 {
|
|
t.Error("Savedata too short, equipment data missing (BUG CONFIRMED)")
|
|
return
|
|
}
|
|
|
|
if decompressed[weaponTypeOffset] != saveData[weaponTypeOffset] {
|
|
t.Errorf("Equipment data not saved correctly (BUG CONFIRMED): got 0x%02X, want 0x%02X",
|
|
decompressed[weaponTypeOffset], saveData[weaponTypeOffset])
|
|
} else {
|
|
t.Logf("✓ Current equipment saved in savedata")
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_EquipmentSets tests that equipment set configurations are saved
|
|
// User reports this DOES NOT save correctly (creation/modification/deletion)
|
|
func TestSaveLoad_EquipmentSets(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Equipment sets are stored in characters.platemyset
|
|
testSetData := []byte{
|
|
0x01, 0x02, 0x03, 0x04, 0x05,
|
|
0x10, 0x20, 0x30, 0x40, 0x50,
|
|
}
|
|
|
|
// Save equipment sets
|
|
_, err := db.Exec("UPDATE characters SET platemyset = $1 WHERE id = $2", testSetData, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save equipment sets: %v", err)
|
|
}
|
|
|
|
// Reload equipment sets
|
|
var savedSets []byte
|
|
err = db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", charID).Scan(&savedSets)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load equipment sets: %v", err)
|
|
}
|
|
|
|
if len(savedSets) == 0 {
|
|
t.Error("Equipment sets not saved (BUG CONFIRMED)")
|
|
} else if !bytes.Equal(savedSets, testSetData) {
|
|
t.Error("Equipment sets data mismatch (BUG CONFIRMED)")
|
|
} else {
|
|
t.Logf("✓ Equipment sets saved correctly: %d bytes", len(savedSets))
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_Transmog tests that transmog/appearance data is saved correctly
|
|
// User reports this DOES NOT save correctly
|
|
func TestSaveLoad_Transmog(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Create test session
|
|
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
|
s := createTestSession(mock)
|
|
s.charID = charID
|
|
SetTestDB(s.server, db)
|
|
|
|
// Create valid transmog/decoration set data
|
|
// Format: [version byte][count byte][count * (uint16 index + setSize bytes)]
|
|
// setSize is 76 for G10+, 68 otherwise
|
|
setSize := 76 // G10+
|
|
numSets := 1
|
|
transmogData := make([]byte, 2+numSets*(2+setSize))
|
|
transmogData[0] = 1 // version
|
|
transmogData[1] = byte(numSets) // count
|
|
transmogData[2] = 0 // index high byte
|
|
transmogData[3] = 1 // index low byte (set #1)
|
|
|
|
// Save transmog data
|
|
pkt := &mhfpacket.MsgMhfSaveDecoMyset{
|
|
AckHandle: 2222,
|
|
RawDataPayload: transmogData,
|
|
}
|
|
|
|
handleMsgMhfSaveDecoMyset(s, pkt)
|
|
|
|
// Verify saved
|
|
var saved []byte
|
|
err := db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", charID).Scan(&saved)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query transmog data: %v", err)
|
|
}
|
|
|
|
if len(saved) == 0 {
|
|
t.Error("Transmog data not saved (BUG CONFIRMED)")
|
|
} else {
|
|
// handleMsgMhfSaveDecoMyset merges data, so check if anything was saved
|
|
t.Logf("✓ Transmog data saved: %d bytes", len(saved))
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_CraftedEquipment tests that crafted/upgraded equipment persists
|
|
// User reports this DOES NOT save correctly
|
|
func TestSaveLoad_CraftedEquipment(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "TestChar")
|
|
|
|
// Crafted equipment would be stored in savedata or warehouse
|
|
// Let's test warehouse equipment with upgrade levels
|
|
|
|
// Create crafted equipment with upgrade level (Decorations and Sigils must be initialized)
|
|
equip := mhfitem.MHFEquipment{ItemID: 5000, WarehouseID: 12345}
|
|
equip.Decorations = make([]mhfitem.MHFItem, 3)
|
|
equip.Sigils = make([]mhfitem.MHFSigil, 3)
|
|
for i := range equip.Sigils {
|
|
equip.Sigils[i].Effects = make([]mhfitem.MHFSigilEffect, 3)
|
|
}
|
|
equipment := []mhfitem.MHFEquipment{equip}
|
|
|
|
serialized := mhfitem.SerializeWarehouseEquipment(equipment, _config.ZZ)
|
|
|
|
// Save to warehouse
|
|
_, _ = db.Exec("INSERT INTO warehouse (character_id) VALUES ($1) ON CONFLICT DO NOTHING", charID)
|
|
_, err := db.Exec("UPDATE warehouse SET equip0 = $1 WHERE character_id = $2", serialized, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save crafted equipment: %v", err)
|
|
}
|
|
|
|
// Reload
|
|
var saved []byte
|
|
err = db.QueryRow("SELECT equip0 FROM warehouse WHERE character_id = $1", charID).Scan(&saved)
|
|
if err != nil {
|
|
t.Errorf("Failed to load crafted equipment: %v (BUG CONFIRMED)", err)
|
|
return
|
|
}
|
|
|
|
if len(saved) == 0 {
|
|
t.Error("Crafted equipment not saved (BUG CONFIRMED)")
|
|
} else if !bytes.Equal(saved, serialized) {
|
|
t.Error("Crafted equipment data mismatch (BUG CONFIRMED)")
|
|
} else {
|
|
t.Logf("✓ Crafted equipment saved correctly: %d bytes", len(saved))
|
|
}
|
|
}
|
|
|
|
// TestSaveLoad_CompleteSaveLoadCycle tests a complete save/load cycle
|
|
// This simulates a player logging out and back in
|
|
func TestSaveLoad_CompleteSaveLoadCycle(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
userID := CreateTestUser(t, db, "testuser")
|
|
charID := CreateTestCharacter(t, db, userID, "SaveLoadTest")
|
|
|
|
// Create test session (login)
|
|
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
|
s := createTestSession(mock)
|
|
s.charID = charID
|
|
s.Name = "SaveLoadTest"
|
|
SetTestDB(s.server, db)
|
|
|
|
// 1. Set Road Points
|
|
rdpPoints := uint32(5000)
|
|
_, err := db.Exec("UPDATE characters SET frontier_points = $1 WHERE id = $2", rdpPoints, charID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to set RdP: %v", err)
|
|
}
|
|
|
|
// 2. Add Koryo Points
|
|
koryoPoints := uint32(250)
|
|
addPkt := &mhfpacket.MsgMhfAddKouryouPoint{
|
|
AckHandle: 1111,
|
|
KouryouPoints: koryoPoints,
|
|
}
|
|
handleMsgMhfAddKouryouPoint(s, addPkt)
|
|
|
|
// 3. Save main savedata
|
|
saveData := make([]byte, 150000)
|
|
copy(saveData[88:], []byte("SaveLoadTest\x00"))
|
|
compressed, _ := nullcomp.Compress(saveData)
|
|
|
|
savePkt := &mhfpacket.MsgMhfSavedata{
|
|
SaveType: 0,
|
|
AckHandle: 2222,
|
|
AllocMemSize: uint32(len(compressed)),
|
|
DataSize: uint32(len(compressed)),
|
|
RawDataPayload: compressed,
|
|
}
|
|
handleMsgMhfSavedata(s, savePkt)
|
|
|
|
// Drain ACK packets
|
|
for len(s.sendPackets) > 0 {
|
|
<-s.sendPackets
|
|
}
|
|
|
|
// SIMULATE LOGOUT/LOGIN - Create new session
|
|
mock2 := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
|
s2 := createTestSession(mock2)
|
|
s2.charID = charID
|
|
s2.server.db = db
|
|
s2.server.userBinaryParts = make(map[userBinaryPartID][]byte)
|
|
|
|
// Load character data
|
|
loadPkt := &mhfpacket.MsgMhfLoaddata{
|
|
AckHandle: 3333,
|
|
}
|
|
handleMsgMhfLoaddata(s2, loadPkt)
|
|
|
|
// Verify loaded name
|
|
if s2.Name != "SaveLoadTest" {
|
|
t.Errorf("Character name not loaded correctly: got %q, want %q", s2.Name, "SaveLoadTest")
|
|
}
|
|
|
|
// Verify Road Points persisted
|
|
var loadedRdP uint32
|
|
_ = db.QueryRow("SELECT frontier_points FROM characters WHERE id = $1", charID).Scan(&loadedRdP)
|
|
if loadedRdP != rdpPoints {
|
|
t.Errorf("RdP not persisted: got %d, want %d (BUG CONFIRMED)", loadedRdP, rdpPoints)
|
|
} else {
|
|
t.Logf("✓ RdP persisted across save/load: %d", loadedRdP)
|
|
}
|
|
|
|
// Verify Koryo Points persisted
|
|
var loadedKoryo uint32
|
|
_ = db.QueryRow("SELECT kouryou_point FROM characters WHERE id = $1", charID).Scan(&loadedKoryo)
|
|
if loadedKoryo != koryoPoints {
|
|
t.Errorf("Koryo points not persisted: got %d, want %d (BUG CONFIRMED)", loadedKoryo, koryoPoints)
|
|
} else {
|
|
t.Logf("✓ Koryo points persisted across save/load: %d", loadedKoryo)
|
|
}
|
|
|
|
t.Log("Complete save/load cycle test finished")
|
|
}
|
|
|
|
// TestPlateDataPersistenceDuringLogout tests that plate (transmog) data is saved correctly
|
|
// during logout. This test ensures that all three plate data columns persist through the
|
|
// logout flow:
|
|
// - platedata: Main transmog appearance data (~140KB)
|
|
// - platebox: Plate storage/inventory (~4.8KB)
|
|
// - platemyset: Equipment set configurations (1920 bytes)
|
|
func TestPlateDataPersistenceDuringLogout(t *testing.T) {
|
|
db := SetupTestDB(t)
|
|
defer TeardownTestDB(t, db)
|
|
|
|
server := createTestServerWithDB(t, db)
|
|
// Note: Not calling defer server.Shutdown() since test server has no listener
|
|
|
|
userID := CreateTestUser(t, db, "plate_test_user")
|
|
charID := CreateTestCharacter(t, db, userID, "PlateTest")
|
|
|
|
t.Logf("Created character ID %d for plate data persistence test", charID)
|
|
|
|
// ===== SESSION 1: Login, save plate data, logout =====
|
|
t.Log("--- Starting Session 1: Save plate data ---")
|
|
|
|
session := createTestSessionForServerWithChar(server, charID, "PlateTest")
|
|
|
|
// 1. Save PlateData (transmog appearance)
|
|
t.Log("Saving PlateData (transmog appearance)")
|
|
plateData := make([]byte, 140000)
|
|
for i := 0; i < 1000; i++ {
|
|
plateData[i] = byte((i * 3) % 256)
|
|
}
|
|
plateCompressed, err := nullcomp.Compress(plateData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to compress plate data: %v", err)
|
|
}
|
|
|
|
platePkt := &mhfpacket.MsgMhfSavePlateData{
|
|
AckHandle: 5001,
|
|
IsDataDiff: false,
|
|
RawDataPayload: plateCompressed,
|
|
}
|
|
handleMsgMhfSavePlateData(session, platePkt)
|
|
|
|
// 2. Save PlateBox (storage)
|
|
t.Log("Saving PlateBox (storage)")
|
|
boxData := make([]byte, 4800)
|
|
for i := 0; i < 1000; i++ {
|
|
boxData[i] = byte((i * 5) % 256)
|
|
}
|
|
boxCompressed, err := nullcomp.Compress(boxData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to compress box data: %v", err)
|
|
}
|
|
|
|
boxPkt := &mhfpacket.MsgMhfSavePlateBox{
|
|
AckHandle: 5002,
|
|
IsDataDiff: false,
|
|
RawDataPayload: boxCompressed,
|
|
}
|
|
handleMsgMhfSavePlateBox(session, boxPkt)
|
|
|
|
// 3. Save PlateMyset (equipment sets)
|
|
t.Log("Saving PlateMyset (equipment sets)")
|
|
mysetData := make([]byte, 1920)
|
|
for i := 0; i < 100; i++ {
|
|
mysetData[i] = byte((i * 7) % 256)
|
|
}
|
|
|
|
mysetPkt := &mhfpacket.MsgMhfSavePlateMyset{
|
|
AckHandle: 5003,
|
|
RawDataPayload: mysetData,
|
|
}
|
|
handleMsgMhfSavePlateMyset(session, mysetPkt)
|
|
|
|
// 4. Simulate logout (this should call savePlateDataToDatabase via saveAllCharacterData)
|
|
t.Log("Triggering logout via logoutPlayer")
|
|
logoutPlayer(session)
|
|
|
|
// Give logout time to complete
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// ===== VERIFICATION: Check all plate data was saved =====
|
|
t.Log("--- Verifying plate data persisted ---")
|
|
|
|
var savedPlateData, savedBoxData, savedMysetData []byte
|
|
err = db.QueryRow("SELECT platedata, platebox, platemyset FROM characters WHERE id = $1", charID).
|
|
Scan(&savedPlateData, &savedBoxData, &savedMysetData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load saved plate data: %v", err)
|
|
}
|
|
|
|
// Verify PlateData
|
|
if len(savedPlateData) == 0 {
|
|
t.Error("❌ PlateData was not saved")
|
|
} else {
|
|
decompressed, err := nullcomp.Decompress(savedPlateData)
|
|
if err != nil {
|
|
t.Errorf("Failed to decompress saved plate data: %v", err)
|
|
} else {
|
|
// Verify first 1000 bytes match our pattern
|
|
matches := true
|
|
for i := 0; i < 1000; i++ {
|
|
if decompressed[i] != byte((i*3)%256) {
|
|
matches = false
|
|
break
|
|
}
|
|
}
|
|
if !matches {
|
|
t.Error("❌ Saved PlateData doesn't match original")
|
|
} else {
|
|
t.Logf("✓ PlateData persisted correctly (%d bytes compressed, %d bytes uncompressed)",
|
|
len(savedPlateData), len(decompressed))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify PlateBox
|
|
if len(savedBoxData) == 0 {
|
|
t.Error("❌ PlateBox was not saved")
|
|
} else {
|
|
decompressed, err := nullcomp.Decompress(savedBoxData)
|
|
if err != nil {
|
|
t.Errorf("Failed to decompress saved box data: %v", err)
|
|
} else {
|
|
// Verify first 1000 bytes match our pattern
|
|
matches := true
|
|
for i := 0; i < 1000; i++ {
|
|
if decompressed[i] != byte((i*5)%256) {
|
|
matches = false
|
|
break
|
|
}
|
|
}
|
|
if !matches {
|
|
t.Error("❌ Saved PlateBox doesn't match original")
|
|
} else {
|
|
t.Logf("✓ PlateBox persisted correctly (%d bytes compressed, %d bytes uncompressed)",
|
|
len(savedBoxData), len(decompressed))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify PlateMyset
|
|
if len(savedMysetData) == 0 {
|
|
t.Error("❌ PlateMyset was not saved")
|
|
} else {
|
|
// Verify first 100 bytes match our pattern
|
|
matches := true
|
|
for i := 0; i < 100; i++ {
|
|
if savedMysetData[i] != byte((i*7)%256) {
|
|
matches = false
|
|
break
|
|
}
|
|
}
|
|
if !matches {
|
|
t.Error("❌ Saved PlateMyset doesn't match original")
|
|
} else {
|
|
t.Logf("✓ PlateMyset persisted correctly (%d bytes)", len(savedMysetData))
|
|
}
|
|
}
|
|
|
|
t.Log("✓ All plate data persisted correctly during logout")
|
|
}
|