539 lines
16 KiB
Go
539 lines
16 KiB
Go
package chat
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
func TestNewMasterList(t *testing.T) {
|
|
masterList := NewMasterList()
|
|
|
|
if masterList == nil {
|
|
t.Fatal("NewMasterList returned nil")
|
|
}
|
|
|
|
if masterList.GetChannelCount() != 0 {
|
|
t.Errorf("Expected count 0, got %d", masterList.GetChannelCount())
|
|
}
|
|
}
|
|
|
|
func TestMasterListBasicOperations(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Create test channels
|
|
channel1 := NewWithData(1001, "Auction", ChannelTypeWorld, db)
|
|
channel2 := NewWithData(1002, "Custom Channel", ChannelTypeCustom, db)
|
|
|
|
// Test adding
|
|
if !masterList.AddChannel(channel1) {
|
|
t.Error("Should successfully add channel1")
|
|
}
|
|
|
|
if !masterList.AddChannel(channel2) {
|
|
t.Error("Should successfully add channel2")
|
|
}
|
|
|
|
// Test duplicate add (should fail)
|
|
if masterList.AddChannel(channel1) {
|
|
t.Error("Should not add duplicate channel")
|
|
}
|
|
|
|
if masterList.GetChannelCount() != 2 {
|
|
t.Errorf("Expected count 2, got %d", masterList.GetChannelCount())
|
|
}
|
|
|
|
// Test retrieving
|
|
retrieved := masterList.GetChannel(1001)
|
|
if retrieved == nil {
|
|
t.Error("Should retrieve added channel")
|
|
}
|
|
|
|
if retrieved.GetName() != "Auction" {
|
|
t.Errorf("Expected name 'Auction', got '%s'", retrieved.GetName())
|
|
}
|
|
|
|
// Test safe retrieval
|
|
retrieved, exists := masterList.GetChannelSafe(1001)
|
|
if !exists || retrieved == nil {
|
|
t.Error("GetChannelSafe should return channel and true")
|
|
}
|
|
|
|
_, exists = masterList.GetChannelSafe(9999)
|
|
if exists {
|
|
t.Error("GetChannelSafe should return false for non-existent ID")
|
|
}
|
|
|
|
// Test HasChannel
|
|
if !masterList.HasChannel(1001) {
|
|
t.Error("HasChannel should return true for existing ID")
|
|
}
|
|
|
|
if masterList.HasChannel(9999) {
|
|
t.Error("HasChannel should return false for non-existent ID")
|
|
}
|
|
|
|
// Test removing
|
|
if !masterList.RemoveChannel(1001) {
|
|
t.Error("Should successfully remove channel")
|
|
}
|
|
|
|
if masterList.GetChannelCount() != 1 {
|
|
t.Errorf("Expected count 1, got %d", masterList.GetChannelCount())
|
|
}
|
|
|
|
if masterList.HasChannel(1001) {
|
|
t.Error("Channel should be removed")
|
|
}
|
|
|
|
// Test clear
|
|
masterList.ClearChannels()
|
|
if masterList.GetChannelCount() != 0 {
|
|
t.Errorf("Expected count 0 after clear, got %d", masterList.GetChannelCount())
|
|
}
|
|
}
|
|
|
|
func TestMasterListFiltering(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add test data
|
|
channels := []*Channel{
|
|
NewWithData(1, "Auction", ChannelTypeWorld, db),
|
|
NewWithData(2, "Trade", ChannelTypeWorld, db),
|
|
NewWithData(3, "Custom Chat", ChannelTypeCustom, db),
|
|
NewWithData(4, "Player Channel", ChannelTypeCustom, db),
|
|
}
|
|
|
|
for _, ch := range channels {
|
|
masterList.AddChannel(ch)
|
|
}
|
|
|
|
// Test FindChannelsByName
|
|
auctionChannels := masterList.FindChannelsByName("Auction")
|
|
if len(auctionChannels) != 1 {
|
|
t.Errorf("FindChannelsByName('Auction') returned %v results, want 1", len(auctionChannels))
|
|
}
|
|
|
|
chatChannels := masterList.FindChannelsByName("Channel")
|
|
if len(chatChannels) != 1 {
|
|
t.Errorf("FindChannelsByName('Channel') returned %v results, want 1", len(chatChannels))
|
|
}
|
|
|
|
// Test FindChannelsByType
|
|
worldChannels := masterList.FindChannelsByType(ChannelTypeWorld)
|
|
if len(worldChannels) != 2 {
|
|
t.Errorf("FindChannelsByType(World) returned %v results, want 2", len(worldChannels))
|
|
}
|
|
|
|
customChannels := masterList.FindChannelsByType(ChannelTypeCustom)
|
|
if len(customChannels) != 2 {
|
|
t.Errorf("FindChannelsByType(Custom) returned %v results, want 2", len(customChannels))
|
|
}
|
|
|
|
// Test GetWorldChannels
|
|
worldList := masterList.GetWorldChannels()
|
|
if len(worldList) != 2 {
|
|
t.Errorf("GetWorldChannels() returned %v results, want 2", len(worldList))
|
|
}
|
|
|
|
// Test GetCustomChannels
|
|
customList := masterList.GetCustomChannels()
|
|
if len(customList) != 2 {
|
|
t.Errorf("GetCustomChannels() returned %v results, want 2", len(customList))
|
|
}
|
|
|
|
// Test GetActiveChannels (all channels are empty initially)
|
|
activeChannels := masterList.GetActiveChannels()
|
|
if len(activeChannels) != 0 {
|
|
t.Errorf("GetActiveChannels() returned %v results, want 0", len(activeChannels))
|
|
}
|
|
|
|
// Add members to make channels active
|
|
channels[0].JoinChannel(1001)
|
|
channels[1].JoinChannel(1002)
|
|
|
|
activeChannels = masterList.GetActiveChannels()
|
|
if len(activeChannels) != 2 {
|
|
t.Errorf("GetActiveChannels() returned %v results, want 2", len(activeChannels))
|
|
}
|
|
|
|
// Test GetEmptyChannels
|
|
emptyChannels := masterList.GetEmptyChannels()
|
|
if len(emptyChannels) != 2 {
|
|
t.Errorf("GetEmptyChannels() returned %v results, want 2", len(emptyChannels))
|
|
}
|
|
}
|
|
|
|
func TestMasterListGetByName(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add test channels with different names to test indexing
|
|
channel1 := NewWithData(100, "Auction", ChannelTypeWorld, db)
|
|
channel2 := NewWithData(200, "Trade", ChannelTypeWorld, db)
|
|
channel3 := NewWithData(300, "Custom Channel", ChannelTypeCustom, db)
|
|
masterList.AddChannel(channel1)
|
|
masterList.AddChannel(channel2)
|
|
masterList.AddChannel(channel3)
|
|
|
|
// Test case-insensitive lookup
|
|
found := masterList.GetChannelByName("auction")
|
|
if found == nil || found.ID != 100 {
|
|
t.Error("GetChannelByName should find 'Auction' channel (case insensitive)")
|
|
}
|
|
|
|
found = masterList.GetChannelByName("TRADE")
|
|
if found == nil || found.ID != 200 {
|
|
t.Error("GetChannelByName should find 'Trade' channel (uppercase)")
|
|
}
|
|
|
|
found = masterList.GetChannelByName("custom channel")
|
|
if found == nil || found.ID != 300 {
|
|
t.Error("GetChannelByName should find 'Custom Channel' channel (lowercase)")
|
|
}
|
|
|
|
found = masterList.GetChannelByName("NonExistent")
|
|
if found != nil {
|
|
t.Error("GetChannelByName should return nil for non-existent channel")
|
|
}
|
|
|
|
// Test HasChannelByName
|
|
if !masterList.HasChannelByName("auction") {
|
|
t.Error("HasChannelByName should return true for existing channel")
|
|
}
|
|
|
|
if masterList.HasChannelByName("NonExistent") {
|
|
t.Error("HasChannelByName should return false for non-existent channel")
|
|
}
|
|
}
|
|
|
|
func TestMasterListCompatibility(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Create channels with restrictions
|
|
channel1 := NewWithData(1, "LowLevel", ChannelTypeWorld, db)
|
|
channel1.SetLevelRestriction(5)
|
|
|
|
channel2 := NewWithData(2, "HighLevel", ChannelTypeWorld, db)
|
|
channel2.SetLevelRestriction(50)
|
|
|
|
channel3 := NewWithData(3, "RaceRestricted", ChannelTypeWorld, db)
|
|
channel3.SetRacesAllowed(1 << 1) // Only race 1 allowed
|
|
|
|
masterList.AddChannel(channel1)
|
|
masterList.AddChannel(channel2)
|
|
masterList.AddChannel(channel3)
|
|
|
|
// Test compatibility for level 10, race 1, class 1 player
|
|
compatible := masterList.GetCompatibleChannels(10, 1, 1)
|
|
if len(compatible) != 2 { // Should match channel1 and channel3
|
|
t.Errorf("GetCompatibleChannels(10,1,1) returned %v results, want 2", len(compatible))
|
|
}
|
|
|
|
// Test compatibility for level 60, race 2, class 1 player
|
|
compatible = masterList.GetCompatibleChannels(60, 2, 1)
|
|
if len(compatible) != 2 { // Should match channel1 and channel2 (not channel3)
|
|
t.Errorf("GetCompatibleChannels(60,2,1) returned %v results, want 2", len(compatible))
|
|
}
|
|
|
|
// Test compatibility for level 1, race 1, class 1 player
|
|
compatible = masterList.GetCompatibleChannels(1, 1, 1)
|
|
if len(compatible) != 1 { // Should only match channel3 (no level restriction)
|
|
t.Errorf("GetCompatibleChannels(1,1,1) returned %v results, want 1", len(compatible))
|
|
}
|
|
}
|
|
|
|
func TestMasterListGetAll(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add test channels
|
|
for i := int32(1); i <= 3; i++ {
|
|
ch := NewWithData(i*100, "Test", ChannelTypeWorld, db)
|
|
masterList.AddChannel(ch)
|
|
}
|
|
|
|
// Test GetAllChannels (map)
|
|
allMap := masterList.GetAllChannels()
|
|
if len(allMap) != 3 {
|
|
t.Errorf("GetAllChannels() returned %v items, want 3", len(allMap))
|
|
}
|
|
|
|
// Verify it's a copy by modifying returned map
|
|
delete(allMap, 100)
|
|
if masterList.GetChannelCount() != 3 {
|
|
t.Error("Modifying returned map affected internal state")
|
|
}
|
|
|
|
// Test GetAllChannelsList (slice)
|
|
allList := masterList.GetAllChannelsList()
|
|
if len(allList) != 3 {
|
|
t.Errorf("GetAllChannelsList() returned %v items, want 3", len(allList))
|
|
}
|
|
}
|
|
|
|
func TestMasterListValidation(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add valid channels
|
|
ch1 := NewWithData(100, "Valid Channel", ChannelTypeWorld, db)
|
|
masterList.AddChannel(ch1)
|
|
|
|
issues := masterList.ValidateChannels()
|
|
if len(issues) != 0 {
|
|
t.Errorf("ValidateChannels() returned issues for valid data: %v", issues)
|
|
}
|
|
|
|
if !masterList.IsValid() {
|
|
t.Error("IsValid() should return true for valid data")
|
|
}
|
|
|
|
// Add invalid channel (empty name)
|
|
ch2 := NewWithData(200, "", ChannelTypeWorld, db)
|
|
masterList.AddChannel(ch2)
|
|
|
|
issues = masterList.ValidateChannels()
|
|
if len(issues) == 0 {
|
|
t.Error("ValidateChannels() should return issues for invalid data")
|
|
}
|
|
|
|
if masterList.IsValid() {
|
|
t.Error("IsValid() should return false for invalid data")
|
|
}
|
|
}
|
|
|
|
func TestMasterListStatistics(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add channels with different types
|
|
masterList.AddChannel(NewWithData(10, "World1", ChannelTypeWorld, db))
|
|
masterList.AddChannel(NewWithData(20, "World2", ChannelTypeWorld, db))
|
|
masterList.AddChannel(NewWithData(30, "Custom1", ChannelTypeCustom, db))
|
|
masterList.AddChannel(NewWithData(40, "Custom2", ChannelTypeCustom, db))
|
|
masterList.AddChannel(NewWithData(50, "Custom3", ChannelTypeCustom, db))
|
|
|
|
// Add some members
|
|
masterList.GetChannel(10).JoinChannel(1001)
|
|
masterList.GetChannel(20).JoinChannel(1002)
|
|
|
|
stats := masterList.GetStatistics()
|
|
|
|
if total, ok := stats["total_channels"].(int); !ok || total != 5 {
|
|
t.Errorf("total_channels = %v, want 5", stats["total_channels"])
|
|
}
|
|
|
|
if worldChannels, ok := stats["world_channels"].(int); !ok || worldChannels != 2 {
|
|
t.Errorf("world_channels = %v, want 2", stats["world_channels"])
|
|
}
|
|
|
|
if customChannels, ok := stats["custom_channels"].(int); !ok || customChannels != 3 {
|
|
t.Errorf("custom_channels = %v, want 3", stats["custom_channels"])
|
|
}
|
|
|
|
if activeChannels, ok := stats["active_channels"].(int); !ok || activeChannels != 2 {
|
|
t.Errorf("active_channels = %v, want 2", stats["active_channels"])
|
|
}
|
|
|
|
if totalMembers, ok := stats["total_members"].(int); !ok || totalMembers != 2 {
|
|
t.Errorf("total_members = %v, want 2", stats["total_members"])
|
|
}
|
|
|
|
if minID, ok := stats["min_id"].(int32); !ok || minID != 10 {
|
|
t.Errorf("min_id = %v, want 10", stats["min_id"])
|
|
}
|
|
|
|
if maxID, ok := stats["max_id"].(int32); !ok || maxID != 50 {
|
|
t.Errorf("max_id = %v, want 50", stats["max_id"])
|
|
}
|
|
|
|
if idRange, ok := stats["id_range"].(int32); !ok || idRange != 40 {
|
|
t.Errorf("id_range = %v, want 40", stats["id_range"])
|
|
}
|
|
}
|
|
|
|
func TestMasterListBespokeFeatures(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add test channels with different properties
|
|
ch1 := NewWithData(101, "Test Channel", ChannelTypeWorld, db)
|
|
ch1.SetLevelRestriction(10)
|
|
|
|
ch2 := NewWithData(102, "Another Test", ChannelTypeCustom, db)
|
|
ch2.SetLevelRestriction(20)
|
|
|
|
ch3 := NewWithData(103, "Empty Channel", ChannelTypeWorld, db)
|
|
ch3.SetLevelRestriction(10)
|
|
|
|
masterList.AddChannel(ch1)
|
|
masterList.AddChannel(ch2)
|
|
masterList.AddChannel(ch3)
|
|
|
|
// Add some members to make channels active/empty
|
|
ch1.JoinChannel(1001)
|
|
masterList.RefreshChannelIndices(ch1, 0) // Update from 0 to 1 member
|
|
ch1.JoinChannel(1002)
|
|
masterList.RefreshChannelIndices(ch1, 1) // Update from 1 to 2 members
|
|
ch2.JoinChannel(1003)
|
|
masterList.RefreshChannelIndices(ch2, 0) // Update from 0 to 1 member
|
|
|
|
// Test GetChannelsByMemberCount
|
|
zeroMemberChannels := masterList.GetChannelsByMemberCount(0)
|
|
if len(zeroMemberChannels) != 1 {
|
|
t.Errorf("GetChannelsByMemberCount(0) returned %v results, want 1", len(zeroMemberChannels))
|
|
}
|
|
|
|
twoMemberChannels := masterList.GetChannelsByMemberCount(2)
|
|
if len(twoMemberChannels) != 1 {
|
|
t.Errorf("GetChannelsByMemberCount(2) returned %v results, want 1", len(twoMemberChannels))
|
|
}
|
|
|
|
oneMemberChannels := masterList.GetChannelsByMemberCount(1)
|
|
if len(oneMemberChannels) != 1 {
|
|
t.Errorf("GetChannelsByMemberCount(1) returned %v results, want 1", len(oneMemberChannels))
|
|
}
|
|
|
|
// Test GetChannelsByLevelRestriction
|
|
level10Channels := masterList.GetChannelsByLevelRestriction(10)
|
|
if len(level10Channels) != 2 {
|
|
t.Errorf("GetChannelsByLevelRestriction(10) returned %v results, want 2", len(level10Channels))
|
|
}
|
|
|
|
level20Channels := masterList.GetChannelsByLevelRestriction(20)
|
|
if len(level20Channels) != 1 {
|
|
t.Errorf("GetChannelsByLevelRestriction(20) returned %v results, want 1", len(level20Channels))
|
|
}
|
|
|
|
// Test UpdateChannel
|
|
updatedCh := &Channel{
|
|
ID: 101,
|
|
Name: "Updated Channel Name",
|
|
ChannelType: ChannelTypeCustom, // Changed type
|
|
db: db,
|
|
isNew: false,
|
|
members: make([]int32, 0),
|
|
}
|
|
|
|
err := masterList.UpdateChannel(updatedCh)
|
|
if err != nil {
|
|
t.Errorf("UpdateChannel failed: %v", err)
|
|
}
|
|
|
|
// Verify the update worked
|
|
retrieved := masterList.GetChannel(101)
|
|
if retrieved.Name != "Updated Channel Name" {
|
|
t.Errorf("Expected updated name 'Updated Channel Name', got '%s'", retrieved.Name)
|
|
}
|
|
|
|
if retrieved.ChannelType != ChannelTypeCustom {
|
|
t.Errorf("Expected updated type %d, got %d", ChannelTypeCustom, retrieved.ChannelType)
|
|
}
|
|
|
|
// Test updating non-existent channel
|
|
nonExistentCh := &Channel{ID: 9999, Name: "Non-existent", db: db}
|
|
err = masterList.UpdateChannel(nonExistentCh)
|
|
if err == nil {
|
|
t.Error("UpdateChannel should fail for non-existent channel")
|
|
}
|
|
}
|
|
|
|
func TestMasterListConcurrency(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
masterList := NewMasterList()
|
|
|
|
// Add initial channels
|
|
for i := 1; i <= 100; i++ {
|
|
ch := NewWithData(int32(i), fmt.Sprintf("Channel%d", i), ChannelTypeWorld, db)
|
|
masterList.AddChannel(ch)
|
|
}
|
|
|
|
// Test concurrent access
|
|
done := make(chan bool, 10)
|
|
|
|
// Concurrent readers
|
|
for i := 0; i < 5; i++ {
|
|
go func() {
|
|
defer func() { done <- true }()
|
|
for j := 0; j < 100; j++ {
|
|
masterList.GetChannel(int32(j%100 + 1))
|
|
masterList.FindChannelsByType(ChannelTypeWorld)
|
|
masterList.GetChannelByName(fmt.Sprintf("channel%d", j%100+1))
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Concurrent writers
|
|
for i := 0; i < 5; i++ {
|
|
go func(workerID int) {
|
|
defer func() { done <- true }()
|
|
for j := 0; j < 10; j++ {
|
|
chID := int32(workerID*1000 + j + 1)
|
|
ch := NewWithData(chID, fmt.Sprintf("Worker%d-Channel%d", workerID, j), ChannelTypeCustom, db)
|
|
masterList.AddChannel(ch) // Some may fail due to concurrent additions
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Verify final state - should have at least 100 initial channels
|
|
finalCount := masterList.GetChannelCount()
|
|
if finalCount < 100 {
|
|
t.Errorf("Expected at least 100 channels after concurrent operations, got %d", finalCount)
|
|
}
|
|
if finalCount > 150 {
|
|
t.Errorf("Expected at most 150 channels after concurrent operations, got %d", finalCount)
|
|
}
|
|
}
|
|
|
|
func TestContainsFunction(t *testing.T) {
|
|
tests := []struct {
|
|
str string
|
|
substr string
|
|
want bool
|
|
}{
|
|
{"hello world", "world", true},
|
|
{"hello world", "World", false}, // Case sensitive
|
|
{"hello", "hello world", false},
|
|
{"hello", "", true},
|
|
{"", "hello", false},
|
|
{"", "", true},
|
|
{"abcdef", "cde", true},
|
|
{"abcdef", "xyz", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
if got := contains(tt.str, tt.substr); got != tt.want {
|
|
t.Errorf("contains(%q, %q) = %v, want %v", tt.str, tt.substr, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
} |