mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-25 09:03:10 +01:00
tests(integration): more complete tests with integration of a test database,
This commit is contained in:
@@ -3,9 +3,12 @@ package channelserver
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"testing"
|
||||
)
|
||||
@@ -334,3 +337,318 @@ func BenchmarkPacketQueueing(b *testing.B) {
|
||||
// The current architecture doesn't easily support interface-based testing
|
||||
b.Skip("benchmark requires interface-based CryptConn mock")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Integration Tests (require test database)
|
||||
// Run with: docker-compose -f docker/docker-compose.test.yml up -d
|
||||
// ============================================================================
|
||||
|
||||
// TestHandleMsgMhfSavedata_Integration tests the actual save data handler with database
|
||||
func TestHandleMsgMhfSavedata_Integration(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
|
||||
// Create test user and character
|
||||
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"
|
||||
s.server.db = db
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
saveType uint8
|
||||
payloadFunc func() []byte
|
||||
wantSuccess bool
|
||||
}{
|
||||
{
|
||||
name: "blob_save",
|
||||
saveType: 0,
|
||||
payloadFunc: func() []byte {
|
||||
// Create minimal valid savedata (large enough for all game mode pointers)
|
||||
data := make([]byte, 150000)
|
||||
copy(data[88:], []byte("TestChar\x00")) // Name at offset 88
|
||||
compressed, _ := nullcomp.Compress(data)
|
||||
return compressed
|
||||
},
|
||||
wantSuccess: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
payload := tt.payloadFunc()
|
||||
pkt := &mhfpacket.MsgMhfSavedata{
|
||||
SaveType: tt.saveType,
|
||||
AckHandle: 1234,
|
||||
AllocMemSize: uint32(len(payload)),
|
||||
DataSize: uint32(len(payload)),
|
||||
RawDataPayload: payload,
|
||||
}
|
||||
|
||||
handleMsgMhfSavedata(s, pkt)
|
||||
|
||||
// Check if ACK was sent
|
||||
if len(s.sendPackets) == 0 {
|
||||
t.Error("no ACK packet was sent")
|
||||
} else {
|
||||
// Drain the channel
|
||||
<-s.sendPackets
|
||||
}
|
||||
|
||||
// Verify database was updated (for success case)
|
||||
if tt.wantSuccess {
|
||||
var savedData []byte
|
||||
err := db.QueryRow("SELECT savedata FROM characters WHERE id = $1", charID).Scan(&savedData)
|
||||
if err != nil {
|
||||
t.Errorf("failed to query saved data: %v", err)
|
||||
}
|
||||
if len(savedData) == 0 {
|
||||
t.Error("savedata was not written to database")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgMhfLoaddata_Integration tests loading character data
|
||||
func TestHandleMsgMhfLoaddata_Integration(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
|
||||
// Create test user and character
|
||||
userID := CreateTestUser(t, db, "testuser")
|
||||
|
||||
// Create savedata
|
||||
saveData := make([]byte, 200)
|
||||
copy(saveData[88:], []byte("LoadTest\x00"))
|
||||
compressed, _ := nullcomp.Compress(saveData)
|
||||
|
||||
var charID uint32
|
||||
err := db.QueryRow(`
|
||||
INSERT INTO characters (user_id, is_female, is_new_character, name, unk_desc_string, gr, hr, weapon_type, last_login, savedata, decomyset, savemercenary)
|
||||
VALUES ($1, false, false, 'LoadTest', '', 0, 0, 0, 0, $2, '', '')
|
||||
RETURNING id
|
||||
`, userID, compressed).Scan(&charID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test character: %v", err)
|
||||
}
|
||||
|
||||
// Create test session
|
||||
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
||||
s := createTestSession(mock)
|
||||
s.charID = charID
|
||||
s.server.db = db
|
||||
s.server.userBinaryParts = make(map[userBinaryPartID][]byte)
|
||||
s.server.userBinaryPartsLock.Lock()
|
||||
defer s.server.userBinaryPartsLock.Unlock()
|
||||
|
||||
pkt := &mhfpacket.MsgMhfLoaddata{
|
||||
AckHandle: 5678,
|
||||
}
|
||||
|
||||
handleMsgMhfLoaddata(s, pkt)
|
||||
|
||||
// Verify ACK was sent
|
||||
if len(s.sendPackets) == 0 {
|
||||
t.Error("no ACK packet was sent")
|
||||
}
|
||||
|
||||
// Verify name was extracted
|
||||
if s.Name != "LoadTest" {
|
||||
t.Errorf("character name not loaded, got %q, want %q", s.Name, "LoadTest")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgMhfSaveScenarioData_Integration tests scenario data saving
|
||||
func TestHandleMsgMhfSaveScenarioData_Integration(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
|
||||
// Create test user and character
|
||||
userID := CreateTestUser(t, db, "testuser")
|
||||
charID := CreateTestCharacter(t, db, userID, "ScenarioTest")
|
||||
|
||||
// Create test session
|
||||
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
||||
s := createTestSession(mock)
|
||||
s.charID = charID
|
||||
s.server.db = db
|
||||
|
||||
scenarioData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}
|
||||
|
||||
pkt := &mhfpacket.MsgMhfSaveScenarioData{
|
||||
AckHandle: 9999,
|
||||
DataSize: uint32(len(scenarioData)),
|
||||
RawDataPayload: scenarioData,
|
||||
}
|
||||
|
||||
handleMsgMhfSaveScenarioData(s, pkt)
|
||||
|
||||
// Verify ACK was sent
|
||||
if len(s.sendPackets) == 0 {
|
||||
t.Error("no ACK packet was sent")
|
||||
} else {
|
||||
<-s.sendPackets
|
||||
}
|
||||
|
||||
// Verify scenario data was saved
|
||||
var saved []byte
|
||||
err := db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", charID).Scan(&saved)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query scenario data: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(saved, scenarioData) {
|
||||
t.Errorf("scenario data mismatch: got %v, want %v", saved, scenarioData)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgMhfLoadScenarioData_Integration tests scenario data loading
|
||||
func TestHandleMsgMhfLoadScenarioData_Integration(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
|
||||
// Create test user and character
|
||||
userID := CreateTestUser(t, db, "testuser")
|
||||
|
||||
scenarioData := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44}
|
||||
|
||||
var charID uint32
|
||||
err := db.QueryRow(`
|
||||
INSERT INTO characters (user_id, is_female, is_new_character, name, unk_desc_string, gr, hr, weapon_type, last_login, savedata, decomyset, savemercenary, scenariodata)
|
||||
VALUES ($1, false, false, 'ScenarioLoad', '', 0, 0, 0, 0, $2, '', '', $3)
|
||||
RETURNING id
|
||||
`, userID, []byte{0x00, 0x00, 0x00, 0x00}, scenarioData).Scan(&charID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test character: %v", err)
|
||||
}
|
||||
|
||||
// Create test session
|
||||
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
||||
s := createTestSession(mock)
|
||||
s.charID = charID
|
||||
s.server.db = db
|
||||
|
||||
pkt := &mhfpacket.MsgMhfLoadScenarioData{
|
||||
AckHandle: 1111,
|
||||
}
|
||||
|
||||
handleMsgMhfLoadScenarioData(s, pkt)
|
||||
|
||||
// Verify ACK was sent
|
||||
if len(s.sendPackets) == 0 {
|
||||
t.Fatal("no ACK packet was sent")
|
||||
}
|
||||
|
||||
// The ACK should contain the scenario data
|
||||
ackPkt := <-s.sendPackets
|
||||
if len(ackPkt.data) < len(scenarioData) {
|
||||
t.Errorf("ACK packet too small: got %d bytes, expected at least %d", len(ackPkt.data), len(scenarioData))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSaveDataCorruptionDetection_Integration tests that corrupted saves are rejected
|
||||
func TestSaveDataCorruptionDetection_Integration(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
|
||||
// Create test user and character
|
||||
userID := CreateTestUser(t, db, "testuser")
|
||||
charID := CreateTestCharacter(t, db, userID, "OriginalName")
|
||||
|
||||
// Create test session
|
||||
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
||||
s := createTestSession(mock)
|
||||
s.charID = charID
|
||||
s.Name = "OriginalName"
|
||||
s.server.db = db
|
||||
s.server.erupeConfig.DeleteOnSaveCorruption = false
|
||||
|
||||
// Create save data with a DIFFERENT name (corruption)
|
||||
corruptedData := make([]byte, 200)
|
||||
copy(corruptedData[88:], []byte("HackedName\x00"))
|
||||
compressed, _ := nullcomp.Compress(corruptedData)
|
||||
|
||||
pkt := &mhfpacket.MsgMhfSavedata{
|
||||
SaveType: 0,
|
||||
AckHandle: 4444,
|
||||
AllocMemSize: uint32(len(compressed)),
|
||||
DataSize: uint32(len(compressed)),
|
||||
RawDataPayload: compressed,
|
||||
}
|
||||
|
||||
handleMsgMhfSavedata(s, pkt)
|
||||
|
||||
// The save should be rejected, connection should be closed
|
||||
// In a real scenario, s.rawConn.Close() is called
|
||||
// We can't easily test that, but we can verify the data wasn't saved
|
||||
|
||||
// Check that database wasn't updated with corrupted data
|
||||
var savedName string
|
||||
db.QueryRow("SELECT name FROM characters WHERE id = $1", charID).Scan(&savedName)
|
||||
if savedName == "HackedName" {
|
||||
t.Error("corrupted save data was incorrectly written to database")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentSaveData_Integration tests concurrent save operations
|
||||
func TestConcurrentSaveData_Integration(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
|
||||
// Create test user and multiple characters
|
||||
userID := CreateTestUser(t, db, "testuser")
|
||||
charIDs := make([]uint32, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
charIDs[i] = CreateTestCharacter(t, db, userID, fmt.Sprintf("Char%d", i))
|
||||
}
|
||||
|
||||
// Run concurrent saves
|
||||
done := make(chan bool, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
go func(index int) {
|
||||
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
|
||||
s := createTestSession(mock)
|
||||
s.charID = charIDs[index]
|
||||
s.Name = fmt.Sprintf("Char%d", index)
|
||||
s.server.db = db
|
||||
|
||||
saveData := make([]byte, 200)
|
||||
copy(saveData[88:], []byte(fmt.Sprintf("Char%d\x00", index)))
|
||||
compressed, _ := nullcomp.Compress(saveData)
|
||||
|
||||
pkt := &mhfpacket.MsgMhfSavedata{
|
||||
SaveType: 0,
|
||||
AckHandle: uint32(index),
|
||||
AllocMemSize: uint32(len(compressed)),
|
||||
DataSize: uint32(len(compressed)),
|
||||
RawDataPayload: compressed,
|
||||
}
|
||||
|
||||
handleMsgMhfSavedata(s, pkt)
|
||||
done <- true
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all saves to complete
|
||||
for i := 0; i < 5; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
// Verify all characters were saved
|
||||
for i := 0; i < 5; i++ {
|
||||
var saveData []byte
|
||||
err := db.QueryRow("SELECT savedata FROM characters WHERE id = $1", charIDs[i]).Scan(&saveData)
|
||||
if err != nil {
|
||||
t.Errorf("character %d: failed to load savedata: %v", i, err)
|
||||
}
|
||||
if len(saveData) == 0 {
|
||||
t.Errorf("character %d: savedata is empty", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user