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:
Houmgaor
2026-02-02 16:48:57 +01:00
parent 813cf169c9
commit dbc3b21827
4 changed files with 934 additions and 2 deletions

179
network/crypt_conn_test.go Normal file
View 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")
}
}

View File

@@ -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")
}
}

View File

@@ -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")
}
}

View 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")
}
}