eq2go/internal/classes/classes_test.go

1703 lines
44 KiB
Go

package classes
import (
"strings"
"sync"
"testing"
)
// 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]any)
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]any{
"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]any{
"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]any{
"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]any{}
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 := range 100 {
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 := range 100 {
wg.Add(1)
go func(classID int8) {
defer wg.Done()
manager.RegisterClassUsage(classID)
}(int8(i % 10)) // Use first 10 classes
}
// Concurrent stats reading
for range 50 {
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 := range 100 {
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()
for b.Loop() {
classes.GetClassID("WARRIOR")
}
}
func BenchmarkGetClassName(b *testing.B) {
classes := NewClasses()
for b.Loop() {
classes.GetClassName(ClassWarrior)
}
}
func BenchmarkGetBaseClass(b *testing.B) {
classes := NewClasses()
for b.Loop() {
classes.GetBaseClass(ClassGuardian)
}
}
func BenchmarkGetAllClasses(b *testing.B) {
classes := NewClasses()
for b.Loop() {
classes.GetAllClasses()
}
}
func BenchmarkParseClassName(b *testing.B) {
utils := NewClassUtils()
for b.Loop() {
utils.ParseClassName("WARRIOR")
}
}
func BenchmarkGetRandomClassByType(b *testing.B) {
utils := NewClassUtils()
for b.Loop() {
utils.GetRandomClassByType(ClassTypeAdventure)
}
}
func BenchmarkRegisterClassUsage(b *testing.B) {
manager := NewClassManager()
for b.Loop() {
manager.RegisterClassUsage(ClassWarrior)
}
}
func BenchmarkValidateEntityClass(b *testing.B) {
integration := NewClassIntegration()
entity := NewMockClassAware(ClassWarrior)
for b.Loop() {
integration.ValidateEntityClass(entity)
}
}
func BenchmarkMockClassAwareGetSet(b *testing.B) {
entity := NewMockClassAware(ClassWarrior)
for b.Loop() {
entity.SetClass(ClassCleric)
entity.GetClass()
}
}