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