mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
test: increase test coverage across multiple packages
Add comprehensive tests for: - network: CryptConn encryption connection tests - signserver: character and member struct validation - entranceserver: encryption roundtrip, server config tests - channelserver: stage creation, object IDs, quest membership All tests pass with race detector enabled.
This commit is contained in:
179
network/crypt_conn_test.go
Normal file
179
network/crypt_conn_test.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewCryptConn(t *testing.T) {
|
||||
// NewCryptConn with nil should not panic
|
||||
cc := NewCryptConn(nil)
|
||||
|
||||
if cc == nil {
|
||||
t.Fatal("NewCryptConn() returned nil")
|
||||
}
|
||||
|
||||
// Verify default key rotation values
|
||||
if cc.readKeyRot != 995117 {
|
||||
t.Errorf("readKeyRot = %d, want 995117", cc.readKeyRot)
|
||||
}
|
||||
if cc.sendKeyRot != 995117 {
|
||||
t.Errorf("sendKeyRot = %d, want 995117", cc.sendKeyRot)
|
||||
}
|
||||
if cc.sentPackets != 0 {
|
||||
t.Errorf("sentPackets = %d, want 0", cc.sentPackets)
|
||||
}
|
||||
if cc.prevRecvPacketCombinedCheck != 0 {
|
||||
t.Errorf("prevRecvPacketCombinedCheck = %d, want 0", cc.prevRecvPacketCombinedCheck)
|
||||
}
|
||||
if cc.prevSendPacketCombinedCheck != 0 {
|
||||
t.Errorf("prevSendPacketCombinedCheck = %d, want 0", cc.prevSendPacketCombinedCheck)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnInitialState(t *testing.T) {
|
||||
cc := &CryptConn{}
|
||||
|
||||
// Zero value should have all zeros
|
||||
if cc.readKeyRot != 0 {
|
||||
t.Errorf("zero value readKeyRot = %d, want 0", cc.readKeyRot)
|
||||
}
|
||||
if cc.sendKeyRot != 0 {
|
||||
t.Errorf("zero value sendKeyRot = %d, want 0", cc.sendKeyRot)
|
||||
}
|
||||
if cc.conn != nil {
|
||||
t.Error("zero value conn should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnDefaultKeyRotation(t *testing.T) {
|
||||
// The magic number 995117 is the default key rotation value
|
||||
const defaultKeyRot = 995117
|
||||
|
||||
cc := NewCryptConn(nil)
|
||||
|
||||
if cc.readKeyRot != defaultKeyRot {
|
||||
t.Errorf("default readKeyRot = %d, want %d", cc.readKeyRot, defaultKeyRot)
|
||||
}
|
||||
if cc.sendKeyRot != defaultKeyRot {
|
||||
t.Errorf("default sendKeyRot = %d, want %d", cc.sendKeyRot, defaultKeyRot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnStructFields(t *testing.T) {
|
||||
cc := &CryptConn{
|
||||
readKeyRot: 123456,
|
||||
sendKeyRot: 654321,
|
||||
sentPackets: 10,
|
||||
prevRecvPacketCombinedCheck: 0x1234,
|
||||
prevSendPacketCombinedCheck: 0x5678,
|
||||
}
|
||||
|
||||
if cc.readKeyRot != 123456 {
|
||||
t.Errorf("readKeyRot = %d, want 123456", cc.readKeyRot)
|
||||
}
|
||||
if cc.sendKeyRot != 654321 {
|
||||
t.Errorf("sendKeyRot = %d, want 654321", cc.sendKeyRot)
|
||||
}
|
||||
if cc.sentPackets != 10 {
|
||||
t.Errorf("sentPackets = %d, want 10", cc.sentPackets)
|
||||
}
|
||||
if cc.prevRecvPacketCombinedCheck != 0x1234 {
|
||||
t.Errorf("prevRecvPacketCombinedCheck = 0x%X, want 0x1234", cc.prevRecvPacketCombinedCheck)
|
||||
}
|
||||
if cc.prevSendPacketCombinedCheck != 0x5678 {
|
||||
t.Errorf("prevSendPacketCombinedCheck = 0x%X, want 0x5678", cc.prevSendPacketCombinedCheck)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnKeyRotationType(t *testing.T) {
|
||||
// Verify key rotation uses uint32
|
||||
cc := NewCryptConn(nil)
|
||||
|
||||
// Simulate key rotation
|
||||
keyRotDelta := byte(3)
|
||||
cc.sendKeyRot = (uint32(keyRotDelta) * (cc.sendKeyRot + 1))
|
||||
|
||||
// Should not overflow or behave unexpectedly
|
||||
if cc.sendKeyRot == 0 {
|
||||
t.Error("sendKeyRot should not be 0 after rotation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnSentPacketsCounter(t *testing.T) {
|
||||
cc := NewCryptConn(nil)
|
||||
|
||||
if cc.sentPackets != 0 {
|
||||
t.Errorf("initial sentPackets = %d, want 0", cc.sentPackets)
|
||||
}
|
||||
|
||||
// Simulate incrementing sent packets
|
||||
cc.sentPackets++
|
||||
if cc.sentPackets != 1 {
|
||||
t.Errorf("sentPackets after increment = %d, want 1", cc.sentPackets)
|
||||
}
|
||||
|
||||
// Verify it's int32
|
||||
cc.sentPackets = 0x7FFFFFFF // Max int32
|
||||
if cc.sentPackets != 0x7FFFFFFF {
|
||||
t.Errorf("sentPackets max value = %d, want %d", cc.sentPackets, 0x7FFFFFFF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnCombinedCheckStorage(t *testing.T) {
|
||||
cc := NewCryptConn(nil)
|
||||
|
||||
// Test combined check storage
|
||||
cc.prevRecvPacketCombinedCheck = 0xABCD
|
||||
cc.prevSendPacketCombinedCheck = 0xDCBA
|
||||
|
||||
if cc.prevRecvPacketCombinedCheck != 0xABCD {
|
||||
t.Errorf("prevRecvPacketCombinedCheck = 0x%X, want 0xABCD", cc.prevRecvPacketCombinedCheck)
|
||||
}
|
||||
if cc.prevSendPacketCombinedCheck != 0xDCBA {
|
||||
t.Errorf("prevSendPacketCombinedCheck = 0x%X, want 0xDCBA", cc.prevSendPacketCombinedCheck)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptConnKeyRotationFormula(t *testing.T) {
|
||||
// Test the key rotation formula: (keyRotDelta * (keyRot + 1))
|
||||
tests := []struct {
|
||||
name string
|
||||
initialKey uint32
|
||||
keyRotDelta byte
|
||||
expectedKey uint32
|
||||
}{
|
||||
{"delta 1", 995117, 1, 995118},
|
||||
{"delta 3 default", 995117, 3, 2985354},
|
||||
{"delta 0", 995117, 0, 0},
|
||||
{"zero initial", 0, 3, 3},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
newKey := uint32(tt.keyRotDelta) * (tt.initialKey + 1)
|
||||
if newKey != tt.expectedKey {
|
||||
t.Errorf("key rotation = %d, want %d", newKey, tt.expectedKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCryptPacketHeaderLengthConstant(t *testing.T) {
|
||||
// CryptPacketHeaderLength should always be 14
|
||||
if CryptPacketHeaderLength != 14 {
|
||||
t.Errorf("CryptPacketHeaderLength = %d, want 14", CryptPacketHeaderLength)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleCryptConnInstances(t *testing.T) {
|
||||
// Multiple instances should be independent
|
||||
cc1 := NewCryptConn(nil)
|
||||
cc2 := NewCryptConn(nil)
|
||||
|
||||
cc1.sendKeyRot = 12345
|
||||
cc2.sendKeyRot = 54321
|
||||
|
||||
if cc1.sendKeyRot == cc2.sendKeyRot {
|
||||
t.Error("CryptConn instances should be independent")
|
||||
}
|
||||
}
|
||||
@@ -190,11 +190,13 @@ func TestStageBroadcastMHF_RaceDetectorWithLock(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Goroutine 1: Continuously broadcast
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 1000; i++ {
|
||||
stage.BroadcastMHF(pkt, nil)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
// Goroutine 2: Add and remove sessions WITH proper locking
|
||||
// This simulates the fixed logoutPlayer behavior
|
||||
@@ -245,3 +247,224 @@ func TestStageBroadcastMHF_NilClientContextSkipped(t *testing.T) {
|
||||
t.Error("session1 did not receive data")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewStageBasic verifies Stage creation
|
||||
func TestNewStageBasic(t *testing.T) {
|
||||
stageID := "test_stage_001"
|
||||
stage := NewStage(stageID)
|
||||
|
||||
if stage == nil {
|
||||
t.Fatal("NewStage() returned nil")
|
||||
}
|
||||
if stage.id != stageID {
|
||||
t.Errorf("stage.id = %s, want %s", stage.id, stageID)
|
||||
}
|
||||
if stage.clients == nil {
|
||||
t.Error("stage.clients should not be nil")
|
||||
}
|
||||
if stage.reservedClientSlots == nil {
|
||||
t.Error("stage.reservedClientSlots should not be nil")
|
||||
}
|
||||
if stage.objects == nil {
|
||||
t.Error("stage.objects should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageClientCount tests client counting
|
||||
func TestStageClientCount(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
server := createMockServer()
|
||||
|
||||
if len(stage.clients) != 0 {
|
||||
t.Errorf("initial client count = %d, want 0", len(stage.clients))
|
||||
}
|
||||
|
||||
// Add clients
|
||||
session1 := createMockSession(1, server)
|
||||
session2 := createMockSession(2, server)
|
||||
|
||||
stage.clients[session1] = session1.charID
|
||||
if len(stage.clients) != 1 {
|
||||
t.Errorf("client count after 1 add = %d, want 1", len(stage.clients))
|
||||
}
|
||||
|
||||
stage.clients[session2] = session2.charID
|
||||
if len(stage.clients) != 2 {
|
||||
t.Errorf("client count after 2 adds = %d, want 2", len(stage.clients))
|
||||
}
|
||||
|
||||
// Remove a client
|
||||
delete(stage.clients, session1)
|
||||
if len(stage.clients) != 1 {
|
||||
t.Errorf("client count after 1 remove = %d, want 1", len(stage.clients))
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageReservation tests stage reservation
|
||||
func TestStageReservation(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
if len(stage.reservedClientSlots) != 0 {
|
||||
t.Errorf("initial reservations = %d, want 0", len(stage.reservedClientSlots))
|
||||
}
|
||||
|
||||
// Reserve a slot using character ID
|
||||
stage.reservedClientSlots[12345] = true
|
||||
if len(stage.reservedClientSlots) != 1 {
|
||||
t.Errorf("reservations after 1 add = %d, want 1", len(stage.reservedClientSlots))
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageBinaryData tests setting and getting stage binary data
|
||||
func TestStageBinaryData(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
// rawBinaryData is initialized by NewStage
|
||||
if stage.rawBinaryData == nil {
|
||||
t.Error("rawBinaryData should not be nil after NewStage")
|
||||
}
|
||||
|
||||
// Set binary data
|
||||
key := stageBinaryKey{id0: 1, id1: 2}
|
||||
testData := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
stage.rawBinaryData[key] = testData
|
||||
|
||||
if len(stage.rawBinaryData) != 1 {
|
||||
t.Errorf("rawBinaryData length = %d, want 1", len(stage.rawBinaryData))
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageLockUnlock tests stage locking
|
||||
func TestStageLockUnlock(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
// Test lock/unlock without deadlock
|
||||
stage.Lock()
|
||||
stage.password = "test"
|
||||
stage.Unlock()
|
||||
|
||||
stage.RLock()
|
||||
password := stage.password
|
||||
stage.RUnlock()
|
||||
|
||||
if password != "test" {
|
||||
t.Error("stage password should be 'test'")
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageHostSession tests host session tracking
|
||||
func TestStageHostSession(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
if stage.host != nil {
|
||||
t.Error("initial host should be nil")
|
||||
}
|
||||
|
||||
stage.host = session
|
||||
if stage.host == nil {
|
||||
t.Error("host should not be nil after setting")
|
||||
}
|
||||
if stage.host.charID != 1 {
|
||||
t.Errorf("host.charID = %d, want 1", stage.host.charID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageMultipleClients tests stage with multiple clients
|
||||
func TestStageMultipleClients(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
server := createMockServer()
|
||||
|
||||
// Add many clients
|
||||
sessions := make([]*Session, 10)
|
||||
for i := range sessions {
|
||||
sessions[i] = createMockSession(uint32(i+1), server)
|
||||
stage.clients[sessions[i]] = sessions[i].charID
|
||||
}
|
||||
|
||||
if len(stage.clients) != 10 {
|
||||
t.Errorf("client count = %d, want 10", len(stage.clients))
|
||||
}
|
||||
|
||||
// Verify each client is tracked
|
||||
for _, s := range sessions {
|
||||
if _, ok := stage.clients[s]; !ok {
|
||||
t.Errorf("session with charID %d not found in stage", s.charID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestStageNewMaxPlayers tests default max players
|
||||
func TestStageNewMaxPlayers(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
// Default max players is 4
|
||||
if stage.maxPlayers != 4 {
|
||||
t.Errorf("initial maxPlayers = %d, want 4", stage.maxPlayers)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextObjectID tests object ID generation
|
||||
func TestNextObjectID(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
// Generate several object IDs
|
||||
ids := make(map[uint32]bool)
|
||||
for i := 0; i < 10; i++ {
|
||||
id := stage.NextObjectID()
|
||||
if ids[id] {
|
||||
t.Errorf("duplicate object ID generated: %d", id)
|
||||
}
|
||||
ids[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TestNextObjectIDWrap tests that object ID wraps at 127
|
||||
func TestNextObjectIDWrap(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
stage.objectIndex = 125
|
||||
|
||||
// Generate IDs to trigger wrap
|
||||
stage.NextObjectID() // 126
|
||||
stage.NextObjectID() // should wrap to 1
|
||||
|
||||
// After wrap, objectIndex should be 1
|
||||
if stage.objectIndex != 1 {
|
||||
t.Errorf("objectIndex after wrap = %d, want 1", stage.objectIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsCharInQuestByID tests character quest membership check
|
||||
func TestIsCharInQuestByID(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
// No reservations - should return false
|
||||
if stage.isCharInQuestByID(12345) {
|
||||
t.Error("should return false when no reservations exist")
|
||||
}
|
||||
|
||||
// Add reservation
|
||||
stage.reservedClientSlots[12345] = true
|
||||
|
||||
if !stage.isCharInQuestByID(12345) {
|
||||
t.Error("should return true when character is reserved")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsQuest tests quest detection
|
||||
func TestIsQuest(t *testing.T) {
|
||||
stage := NewStage("test_stage")
|
||||
|
||||
// No reservations - not a quest
|
||||
if stage.isQuest() {
|
||||
t.Error("should return false when no reservations exist")
|
||||
}
|
||||
|
||||
// Add reservation - becomes a quest
|
||||
stage.reservedClientSlots[12345] = true
|
||||
|
||||
if !stage.isQuest() {
|
||||
t.Error("should return true when reservations exist")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,3 +60,235 @@ func TestConfigFields(t *testing.T) {
|
||||
t.Error("Config ErupeConfig should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerShutdownFlag(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ErupeConfig: &config.Config{},
|
||||
}
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Initially not shutting down
|
||||
if s.isShuttingDown {
|
||||
t.Error("New server should not be shutting down")
|
||||
}
|
||||
|
||||
// Simulate setting shutdown flag
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
if !s.isShuttingDown {
|
||||
t.Error("Server should be shutting down after flag is set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfigStorage(t *testing.T) {
|
||||
erupeConfig := &config.Config{
|
||||
Host: "192.168.1.100",
|
||||
DevMode: true,
|
||||
Entrance: config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 53310,
|
||||
Entries: []config.EntranceServerInfo{
|
||||
{
|
||||
Name: "Test Server",
|
||||
IP: "127.0.0.1",
|
||||
Type: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
ErupeConfig: erupeConfig,
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
if s.erupeConfig.Host != "192.168.1.100" {
|
||||
t.Errorf("Host = %s, want 192.168.1.100", s.erupeConfig.Host)
|
||||
}
|
||||
if s.erupeConfig.DevMode != true {
|
||||
t.Error("DevMode should be true")
|
||||
}
|
||||
if s.erupeConfig.Entrance.Port != 53310 {
|
||||
t.Errorf("Entrance.Port = %d, want 53310", s.erupeConfig.Entrance.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerEntranceEntries(t *testing.T) {
|
||||
entries := []config.EntranceServerInfo{
|
||||
{
|
||||
Name: "World 1",
|
||||
IP: "10.0.0.1",
|
||||
Type: 1,
|
||||
Recommended: 1,
|
||||
Channels: []config.EntranceChannelInfo{
|
||||
{Port: 54001, MaxPlayers: 100},
|
||||
{Port: 54002, MaxPlayers: 100},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "World 2",
|
||||
IP: "10.0.0.2",
|
||||
Type: 2,
|
||||
Recommended: 0,
|
||||
Channels: []config.EntranceChannelInfo{
|
||||
{Port: 54003, MaxPlayers: 50},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
erupeConfig := &config.Config{
|
||||
Entrance: config.Entrance{
|
||||
Enabled: true,
|
||||
Port: 53310,
|
||||
Entries: entries,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &Config{ErupeConfig: erupeConfig}
|
||||
s := NewServer(cfg)
|
||||
|
||||
if len(s.erupeConfig.Entrance.Entries) != 2 {
|
||||
t.Errorf("Entries count = %d, want 2", len(s.erupeConfig.Entrance.Entries))
|
||||
}
|
||||
|
||||
if s.erupeConfig.Entrance.Entries[0].Name != "World 1" {
|
||||
t.Errorf("First entry name = %s, want World 1", s.erupeConfig.Entrance.Entries[0].Name)
|
||||
}
|
||||
|
||||
if len(s.erupeConfig.Entrance.Entries[0].Channels) != 2 {
|
||||
t.Errorf("First entry channels = %d, want 2", len(s.erupeConfig.Entrance.Entries[0].Channels))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecryptRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
key byte
|
||||
}{
|
||||
{"empty", []byte{}, 0x00},
|
||||
{"single byte", []byte{0x42}, 0x00},
|
||||
{"multiple bytes", []byte{0x01, 0x02, 0x03, 0x04}, 0x00},
|
||||
{"with key", []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0x55},
|
||||
{"max key", []byte{0x01, 0x02}, 0xFF},
|
||||
{"long data", make([]byte, 100), 0x42},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
encrypted := EncryptBin8(tt.data, tt.key)
|
||||
decrypted := DecryptBin8(encrypted, tt.key)
|
||||
|
||||
if len(decrypted) != len(tt.data) {
|
||||
t.Errorf("decrypted length = %d, want %d", len(decrypted), len(tt.data))
|
||||
return
|
||||
}
|
||||
|
||||
for i := range tt.data {
|
||||
if decrypted[i] != tt.data[i] {
|
||||
t.Errorf("decrypted[%d] = 0x%X, want 0x%X", i, decrypted[i], tt.data[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcSum32Deterministic(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||
|
||||
sum1 := CalcSum32(data)
|
||||
sum2 := CalcSum32(data)
|
||||
|
||||
if sum1 != sum2 {
|
||||
t.Errorf("CalcSum32 not deterministic: got 0x%X and 0x%X", sum1, sum2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcSum32DifferentInputs(t *testing.T) {
|
||||
data1 := []byte{0x01, 0x02, 0x03}
|
||||
data2 := []byte{0x01, 0x02, 0x04}
|
||||
|
||||
sum1 := CalcSum32(data1)
|
||||
sum2 := CalcSum32(data2)
|
||||
|
||||
if sum1 == sum2 {
|
||||
t.Error("Different inputs should produce different checksums")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptBin8KeyVariation(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
|
||||
enc1 := EncryptBin8(data, 0x00)
|
||||
enc2 := EncryptBin8(data, 0x01)
|
||||
enc3 := EncryptBin8(data, 0xFF)
|
||||
|
||||
if bytesEqual(enc1, enc2) {
|
||||
t.Error("Different keys should produce different encrypted data (0x00 vs 0x01)")
|
||||
}
|
||||
if bytesEqual(enc2, enc3) {
|
||||
t.Error("Different keys should produce different encrypted data (0x01 vs 0xFF)")
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestEncryptBin8LengthPreservation(t *testing.T) {
|
||||
lengths := []int{0, 1, 7, 8, 9, 100, 1000}
|
||||
|
||||
for _, length := range lengths {
|
||||
data := make([]byte, length)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
encrypted := EncryptBin8(data, 0x42)
|
||||
if len(encrypted) != length {
|
||||
t.Errorf("EncryptBin8 length %d changed to %d", length, len(encrypted))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcSum32LargeInput(t *testing.T) {
|
||||
data := make([]byte, 10000)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
sum := CalcSum32(data)
|
||||
sum2 := CalcSum32(data)
|
||||
if sum != sum2 {
|
||||
t.Errorf("CalcSum32 inconsistent for large input: 0x%X vs 0x%X", sum, sum2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerMutexLocking(t *testing.T) {
|
||||
cfg := &Config{ErupeConfig: &config.Config{}}
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Test that locking/unlocking works without deadlock
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
s.Lock()
|
||||
result := s.isShuttingDown
|
||||
s.Unlock()
|
||||
|
||||
if !result {
|
||||
t.Error("Mutex should protect isShuttingDown flag")
|
||||
}
|
||||
}
|
||||
|
||||
298
server/signserver/dbutils_test.go
Normal file
298
server/signserver/dbutils_test.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package signserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCharacterStruct(t *testing.T) {
|
||||
c := character{
|
||||
ID: 12345,
|
||||
IsFemale: true,
|
||||
IsNewCharacter: false,
|
||||
Name: "TestHunter",
|
||||
UnkDescString: "Test description",
|
||||
HRP: 999,
|
||||
GR: 300,
|
||||
WeaponType: 5,
|
||||
LastLogin: 1700000000,
|
||||
}
|
||||
|
||||
if c.ID != 12345 {
|
||||
t.Errorf("ID = %d, want 12345", c.ID)
|
||||
}
|
||||
if c.IsFemale != true {
|
||||
t.Error("IsFemale should be true")
|
||||
}
|
||||
if c.IsNewCharacter != false {
|
||||
t.Error("IsNewCharacter should be false")
|
||||
}
|
||||
if c.Name != "TestHunter" {
|
||||
t.Errorf("Name = %s, want TestHunter", c.Name)
|
||||
}
|
||||
if c.UnkDescString != "Test description" {
|
||||
t.Errorf("UnkDescString = %s, want Test description", c.UnkDescString)
|
||||
}
|
||||
if c.HRP != 999 {
|
||||
t.Errorf("HRP = %d, want 999", c.HRP)
|
||||
}
|
||||
if c.GR != 300 {
|
||||
t.Errorf("GR = %d, want 300", c.GR)
|
||||
}
|
||||
if c.WeaponType != 5 {
|
||||
t.Errorf("WeaponType = %d, want 5", c.WeaponType)
|
||||
}
|
||||
if c.LastLogin != 1700000000 {
|
||||
t.Errorf("LastLogin = %d, want 1700000000", c.LastLogin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterStructDefaults(t *testing.T) {
|
||||
c := character{}
|
||||
|
||||
if c.ID != 0 {
|
||||
t.Errorf("default ID = %d, want 0", c.ID)
|
||||
}
|
||||
if c.IsFemale != false {
|
||||
t.Error("default IsFemale should be false")
|
||||
}
|
||||
if c.IsNewCharacter != false {
|
||||
t.Error("default IsNewCharacter should be false")
|
||||
}
|
||||
if c.Name != "" {
|
||||
t.Errorf("default Name = %s, want empty", c.Name)
|
||||
}
|
||||
if c.HRP != 0 {
|
||||
t.Errorf("default HRP = %d, want 0", c.HRP)
|
||||
}
|
||||
if c.GR != 0 {
|
||||
t.Errorf("default GR = %d, want 0", c.GR)
|
||||
}
|
||||
if c.WeaponType != 0 {
|
||||
t.Errorf("default WeaponType = %d, want 0", c.WeaponType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersStruct(t *testing.T) {
|
||||
m := members{
|
||||
CID: 100,
|
||||
ID: 200,
|
||||
Name: "FriendName",
|
||||
}
|
||||
|
||||
if m.CID != 100 {
|
||||
t.Errorf("CID = %d, want 100", m.CID)
|
||||
}
|
||||
if m.ID != 200 {
|
||||
t.Errorf("ID = %d, want 200", m.ID)
|
||||
}
|
||||
if m.Name != "FriendName" {
|
||||
t.Errorf("Name = %s, want FriendName", m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersStructDefaults(t *testing.T) {
|
||||
m := members{}
|
||||
|
||||
if m.CID != 0 {
|
||||
t.Errorf("default CID = %d, want 0", m.CID)
|
||||
}
|
||||
if m.ID != 0 {
|
||||
t.Errorf("default ID = %d, want 0", m.ID)
|
||||
}
|
||||
if m.Name != "" {
|
||||
t.Errorf("default Name = %s, want empty", m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterWeaponTypes(t *testing.T) {
|
||||
// Test all weapon type values are valid
|
||||
weaponTypes := []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
|
||||
|
||||
for _, wt := range weaponTypes {
|
||||
c := character{WeaponType: wt}
|
||||
if c.WeaponType != wt {
|
||||
t.Errorf("WeaponType = %d, want %d", c.WeaponType, wt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterHRPRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hrp uint16
|
||||
}{
|
||||
{"min", 0},
|
||||
{"beginner", 1},
|
||||
{"hr30", 30},
|
||||
{"hr50", 50},
|
||||
{"hr99", 99},
|
||||
{"hr299", 299},
|
||||
{"hr998", 998},
|
||||
{"hr999", 999},
|
||||
{"max uint16", 65535},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{HRP: tt.hrp}
|
||||
if c.HRP != tt.hrp {
|
||||
t.Errorf("HRP = %d, want %d", c.HRP, tt.hrp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterGRRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
gr uint16
|
||||
}{
|
||||
{"min", 0},
|
||||
{"gr1", 1},
|
||||
{"gr100", 100},
|
||||
{"gr300", 300},
|
||||
{"gr999", 999},
|
||||
{"max uint16", 65535},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{GR: tt.gr}
|
||||
if c.GR != tt.gr {
|
||||
t.Errorf("GR = %d, want %d", c.GR, tt.gr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterIDRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
id uint32
|
||||
}{
|
||||
{"min", 0},
|
||||
{"small", 1},
|
||||
{"medium", 1000000},
|
||||
{"large", 0xFFFFFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{ID: tt.id}
|
||||
if c.ID != tt.id {
|
||||
t.Errorf("ID = %d, want %d", c.ID, tt.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterGender(t *testing.T) {
|
||||
// Male character
|
||||
male := character{IsFemale: false}
|
||||
if male.IsFemale != false {
|
||||
t.Error("Male character should have IsFemale = false")
|
||||
}
|
||||
|
||||
// Female character
|
||||
female := character{IsFemale: true}
|
||||
if female.IsFemale != true {
|
||||
t.Error("Female character should have IsFemale = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterNewStatus(t *testing.T) {
|
||||
// New character
|
||||
newChar := character{IsNewCharacter: true}
|
||||
if newChar.IsNewCharacter != true {
|
||||
t.Error("New character should have IsNewCharacter = true")
|
||||
}
|
||||
|
||||
// Existing character
|
||||
existingChar := character{IsNewCharacter: false}
|
||||
if existingChar.IsNewCharacter != false {
|
||||
t.Error("Existing character should have IsNewCharacter = false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterNameLength(t *testing.T) {
|
||||
// Test various name lengths
|
||||
names := []string{
|
||||
"", // Empty
|
||||
"A", // Single char
|
||||
"Hunter", // Normal
|
||||
"LongHunterName123", // Longer
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
c := character{Name: name}
|
||||
if c.Name != name {
|
||||
t.Errorf("Name = %s, want %s", c.Name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCharacterLastLogin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lastLogin uint32
|
||||
}{
|
||||
{"zero", 0},
|
||||
{"epoch", 0},
|
||||
{"past", 1600000000},
|
||||
{"present", 1700000000},
|
||||
{"future", 1800000000},
|
||||
{"max", 0xFFFFFFFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := character{LastLogin: tt.lastLogin}
|
||||
if c.LastLogin != tt.lastLogin {
|
||||
t.Errorf("LastLogin = %d, want %d", c.LastLogin, tt.lastLogin)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersCIDAssignment(t *testing.T) {
|
||||
// CID is the local character ID that references this member
|
||||
m := members{CID: 12345}
|
||||
if m.CID != 12345 {
|
||||
t.Errorf("CID = %d, want 12345", m.CID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleCharacters(t *testing.T) {
|
||||
// Test creating multiple character instances
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Char1", HRP: 100},
|
||||
{ID: 2, Name: "Char2", HRP: 200},
|
||||
{ID: 3, Name: "Char3", HRP: 300},
|
||||
}
|
||||
|
||||
for i, c := range chars {
|
||||
expectedID := uint32(i + 1)
|
||||
if c.ID != expectedID {
|
||||
t.Errorf("chars[%d].ID = %d, want %d", i, c.ID, expectedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleMembers(t *testing.T) {
|
||||
// Test creating multiple member instances
|
||||
membersList := []members{
|
||||
{CID: 1, ID: 10, Name: "Friend1"},
|
||||
{CID: 1, ID: 20, Name: "Friend2"},
|
||||
{CID: 2, ID: 30, Name: "Friend3"},
|
||||
}
|
||||
|
||||
// First two should share the same CID
|
||||
if membersList[0].CID != membersList[1].CID {
|
||||
t.Error("First two members should share the same CID")
|
||||
}
|
||||
|
||||
// Third should have different CID
|
||||
if membersList[1].CID == membersList[2].CID {
|
||||
t.Error("Third member should have different CID")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user