354 lines
8.7 KiB
Go
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))
|
|
}
|
|
}
|