1703 lines
44 KiB
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()
|
|
}
|
|
}
|