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)) } }