package titles import ( "fmt" "path/filepath" "sync" "testing" "time" ) // Test Title struct creation and basic methods func TestNewTitle(t *testing.T) { title := NewTitle(1, "Test Title") if title == nil { t.Fatal("NewTitle returned nil") } if title.ID != 1 { t.Errorf("Expected ID 1, got %d", title.ID) } if title.Name != "Test Title" { t.Errorf("Expected name 'Test Title', got '%s'", title.Name) } if title.Position != TitlePositionSuffix { t.Errorf("Expected default position %d, got %d", TitlePositionSuffix, title.Position) } } func TestTitleFlags(t *testing.T) { title := NewTitle(1, "Test Title") // Test setting flags title.SetFlag(FlagHidden) if !title.HasFlag(FlagHidden) { t.Error("Expected title to have FlagHidden after setting") } title.SetFlag(FlagTemporary) if !title.HasFlag(FlagTemporary) { t.Error("Expected title to have FlagTemporary after setting") } // Test clearing flags title.ClearFlag(FlagHidden) if title.HasFlag(FlagHidden) { t.Error("Expected title not to have FlagHidden after clearing") } // Test toggle flags (manually implement toggle) if title.HasFlag(FlagUnique) { title.ClearFlag(FlagUnique) } else { title.SetFlag(FlagUnique) } if !title.HasFlag(FlagUnique) { t.Error("Expected title to have FlagUnique after toggling") } // Toggle again if title.HasFlag(FlagUnique) { title.ClearFlag(FlagUnique) } else { title.SetFlag(FlagUnique) } if title.HasFlag(FlagUnique) { t.Error("Expected title not to have FlagUnique after toggling again") } } func TestTitleProperties(t *testing.T) { title := NewTitle(1, "Test Title") // Test description title.SetDescription("A test description") if title.Description != "A test description" { t.Errorf("Expected description 'A test description', got '%s'", title.Description) } // Test category title.SetCategory(CategoryAchievement) if title.Category != CategoryAchievement { t.Errorf("Expected category '%s', got '%s'", CategoryAchievement, title.Category) } // Test rarity title.SetRarity(TitleRarityRare) if title.Rarity != TitleRarityRare { t.Errorf("Expected rarity %d, got %d", TitleRarityRare, title.Rarity) } // Test display name title.Position = TitlePositionPrefix displayName := title.GetDisplayName() if displayName != "Test Title" { t.Errorf("Expected display name 'Test Title', got '%s'", displayName) } } func TestTitleTypeMethods(t *testing.T) { title := NewTitle(1, "Test Title") // Test hidden flag title.SetFlag(FlagHidden) if !title.IsHidden() { t.Error("Expected title to be hidden") } // Test temporary flag title.ClearFlag(FlagHidden) title.SetFlag(FlagTemporary) if !title.IsTemporary() { t.Error("Expected title to be temporary") } // Test unique flag title.ClearFlag(FlagTemporary) title.SetFlag(FlagUnique) if !title.IsUnique() { t.Error("Expected title to be unique") } // Test account-wide flag title.ClearFlag(FlagUnique) title.SetFlag(FlagAccountWide) if !title.IsAccountWide() { t.Error("Expected title to be account-wide") } } // Test MasterTitlesList func TestMasterTitlesList(t *testing.T) { mtl := NewMasterTitlesList() if mtl == nil { t.Fatal("NewMasterTitlesList returned nil") } // Test default titles are loaded citizen, exists := mtl.GetTitle(TitleIDCitizen) if !exists || citizen == nil { t.Error("Expected Citizen title to be loaded by default") } else if citizen.Name != "Citizen" { t.Errorf("Expected Citizen title name, got '%s'", citizen.Name) } visitor, exists := mtl.GetTitle(TitleIDVisitor) if !exists || visitor == nil { t.Error("Expected Visitor title to be loaded by default") } newcomer, exists := mtl.GetTitle(TitleIDNewcomer) if !exists || newcomer == nil { t.Error("Expected Newcomer title to be loaded by default") } returning, exists := mtl.GetTitle(TitleIDReturning) if !exists || returning == nil { t.Error("Expected Returning title to be loaded by default") } // Test adding new title testTitle := NewTitle(0, "Test Title") // ID will be assigned testTitle.SetDescription("Test description") testTitle.SetCategory(CategoryMiscellaneous) err := mtl.AddTitle(testTitle) if err != nil { t.Fatalf("Failed to add title: %v", err) } // ID should have been assigned if testTitle.ID == 0 { t.Error("Title ID should have been assigned") } retrieved, exists := mtl.GetTitle(testTitle.ID) if !exists || retrieved == nil { t.Error("Failed to retrieve added title") } else if retrieved.Name != "Test Title" { t.Errorf("Expected retrieved title name 'Test Title', got '%s'", retrieved.Name) } // Test duplicate ID duplicateTitle := NewTitle(testTitle.ID, "Duplicate") err = mtl.AddTitle(duplicateTitle) if err == nil { t.Error("Should have failed to add title with duplicate ID") } } func TestMasterTitlesListCategories(t *testing.T) { mtl := NewMasterTitlesList() // Add titles in different categories for i := 0; i < 3; i++ { title := NewTitle(0, fmt.Sprintf("Achievement Title %d", i)) title.SetCategory(CategoryAchievement) mtl.AddTitle(title) } for i := 0; i < 2; i++ { title := NewTitle(0, fmt.Sprintf("Quest Title %d", i)) title.SetCategory(CategoryQuest) mtl.AddTitle(title) } // Test getting titles by category achievementTitles := mtl.GetTitlesByCategory(CategoryAchievement) if len(achievementTitles) < 3 { t.Errorf("Expected at least 3 achievement titles, got %d", len(achievementTitles)) } questTitles := mtl.GetTitlesByCategory(CategoryQuest) if len(questTitles) < 2 { t.Errorf("Expected at least 2 quest titles, got %d", len(questTitles)) } // Test getting available categories categories := mtl.GetAvailableCategories() hasAchievement := false hasQuest := false for _, cat := range categories { if cat == CategoryAchievement { hasAchievement = true } if cat == CategoryQuest { hasQuest = true } } if !hasAchievement { t.Error("Expected CategoryAchievement in available categories") } if !hasQuest { t.Error("Expected CategoryQuest in available categories") } } func TestMasterTitlesListSourceAndRarity(t *testing.T) { mtl := NewMasterTitlesList() // Add titles with different sources title1 := NewTitle(0, "Achievement Source") title1.Source = TitleSourceAchievement title1.SetRarity(TitleRarityCommon) mtl.AddTitle(title1) title2 := NewTitle(0, "Quest Source") title2.Source = TitleSourceQuest title2.SetRarity(TitleRarityRare) mtl.AddTitle(title2) title3 := NewTitle(0, "PvP Source") title3.Source = TitleSourcePvP title3.SetRarity(TitleRarityEpic) mtl.AddTitle(title3) // Test getting titles by source achievementSourceTitles := mtl.GetTitlesBySource(TitleSourceAchievement) found := false for _, t := range achievementSourceTitles { if t.Name == "Achievement Source" { found = true break } } if !found { t.Error("Failed to find achievement source title") } // Test getting titles by rarity rareTitles := mtl.GetTitlesByRarity(TitleRarityRare) found = false for _, t := range rareTitles { if t.Name == "Quest Source" { found = true break } } if !found { t.Error("Failed to find rare title") } } // Test PlayerTitlesList func TestPlayerTitlesList(t *testing.T) { mtl := NewMasterTitlesList() // Add some titles to master list title1 := NewTitle(100, "Title One") title1.Position = TitlePositionPrefix mtl.AddTitle(title1) title2 := NewTitle(101, "Title Two") title2.Position = TitlePositionSuffix mtl.AddTitle(title2) ptl := NewPlayerTitlesList(123, mtl) if ptl == nil { t.Fatal("NewPlayerTitlesList returned nil") } // Test adding title err := ptl.AddTitle(100, 0, 0) if err != nil { t.Fatalf("Failed to add title to player: %v", err) } err = ptl.AddTitle(101, 0, 0) if err != nil { t.Fatalf("Failed to add second title to player: %v", err) } // Test getting titles (player starts with default Citizen title) titles := ptl.GetAllTitles() expectedTitleCount := 3 // Citizen + our 2 titles if len(titles) != expectedTitleCount { t.Errorf("Expected %d titles, got %d", expectedTitleCount, len(titles)) } // Test has title if !ptl.HasTitle(100) { t.Error("Expected player to have title 100") } if !ptl.HasTitle(101) { t.Error("Expected player to have title 101") } if ptl.HasTitle(999) { t.Error("Expected player not to have title 999") } // Test setting active prefix err = ptl.SetActivePrefix(100) if err != nil { t.Fatalf("Failed to set active prefix: %v", err) } activePrefixTitle, hasPrefix := ptl.GetActivePrefixTitle() if !hasPrefix || activePrefixTitle.ID != 100 { t.Errorf("Expected active prefix 100, got title ID: %v", activePrefixTitle) } // Test setting active suffix err = ptl.SetActiveSuffix(101) if err != nil { t.Fatalf("Failed to set active suffix: %v", err) } activeSuffixTitle, hasSuffix := ptl.GetActiveSuffixTitle() if !hasSuffix || activeSuffixTitle.ID != 101 { t.Errorf("Expected active suffix 101, got title ID: %v", activeSuffixTitle) } // Test formatted name (the actual format may vary) formattedName := ptl.GetFormattedName("PlayerName") // Just check that it contains both titles and the player name if !contains(formattedName, "Title One") { t.Errorf("Formatted name should contain 'Title One', got '%s'", formattedName) } if !contains(formattedName, "Title Two") { t.Errorf("Formatted name should contain 'Title Two', got '%s'", formattedName) } if !contains(formattedName, "PlayerName") { t.Errorf("Formatted name should contain 'PlayerName', got '%s'", formattedName) } // Test removing title err = ptl.RemoveTitle(100) if err != nil { t.Fatalf("Failed to remove title: %v", err) } if ptl.HasTitle(100) { t.Error("Title 100 should have been removed") } // Active prefix should be cleared _, hasPrefix = ptl.GetActivePrefixTitle() if hasPrefix { t.Error("Active prefix should be cleared after removing the title") } } func TestPlayerTitlesExpiration(t *testing.T) { mtl := NewMasterTitlesList() // Add a temporary title to master list tempTitle := NewTitle(200, "Temporary Title") tempTitle.SetFlag(FlagTemporary) tempTitle.ExpirationHours = 1 // Expires after 1 hour mtl.AddTitle(tempTitle) ptl := NewPlayerTitlesList(456, mtl) // Add the temporary title err := ptl.AddTitle(200, 0, 0) if err != nil { t.Fatalf("Failed to add temporary title: %v", err) } // Title should exist if !ptl.HasTitle(200) { t.Error("Expected player to have temporary title") } // Manually set expiration to past if playerTitle, exists := ptl.titles[200]; exists { playerTitle.ExpiresAt = time.Now().Add(-1 * time.Hour) } // Clean up expired titles expired := ptl.CleanupExpiredTitles() if expired != 1 { t.Errorf("Expected 1 expired title, got %d", expired) } // Title should be removed if ptl.HasTitle(200) { t.Error("Expired title should have been removed") } } // Test TitleManager func TestTitleManager(t *testing.T) { tm := NewTitleManager() if tm == nil { t.Fatal("NewTitleManager returned nil") } defer tm.Shutdown() // Test getting player titles playerTitles := tm.GetPlayerTitles(456) if playerTitles == nil { t.Error("GetPlayerTitles returned nil") } // Test granting title (use Visitor instead of Citizen to avoid conflicts) err := tm.GrantTitle(456, TitleIDVisitor, 0, 0) if err != nil { t.Fatalf("Failed to grant title: %v", err) } // Verify title was granted (player starts with default Citizen title) playerTitles = tm.GetPlayerTitles(456) titles := playerTitles.GetAllTitles() expectedCount := 2 // Citizen + Visitor if len(titles) != expectedCount { t.Errorf("Expected %d titles for player, got %d", expectedCount, len(titles)) } // Verify the player has the visitor title if !playerTitles.HasTitle(TitleIDVisitor) { t.Error("Player should have Visitor title") } // Test revoking title err = tm.RevokeTitle(456, TitleIDVisitor) if err != nil { t.Fatalf("Failed to revoke title: %v", err) } // Verify title was revoked (should still have Citizen) playerTitles = tm.GetPlayerTitles(456) titles = playerTitles.GetAllTitles() if len(titles) != 1 { t.Errorf("Expected 1 title after revoke, got %d", len(titles)) } } func TestTitleManagerCreateVariants(t *testing.T) { tm := NewTitleManager() defer tm.Shutdown() // Test creating regular title title, err := tm.CreateTitle("Regular Title", "A regular title", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon) if err != nil { t.Fatalf("Failed to create regular title: %v", err) } if title == nil { t.Fatal("CreateTitle returned nil") } // Test creating achievement title achievementTitle, err := tm.CreateAchievementTitle("Achievement Title", "An achievement title", 1000, TitlePositionPrefix, TitleRarityRare) if err != nil { t.Fatalf("Failed to create achievement title: %v", err) } if achievementTitle.AchievementID != 1000 { t.Errorf("Expected achievement ID 1000, got %d", achievementTitle.AchievementID) } // Test creating temporary title tempTitle, err := tm.CreateTemporaryTitle("Temp Title", "A temporary title", 24, TitlePositionSuffix, TitleSourceHoliday, TitleRarityCommon) if err != nil { t.Fatalf("Failed to create temporary title: %v", err) } if !tempTitle.HasFlag(FlagTemporary) { t.Error("Temporary title should have FlagTemporary") } if tempTitle.ExpirationHours != 24 { t.Errorf("Expected expiration hours 24, got %d", tempTitle.ExpirationHours) } // Test creating unique title uniqueTitle, err := tm.CreateUniqueTitle("Unique Title", "A unique title", TitlePositionPrefix, TitleSourceMiscellaneous) if err != nil { t.Fatalf("Failed to create unique title: %v", err) } if !uniqueTitle.HasFlag(FlagUnique) { t.Error("Unique title should have FlagUnique") } if uniqueTitle.Rarity != TitleRarityUnique { t.Errorf("Expected rarity %d for unique title, got %d", TitleRarityUnique, uniqueTitle.Rarity) } } func TestTitleManagerSearch(t *testing.T) { tm := NewTitleManager() defer tm.Shutdown() // Create test titles tm.CreateTitle("Dragon Slayer", "Defeated a dragon", CategoryCombat, TitlePositionSuffix, TitleSourceQuest, TitleRarityEpic) tm.CreateTitle("Master Crafter", "Master of crafting", CategoryTradeskill, TitlePositionPrefix, TitleSourceTradeskill, TitleRarityRare) tm.CreateTitle("Explorer", "Explored the world", CategoryExploration, TitlePositionSuffix, TitleSourceAchievement, TitleRarityCommon) // Test search results := tm.SearchTitles("dragon") found := false for _, title := range results { if title.Name == "Dragon Slayer" { found = true break } } if !found { t.Error("Failed to find 'Dragon Slayer' in search results") } // Test search with description results = tm.SearchTitles("crafting") found = false for _, title := range results { if title.Name == "Master Crafter" { found = true break } } if !found { t.Error("Failed to find 'Master Crafter' when searching description") } } func TestTitleManagerStatistics(t *testing.T) { tm := NewTitleManager() defer tm.Shutdown() // Grant some titles (avoid Citizen which may be default) tm.GrantTitle(1, TitleIDVisitor, 0, 0) tm.GrantTitle(2, TitleIDNewcomer, 0, 0) tm.GrantTitle(1, TitleIDReturning, 0, 0) stats := tm.GetStatistics() // Check statistics if totalPlayers, ok := stats["total_players"].(int); ok { if totalPlayers != 2 { t.Errorf("Expected 2 total players, got %d", totalPlayers) } } else { t.Error("Missing total_players in statistics") } if titlesGranted, ok := stats["titles_granted"].(int64); ok { if titlesGranted != 3 { t.Errorf("Expected 3 titles granted, got %d", titlesGranted) } } else { t.Error("Missing titles_granted in statistics") } } func TestTitleManagerConcurrency(t *testing.T) { tm := NewTitleManager() defer tm.Shutdown() // Test concurrent access to player titles var wg sync.WaitGroup numPlayers := 10 numOperations := 100 for i := 0; i < numPlayers; i++ { wg.Add(1) go func(playerID int32) { defer wg.Done() for j := 0; j < numOperations; j++ { // Grant title tm.GrantTitle(playerID, TitleIDCitizen, 0, 0) // Get player titles ptl := tm.GetPlayerTitles(playerID) _ = ptl.GetAllTitles() // Set active title tm.SetPlayerActivePrefix(playerID, TitleIDCitizen) // Get formatted name tm.GetPlayerFormattedName(playerID, fmt.Sprintf("Player%d", playerID)) // Revoke title tm.RevokeTitle(playerID, TitleIDCitizen) } }(int32(i)) } wg.Wait() // Verify no crashes or data races stats := tm.GetStatistics() if stats == nil { t.Error("Failed to get statistics after concurrent operations") } } // Test Database Integration func TestDatabaseIntegration(t *testing.T) { // Create temporary database tempDir := t.TempDir() dbPath := filepath.Join(tempDir, "test_titles.db") db, err := OpenDB(dbPath) if err != nil { t.Fatalf("Failed to open database: %v", err) } defer db.Close() // Create tables err = db.CreateTables() if err != nil { t.Fatalf("Failed to create tables: %v", err) } // Test master list database operations mtl := NewMasterTitlesList() // Add some test titles title1 := NewTitle(500, "Database Test Title 1") title1.SetDescription("Test description 1") title1.SetCategory(CategoryMiscellaneous) title1.Position = TitlePositionPrefix // Set as prefix title mtl.AddTitle(title1) title2 := NewTitle(501, "Database Test Title 2") title2.SetDescription("Test description 2") title2.SetCategory(CategoryAchievement) title2.Position = TitlePositionSuffix // Set as suffix title title2.AchievementID = 2000 mtl.AddTitle(title2) // Save to database err = mtl.SaveToDatabase(db) if err != nil { t.Fatalf("Failed to save master titles to database: %v", err) } // Create new master list and load from database mtl2 := &MasterTitlesList{ titles: make(map[int32]*Title), categorized: make(map[string][]*Title), bySource: make(map[int32][]*Title), byRarity: make(map[int32][]*Title), byAchievement: make(map[uint32]*Title), nextID: 1, } err = mtl2.LoadFromDatabase(db) if err != nil { t.Fatalf("Failed to load master titles from database: %v", err) } // Verify loaded titles loadedTitle1, exists := mtl2.GetTitle(500) if !exists || loadedTitle1 == nil { t.Fatal("Failed to load title 500 from database") } if loadedTitle1.Name != "Database Test Title 1" { t.Errorf("Expected title name 'Database Test Title 1', got '%s'", loadedTitle1.Name) } loadedTitle2, exists := mtl2.GetTitle(501) if !exists || loadedTitle2 == nil { t.Fatal("Failed to load title 501 from database") } if loadedTitle2.AchievementID != 2000 { t.Errorf("Expected achievement ID 2000, got %d", loadedTitle2.AchievementID) } // Test player titles database operations ptl := NewPlayerTitlesList(789, mtl2) // Use mtl2 which has titles loaded from database ptl.AddTitle(500, 0, 0) ptl.AddTitle(501, 2000, 0) ptl.SetActivePrefix(500) ptl.SetActiveSuffix(501) // Save player titles err = ptl.SaveToDatabase(db) if err != nil { t.Fatalf("Failed to save player titles to database: %v", err) } // Load player titles ptl2 := NewPlayerTitlesList(789, mtl2) err = ptl2.LoadFromDatabase(db) if err != nil { t.Fatalf("Failed to load player titles from database: %v", err) } // Verify loaded player titles if !ptl2.HasTitle(500) { t.Error("Expected player to have title 500") } if !ptl2.HasTitle(501) { t.Error("Expected player to have title 501") } activePrefixTitle, hasPrefix := ptl2.GetActivePrefixTitle() if !hasPrefix { t.Error("Expected to have active prefix title") } else if activePrefixTitle.ID != 500 { t.Errorf("Expected active prefix 500, got %d", activePrefixTitle.ID) } activeSuffixTitle, hasSuffix := ptl2.GetActiveSuffixTitle() if !hasSuffix { t.Error("Expected to have active suffix title") } else if activeSuffixTitle.ID != 501 { t.Errorf("Expected active suffix 501, got %d", activeSuffixTitle.ID) } } func TestDatabaseHelperFunctions(t *testing.T) { // Test nullableUint32 if val := nullableUint32(0); val != nil { t.Error("nullableUint32(0) should return nil") } if val := nullableUint32(123); val != uint32(123) { t.Errorf("nullableUint32(123) should return 123, got %v", val) } // Test nullableTime zeroTime := time.Time{} if val := nullableTime(zeroTime); val != nil { t.Error("nullableTime(zero) should return nil") } now := time.Now() if val := nullableTime(now); val != now.Unix() { t.Errorf("nullableTime(now) should return Unix timestamp, got %v", val) } } // Benchmark tests func BenchmarkTitleCreation(b *testing.B) { for i := 0; i < b.N; i++ { title := NewTitle(int32(i), fmt.Sprintf("Title %d", i)) title.SetDescription("Description") title.SetCategory(CategoryMiscellaneous) } } func BenchmarkMasterListAdd(b *testing.B) { mtl := NewMasterTitlesList() b.ResetTimer() for i := 0; i < b.N; i++ { title := NewTitle(int32(i+1000), fmt.Sprintf("Title %d", i)) mtl.AddTitle(title) } } func BenchmarkPlayerListAdd(b *testing.B) { mtl := NewMasterTitlesList() // Pre-populate master list for i := 0; i < 1000; i++ { title := NewTitle(int32(i), fmt.Sprintf("Title %d", i)) mtl.AddTitle(title) } ptl := NewPlayerTitlesList(1, mtl) b.ResetTimer() for i := 0; i < b.N; i++ { titleID := int32(i % 1000) ptl.AddTitle(titleID, 0, 0) ptl.RemoveTitle(titleID) } } func BenchmarkTitleManagerGrant(b *testing.B) { tm := NewTitleManager() defer tm.Shutdown() // Pre-create titles for i := 0; i < 100; i++ { tm.CreateTitle(fmt.Sprintf("Title %d", i), "Description", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon) } b.ResetTimer() for i := 0; i < b.N; i++ { playerID := int32(i % 100) titleID := int32((i % 100) + 1) tm.GrantTitle(playerID, titleID, 0, 0) } } func BenchmarkTitleSearch(b *testing.B) { tm := NewTitleManager() defer tm.Shutdown() // Create fewer titles for faster benchmark for i := 0; i < 100; i++ { name := fmt.Sprintf("Title %d", i) if i%10 == 0 { name = fmt.Sprintf("Dragon %d", i) } tm.CreateTitle(name, "Description", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon) } b.ResetTimer() for i := 0; i < b.N; i++ { tm.SearchTitles("dragon") } } func BenchmarkConcurrentAccess(b *testing.B) { tm := NewTitleManager() defer tm.Shutdown() // Pre-populate with titles for i := 0; i < 10; i++ { tm.CreateTitle(fmt.Sprintf("Title %d", i), "Description", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon) } b.ResetTimer() // Use simpler sequential approach instead of RunParallel to avoid deadlocks for i := 0; i < b.N; i++ { playerID := int32(i % 10) titleID := int32((i % 10) + 1) tm.GrantTitle(playerID, titleID, 0, 0) tm.GetPlayerFormattedName(playerID, "Player") } }