eq2go/internal/alt_advancement/alt_advancement_test.go
2025-08-08 10:28:46 -05:00

354 lines
8.7 KiB
Go

package alt_advancement
import (
"sync"
"testing"
"eq2emu/internal/database"
)
// TestSimpleAltAdvancement tests the basic new AltAdvancement functionality
func TestSimpleAltAdvancement(t *testing.T) {
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
t.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
// Test creating a new alternate advancement
aa := New(db)
if aa == nil {
t.Fatal("New returned nil")
}
if !aa.IsNew() {
t.Error("New AA should be marked as new")
}
// Test setting values
aa.SpellID = 1001
aa.NodeID = 1001
aa.Name = "Dragon's Strength"
aa.Group = AA_CLASS
aa.RankCost = 1
aa.MaxRank = 5
if aa.GetID() != 1001 {
t.Errorf("Expected GetID() to return 1001, got %d", aa.GetID())
}
// Test validation
if !aa.IsValid() {
t.Error("AA should be valid after setting required fields")
}
// Test Clone
clone := aa.Clone()
if clone == nil {
t.Fatal("Clone returned nil")
}
if clone.NodeID != aa.NodeID {
t.Errorf("Expected clone ID %d, got %d", aa.NodeID, clone.NodeID)
}
if clone.Name != aa.Name {
t.Errorf("Expected clone name %s, got %s", aa.Name, clone.Name)
}
// Ensure clone is not the same instance
if clone == aa {
t.Error("Clone should return a different instance")
}
}
// TestMasterList tests the bespoke master list implementation
func TestMasterList(t *testing.T) {
masterList := NewMasterList()
if masterList == nil {
t.Fatal("NewMasterList returned nil")
}
if masterList.Size() != 0 {
t.Errorf("Expected size 0, got %d", masterList.Size())
}
// Create test database
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
t.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
// Create AAs for testing
aa1 := New(db)
aa1.SpellID = 1001
aa1.NodeID = 1001
aa1.Name = "Dragon's Strength"
aa1.Group = AA_CLASS
aa1.ClassReq = 1 // Fighter
aa1.MinLevel = 10
aa1.RankCost = 1
aa1.MaxRank = 5
aa2 := New(db)
aa2.SpellID = 1002
aa2.NodeID = 1002
aa2.Name = "Spell Mastery"
aa2.Group = AA_SUBCLASS
aa2.ClassReq = 2 // Mage
aa2.MinLevel = 15
aa2.RankCost = 2
aa2.MaxRank = 3
aa3 := New(db)
aa3.SpellID = 1003
aa3.NodeID = 1003
aa3.Name = "Universal Skill"
aa3.Group = AA_CLASS
aa3.ClassReq = 0 // Universal (no class requirement)
aa3.MinLevel = 5
aa3.RankCost = 1
aa3.MaxRank = 10
// Test adding
if !masterList.AddAltAdvancement(aa1) {
t.Error("Should successfully add aa1")
}
if !masterList.AddAltAdvancement(aa2) {
t.Error("Should successfully add aa2")
}
if !masterList.AddAltAdvancement(aa3) {
t.Error("Should successfully add aa3")
}
if masterList.Size() != 3 {
t.Errorf("Expected size 3, got %d", masterList.Size())
}
// Test duplicate add (should fail)
if masterList.AddAltAdvancement(aa1) {
t.Error("Should not add duplicate alternate advancement")
}
// Test retrieving
retrieved := masterList.GetAltAdvancement(1001)
if retrieved == nil {
t.Error("Should retrieve added alternate advancement")
}
if retrieved.Name != "Dragon's Strength" {
t.Errorf("Expected name 'Dragon's Strength', got '%s'", retrieved.Name)
}
// Test group filtering
classAAs := masterList.GetAltAdvancementsByGroup(AA_CLASS)
if len(classAAs) != 2 {
t.Errorf("Expected 2 AAs in Class group, got %d", len(classAAs))
}
subclassAAs := masterList.GetAltAdvancementsByGroup(AA_SUBCLASS)
if len(subclassAAs) != 1 {
t.Errorf("Expected 1 AA in Subclass group, got %d", len(subclassAAs))
}
// Test class filtering (includes universal AAs)
fighterAAs := masterList.GetAltAdvancementsByClass(1)
if len(fighterAAs) != 2 {
t.Errorf("Expected 2 AAs for Fighter (1 specific + 1 universal), got %d", len(fighterAAs))
}
mageAAs := masterList.GetAltAdvancementsByClass(2)
if len(mageAAs) != 2 {
t.Errorf("Expected 2 AAs for Mage (1 specific + 1 universal), got %d", len(mageAAs))
}
// Test level filtering
level10AAs := masterList.GetAltAdvancementsByLevel(10)
if len(level10AAs) != 2 {
t.Errorf("Expected 2 AAs available at level 10 (levels 5 and 10), got %d", len(level10AAs))
}
level20AAs := masterList.GetAltAdvancementsByLevel(20)
if len(level20AAs) != 3 {
t.Errorf("Expected 3 AAs available at level 20 (all), got %d", len(level20AAs))
}
// Test combined filtering
combined := masterList.GetAltAdvancementsByGroupAndClass(AA_CLASS, 1)
if len(combined) != 2 {
t.Errorf("Expected 2 AAs matching Class+Fighter, got %d", len(combined))
}
// Test metadata caching
groups := masterList.GetGroups()
if len(groups) != 2 {
t.Errorf("Expected 2 unique groups, got %d", len(groups))
}
classes := masterList.GetClasses()
if len(classes) != 2 {
t.Errorf("Expected 2 unique classes (1,2), got %d", len(classes))
}
// Test clone
clone := masterList.GetAltAdvancementClone(1001)
if clone == nil {
t.Error("Should return cloned alternate advancement")
}
if clone.Name != "Dragon's Strength" {
t.Errorf("Expected cloned name 'Dragon's Strength', got '%s'", clone.Name)
}
// Test GetAllAltAdvancements
allAAs := masterList.GetAllAltAdvancements()
if len(allAAs) != 3 {
t.Errorf("Expected 3 AAs in GetAll, got %d", len(allAAs))
}
// Test update
updatedAA := New(db)
updatedAA.SpellID = 1001
updatedAA.NodeID = 1001
updatedAA.Name = "Updated Strength"
updatedAA.Group = AA_SUBCLASS
updatedAA.ClassReq = 3
updatedAA.MinLevel = 20
updatedAA.RankCost = 3
updatedAA.MaxRank = 7
if err := masterList.UpdateAltAdvancement(updatedAA); err != nil {
t.Errorf("Update should succeed: %v", err)
}
// Verify update worked
retrievedUpdated := masterList.GetAltAdvancement(1001)
if retrievedUpdated.Name != "Updated Strength" {
t.Errorf("Expected updated name 'Updated Strength', got '%s'", retrievedUpdated.Name)
}
// Verify group index updated
subclassUpdatedAAs := masterList.GetAltAdvancementsByGroup(AA_SUBCLASS)
if len(subclassUpdatedAAs) != 2 {
t.Errorf("Expected 2 AAs in Subclass group after update, got %d", len(subclassUpdatedAAs))
}
// Test removal
if !masterList.RemoveAltAdvancement(1001) {
t.Error("Should successfully remove alternate advancement")
}
if masterList.Size() != 2 {
t.Errorf("Expected size 2 after removal, got %d", masterList.Size())
}
// Test clear
masterList.Clear()
if masterList.Size() != 0 {
t.Errorf("Expected size 0 after clear, got %d", masterList.Size())
}
}
// TestAltAdvancementValidation tests validation functionality
func TestAltAdvancementValidation(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
// Test valid AA
validAA := New(db)
validAA.SpellID = 100
validAA.NodeID = 100
validAA.Name = "Test AA"
validAA.RankCost = 1
validAA.MaxRank = 5
if !validAA.IsValid() {
t.Error("Valid AA should pass validation")
}
// Test invalid AA - missing name
invalidAA := New(db)
invalidAA.SpellID = 100
invalidAA.NodeID = 100
invalidAA.RankCost = 1
invalidAA.MaxRank = 5
// Name is empty
if invalidAA.IsValid() {
t.Error("Invalid AA (missing name) should fail validation")
}
}
// TestMasterListConcurrency tests thread safety of the master list
func TestMasterListConcurrency(t *testing.T) {
masterList := NewMasterList()
// Create test database
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
t.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
const numWorkers = 10
const aasPerWorker = 100
var wg sync.WaitGroup
// Concurrently add alternate advancements
wg.Add(numWorkers)
for i := 0; i < numWorkers; i++ {
go func(workerID int) {
defer wg.Done()
for j := 0; j < aasPerWorker; j++ {
aa := New(db)
aa.NodeID = int32(workerID*aasPerWorker + j + 1)
aa.SpellID = aa.NodeID
aa.Name = "Concurrent Test"
aa.Group = AA_CLASS
aa.ClassReq = int8((workerID % 3) + 1)
aa.MinLevel = int8((j % 20) + 1)
aa.RankCost = 1
aa.MaxRank = 5
masterList.AddAltAdvancement(aa)
}
}(i)
}
// Concurrently read alternate advancements
wg.Add(numWorkers)
for i := 0; i < numWorkers; i++ {
go func() {
defer wg.Done()
for j := 0; j < aasPerWorker; j++ {
// Random reads
_ = masterList.GetAltAdvancement(int32(j + 1))
_ = masterList.GetAltAdvancementsByGroup(AA_CLASS)
_ = masterList.GetAltAdvancementsByClass(1)
_ = masterList.Size()
}
}()
}
wg.Wait()
// Verify final state
expectedSize := numWorkers * aasPerWorker
if masterList.Size() != expectedSize {
t.Errorf("Expected size %d, got %d", expectedSize, masterList.Size())
}
groups := masterList.GetGroups()
if len(groups) != 1 || groups[0] != AA_CLASS {
t.Errorf("Expected 1 group 'AA_CLASS', got %v", groups)
}
classes := masterList.GetClasses()
if len(classes) != 3 {
t.Errorf("Expected 3 classes, got %d", len(classes))
}
}