907 lines
20 KiB
Go
907 lines
20 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// EntityWithGetID helper type for testing
|
|
type EntityWithGetID struct {
|
|
id int32
|
|
}
|
|
|
|
func (e *EntityWithGetID) GetID() int32 {
|
|
return e.id
|
|
}
|
|
|
|
// Mock implementations for testing
|
|
type MockChannelDatabase struct {
|
|
channels map[string]ChatChannelData
|
|
mu sync.RWMutex
|
|
loadErr error
|
|
saveErr error
|
|
delErr error
|
|
}
|
|
|
|
func NewMockChannelDatabase() *MockChannelDatabase {
|
|
return &MockChannelDatabase{
|
|
channels: make(map[string]ChatChannelData),
|
|
}
|
|
}
|
|
|
|
func (m *MockChannelDatabase) LoadWorldChannels(ctx context.Context) ([]ChatChannelData, error) {
|
|
if m.loadErr != nil {
|
|
return nil, m.loadErr
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var channels []ChatChannelData
|
|
for _, channel := range m.channels {
|
|
channels = append(channels, channel)
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
func (m *MockChannelDatabase) SaveChannel(ctx context.Context, channel ChatChannelData) error {
|
|
if m.saveErr != nil {
|
|
return m.saveErr
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.channels[channel.Name] = channel
|
|
return nil
|
|
}
|
|
|
|
func (m *MockChannelDatabase) DeleteChannel(ctx context.Context, channelName string) error {
|
|
if m.delErr != nil {
|
|
return m.delErr
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, exists := m.channels[channelName]; !exists {
|
|
return fmt.Errorf("channel not found")
|
|
}
|
|
delete(m.channels, channelName)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockChannelDatabase) SetLoadError(err error) {
|
|
m.loadErr = err
|
|
}
|
|
|
|
func (m *MockChannelDatabase) SetSaveError(err error) {
|
|
m.saveErr = err
|
|
}
|
|
|
|
func (m *MockChannelDatabase) SetDeleteError(err error) {
|
|
m.delErr = err
|
|
}
|
|
|
|
type MockClientManager struct {
|
|
sentMessages []ChannelMessage
|
|
sentLists map[int32][]ChannelInfo
|
|
sentUpdates []ChatUpdate
|
|
sentUserLists map[int32][]ChannelMember
|
|
connectedClients map[int32]bool
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type ChatUpdate struct {
|
|
CharacterID int32
|
|
ChannelName string
|
|
Action int
|
|
CharacterName string
|
|
}
|
|
|
|
func NewMockClientManager() *MockClientManager {
|
|
return &MockClientManager{
|
|
sentLists: make(map[int32][]ChannelInfo),
|
|
sentUserLists: make(map[int32][]ChannelMember),
|
|
connectedClients: make(map[int32]bool),
|
|
}
|
|
}
|
|
|
|
func (m *MockClientManager) SendChannelList(characterID int32, channels []ChannelInfo) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.sentLists[characterID] = channels
|
|
return nil
|
|
}
|
|
|
|
func (m *MockClientManager) SendChannelMessage(characterID int32, message ChannelMessage) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.sentMessages = append(m.sentMessages, message)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockClientManager) SendChannelUpdate(characterID int32, channelName string, action int, characterName string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.sentUpdates = append(m.sentUpdates, ChatUpdate{
|
|
CharacterID: characterID,
|
|
ChannelName: channelName,
|
|
Action: action,
|
|
CharacterName: characterName,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (m *MockClientManager) SendChannelUserList(characterID int32, channelName string, members []ChannelMember) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.sentUserLists[characterID] = members
|
|
return nil
|
|
}
|
|
|
|
func (m *MockClientManager) IsClientConnected(characterID int32) bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return m.connectedClients[characterID]
|
|
}
|
|
|
|
func (m *MockClientManager) SetClientConnected(characterID int32, connected bool) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.connectedClients[characterID] = connected
|
|
}
|
|
|
|
func (m *MockClientManager) GetSentMessages() []ChannelMessage {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return append([]ChannelMessage{}, m.sentMessages...)
|
|
}
|
|
|
|
type MockPlayerManager struct {
|
|
players map[int32]PlayerInfo
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewMockPlayerManager() *MockPlayerManager {
|
|
return &MockPlayerManager{
|
|
players: make(map[int32]PlayerInfo),
|
|
}
|
|
}
|
|
|
|
func (m *MockPlayerManager) GetPlayerInfo(characterID int32) (PlayerInfo, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if player, exists := m.players[characterID]; exists {
|
|
return player, nil
|
|
}
|
|
return PlayerInfo{}, fmt.Errorf("player not found")
|
|
}
|
|
|
|
func (m *MockPlayerManager) ValidatePlayer(characterID int32, levelReq, raceReq, classReq int32) bool {
|
|
player, err := m.GetPlayerInfo(characterID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if levelReq > 0 && player.Level < levelReq {
|
|
return false
|
|
}
|
|
if raceReq > 0 && (raceReq&(1<<player.Race)) == 0 {
|
|
return false
|
|
}
|
|
if classReq > 0 && (classReq&(1<<player.Class)) == 0 {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *MockPlayerManager) GetPlayerLanguages(characterID int32) ([]int32, error) {
|
|
return []int32{0, 1}, nil // Default languages
|
|
}
|
|
|
|
func (m *MockPlayerManager) AddPlayer(player PlayerInfo) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.players[player.CharacterID] = player
|
|
}
|
|
|
|
type MockLanguageProcessor struct{}
|
|
|
|
func (m *MockLanguageProcessor) ProcessMessage(senderID, receiverID int32, message string, languageID int32) (string, error) {
|
|
return message, nil // No scrambling for tests
|
|
}
|
|
|
|
func (m *MockLanguageProcessor) CanUnderstand(senderID, receiverID int32, languageID int32) bool {
|
|
return true // Everyone understands everything in tests
|
|
}
|
|
|
|
func (m *MockLanguageProcessor) GetDefaultLanguage(characterID int32) int32 {
|
|
return 0 // Common tongue
|
|
}
|
|
|
|
// Channel tests
|
|
func TestNewChannel(t *testing.T) {
|
|
channel := NewChannel("test")
|
|
|
|
if channel == nil {
|
|
t.Fatal("NewChannel returned nil")
|
|
}
|
|
|
|
if channel.GetName() != "test" {
|
|
t.Errorf("Channel name = %v, want test", channel.GetName())
|
|
}
|
|
|
|
if channel.GetNumClients() != 0 {
|
|
t.Errorf("New channel should have 0 clients, got %v", channel.GetNumClients())
|
|
}
|
|
}
|
|
|
|
func TestChannelSettersAndGetters(t *testing.T) {
|
|
channel := NewChannel("test")
|
|
|
|
// Test name
|
|
channel.SetName("newname")
|
|
if channel.GetName() != "newname" {
|
|
t.Errorf("SetName failed: got %v, want newname", channel.GetName())
|
|
}
|
|
|
|
// Test password
|
|
channel.SetPassword("secret")
|
|
if !channel.HasPassword() {
|
|
t.Error("HasPassword should return true after SetPassword")
|
|
}
|
|
|
|
if !channel.PasswordMatches("secret") {
|
|
t.Error("PasswordMatches should return true for correct password")
|
|
}
|
|
|
|
if channel.PasswordMatches("wrong") {
|
|
t.Error("PasswordMatches should return false for incorrect password")
|
|
}
|
|
|
|
// Test type
|
|
channel.SetType(ChannelTypeWorld)
|
|
if channel.GetType() != ChannelTypeWorld {
|
|
t.Errorf("SetType failed: got %v, want %v", channel.GetType(), ChannelTypeWorld)
|
|
}
|
|
|
|
// Test restrictions
|
|
channel.SetLevelRestriction(50)
|
|
if !channel.CanJoinChannelByLevel(50) {
|
|
t.Error("CanJoinChannelByLevel should allow level 50")
|
|
}
|
|
|
|
if channel.CanJoinChannelByLevel(49) {
|
|
t.Error("CanJoinChannelByLevel should not allow level 49")
|
|
}
|
|
|
|
// Test race restriction (bitmask)
|
|
channel.SetRacesAllowed(1 << 1) // Only race ID 1 allowed
|
|
if !channel.CanJoinChannelByRace(1) {
|
|
t.Error("CanJoinChannelByRace should allow race 1")
|
|
}
|
|
|
|
if channel.CanJoinChannelByRace(2) {
|
|
t.Error("CanJoinChannelByRace should not allow race 2")
|
|
}
|
|
|
|
// Test class restriction (bitmask)
|
|
channel.SetClassesAllowed(1 << 3) // Only class ID 3 allowed
|
|
if !channel.CanJoinChannelByClass(3) {
|
|
t.Error("CanJoinChannelByClass should allow class 3")
|
|
}
|
|
|
|
if channel.CanJoinChannelByClass(4) {
|
|
t.Error("CanJoinChannelByClass should not allow class 4")
|
|
}
|
|
}
|
|
|
|
func TestChannelMembership(t *testing.T) {
|
|
channel := NewChannel("test")
|
|
|
|
// Test initial state
|
|
if channel.IsInChannel(100) {
|
|
t.Error("IsInChannel should return false for new channel")
|
|
}
|
|
|
|
if !channel.IsEmpty() {
|
|
t.Error("New channel should be empty")
|
|
}
|
|
|
|
// Test joining
|
|
err := channel.JoinChannel(100)
|
|
if err != nil {
|
|
t.Errorf("JoinChannel failed: %v", err)
|
|
}
|
|
|
|
if !channel.IsInChannel(100) {
|
|
t.Error("IsInChannel should return true after joining")
|
|
}
|
|
|
|
if channel.GetNumClients() != 1 {
|
|
t.Errorf("GetNumClients = %v, want 1", channel.GetNumClients())
|
|
}
|
|
|
|
if channel.IsEmpty() {
|
|
t.Error("Channel should not be empty after joining")
|
|
}
|
|
|
|
// Test duplicate join
|
|
err = channel.JoinChannel(100)
|
|
if err == nil {
|
|
t.Error("JoinChannel should fail for duplicate member")
|
|
}
|
|
|
|
// Test multiple members
|
|
err = channel.JoinChannel(200)
|
|
if err != nil {
|
|
t.Errorf("JoinChannel failed for second member: %v", err)
|
|
}
|
|
|
|
if channel.GetNumClients() != 2 {
|
|
t.Errorf("GetNumClients = %v, want 2", channel.GetNumClients())
|
|
}
|
|
|
|
// Test GetMembers
|
|
members := channel.GetMembers()
|
|
if len(members) != 2 {
|
|
t.Errorf("GetMembers returned %v members, want 2", len(members))
|
|
}
|
|
|
|
// Verify members contains both IDs
|
|
found100, found200 := false, false
|
|
for _, id := range members {
|
|
if id == 100 {
|
|
found100 = true
|
|
}
|
|
if id == 200 {
|
|
found200 = true
|
|
}
|
|
}
|
|
|
|
if !found100 || !found200 {
|
|
t.Error("GetMembers should contain both member IDs")
|
|
}
|
|
|
|
// Test leaving
|
|
err = channel.LeaveChannel(100)
|
|
if err != nil {
|
|
t.Errorf("LeaveChannel failed: %v", err)
|
|
}
|
|
|
|
if channel.IsInChannel(100) {
|
|
t.Error("IsInChannel should return false after leaving")
|
|
}
|
|
|
|
if channel.GetNumClients() != 1 {
|
|
t.Errorf("GetNumClients = %v, want 1 after leaving", channel.GetNumClients())
|
|
}
|
|
|
|
// Test leaving non-member
|
|
err = channel.LeaveChannel(300)
|
|
if err == nil {
|
|
t.Error("LeaveChannel should fail for non-member")
|
|
}
|
|
|
|
// Test leaving last member
|
|
err = channel.LeaveChannel(200)
|
|
if err != nil {
|
|
t.Errorf("LeaveChannel failed for last member: %v", err)
|
|
}
|
|
|
|
if !channel.IsEmpty() {
|
|
t.Error("Channel should be empty after all members leave")
|
|
}
|
|
}
|
|
|
|
func TestChannelValidateJoin(t *testing.T) {
|
|
channel := NewChannel("test")
|
|
channel.SetPassword("secret")
|
|
channel.SetLevelRestriction(10)
|
|
channel.SetRacesAllowed(1 << 1) // Race 1 only
|
|
channel.SetClassesAllowed(1 << 2) // Class 2 only
|
|
|
|
tests := []struct {
|
|
name string
|
|
level int32
|
|
race int32
|
|
class int32
|
|
password string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid join",
|
|
level: 15,
|
|
race: 1,
|
|
class: 2,
|
|
password: "secret",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "wrong password",
|
|
level: 15,
|
|
race: 1,
|
|
class: 2,
|
|
password: "wrong",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "level too low",
|
|
level: 5,
|
|
race: 1,
|
|
class: 2,
|
|
password: "secret",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "wrong race",
|
|
level: 15,
|
|
race: 2,
|
|
class: 2,
|
|
password: "secret",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "wrong class",
|
|
level: 15,
|
|
race: 1,
|
|
class: 3,
|
|
password: "secret",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := channel.ValidateJoin(tt.level, tt.race, tt.class, tt.password)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ValidateJoin() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestChannelGetChannelInfo(t *testing.T) {
|
|
channel := NewChannel("testchannel")
|
|
channel.SetType(ChannelTypeWorld)
|
|
channel.SetPassword("secret")
|
|
channel.SetLevelRestriction(20)
|
|
channel.SetRacesAllowed(15) // Multiple races
|
|
channel.SetClassesAllowed(31) // Multiple classes
|
|
channel.JoinChannel(100)
|
|
channel.JoinChannel(200)
|
|
|
|
info := channel.GetChannelInfo()
|
|
|
|
if info.Name != "testchannel" {
|
|
t.Errorf("ChannelInfo Name = %v, want testchannel", info.Name)
|
|
}
|
|
|
|
if !info.HasPassword {
|
|
t.Error("ChannelInfo should indicate has password")
|
|
}
|
|
|
|
if info.MemberCount != 2 {
|
|
t.Errorf("ChannelInfo MemberCount = %v, want 2", info.MemberCount)
|
|
}
|
|
|
|
if info.LevelRestriction != 20 {
|
|
t.Errorf("ChannelInfo LevelRestriction = %v, want 20", info.LevelRestriction)
|
|
}
|
|
|
|
if info.ChannelType != ChannelTypeWorld {
|
|
t.Errorf("ChannelInfo ChannelType = %v, want %v", info.ChannelType, ChannelTypeWorld)
|
|
}
|
|
}
|
|
|
|
func TestChannelCopy(t *testing.T) {
|
|
original := NewChannel("original")
|
|
original.SetPassword("secret")
|
|
original.SetType(ChannelTypeCustom)
|
|
original.SetLevelRestriction(25)
|
|
original.JoinChannel(100)
|
|
original.JoinChannel(200)
|
|
|
|
copy := original.Copy()
|
|
|
|
if copy == original {
|
|
t.Error("Copy should return different instance")
|
|
}
|
|
|
|
if copy.GetName() != original.GetName() {
|
|
t.Error("Copy should have same name")
|
|
}
|
|
|
|
if copy.GetType() != original.GetType() {
|
|
t.Error("Copy should have same type")
|
|
}
|
|
|
|
if copy.GetNumClients() != original.GetNumClients() {
|
|
t.Error("Copy should have same member count")
|
|
}
|
|
|
|
// Test that modifying copy doesn't affect original
|
|
copy.JoinChannel(300)
|
|
if original.GetNumClients() == copy.GetNumClients() {
|
|
t.Error("Modifying copy should not affect original")
|
|
}
|
|
}
|
|
|
|
// Database tests
|
|
func TestMockChannelDatabase(t *testing.T) {
|
|
db := NewMockChannelDatabase()
|
|
ctx := context.Background()
|
|
|
|
// Test empty database
|
|
channels, err := db.LoadWorldChannels(ctx)
|
|
if err != nil {
|
|
t.Errorf("LoadWorldChannels failed: %v", err)
|
|
}
|
|
|
|
if len(channels) != 0 {
|
|
t.Errorf("Expected 0 channels, got %v", len(channels))
|
|
}
|
|
|
|
// Test saving channel
|
|
testChannel := ChatChannelData{
|
|
Name: "testchannel",
|
|
Password: "secret",
|
|
LevelRestriction: 10,
|
|
ClassRestriction: 15,
|
|
RaceRestriction: 7,
|
|
}
|
|
|
|
err = db.SaveChannel(ctx, testChannel)
|
|
if err != nil {
|
|
t.Errorf("SaveChannel failed: %v", err)
|
|
}
|
|
|
|
// Test loading after save
|
|
channels, err = db.LoadWorldChannels(ctx)
|
|
if err != nil {
|
|
t.Errorf("LoadWorldChannels failed: %v", err)
|
|
}
|
|
|
|
if len(channels) != 1 {
|
|
t.Errorf("Expected 1 channel, got %v", len(channels))
|
|
}
|
|
|
|
if channels[0].Name != testChannel.Name {
|
|
t.Errorf("Channel name = %v, want %v", channels[0].Name, testChannel.Name)
|
|
}
|
|
|
|
// Test delete
|
|
err = db.DeleteChannel(ctx, "testchannel")
|
|
if err != nil {
|
|
t.Errorf("DeleteChannel failed: %v", err)
|
|
}
|
|
|
|
// Test delete non-existent
|
|
err = db.DeleteChannel(ctx, "nonexistent")
|
|
if err == nil {
|
|
t.Error("DeleteChannel should fail for non-existent channel")
|
|
}
|
|
|
|
// Test error conditions
|
|
db.SetLoadError(fmt.Errorf("load error"))
|
|
_, err = db.LoadWorldChannels(ctx)
|
|
if err == nil {
|
|
t.Error("LoadWorldChannels should return error when set")
|
|
}
|
|
|
|
db.SetSaveError(fmt.Errorf("save error"))
|
|
err = db.SaveChannel(ctx, testChannel)
|
|
if err == nil {
|
|
t.Error("SaveChannel should return error when set")
|
|
}
|
|
|
|
db.SetDeleteError(fmt.Errorf("delete error"))
|
|
err = db.DeleteChannel(ctx, "test")
|
|
if err == nil {
|
|
t.Error("DeleteChannel should return error when set")
|
|
}
|
|
}
|
|
|
|
// Interface tests
|
|
func TestEntityChatAdapter(t *testing.T) {
|
|
playerManager := NewMockPlayerManager()
|
|
playerManager.AddPlayer(PlayerInfo{
|
|
CharacterID: 100,
|
|
CharacterName: "TestPlayer",
|
|
Level: 25,
|
|
Race: 1,
|
|
Class: 2,
|
|
IsOnline: true,
|
|
})
|
|
|
|
entityWithGetID := &EntityWithGetID{id: 100}
|
|
|
|
adapter := &EntityChatAdapter{
|
|
entity: entityWithGetID,
|
|
playerManager: playerManager,
|
|
}
|
|
|
|
if adapter.GetCharacterID() != 100 {
|
|
t.Errorf("GetCharacterID() = %v, want 100", adapter.GetCharacterID())
|
|
}
|
|
|
|
if adapter.GetCharacterName() != "TestPlayer" {
|
|
t.Errorf("GetCharacterName() = %v, want TestPlayer", adapter.GetCharacterName())
|
|
}
|
|
|
|
if adapter.GetLevel() != 25 {
|
|
t.Errorf("GetLevel() = %v, want 25", adapter.GetLevel())
|
|
}
|
|
|
|
if adapter.GetRace() != 1 {
|
|
t.Errorf("GetRace() = %v, want 1", adapter.GetRace())
|
|
}
|
|
|
|
if adapter.GetClass() != 2 {
|
|
t.Errorf("GetClass() = %v, want 2", adapter.GetClass())
|
|
}
|
|
|
|
// Test with non-existent player
|
|
entityWithGetID2 := &EntityWithGetID{id: 999}
|
|
|
|
adapter2 := &EntityChatAdapter{
|
|
entity: entityWithGetID2,
|
|
playerManager: playerManager,
|
|
}
|
|
|
|
if adapter2.GetCharacterName() != "" {
|
|
t.Error("GetCharacterName should return empty string for non-existent player")
|
|
}
|
|
|
|
if adapter2.GetLevel() != 0 {
|
|
t.Error("GetLevel should return 0 for non-existent player")
|
|
}
|
|
}
|
|
|
|
// Constants tests
|
|
func TestConstants(t *testing.T) {
|
|
// Test channel types
|
|
if ChannelTypeNone != 0 {
|
|
t.Errorf("ChannelTypeNone = %v, want 0", ChannelTypeNone)
|
|
}
|
|
|
|
if ChannelTypeWorld != 1 {
|
|
t.Errorf("ChannelTypeWorld = %v, want 1", ChannelTypeWorld)
|
|
}
|
|
|
|
if ChannelTypeCustom != 2 {
|
|
t.Errorf("ChannelTypeCustom = %v, want 2", ChannelTypeCustom)
|
|
}
|
|
|
|
// Test chat actions
|
|
if ChatChannelJoin != 0 {
|
|
t.Errorf("ChatChannelJoin = %v, want 0", ChatChannelJoin)
|
|
}
|
|
|
|
if ChatChannelLeave != 1 {
|
|
t.Errorf("ChatChannelLeave = %v, want 1", ChatChannelLeave)
|
|
}
|
|
|
|
// Test restrictions
|
|
if NoLevelRestriction != 0 {
|
|
t.Errorf("NoLevelRestriction = %v, want 0", NoLevelRestriction)
|
|
}
|
|
|
|
if NoRaceRestriction != 0 {
|
|
t.Errorf("NoRaceRestriction = %v, want 0", NoRaceRestriction)
|
|
}
|
|
|
|
if NoClassRestriction != 0 {
|
|
t.Errorf("NoClassRestriction = %v, want 0", NoClassRestriction)
|
|
}
|
|
}
|
|
|
|
// Concurrency tests
|
|
func TestChannelConcurrency(t *testing.T) {
|
|
channel := NewChannel("concurrent")
|
|
var wg sync.WaitGroup
|
|
|
|
// Concurrent joins
|
|
for i := range 100 {
|
|
wg.Add(1)
|
|
go func(id int32) {
|
|
defer wg.Done()
|
|
channel.JoinChannel(id)
|
|
}(int32(i))
|
|
}
|
|
|
|
// Concurrent reads
|
|
for i := range 50 {
|
|
wg.Add(1)
|
|
go func(id int32) {
|
|
defer wg.Done()
|
|
channel.IsInChannel(id)
|
|
channel.GetMembers()
|
|
channel.GetChannelInfo()
|
|
}(int32(i))
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify final state
|
|
if channel.GetNumClients() != 100 {
|
|
t.Errorf("After concurrent joins, got %v members, want 100", channel.GetNumClients())
|
|
}
|
|
|
|
// Concurrent leaves
|
|
for i := range 50 {
|
|
wg.Add(1)
|
|
go func(id int32) {
|
|
defer wg.Done()
|
|
channel.LeaveChannel(id)
|
|
}(int32(i))
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
if channel.GetNumClients() != 50 {
|
|
t.Errorf("After concurrent leaves, got %v members, want 50", channel.GetNumClients())
|
|
}
|
|
}
|
|
|
|
func TestMockClientManager(t *testing.T) {
|
|
client := NewMockClientManager()
|
|
|
|
// Test client connection status
|
|
if client.IsClientConnected(100) {
|
|
t.Error("IsClientConnected should return false initially")
|
|
}
|
|
|
|
client.SetClientConnected(100, true)
|
|
if !client.IsClientConnected(100) {
|
|
t.Error("IsClientConnected should return true after setting")
|
|
}
|
|
|
|
// Test channel list
|
|
channels := []ChannelInfo{
|
|
{Name: "test1", MemberCount: 5},
|
|
{Name: "test2", MemberCount: 10},
|
|
}
|
|
|
|
err := client.SendChannelList(100, channels)
|
|
if err != nil {
|
|
t.Errorf("SendChannelList failed: %v", err)
|
|
}
|
|
|
|
// Test channel message
|
|
message := ChannelMessage{
|
|
SenderID: 200,
|
|
SenderName: "TestSender",
|
|
Message: "Hello world",
|
|
ChannelName: "test",
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
err = client.SendChannelMessage(100, message)
|
|
if err != nil {
|
|
t.Errorf("SendChannelMessage failed: %v", err)
|
|
}
|
|
|
|
sentMessages := client.GetSentMessages()
|
|
if len(sentMessages) != 1 {
|
|
t.Errorf("Expected 1 sent message, got %v", len(sentMessages))
|
|
}
|
|
|
|
if sentMessages[0].Message != "Hello world" {
|
|
t.Errorf("Sent message = %v, want Hello world", sentMessages[0].Message)
|
|
}
|
|
}
|
|
|
|
func TestMockPlayerManager(t *testing.T) {
|
|
playerMgr := NewMockPlayerManager()
|
|
|
|
// Test non-existent player
|
|
_, err := playerMgr.GetPlayerInfo(999)
|
|
if err == nil {
|
|
t.Error("GetPlayerInfo should fail for non-existent player")
|
|
}
|
|
|
|
// Add player
|
|
player := PlayerInfo{
|
|
CharacterID: 100,
|
|
CharacterName: "TestPlayer",
|
|
Level: 30,
|
|
Race: 2,
|
|
Class: 4,
|
|
IsOnline: true,
|
|
}
|
|
|
|
playerMgr.AddPlayer(player)
|
|
|
|
// Test existing player
|
|
retrieved, err := playerMgr.GetPlayerInfo(100)
|
|
if err != nil {
|
|
t.Errorf("GetPlayerInfo failed: %v", err)
|
|
}
|
|
|
|
if retrieved.CharacterName != "TestPlayer" {
|
|
t.Errorf("Player name = %v, want TestPlayer", retrieved.CharacterName)
|
|
}
|
|
|
|
// Test validation
|
|
if !playerMgr.ValidatePlayer(100, 25, 0, 0) {
|
|
t.Error("ValidatePlayer should pass for level requirement")
|
|
}
|
|
|
|
if playerMgr.ValidatePlayer(100, 35, 0, 0) {
|
|
t.Error("ValidatePlayer should fail for level requirement")
|
|
}
|
|
|
|
// Test languages
|
|
languages, err := playerMgr.GetPlayerLanguages(100)
|
|
if err != nil {
|
|
t.Errorf("GetPlayerLanguages failed: %v", err)
|
|
}
|
|
|
|
if len(languages) == 0 {
|
|
t.Error("GetPlayerLanguages should return some languages")
|
|
}
|
|
}
|
|
|
|
func TestMockLanguageProcessor(t *testing.T) {
|
|
processor := &MockLanguageProcessor{}
|
|
|
|
// Test message processing
|
|
processed, err := processor.ProcessMessage(100, 200, "test message", 0)
|
|
if err != nil {
|
|
t.Errorf("ProcessMessage failed: %v", err)
|
|
}
|
|
|
|
if processed != "test message" {
|
|
t.Errorf("ProcessMessage = %v, want test message", processed)
|
|
}
|
|
|
|
// Test understanding
|
|
if !processor.CanUnderstand(100, 200, 0) {
|
|
t.Error("CanUnderstand should return true in mock")
|
|
}
|
|
|
|
// Test default language
|
|
if processor.GetDefaultLanguage(100) != 0 {
|
|
t.Error("GetDefaultLanguage should return 0 in mock")
|
|
}
|
|
}
|
|
|
|
// Benchmarks
|
|
func BenchmarkChannelJoin(b *testing.B) {
|
|
channel := NewChannel("benchmark")
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
channel.JoinChannel(int32(i))
|
|
}
|
|
}
|
|
|
|
func BenchmarkChannelIsInChannel(b *testing.B) {
|
|
channel := NewChannel("benchmark")
|
|
|
|
for i := range 1000 {
|
|
channel.JoinChannel(int32(i))
|
|
}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
channel.IsInChannel(int32(i % 1000))
|
|
}
|
|
}
|
|
|
|
func BenchmarkChannelGetMembers(b *testing.B) {
|
|
channel := NewChannel("benchmark")
|
|
|
|
for i := range 1000 {
|
|
channel.JoinChannel(int32(i))
|
|
}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
channel.GetMembers()
|
|
}
|
|
}
|