eq2go/internal/classes/classes_test.go

578 lines
17 KiB
Go

package classes
import (
"strings"
"testing"
)
func TestClassBasics(t *testing.T) {
class := &Class{
ID: ClassWarrior,
Name: "WARRIOR",
DisplayName: "Warrior",
BaseClass: ClassFighter,
SecondaryBaseClass: ClassCommoner,
ClassType: ClassTypeAdventure,
IsAdventure: true,
IsTradeskill: false,
}
if class.GetID() != ClassWarrior {
t.Errorf("Expected ID %d, got %d", ClassWarrior, class.GetID())
}
if class.GetName() != "WARRIOR" {
t.Errorf("Expected name 'WARRIOR', got %s", class.GetName())
}
if class.GetDisplayName() != "Warrior" {
t.Errorf("Expected display name 'Warrior', got %s", class.GetDisplayName())
}
if class.GetBaseClass() != ClassFighter {
t.Errorf("Expected base class %d, got %d", ClassFighter, class.GetBaseClass())
}
if class.GetSecondaryBaseClass() != ClassCommoner {
t.Errorf("Expected secondary base class %d, got %d", ClassCommoner, class.GetSecondaryBaseClass())
}
if !class.IsValid() {
t.Error("Expected class to be valid")
}
// Test invalid class
invalidClass := &Class{ID: int8(99)}
if invalidClass.IsValid() {
t.Error("Expected invalid class to be invalid")
}
}
func TestManagerCreation(t *testing.T) {
manager := NewManager()
if manager == nil {
t.Fatal("Manager creation failed")
}
stats := manager.GetStatistics()
if stats["classes_loaded"].(int32) == 0 {
t.Error("Expected classes to be loaded during initialization")
}
if stats["total_classes"].(int32) != MaxClasses {
t.Errorf("Expected %d total classes, got %d", MaxClasses, stats["total_classes"])
}
t.Logf("Manager initialized with %d classes", stats["total_classes"])
}
func TestGetClassID(t *testing.T) {
manager := NewManager()
// Test valid class names (C++ API compatibility)
testCases := map[string]int8{
"WARRIOR": ClassWarrior,
"GUARDIAN": ClassGuardian,
"TEMPLAR": ClassTemplar,
"WIZARD": ClassWizard,
"RANGER": ClassRanger,
"BEASTLORD": ClassBeastlord,
"CHANNELER": ClassChanneler,
"ARTISAN": ClassArtisan,
"PROVISIONER": ClassProvisioner,
"ALCHEMIST": ClassAlchemist,
"COMMONER": ClassCommoner,
}
for name, expectedID := range testCases {
id := manager.GetClassID(name)
if id != expectedID {
t.Errorf("GetClassID('%s'): expected %d, got %d", name, expectedID, id)
}
// Test case insensitivity and whitespace handling
id = manager.GetClassID(" " + strings.ToLower(name) + " ")
if id != expectedID {
t.Errorf("GetClassID('%s') with whitespace and lowercase: expected %d, got %d", name, expectedID, id)
}
}
// Test invalid class name
invalidID := manager.GetClassID("INVALID_CLASS")
if invalidID != -1 {
t.Errorf("Expected -1 for invalid class name, got %d", invalidID)
}
// Test empty string
emptyID := manager.GetClassID("")
if emptyID != -1 {
t.Errorf("Expected -1 for empty class name, got %d", emptyID)
}
}
func TestGetClassName(t *testing.T) {
manager := NewManager()
// Test valid class IDs (C++ API compatibility)
testCases := map[int8]string{
ClassWarrior: "WARRIOR",
ClassGuardian: "GUARDIAN",
ClassTemplar: "TEMPLAR",
ClassWizard: "WIZARD",
ClassRanger: "RANGER",
ClassBeastlord: "BEASTLORD",
ClassChanneler: "CHANNELER",
ClassArtisan: "ARTISAN",
ClassProvisioner: "PROVISIONER",
ClassAlchemist: "ALCHEMIST",
ClassCommoner: "COMMONER",
}
for classID, expectedName := range testCases {
name := manager.GetClassName(classID)
if name != expectedName {
t.Errorf("GetClassName(%d): expected '%s', got '%s'", classID, expectedName, name)
}
}
// Test invalid class ID
invalidName := manager.GetClassName(int8(99))
if invalidName != "" {
t.Errorf("Expected empty string for invalid class ID, got '%s'", invalidName)
}
}
func TestGetClassNameCase(t *testing.T) {
manager := NewManager()
// Test display names (C++ API compatibility)
testCases := map[int8]string{
ClassWarrior: "Warrior",
ClassGuardian: "Guardian",
ClassTemplar: "Templar",
ClassWizard: "Wizard",
ClassRanger: "Ranger",
ClassBeastlord: "Beastlord",
ClassChanneler: "Channeler",
ClassArtisan: "Artisan",
ClassProvisioner: "Provisioner",
ClassAlchemist: "Alchemist",
ClassCommoner: "Commoner",
}
for classID, expectedDisplayName := range testCases {
displayName := manager.GetClassNameCase(classID)
if displayName != expectedDisplayName {
t.Errorf("GetClassNameCase(%d): expected '%s', got '%s'", classID, expectedDisplayName, displayName)
}
}
// Test invalid class ID
invalidDisplayName := manager.GetClassNameCase(int8(99))
if invalidDisplayName != "" {
t.Errorf("Expected empty string for invalid class ID, got '%s'", invalidDisplayName)
}
}
func TestGetBaseClass(t *testing.T) {
manager := NewManager()
// Test base class mappings (C++ API compatibility)
testCases := map[int8]int8{
// Fighter archetype
ClassWarrior: ClassFighter,
ClassGuardian: ClassFighter,
ClassBerserker: ClassFighter,
ClassMonk: ClassFighter,
ClassShadowknight: ClassFighter,
ClassPaladin: ClassFighter,
// Priest archetype
ClassCleric: ClassPriest,
ClassTemplar: ClassPriest,
ClassInquisitor: ClassPriest,
ClassWarden: ClassPriest,
ClassFury: ClassPriest,
ClassMystic: ClassPriest,
ClassDefiler: ClassPriest,
ClassChanneler: ClassPriest,
// Mage archetype
ClassSorcerer: ClassMage,
ClassWizard: ClassMage,
ClassWarlock: ClassMage,
ClassIllusionist: ClassMage,
ClassCoercer: ClassMage,
ClassConjuror: ClassMage,
ClassNecromancer: ClassMage,
// Scout archetype
ClassRogue: ClassScout,
ClassSwashbuckler: ClassScout,
ClassBrigand: ClassScout,
ClassTroubador: ClassScout,
ClassDirge: ClassScout,
ClassRanger: ClassScout,
ClassAssassin: ClassScout,
ClassBeastlord: ClassScout,
// Base classes should return themselves
ClassFighter: ClassFighter,
ClassPriest: ClassPriest,
ClassMage: ClassMage,
ClassScout: ClassScout,
ClassCommoner: ClassCommoner,
}
for classID, expectedBase := range testCases {
base := manager.GetBaseClass(classID)
if base != expectedBase {
t.Errorf("GetBaseClass(%d): expected %d, got %d", classID, expectedBase, base)
}
}
// Test invalid class ID
invalidBase := manager.GetBaseClass(int8(99))
if invalidBase != ClassCommoner {
t.Errorf("Expected %d (Commoner) for invalid class ID, got %d", ClassCommoner, invalidBase)
}
}
func TestGetSecondaryBaseClass(t *testing.T) {
manager := NewManager()
// Test secondary base class mappings (C++ API compatibility)
testCases := map[int8]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,
}
for classID, expectedSecondary := range testCases {
secondary := manager.GetSecondaryBaseClass(classID)
if secondary != expectedSecondary {
t.Errorf("GetSecondaryBaseClass(%d): expected %d, got %d", classID, expectedSecondary, secondary)
}
}
// Test base classes (should return Commoner)
baseClasses := []int8{ClassCommoner, ClassFighter, ClassPriest, ClassMage, ClassScout}
for _, classID := range baseClasses {
secondary := manager.GetSecondaryBaseClass(classID)
if secondary != ClassCommoner {
t.Errorf("GetSecondaryBaseClass(%d): expected %d (Commoner), got %d", classID, ClassCommoner, secondary)
}
}
}
func TestClassValidation(t *testing.T) {
manager := NewManager()
// Test valid class IDs
validClasses := []int8{
ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian,
ClassTemplar, ClassWizard, ClassRanger, ClassBeastlord,
ClassChanneler, ClassArtisan, ClassAlchemist,
}
for _, classID := range validClasses {
if !manager.IsValidClassID(classID) {
t.Errorf("Expected class ID %d to be valid", classID)
}
}
// Test invalid class IDs
invalidClasses := []int8{-1, -10, 58, 99, 127}
for _, classID := range invalidClasses {
if manager.IsValidClassID(classID) {
t.Errorf("Expected class ID %d to be invalid", classID)
}
}
// Test boundary conditions
if !manager.IsValidClassID(MinClassID) {
t.Errorf("Expected MinClassID (%d) to be valid", MinClassID)
}
if !manager.IsValidClassID(MaxClassID) {
t.Errorf("Expected MaxClassID (%d) to be valid", MaxClassID)
}
}
func TestClassTypeChecking(t *testing.T) {
manager := NewManager()
// Test adventure classes
adventureClasses := []int8{
ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian,
ClassTemplar, ClassWizard, ClassRanger, ClassBeastlord,
ClassChanneler,
}
for _, classID := range adventureClasses {
if !manager.IsAdventureClass(classID) {
t.Errorf("Expected class ID %d to be an adventure class", classID)
}
if manager.IsTradeskillClass(classID) {
t.Errorf("Expected class ID %d to not be a tradeskill class", classID)
}
if manager.GetClassType(classID) != ClassTypeAdventure {
t.Errorf("Expected class ID %d to have type '%s', got '%s'", classID, ClassTypeAdventure, manager.GetClassType(classID))
}
}
// Test tradeskill classes
tradeskillClasses := []int8{
ClassArtisan, ClassCraftsman, ClassProvisioner,
ClassOutfitter, ClassArmorer, ClassScholar,
ClassJeweler, ClassAlchemist,
}
for _, classID := range tradeskillClasses {
if manager.IsAdventureClass(classID) {
t.Errorf("Expected class ID %d to not be an adventure class", classID)
}
if !manager.IsTradeskillClass(classID) {
t.Errorf("Expected class ID %d to be a tradeskill class", classID)
}
if manager.GetClassType(classID) != ClassTypeTradeskill {
t.Errorf("Expected class ID %d to have type '%s', got '%s'", classID, ClassTypeTradeskill, manager.GetClassType(classID))
}
}
}
func TestClassHierarchy(t *testing.T) {
manager := NewManager()
// Test Guardian hierarchy: Guardian -> Warrior -> Fighter -> Commoner
guardianHierarchy := manager.GetClassHierarchy(ClassGuardian)
expectedGuardian := []int8{ClassGuardian, ClassWarrior, ClassFighter, ClassCommoner}
if len(guardianHierarchy) != len(expectedGuardian) {
t.Errorf("Guardian hierarchy length: expected %d, got %d", len(expectedGuardian), len(guardianHierarchy))
} else {
for i, expected := range expectedGuardian {
if i < len(guardianHierarchy) && guardianHierarchy[i] != expected {
t.Errorf("Guardian hierarchy[%d]: expected %d, got %d", i, expected, guardianHierarchy[i])
}
}
}
// Test Templar hierarchy: Templar -> Cleric -> Priest -> Commoner
templarHierarchy := manager.GetClassHierarchy(ClassTemplar)
expectedTemplar := []int8{ClassTemplar, ClassCleric, ClassPriest, ClassCommoner}
if len(templarHierarchy) != len(expectedTemplar) {
t.Errorf("Templar hierarchy length: expected %d, got %d", len(expectedTemplar), len(templarHierarchy))
} else {
for i, expected := range expectedTemplar {
if i < len(templarHierarchy) && templarHierarchy[i] != expected {
t.Errorf("Templar hierarchy[%d]: expected %d, got %d", i, expected, templarHierarchy[i])
}
}
}
// Test Commoner hierarchy: just Commoner
commonerHierarchy := manager.GetClassHierarchy(ClassCommoner)
if len(commonerHierarchy) != 1 || commonerHierarchy[0] != ClassCommoner {
t.Errorf("Commoner hierarchy: expected [%d], got %v", ClassCommoner, commonerHierarchy)
}
// Test invalid class
invalidHierarchy := manager.GetClassHierarchy(int8(99))
if invalidHierarchy != nil {
t.Errorf("Expected nil hierarchy for invalid class, got %v", invalidHierarchy)
}
}
func TestArchetypeOperations(t *testing.T) {
manager := NewManager()
// Test same archetype checking
if !manager.IsSameArchetype(ClassWarrior, ClassGuardian) {
t.Error("Expected Warrior and Guardian to be same archetype (Fighter)")
}
if !manager.IsSameArchetype(ClassTemplar, ClassMystic) {
t.Error("Expected Templar and Mystic to be same archetype (Priest)")
}
if manager.IsSameArchetype(ClassWarrior, ClassTemplar) {
t.Error("Expected Warrior and Templar to be different archetypes")
}
// Test archetype class listing
fighterClasses := manager.GetArchetypeClasses(ClassFighter)
expectedFighterCount := 10 // Including base Fighter class (Fighter, Warrior, Guardian, Berserker, Brawler, Monk, Bruiser, Crusader, Shadowknight, Paladin)
if len(fighterClasses) != expectedFighterCount {
t.Errorf("Expected %d Fighter archetype classes, got %d", expectedFighterCount, len(fighterClasses))
}
// Verify Fighter is in the list
foundFighter := false
for _, classID := range fighterClasses {
if classID == ClassFighter {
foundFighter = true
break
}
}
if !foundFighter {
t.Error("Expected Fighter base class to be in Fighter archetype list")
}
}
func TestTradeskillClassOperations(t *testing.T) {
manager := NewManager()
// Test tradeskill base class operations (C++ API compatibility)
// Note: These functions use specific C++ logic with offsets
// Test with adventure class (should return as-is for most)
warriorTS := manager.GetTSBaseClass(ClassWarrior)
if warriorTS != ClassWarrior {
t.Errorf("Expected GetTSBaseClass(%d) to return %d, got %d", ClassWarrior, ClassWarrior, warriorTS)
}
// Test secondary tradeskill operations
warriorSecondaryTS := manager.GetSecondaryTSBaseClass(ClassWarrior)
// This should follow the C++ logic with +42 offset calculations
t.Logf("GetSecondaryTSBaseClass(ClassWarrior) = %d", warriorSecondaryTS)
}
func TestManagerStatistics(t *testing.T) {
manager := NewManager()
// Perform some operations to generate statistics
manager.GetClassID("WARRIOR")
manager.GetClassName(ClassTemplar)
manager.GetClassNameCase(ClassRanger)
manager.GetBaseClass(ClassGuardian)
manager.GetClass(ClassBeastlord)
manager.GetClassID("INVALID_CLASS") // This should increment cache misses
stats := manager.GetStatistics()
// Check basic statistics
if stats["classes_loaded"].(int32) == 0 {
t.Error("Expected classes to be loaded")
}
if stats["lookups_performed"].(int32) == 0 {
t.Error("Expected lookups to be performed")
}
if stats["total_classes"].(int32) != MaxClasses {
t.Errorf("Expected %d total classes, got %d", MaxClasses, stats["total_classes"])
}
// Check adventure vs tradeskill counts
adventureCount := stats["adventure_classes"].(int32)
tradeskillCount := stats["tradeskill_classes"].(int32)
if adventureCount == 0 {
t.Error("Expected some adventure classes")
}
if tradeskillCount == 0 {
t.Error("Expected some tradeskill classes")
}
t.Logf("Statistics: %+v", stats)
}
func TestGlobalFunctions(t *testing.T) {
// Test global functions that should initialize manager automatically
// Test basic operations
id := GetClassID("WARRIOR")
if id != ClassWarrior {
t.Errorf("GetClassID('WARRIOR'): expected %d, got %d", ClassWarrior, id)
}
name := GetClassName(ClassTemplar)
if name != "TEMPLAR" {
t.Errorf("GetClassName(%d): expected 'TEMPLAR', got '%s'", ClassTemplar, name)
}
displayName := GetClassNameCase(ClassRanger)
if displayName != "Ranger" {
t.Errorf("GetClassNameCase(%d): expected 'Ranger', got '%s'", ClassRanger, displayName)
}
base := GetBaseClass(ClassGuardian)
if base != ClassFighter {
t.Errorf("GetBaseClass(%d): expected %d, got %d", ClassGuardian, ClassFighter, base)
}
secondary := GetSecondaryBaseClass(ClassGuardian)
if secondary != ClassWarrior {
t.Errorf("GetSecondaryBaseClass(%d): expected %d, got %d", ClassGuardian, ClassWarrior, secondary)
}
// Test utility functions
if !IsValidClassID(ClassBeastlord) {
t.Errorf("Expected ClassBeastlord (%d) to be valid", ClassBeastlord)
}
if !IsAdventureClass(ClassChanneler) {
t.Errorf("Expected ClassChanneler (%d) to be adventure class", ClassChanneler)
}
if !IsTradeskillClass(ClassAlchemist) {
t.Errorf("Expected ClassAlchemist (%d) to be tradeskill class", ClassAlchemist)
}
classType := GetClassType(ClassWarrior)
if classType != ClassTypeAdventure {
t.Errorf("Expected ClassWarrior type to be '%s', got '%s'", ClassTypeAdventure, classType)
}
// Test collection functions
allClasses := GetAllClasses()
if len(allClasses) != MaxClasses {
t.Errorf("Expected %d classes in GetAllClasses(), got %d", MaxClasses, len(allClasses))
}
info := GetClassInfo(ClassTemplar)
if !info["valid"].(bool) {
t.Error("Expected ClassTemplar info to be valid")
}
hierarchy := GetClassHierarchy(ClassGuardian)
if len(hierarchy) == 0 {
t.Error("Expected non-empty hierarchy for ClassGuardian")
}
if !IsSameArchetype(ClassWarrior, ClassGuardian) {
t.Error("Expected Warrior and Guardian to be same archetype")
}
fighterClasses := GetArchetypeClasses(ClassFighter)
if len(fighterClasses) == 0 {
t.Error("Expected non-empty list of Fighter archetype classes")
}
}