package classes import ( "math/rand" "strings" "sync" "testing" "time" ) // Mock entity implementations for testing type MockClassAware struct { classID int8 mutex sync.RWMutex } func (m *MockClassAware) GetClass() int8 { m.mutex.RLock() defer m.mutex.RUnlock() return m.classID } func (m *MockClassAware) SetClass(classID int8) { m.mutex.Lock() defer m.mutex.Unlock() m.classID = classID } type MockEntityWithClass struct { *MockClassAware id int32 name string level int8 } func (m *MockEntityWithClass) GetID() int32 { return m.id } func (m *MockEntityWithClass) GetName() string { return m.name } func (m *MockEntityWithClass) GetLevel() int8 { return m.level } func NewMockClassAware(classID int8) *MockClassAware { return &MockClassAware{classID: classID} } func NewMockEntityWithClass(id int32, name string, level int8, classID int8) *MockEntityWithClass { return &MockEntityWithClass{ MockClassAware: NewMockClassAware(classID), id: id, name: name, level: level, } } // Core Classes tests func TestNewClasses(t *testing.T) { classes := NewClasses() if classes == nil { t.Fatal("NewClasses returned nil") } if len(classes.classMap) == 0 { t.Error("classMap should not be empty after initialization") } if len(classes.displayNameMap) == 0 { t.Error("displayNameMap should not be empty after initialization") } } func TestGetClassID(t *testing.T) { classes := NewClasses() tests := []struct { name string input string expectedID int8 shouldBeValid bool }{ {"Valid class name", "WARRIOR", ClassWarrior, true}, {"Valid class name lowercase", "warrior", ClassWarrior, true}, {"Valid class name with spaces", " WARRIOR ", ClassWarrior, true}, {"Invalid class name", "INVALID", -1, false}, {"Empty string", "", -1, false}, {"Valid priest class", "CLERIC", ClassCleric, true}, {"Valid mage class", "WIZARD", ClassWizard, true}, {"Valid scout class", "RANGER", ClassRanger, true}, {"Valid tradeskill class", "ARMORER", ClassArmorer, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := classes.GetClassID(tt.input) if tt.shouldBeValid { if result != tt.expectedID { t.Errorf("GetClassID(%s) = %d, want %d", tt.input, result, tt.expectedID) } } else { if result != -1 { t.Errorf("GetClassID(%s) should return -1 for invalid input, got %d", tt.input, result) } } }) } } func TestGetClassName(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 expectedName string }{ {ClassWarrior, ClassNameWarrior}, {ClassCleric, ClassNameCleric}, {ClassWizard, ClassNameWizard}, {ClassRanger, ClassNameRanger}, {ClassArmorer, ClassNameArmorer}, {-1, ""}, // Invalid class ID {100, ""}, // Invalid class ID } for _, tt := range tests { t.Run("GetClassName", func(t *testing.T) { result := classes.GetClassName(tt.classID) if result != tt.expectedName { t.Errorf("GetClassName(%d) = %s, want %s", tt.classID, result, tt.expectedName) } }) } } func TestGetClassNameCase(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 expectedDisplay string }{ {ClassWarrior, DisplayNameWarrior}, {ClassCleric, DisplayNameCleric}, {ClassWizard, DisplayNameWizard}, {ClassRanger, DisplayNameRanger}, {ClassArmorer, DisplayNameArmorer}, {-1, ""}, // Invalid class ID {100, ""}, // Invalid class ID } for _, tt := range tests { t.Run("GetClassNameCase", func(t *testing.T) { result := classes.GetClassNameCase(tt.classID) if result != tt.expectedDisplay { t.Errorf("GetClassNameCase(%d) = %s, want %s", tt.classID, result, tt.expectedDisplay) } }) } } func TestGetBaseClass(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 expectedBase int8 }{ {ClassWarrior, ClassFighter}, {ClassGuardian, ClassFighter}, {ClassBerserker, ClassFighter}, {ClassCleric, ClassPriest}, {ClassTemplar, ClassPriest}, {ClassWizard, ClassMage}, {ClassWarlock, ClassMage}, {ClassRanger, ClassScout}, {ClassAssassin, ClassScout}, {ClassShaper, ClassPriest}, {ClassChanneler, ClassPriest}, {ClassCommoner, ClassCommoner}, {-1, ClassCommoner}, // Invalid defaults to Commoner } for _, tt := range tests { t.Run("GetBaseClass", func(t *testing.T) { result := classes.GetBaseClass(tt.classID) if result != tt.expectedBase { t.Errorf("GetBaseClass(%d) = %d, want %d", tt.classID, result, tt.expectedBase) } }) } } func TestGetSecondaryBaseClass(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 expectedSecondaryBase int8 }{ {ClassGuardian, ClassWarrior}, {ClassBerserker, ClassWarrior}, {ClassMonk, ClassBrawler}, {ClassBruiser, ClassBrawler}, {ClassShadowknight, ClassCrusader}, {ClassPaladin, ClassCrusader}, {ClassTemplar, ClassCleric}, {ClassInquisitor, ClassCleric}, {ClassWarden, ClassDruid}, {ClassFury, ClassDruid}, {ClassMystic, ClassShaman}, {ClassDefiler, ClassShaman}, {ClassWizard, ClassSorcerer}, {ClassWarlock, ClassSorcerer}, {ClassIllusionist, ClassEnchanter}, {ClassCoercer, ClassEnchanter}, {ClassConjuror, ClassSummoner}, {ClassNecromancer, ClassSummoner}, {ClassSwashbuckler, ClassRogue}, {ClassBrigand, ClassRogue}, {ClassTroubador, ClassBard}, {ClassDirge, ClassBard}, {ClassRanger, ClassPredator}, {ClassAssassin, ClassPredator}, {ClassBeastlord, ClassAnimalist}, {ClassChanneler, ClassShaper}, {ClassCommoner, ClassCommoner}, // Base classes return Commoner {ClassFighter, ClassCommoner}, } for _, tt := range tests { t.Run("GetSecondaryBaseClass", func(t *testing.T) { result := classes.GetSecondaryBaseClass(tt.classID) if result != tt.expectedSecondaryBase { t.Errorf("GetSecondaryBaseClass(%d) = %d, want %d", tt.classID, result, tt.expectedSecondaryBase) } }) } } func TestIsValidClassID(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 valid bool }{ {ClassCommoner, true}, {ClassWarrior, true}, {ClassAlchemist, true}, {MinClassID, true}, {MaxClassID, true}, {-1, false}, {MaxClassID + 1, false}, {100, false}, } for _, tt := range tests { t.Run("IsValidClassID", func(t *testing.T) { result := classes.IsValidClassID(tt.classID) if result != tt.valid { t.Errorf("IsValidClassID(%d) = %t, want %t", tt.classID, result, tt.valid) } }) } } func TestGetAllClasses(t *testing.T) { classes := NewClasses() allClasses := classes.GetAllClasses() if len(allClasses) == 0 { t.Error("GetAllClasses should not return empty map") } // Check that all classes have valid display names for classID, displayName := range allClasses { if displayName == "" { t.Errorf("Class %d has empty display name", classID) } if !classes.IsValidClassID(classID) { t.Errorf("Invalid class ID in GetAllClasses: %d", classID) } } // Verify we have the expected number of classes expectedCount := MaxClasses if len(allClasses) != expectedCount { t.Errorf("GetAllClasses returned %d classes, expected %d", len(allClasses), expectedCount) } } func TestIsAdventureClass(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 adventure bool }{ {ClassCommoner, true}, {ClassFighter, true}, {ClassWarrior, true}, {ClassChanneler, true}, {ClassArtisan, false}, {ClassCraftsman, false}, {ClassAlchemist, false}, } for _, tt := range tests { t.Run("IsAdventureClass", func(t *testing.T) { result := classes.IsAdventureClass(tt.classID) if result != tt.adventure { t.Errorf("IsAdventureClass(%d) = %t, want %t", tt.classID, result, tt.adventure) } }) } } func TestIsTradeskillClass(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 tradeskill bool }{ {ClassCommoner, false}, {ClassWarrior, false}, {ClassChanneler, false}, {ClassArtisan, true}, {ClassCraftsman, true}, {ClassAlchemist, true}, } for _, tt := range tests { t.Run("IsTradeskillClass", func(t *testing.T) { result := classes.IsTradeskillClass(tt.classID) if result != tt.tradeskill { t.Errorf("IsTradeskillClass(%d) = %t, want %t", tt.classID, result, tt.tradeskill) } }) } } func TestGetClassType(t *testing.T) { classes := NewClasses() tests := []struct { classID int8 expectedType string }{ {ClassWarrior, ClassTypeAdventure}, {ClassCleric, ClassTypeAdventure}, {ClassChanneler, ClassTypeAdventure}, {ClassArtisan, ClassTypeTradeskill}, {ClassCraftsman, ClassTypeTradeskill}, {ClassAlchemist, ClassTypeTradeskill}, {-1, ClassTypeSpecial}, // Invalid class {100, ClassTypeSpecial}, // Invalid class } for _, tt := range tests { t.Run("GetClassType", func(t *testing.T) { result := classes.GetClassType(tt.classID) if result != tt.expectedType { t.Errorf("GetClassType(%d) = %s, want %s", tt.classID, result, tt.expectedType) } }) } } func TestGetClassCount(t *testing.T) { classes := NewClasses() count := classes.GetClassCount() if count != MaxClasses { t.Errorf("GetClassCount() = %d, want %d", count, MaxClasses) } } func TestGetClassInfo(t *testing.T) { classes := NewClasses() // Test valid class info := classes.GetClassInfo(ClassWarrior) if !info["valid"].(bool) { t.Error("GetClassInfo should indicate valid class") } if info["class_id"] != ClassWarrior { t.Error("GetClassInfo should return correct class_id") } if info["name"] != ClassNameWarrior { t.Error("GetClassInfo should return correct name") } if info["display_name"] != DisplayNameWarrior { t.Error("GetClassInfo should return correct display_name") } // Test invalid class invalidInfo := classes.GetClassInfo(-1) if invalidInfo["valid"].(bool) { t.Error("GetClassInfo should indicate invalid class for -1") } } func TestGetGlobalClasses(t *testing.T) { classes1 := GetGlobalClasses() classes2 := GetGlobalClasses() if classes1 != classes2 { t.Error("GetGlobalClasses should return the same instance (singleton)") } if classes1 == nil { t.Error("GetGlobalClasses should not return nil") } } // ClassUtils tests func TestNewClassUtils(t *testing.T) { utils := NewClassUtils() if utils == nil { t.Fatal("NewClassUtils returned nil") } if utils.classes == nil { t.Error("ClassUtils should have classes instance") } } func TestParseClassName(t *testing.T) { utils := NewClassUtils() tests := []struct { name string input string expected int8 }{ {"Exact match", "WARRIOR", ClassWarrior}, {"Case insensitive", "warrior", ClassWarrior}, {"With spaces", " Warrior ", ClassWarrior}, {"With underscores", "SHADOW_KNIGHT", ClassShadowknight}, {"With dashes", "SHADOW-KNIGHT", ClassShadowknight}, {"Friendly name", "Warrior", ClassWarrior}, {"Empty string", "", -1}, {"Invalid name", "INVALID", -1}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := utils.ParseClassName(tt.input) if result != tt.expected { t.Errorf("ParseClassName(%s) = %d, want %d", tt.input, result, tt.expected) } }) } } func TestFormatClassName(t *testing.T) { utils := NewClassUtils() tests := []struct { classID int8 format string expected string }{ {ClassWarrior, "display", DisplayNameWarrior}, {ClassWarrior, "friendly", DisplayNameWarrior}, {ClassWarrior, "upper", ClassNameWarrior}, {ClassWarrior, "lowercase", strings.ToLower(ClassNameWarrior)}, {ClassWarrior, "default", DisplayNameWarrior}, {ClassWarrior, "", DisplayNameWarrior}, } for _, tt := range tests { t.Run("FormatClassName", func(t *testing.T) { result := utils.FormatClassName(tt.classID, tt.format) if result != tt.expected { t.Errorf("FormatClassName(%d, %s) = %s, want %s", tt.classID, tt.format, result, tt.expected) } }) } } func TestGetRandomClassByType(t *testing.T) { utils := NewClassUtils() // Test adventure classes adventureClass := utils.GetRandomClassByType(ClassTypeAdventure) classes := GetGlobalClasses() if !classes.IsAdventureClass(adventureClass) { t.Errorf("GetRandomClassByType(adventure) returned non-adventure class: %d", adventureClass) } // Test tradeskill classes tradeskillClass := utils.GetRandomClassByType(ClassTypeTradeskill) if !classes.IsTradeskillClass(tradeskillClass) { t.Errorf("GetRandomClassByType(tradeskill) returned non-tradeskill class: %d", tradeskillClass) } // Test invalid type invalidTypeClass := utils.GetRandomClassByType("invalid") if invalidTypeClass != DefaultClassID { t.Errorf("GetRandomClassByType(invalid) should return DefaultClassID, got %d", invalidTypeClass) } } func TestGetRandomAdventureClass(t *testing.T) { utils := NewClassUtils() classes := GetGlobalClasses() for i := 0; i < 10; i++ { adventureClass := utils.GetRandomAdventureClass() if !classes.IsAdventureClass(adventureClass) { t.Errorf("GetRandomAdventureClass() returned non-adventure class: %d", adventureClass) } } } func TestGetRandomTradeskillClass(t *testing.T) { utils := NewClassUtils() classes := GetGlobalClasses() for i := 0; i < 10; i++ { tradeskillClass := utils.GetRandomTradeskillClass() if !classes.IsTradeskillClass(tradeskillClass) { t.Errorf("GetRandomTradeskillClass() returned non-tradeskill class: %d", tradeskillClass) } } } func TestValidateClassForRace(t *testing.T) { utils := NewClassUtils() // Currently all classes are valid for all races if !utils.ValidateClassForRace(ClassWarrior, 1) { t.Error("ValidateClassForRace should return true for valid class") } if utils.ValidateClassForRace(-1, 1) { t.Error("ValidateClassForRace should return false for invalid class") } } func TestGetClassDescription(t *testing.T) { utils := NewClassUtils() description := utils.GetClassDescription(ClassWarrior) if description == "" { t.Error("GetClassDescription should return non-empty description for Warrior") } if !strings.Contains(strings.ToLower(description), "weapon") { t.Error("Warrior description should contain 'weapon'") } unknownDescription := utils.GetClassDescription(-1) if unknownDescription == "" { t.Error("GetClassDescription should return default description for invalid class") } } func TestGetClassProgression(t *testing.T) { utils := NewClassUtils() // Test final class progression guardianProgression := utils.GetClassProgression(ClassGuardian) expected := []int8{ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian} if len(guardianProgression) != len(expected) { t.Errorf("Guardian progression length = %d, want %d", len(guardianProgression), len(expected)) } for i, classID := range expected { if i < len(guardianProgression) && guardianProgression[i] != classID { t.Errorf("Guardian progression[%d] = %d, want %d", i, guardianProgression[i], classID) } } // Test base class progression (should be shorter) fighterProgression := utils.GetClassProgression(ClassFighter) if len(fighterProgression) >= len(guardianProgression) { t.Error("Fighter progression should be shorter than Guardian progression") } // Test commoner progression commonerProgression := utils.GetClassProgression(ClassCommoner) if len(commonerProgression) != 1 || commonerProgression[0] != ClassCommoner { t.Error("Commoner progression should only contain Commoner") } } func TestGetClassesForBaseClass(t *testing.T) { utils := NewClassUtils() fighterClasses := utils.GetClassesForBaseClass(ClassFighter) if len(fighterClasses) == 0 { t.Error("Should find fighter classes") } // Verify all returned classes have Fighter as base class classes := GetGlobalClasses() for _, classID := range fighterClasses { if classes.GetBaseClass(classID) != ClassFighter { t.Errorf("Class %d should have Fighter as base class", classID) } } } func TestGetClassesBySecondaryBase(t *testing.T) { utils := NewClassUtils() warriorClasses := utils.GetClassesBySecondaryBase(ClassWarrior) if len(warriorClasses) == 0 { t.Error("Should find classes with Warrior as secondary base") } // Verify all returned classes have Warrior as secondary base class classes := GetGlobalClasses() for _, classID := range warriorClasses { if classes.GetSecondaryBaseClass(classID) != ClassWarrior { t.Errorf("Class %d should have Warrior as secondary base class", classID) } } } func TestGetClassesByPattern(t *testing.T) { utils := NewClassUtils() // Search for classes containing "war" warClasses := utils.GetClassesByPattern("war") if len(warClasses) == 0 { t.Error("Should find classes matching 'war'") } // Verify matches classes := GetGlobalClasses() for _, classID := range warClasses { displayName := classes.GetClassNameCase(classID) if !strings.Contains(strings.ToLower(displayName), "war") { t.Errorf("Class %s should contain 'war'", displayName) } } // Test case insensitive search warClassesUpper := utils.GetClassesByPattern("WAR") if len(warClassesUpper) != len(warClasses) { t.Error("Pattern search should be case insensitive") } } func TestValidateClassTransition(t *testing.T) { utils := NewClassUtils() tests := []struct { name string fromClass int8 toClass int8 shouldAllow bool }{ {"Commoner to Fighter", ClassCommoner, ClassFighter, true}, {"Fighter to Warrior", ClassFighter, ClassWarrior, true}, {"Warrior to Guardian", ClassWarrior, ClassGuardian, true}, {"Same class", ClassWarrior, ClassWarrior, false}, {"Regression", ClassGuardian, ClassWarrior, false}, {"Invalid from", -1, ClassWarrior, false}, {"Invalid to", ClassWarrior, -1, false}, {"Incompatible paths", ClassWarrior, ClassCleric, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { valid, _ := utils.ValidateClassTransition(tt.fromClass, tt.toClass) if valid != tt.shouldAllow { t.Errorf("ValidateClassTransition(%d, %d) = %t, want %t", tt.fromClass, tt.toClass, valid, tt.shouldAllow) } }) } } func TestGetClassAliases(t *testing.T) { utils := NewClassUtils() // Test class with known aliases skAliases := utils.GetClassAliases(ClassShadowknight) if len(skAliases) == 0 { t.Error("Shadowknight should have aliases") } // Should contain the alias "SK" foundSK := false for _, alias := range skAliases { if alias == "SK" { foundSK = true break } } if !foundSK { t.Error("Shadowknight aliases should include 'SK'") } // Test class without specific aliases commonerAliases := utils.GetClassAliases(ClassCommoner) if len(commonerAliases) == 0 { t.Error("All classes should have at least their official names as aliases") } } func TestGetClassStatistics(t *testing.T) { utils := NewClassUtils() stats := utils.GetClassStatistics() if stats["total_classes"] == nil { t.Error("Statistics should include total_classes") } if stats["adventure_classes"] == nil { t.Error("Statistics should include adventure_classes") } if stats["tradeskill_classes"] == nil { t.Error("Statistics should include tradeskill_classes") } totalClasses := stats["total_classes"].(int) adventureClasses := stats["adventure_classes"].(int) tradeskillClasses := stats["tradeskill_classes"].(int) if totalClasses != adventureClasses + tradeskillClasses { t.Errorf("Total classes (%d) should equal adventure (%d) + tradeskill (%d)", totalClasses, adventureClasses, tradeskillClasses) } } func TestFormatClassList(t *testing.T) { utils := NewClassUtils() classIDs := []int8{ClassWarrior, ClassCleric, ClassWizard} result := utils.FormatClassList(classIDs, ", ") expected := "Warrior, Cleric, Wizard" if result != expected { t.Errorf("FormatClassList() = %s, want %s", result, expected) } // Test empty list emptyResult := utils.FormatClassList([]int8{}, ", ") if emptyResult != "" { t.Error("FormatClassList with empty list should return empty string") } } func TestGetEQClassName(t *testing.T) { utils := NewClassUtils() // Currently just returns display name result := utils.GetEQClassName(ClassWarrior, 50) expected := DisplayNameWarrior if result != expected { t.Errorf("GetEQClassName() = %s, want %s", result, expected) } } func TestGetStartingClass(t *testing.T) { utils := NewClassUtils() startingClass := utils.GetStartingClass() if startingClass != ClassCommoner { t.Errorf("GetStartingClass() = %d, want %d", startingClass, ClassCommoner) } } func TestIsBaseClass(t *testing.T) { utils := NewClassUtils() tests := []struct { classID int8 isBase bool }{ {ClassFighter, true}, {ClassPriest, true}, {ClassMage, true}, {ClassScout, true}, {ClassWarrior, false}, {ClassCleric, false}, {ClassCommoner, false}, } for _, tt := range tests { t.Run("IsBaseClass", func(t *testing.T) { result := utils.IsBaseClass(tt.classID) if result != tt.isBase { t.Errorf("IsBaseClass(%d) = %t, want %t", tt.classID, result, tt.isBase) } }) } } func TestIsSecondaryBaseClass(t *testing.T) { utils := NewClassUtils() // Warrior is a secondary base class (for Guardian, Berserker) if !utils.IsSecondaryBaseClass(ClassWarrior) { t.Error("Warrior should be a secondary base class") } // Guardian is not a secondary base class if utils.IsSecondaryBaseClass(ClassGuardian) { t.Error("Guardian should not be a secondary base class") } } // ClassIntegration tests func TestNewClassIntegration(t *testing.T) { integration := NewClassIntegration() if integration == nil { t.Fatal("NewClassIntegration returned nil") } if integration.classes == nil { t.Error("ClassIntegration should have classes instance") } if integration.utils == nil { t.Error("ClassIntegration should have utils instance") } } func TestValidateEntityClass(t *testing.T) { integration := NewClassIntegration() // Test valid entity validEntity := NewMockClassAware(ClassWarrior) valid, message, info := integration.ValidateEntityClass(validEntity) if !valid { t.Error("ValidateEntityClass should return true for valid class") } if message != "Valid class" { t.Errorf("ValidateEntityClass message = %s, want 'Valid class'", message) } if info == nil { t.Error("ValidateEntityClass should return class info") } // Test invalid entity invalidEntity := NewMockClassAware(-1) valid, message, info = integration.ValidateEntityClass(invalidEntity) if valid { t.Error("ValidateEntityClass should return false for invalid class") } if info != nil { t.Error("ValidateEntityClass should return nil info for invalid class") } } func TestGetEntityClassInfo(t *testing.T) { integration := NewClassIntegration() entity := NewMockEntityWithClass(100, "TestEntity", 25, ClassWarrior) info := integration.GetEntityClassInfo(entity) if info["entity_id"] != int32(100) { t.Error("GetEntityClassInfo should include entity_id") } if info["entity_name"] != "TestEntity" { t.Error("GetEntityClassInfo should include entity_name") } if info["entity_level"] != int8(25) { t.Error("GetEntityClassInfo should include entity_level") } if info["class"] == nil { t.Error("GetEntityClassInfo should include class information") } if info["description"] == nil { t.Error("GetEntityClassInfo should include description") } } func TestChangeEntityClass(t *testing.T) { integration := NewClassIntegration() entity := NewMockClassAware(ClassCommoner) // Valid class change err := integration.ChangeEntityClass(entity, ClassFighter) if err != nil { t.Errorf("ChangeEntityClass should allow valid transition: %v", err) } if entity.GetClass() != ClassFighter { t.Error("Entity class should be updated after successful change") } // Invalid class change err = integration.ChangeEntityClass(entity, -1) if err == nil { t.Error("ChangeEntityClass should reject invalid class") } // Should not change class on error if entity.GetClass() != ClassFighter { t.Error("Entity class should not change on failed transition") } } func TestGetRandomClassForEntity(t *testing.T) { integration := NewClassIntegration() classes := GetGlobalClasses() adventureClass := integration.GetRandomClassForEntity(ClassTypeAdventure) if !classes.IsAdventureClass(adventureClass) { t.Error("GetRandomClassForEntity should return adventure class when requested") } tradeskillClass := integration.GetRandomClassForEntity(ClassTypeTradeskill) if !classes.IsTradeskillClass(tradeskillClass) { t.Error("GetRandomClassForEntity should return tradeskill class when requested") } } func TestCheckClassCompatibility(t *testing.T) { integration := NewClassIntegration() entity1 := NewMockClassAware(ClassWarrior) entity2 := NewMockClassAware(ClassCleric) // Currently all valid classes are compatible if !integration.CheckClassCompatibility(entity1, entity2) { t.Error("CheckClassCompatibility should return true for valid classes") } // Same class should be compatible entity3 := NewMockClassAware(ClassWarrior) if !integration.CheckClassCompatibility(entity1, entity3) { t.Error("CheckClassCompatibility should return true for same classes") } // Invalid class should not be compatible invalidEntity := NewMockClassAware(-1) if integration.CheckClassCompatibility(entity1, invalidEntity) { t.Error("CheckClassCompatibility should return false for invalid class") } } func TestFormatEntityClass(t *testing.T) { integration := NewClassIntegration() entity := NewMockEntityWithClass(100, "TestEntity", 25, ClassWarrior) // Test default format result := integration.FormatEntityClass(entity, "display") expected := DisplayNameWarrior if result != expected { t.Errorf("FormatEntityClass() = %s, want %s", result, expected) } // Test EQ format eqResult := integration.FormatEntityClass(entity, "eq") if eqResult == "" { t.Error("FormatEntityClass with eq format should not be empty") } } func TestGetEntityBaseClass(t *testing.T) { integration := NewClassIntegration() entity := NewMockClassAware(ClassWarrior) baseClass := integration.GetEntityBaseClass(entity) if baseClass != ClassFighter { t.Errorf("GetEntityBaseClass() = %d, want %d", baseClass, ClassFighter) } } func TestGetEntitySecondaryBaseClass(t *testing.T) { integration := NewClassIntegration() entity := NewMockClassAware(ClassGuardian) secondaryBase := integration.GetEntitySecondaryBaseClass(entity) if secondaryBase != ClassWarrior { t.Errorf("GetEntitySecondaryBaseClass() = %d, want %d", secondaryBase, ClassWarrior) } } func TestIsEntityAdventureClass(t *testing.T) { integration := NewClassIntegration() adventureEntity := NewMockClassAware(ClassWarrior) if !integration.IsEntityAdventureClass(adventureEntity) { t.Error("IsEntityAdventureClass should return true for adventure class") } tradeskillEntity := NewMockClassAware(ClassArmorer) if integration.IsEntityAdventureClass(tradeskillEntity) { t.Error("IsEntityAdventureClass should return false for tradeskill class") } } func TestIsEntityTradeskillClass(t *testing.T) { integration := NewClassIntegration() tradeskillEntity := NewMockClassAware(ClassArmorer) if !integration.IsEntityTradeskillClass(tradeskillEntity) { t.Error("IsEntityTradeskillClass should return true for tradeskill class") } adventureEntity := NewMockClassAware(ClassWarrior) if integration.IsEntityTradeskillClass(adventureEntity) { t.Error("IsEntityTradeskillClass should return false for adventure class") } } func TestGetEntitiesByClass(t *testing.T) { integration := NewClassIntegration() entities := []ClassAware{ NewMockClassAware(ClassWarrior), NewMockClassAware(ClassCleric), NewMockClassAware(ClassWarrior), NewMockClassAware(ClassWizard), } warriors := integration.GetEntitiesByClass(entities, ClassWarrior) if len(warriors) != 2 { t.Errorf("GetEntitiesByClass should find 2 warriors, found %d", len(warriors)) } clerics := integration.GetEntitiesByClass(entities, ClassCleric) if len(clerics) != 1 { t.Errorf("GetEntitiesByClass should find 1 cleric, found %d", len(clerics)) } } func TestGetEntitiesByBaseClass(t *testing.T) { integration := NewClassIntegration() entities := []ClassAware{ NewMockClassAware(ClassWarrior), // Fighter base NewMockClassAware(ClassGuardian), // Fighter base NewMockClassAware(ClassCleric), // Priest base NewMockClassAware(ClassWizard), // Mage base } fighters := integration.GetEntitiesByBaseClass(entities, ClassFighter) if len(fighters) != 2 { t.Errorf("GetEntitiesByBaseClass should find 2 fighters, found %d", len(fighters)) } } func TestGetEntitiesByClassType(t *testing.T) { integration := NewClassIntegration() entities := []ClassAware{ NewMockClassAware(ClassWarrior), // Adventure NewMockClassAware(ClassCleric), // Adventure NewMockClassAware(ClassArmorer), // Tradeskill NewMockClassAware(ClassAlchemist), // Tradeskill } adventurers := integration.GetEntitiesByClassType(entities, ClassTypeAdventure) if len(adventurers) != 2 { t.Errorf("GetEntitiesByClassType should find 2 adventure classes, found %d", len(adventurers)) } crafters := integration.GetEntitiesByClassType(entities, ClassTypeTradeskill) if len(crafters) != 2 { t.Errorf("GetEntitiesByClassType should find 2 tradeskill classes, found %d", len(crafters)) } } func TestIntegrationValidateClassForRace(t *testing.T) { integration := NewClassIntegration() // Valid class and race valid, message := integration.ValidateClassForRace(ClassWarrior, 1) if !valid { t.Error("ValidateClassForRace should return true for valid class") } if message != "" { t.Error("ValidateClassForRace should return empty message for valid combination") } // Invalid class valid, message = integration.ValidateClassForRace(-1, 1) if valid { t.Error("ValidateClassForRace should return false for invalid class") } if message == "" { t.Error("ValidateClassForRace should return error message for invalid class") } } func TestGetClassStartingStats(t *testing.T) { integration := NewClassIntegration() // Test fighter class stats fighterStats := integration.GetClassStartingStats(ClassWarrior) if len(fighterStats) == 0 { t.Error("GetClassStartingStats should return stats") } // Fighters should have bonus strength if fighterStats["strength"] <= 50 { t.Error("Fighters should have strength bonus") } // Test priest class stats priestStats := integration.GetClassStartingStats(ClassCleric) if priestStats["wisdom"] <= 50 { t.Error("Priests should have wisdom bonus") } // Test mage class stats mageStats := integration.GetClassStartingStats(ClassWizard) if mageStats["intelligence"] <= 50 { t.Error("Mages should have intelligence bonus") } // Test scout class stats scoutStats := integration.GetClassStartingStats(ClassRanger) if scoutStats["agility"] <= 50 { t.Error("Scouts should have agility bonus") } } func TestCreateClassSpecificEntity(t *testing.T) { integration := NewClassIntegration() entityData := integration.CreateClassSpecificEntity(ClassWarrior) if entityData == nil { t.Fatal("CreateClassSpecificEntity should not return nil for valid class") } if entityData["class_id"] != ClassWarrior { t.Errorf("Entity data class_id = %v, want %v", entityData["class_id"], ClassWarrior) } if entityData["class_name"] != DisplayNameWarrior { t.Error("Entity data should include correct class_name") } if entityData["starting_stats"] == nil { t.Error("Entity data should include starting_stats") } if entityData["progression"] == nil { t.Error("Entity data should include progression") } // Test invalid class invalidData := integration.CreateClassSpecificEntity(-1) if invalidData != nil { t.Error("CreateClassSpecificEntity should return nil for invalid class") } } func TestGetClassSelectionData(t *testing.T) { integration := NewClassIntegration() selectionData := integration.GetClassSelectionData() if selectionData == nil { t.Fatal("GetClassSelectionData should not return nil") } if selectionData["adventure_classes"] == nil { t.Error("Selection data should include adventure_classes") } if selectionData["statistics"] == nil { t.Error("Selection data should include statistics") } adventureClasses := selectionData["adventure_classes"].([]map[string]interface{}) if len(adventureClasses) == 0 { t.Error("Selection data should include adventure classes") } // Verify no tradeskill classes in selection for _, classData := range adventureClasses { classType := classData["type"].(string) if classType != ClassTypeAdventure { t.Error("Class selection should only include adventure classes") } } } func TestGetGlobalClassIntegration(t *testing.T) { integration1 := GetGlobalClassIntegration() integration2 := GetGlobalClassIntegration() if integration1 != integration2 { t.Error("GetGlobalClassIntegration should return the same instance") } if integration1 == nil { t.Error("GetGlobalClassIntegration should not return nil") } } // ClassManager tests func TestNewClassManager(t *testing.T) { manager := NewClassManager() if manager == nil { t.Fatal("NewClassManager returned nil") } if manager.classes == nil { t.Error("ClassManager should have classes instance") } if manager.utils == nil { t.Error("ClassManager should have utils instance") } if manager.integration == nil { t.Error("ClassManager should have integration instance") } if manager.classUsageStats == nil { t.Error("ClassManager should initialize usage stats") } } func TestRegisterClassUsage(t *testing.T) { manager := NewClassManager() // Register usage for valid class manager.RegisterClassUsage(ClassWarrior) stats := manager.GetClassUsageStats() if stats[ClassWarrior] != 1 { t.Errorf("Usage count for Warrior should be 1, got %d", stats[ClassWarrior]) } // Register more usage manager.RegisterClassUsage(ClassWarrior) manager.RegisterClassUsage(ClassCleric) stats = manager.GetClassUsageStats() if stats[ClassWarrior] != 2 { t.Errorf("Usage count for Warrior should be 2, got %d", stats[ClassWarrior]) } if stats[ClassCleric] != 1 { t.Errorf("Usage count for Cleric should be 1, got %d", stats[ClassCleric]) } // Register usage for invalid class (should be ignored) manager.RegisterClassUsage(-1) stats = manager.GetClassUsageStats() if _, exists := stats[-1]; exists { t.Error("Invalid class usage should not be registered") } } func TestGetMostPopularClass(t *testing.T) { manager := NewClassManager() // No usage yet mostPopular, maxUsage := manager.GetMostPopularClass() if mostPopular != -1 || maxUsage != 0 { t.Error("Most popular class should be -1 with 0 usage when no usage recorded") } // Register some usage manager.RegisterClassUsage(ClassWarrior) manager.RegisterClassUsage(ClassWarrior) manager.RegisterClassUsage(ClassCleric) mostPopular, maxUsage = manager.GetMostPopularClass() if mostPopular != ClassWarrior || maxUsage != 2 { t.Errorf("Most popular should be Warrior with 2 usage, got class %d with %d usage", mostPopular, maxUsage) } } func TestGetLeastPopularClass(t *testing.T) { manager := NewClassManager() // No usage yet leastPopular, minUsage := manager.GetLeastPopularClass() if leastPopular != -1 || minUsage != -1 { t.Error("Least popular class should be -1 with -1 usage when no usage recorded") } // Register some usage manager.RegisterClassUsage(ClassWarrior) manager.RegisterClassUsage(ClassWarrior) manager.RegisterClassUsage(ClassCleric) leastPopular, minUsage = manager.GetLeastPopularClass() if leastPopular != ClassCleric || minUsage != 1 { t.Errorf("Least popular should be Cleric with 1 usage, got class %d with %d usage", leastPopular, minUsage) } } func TestResetUsageStats(t *testing.T) { manager := NewClassManager() // Register some usage manager.RegisterClassUsage(ClassWarrior) manager.RegisterClassUsage(ClassCleric) stats := manager.GetClassUsageStats() if len(stats) == 0 { t.Error("Should have usage stats before reset") } // Reset stats manager.ResetUsageStats() stats = manager.GetClassUsageStats() if len(stats) != 0 { t.Error("Usage stats should be empty after reset") } } func TestProcessClassCommand(t *testing.T) { manager := NewClassManager() // Test list command result, err := manager.ProcessClassCommand("list", []string{}) if err != nil { t.Errorf("List command should not error: %v", err) } if result == "" { t.Error("List command should return results") } // Test info command with valid class result, err = manager.ProcessClassCommand("info", []string{"WARRIOR"}) if err != nil { t.Errorf("Info command should not error: %v", err) } if result == "" { t.Error("Info command should return results") } // Test info command without args _, err = manager.ProcessClassCommand("info", []string{}) if err == nil { t.Error("Info command without args should error") } // Test random command result, err = manager.ProcessClassCommand("random", []string{}) if err != nil { t.Errorf("Random command should not error: %v", err) } if result == "" { t.Error("Random command should return results") } // Test stats command result, err = manager.ProcessClassCommand("stats", []string{}) if err != nil { t.Errorf("Stats command should not error: %v", err) } if result == "" { t.Error("Stats command should return results") } // Test search command with pattern result, err = manager.ProcessClassCommand("search", []string{"war"}) if err != nil { t.Errorf("Search command should not error: %v", err) } if result == "" { t.Error("Search command should return results") } // Test search command without args _, err = manager.ProcessClassCommand("search", []string{}) if err == nil { t.Error("Search command without args should error") } // Test progression command result, err = manager.ProcessClassCommand("progression", []string{"GUARDIAN"}) if err != nil { t.Errorf("Progression command should not error: %v", err) } if result == "" { t.Error("Progression command should return results") } // Test unknown command _, err = manager.ProcessClassCommand("unknown", []string{}) if err == nil { t.Error("Unknown command should error") } } func TestValidateEntityClasses(t *testing.T) { manager := NewClassManager() entities := []ClassAware{ NewMockClassAware(ClassWarrior), NewMockClassAware(ClassCleric), NewMockClassAware(-1), // Invalid NewMockClassAware(ClassWizard), NewMockClassAware(100), // Invalid } results := manager.ValidateEntityClasses(entities) if results["total_entities"] != len(entities) { t.Error("Validation results should include total entity count") } if results["valid_count"] != 3 { t.Errorf("Should find 3 valid entities, found %v", results["valid_count"]) } if results["invalid_count"] != 2 { t.Errorf("Should find 2 invalid entities, found %v", results["invalid_count"]) } if results["class_distribution"] == nil { t.Error("Validation results should include class distribution") } if results["invalid_entities"] == nil { t.Error("Validation results should include invalid entities list") } } func TestGetClassRecommendations(t *testing.T) { manager := NewClassManager() // Test recommendations by class type preferences := map[string]interface{}{ "class_type": ClassTypeAdventure, } recommendations := manager.GetClassRecommendations(preferences) if len(recommendations) == 0 { t.Error("Should get recommendations for adventure classes") } classes := GetGlobalClasses() for _, classID := range recommendations { if !classes.IsAdventureClass(classID) { t.Error("Adventure class recommendations should only include adventure classes") } } // Test recommendations by base class basePreferences := map[string]interface{}{ "base_class": ClassFighter, } baseRecommendations := manager.GetClassRecommendations(basePreferences) if len(baseRecommendations) == 0 { t.Error("Should get recommendations for fighter base class") } // Test recommendations by preferred stats statPreferences := map[string]interface{}{ "preferred_stats": []string{"strength", "stamina"}, } statRecommendations := manager.GetClassRecommendations(statPreferences) if len(statRecommendations) == 0 { t.Error("Should get recommendations for preferred stats") } // Test empty preferences (should get defaults) emptyPreferences := map[string]interface{}{} defaultRecommendations := manager.GetClassRecommendations(emptyPreferences) if len(defaultRecommendations) == 0 { t.Error("Should get default recommendations when no preferences given") } } func TestGetGlobalClassManager(t *testing.T) { manager1 := GetGlobalClassManager() manager2 := GetGlobalClassManager() if manager1 != manager2 { t.Error("GetGlobalClassManager should return the same instance (singleton)") } if manager1 == nil { t.Error("GetGlobalClassManager should not return nil") } } // Constants tests func TestClassConstants(t *testing.T) { // Test class ID constants if ClassCommoner != 0 { t.Errorf("ClassCommoner = %d, want 0", ClassCommoner) } if ClassFighter != 1 { t.Errorf("ClassFighter = %d, want 1", ClassFighter) } if ClassAlchemist != 57 { t.Errorf("ClassAlchemist = %d, want 57", ClassAlchemist) } // Test validation constants if MinClassID != 0 { t.Errorf("MinClassID = %d, want 0", MinClassID) } if MaxClassID != 57 { t.Errorf("MaxClassID = %d, want 57", MaxClassID) } if MaxClasses != 58 { t.Errorf("MaxClasses = %d, want 58", MaxClasses) } // Test class type constants if ClassTypeAdventure != "adventure" { t.Errorf("ClassTypeAdventure = %s, want adventure", ClassTypeAdventure) } if ClassTypeTradeskill != "tradeskill" { t.Errorf("ClassTypeTradeskill = %s, want tradeskill", ClassTypeTradeskill) } } // Concurrency tests func TestClassesConcurrency(t *testing.T) { classes := NewClasses() var wg sync.WaitGroup // Concurrent reads for i := 0; i < 100; i++ { wg.Add(1) go func(classID int8) { defer wg.Done() classes.GetClassName(classID) classes.GetClassNameCase(classID) classes.GetBaseClass(classID) classes.IsValidClassID(classID) classes.GetAllClasses() }(int8(i % MaxClasses)) } wg.Wait() // Verify state consistency after concurrent access allClasses := classes.GetAllClasses() if len(allClasses) != MaxClasses { t.Error("Concurrent access should not affect class data integrity") } } func TestClassManagerConcurrency(t *testing.T) { manager := NewClassManager() var wg sync.WaitGroup // Concurrent usage registration for i := 0; i < 100; i++ { wg.Add(1) go func(classID int8) { defer wg.Done() manager.RegisterClassUsage(classID) }(int8(i % 10)) // Use first 10 classes } // Concurrent stats reading for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() manager.GetClassUsageStats() manager.GetMostPopularClass() manager.GetLeastPopularClass() }() } wg.Wait() // Verify final state stats := manager.GetClassUsageStats() totalUsage := int32(0) for _, count := range stats { totalUsage += count } if totalUsage != 100 { t.Errorf("Total usage should be 100, got %d", totalUsage) } } func TestMockEntitiesConcurrency(t *testing.T) { entity := NewMockClassAware(ClassWarrior) var wg sync.WaitGroup // Concurrent reads and writes for i := 0; i < 100; i++ { wg.Add(1) go func(classID int8) { defer wg.Done() entity.SetClass(classID) entity.GetClass() }(int8(i % MaxClasses)) } wg.Wait() // Entity should have a valid class after concurrent access finalClass := entity.GetClass() classes := GetGlobalClasses() if !classes.IsValidClassID(finalClass) { t.Errorf("Final class %d should be valid after concurrent access", finalClass) } } // Benchmarks func BenchmarkGetClassID(b *testing.B) { classes := NewClasses() b.ResetTimer() for i := 0; i < b.N; i++ { classes.GetClassID("WARRIOR") } } func BenchmarkGetClassName(b *testing.B) { classes := NewClasses() b.ResetTimer() for i := 0; i < b.N; i++ { classes.GetClassName(ClassWarrior) } } func BenchmarkGetBaseClass(b *testing.B) { classes := NewClasses() b.ResetTimer() for i := 0; i < b.N; i++ { classes.GetBaseClass(ClassGuardian) } } func BenchmarkGetAllClasses(b *testing.B) { classes := NewClasses() b.ResetTimer() for i := 0; i < b.N; i++ { classes.GetAllClasses() } } func BenchmarkParseClassName(b *testing.B) { utils := NewClassUtils() b.ResetTimer() for i := 0; i < b.N; i++ { utils.ParseClassName("WARRIOR") } } func BenchmarkGetRandomClassByType(b *testing.B) { utils := NewClassUtils() rand.Seed(time.Now().UnixNano()) b.ResetTimer() for i := 0; i < b.N; i++ { utils.GetRandomClassByType(ClassTypeAdventure) } } func BenchmarkRegisterClassUsage(b *testing.B) { manager := NewClassManager() b.ResetTimer() for i := 0; i < b.N; i++ { manager.RegisterClassUsage(ClassWarrior) } } func BenchmarkValidateEntityClass(b *testing.B) { integration := NewClassIntegration() entity := NewMockClassAware(ClassWarrior) b.ResetTimer() for i := 0; i < b.N; i++ { integration.ValidateEntityClass(entity) } } func BenchmarkMockClassAwareGetSet(b *testing.B) { entity := NewMockClassAware(ClassWarrior) b.ResetTimer() for i := 0; i < b.N; i++ { entity.SetClass(ClassCleric) entity.GetClass() } }