2996 lines
70 KiB
Go
2996 lines
70 KiB
Go
package alt_advancement
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Test AltAdvanceData structure and methods
|
|
func TestAltAdvanceData(t *testing.T) {
|
|
t.Run("Copy", func(t *testing.T) {
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Description: "Test Description",
|
|
Group: AA_CLASS,
|
|
Col: 5,
|
|
Row: 3,
|
|
Icon: 1234,
|
|
RankCost: 2,
|
|
MaxRank: 5,
|
|
MinLevel: 20,
|
|
ClassName: "Fighter",
|
|
SubclassName: "Guardian",
|
|
}
|
|
|
|
copy := aa.Copy()
|
|
if copy == aa {
|
|
t.Error("Copy should return a new instance, not the same pointer")
|
|
}
|
|
|
|
if !reflect.DeepEqual(aa, copy) {
|
|
t.Error("Copy should have identical field values")
|
|
}
|
|
|
|
// Modify original and ensure copy is unaffected
|
|
aa.Name = "Modified"
|
|
if copy.Name == "Modified" {
|
|
t.Error("Copy should not be affected by changes to original")
|
|
}
|
|
})
|
|
|
|
t.Run("IsValid", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
aa *AltAdvanceData
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Valid AA",
|
|
aa: &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Invalid - No SpellID",
|
|
aa: &AltAdvanceData{
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid - No NodeID",
|
|
aa: &AltAdvanceData{
|
|
SpellID: 100,
|
|
Name: "Test AA",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid - No Name",
|
|
aa: &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid - No MaxRank",
|
|
aa: &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
RankCost: 2,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Invalid - No RankCost",
|
|
aa: &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
MaxRank: 5,
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if result := tt.aa.IsValid(); result != tt.expected {
|
|
t.Errorf("IsValid() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test MasterAAList functionality
|
|
func TestMasterAAList(t *testing.T) {
|
|
t.Run("NewMasterAAList", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
if masterList == nil {
|
|
t.Fatal("NewMasterAAList returned nil")
|
|
}
|
|
|
|
if masterList.Size() != 0 {
|
|
t.Error("Expected empty list to have size 0")
|
|
}
|
|
|
|
if masterList.aaList == nil {
|
|
t.Error("Expected aaList to be initialized")
|
|
}
|
|
|
|
if masterList.aaBySpellID == nil || masterList.aaByNodeID == nil || masterList.aaByGroup == nil {
|
|
t.Error("Expected lookup maps to be initialized")
|
|
}
|
|
})
|
|
|
|
t.Run("AddAltAdvancement", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Test adding nil
|
|
err := masterList.AddAltAdvancement(nil)
|
|
if err == nil {
|
|
t.Error("Expected error when adding nil AA")
|
|
}
|
|
|
|
// Test adding invalid AA
|
|
invalidAA := &AltAdvanceData{SpellID: 0}
|
|
err = masterList.AddAltAdvancement(invalidAA)
|
|
if err == nil {
|
|
t.Error("Expected error when adding invalid AA")
|
|
}
|
|
|
|
// Test adding valid AA
|
|
validAA := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
err = masterList.AddAltAdvancement(validAA)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error adding valid AA: %v", err)
|
|
}
|
|
|
|
if masterList.Size() != 1 {
|
|
t.Error("Expected size to be 1 after adding AA")
|
|
}
|
|
|
|
// Test duplicate spell ID
|
|
dupSpellAA := &AltAdvanceData{
|
|
SpellID: 100, // Same spell ID
|
|
NodeID: 201,
|
|
Name: "Duplicate Spell",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
err = masterList.AddAltAdvancement(dupSpellAA)
|
|
if err == nil {
|
|
t.Error("Expected error when adding duplicate spell ID")
|
|
}
|
|
|
|
// Test duplicate node ID
|
|
dupNodeAA := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 200, // Same node ID
|
|
Name: "Duplicate Node",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
err = masterList.AddAltAdvancement(dupNodeAA)
|
|
if err == nil {
|
|
t.Error("Expected error when adding duplicate node ID")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAltAdvancement", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa)
|
|
|
|
// Test getting existing AA
|
|
retrieved := masterList.GetAltAdvancement(100)
|
|
if retrieved == nil {
|
|
t.Fatal("Expected to retrieve AA by spell ID")
|
|
}
|
|
|
|
if retrieved == aa {
|
|
t.Error("GetAltAdvancement should return a copy, not the original")
|
|
}
|
|
|
|
if retrieved.SpellID != aa.SpellID || retrieved.Name != aa.Name {
|
|
t.Error("Retrieved AA should have same data as original")
|
|
}
|
|
|
|
// Test getting non-existent AA
|
|
notFound := masterList.GetAltAdvancement(999)
|
|
if notFound != nil {
|
|
t.Error("Expected nil for non-existent spell ID")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAltAdvancementByNodeID", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa)
|
|
|
|
// Test getting existing AA
|
|
retrieved := masterList.GetAltAdvancementByNodeID(200)
|
|
if retrieved == nil {
|
|
t.Fatal("Expected to retrieve AA by node ID")
|
|
}
|
|
|
|
if retrieved.NodeID != aa.NodeID {
|
|
t.Error("Retrieved AA should have same node ID")
|
|
}
|
|
|
|
// Test getting non-existent AA
|
|
notFound := masterList.GetAltAdvancementByNodeID(999)
|
|
if notFound != nil {
|
|
t.Error("Expected nil for non-existent node ID")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAAsByGroup", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add AAs to different groups
|
|
aa1 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Class AA 1",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa2 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Class AA 2",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa3 := &AltAdvanceData{
|
|
SpellID: 102,
|
|
NodeID: 202,
|
|
Name: "Heroic AA",
|
|
Group: AA_HEROIC,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa1)
|
|
masterList.AddAltAdvancement(aa2)
|
|
masterList.AddAltAdvancement(aa3)
|
|
|
|
// Test getting AAs by group
|
|
classAAs := masterList.GetAAsByGroup(AA_CLASS)
|
|
if len(classAAs) != 2 {
|
|
t.Errorf("Expected 2 class AAs, got %d", len(classAAs))
|
|
}
|
|
|
|
heroicAAs := masterList.GetAAsByGroup(AA_HEROIC)
|
|
if len(heroicAAs) != 1 {
|
|
t.Errorf("Expected 1 heroic AA, got %d", len(heroicAAs))
|
|
}
|
|
|
|
// Test getting empty group
|
|
emptyGroup := masterList.GetAAsByGroup(AA_DRAGON)
|
|
if len(emptyGroup) != 0 {
|
|
t.Error("Expected empty slice for group with no AAs")
|
|
}
|
|
|
|
// Verify copies are returned
|
|
if &classAAs[0] == &aa1 {
|
|
t.Error("GetAAsByGroup should return copies, not originals")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAAsByClass", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add AAs with different class requirements
|
|
aaAllClasses := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "All Classes",
|
|
Group: AA_CLASS,
|
|
ClassReq: 0, // Available to all classes
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aaFighterOnly := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Fighter Only",
|
|
Group: AA_CLASS,
|
|
ClassReq: 1, // Fighter class
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aaMageOnly := &AltAdvanceData{
|
|
SpellID: 102,
|
|
NodeID: 202,
|
|
Name: "Mage Only",
|
|
Group: AA_CLASS,
|
|
ClassReq: 20, // Mage class
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aaAllClasses)
|
|
masterList.AddAltAdvancement(aaFighterOnly)
|
|
masterList.AddAltAdvancement(aaMageOnly)
|
|
|
|
// Test getting AAs for fighter class
|
|
fighterAAs := masterList.GetAAsByClass(1)
|
|
if len(fighterAAs) != 2 {
|
|
t.Errorf("Expected 2 AAs for fighter (all classes + fighter only), got %d", len(fighterAAs))
|
|
}
|
|
|
|
// Test getting AAs for mage class
|
|
mageAAs := masterList.GetAAsByClass(20)
|
|
if len(mageAAs) != 2 {
|
|
t.Errorf("Expected 2 AAs for mage (all classes + mage only), got %d", len(mageAAs))
|
|
}
|
|
|
|
// Test getting AAs for class with no specific AAs
|
|
priestAAs := masterList.GetAAsByClass(10)
|
|
if len(priestAAs) != 1 {
|
|
t.Errorf("Expected 1 AA for priest (all classes only), got %d", len(priestAAs))
|
|
}
|
|
})
|
|
|
|
t.Run("GetAAsByLevel", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add AAs with different level requirements
|
|
aaLevel10 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Level 10 AA",
|
|
Group: AA_CLASS,
|
|
MinLevel: 10,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aaLevel30 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Level 30 AA",
|
|
Group: AA_CLASS,
|
|
MinLevel: 30,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aaLevel50 := &AltAdvanceData{
|
|
SpellID: 102,
|
|
NodeID: 202,
|
|
Name: "Level 50 AA",
|
|
Group: AA_CLASS,
|
|
MinLevel: 50,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aaLevel10)
|
|
masterList.AddAltAdvancement(aaLevel30)
|
|
masterList.AddAltAdvancement(aaLevel50)
|
|
|
|
// Test getting AAs at different levels
|
|
level20AAs := masterList.GetAAsByLevel(20)
|
|
if len(level20AAs) != 1 {
|
|
t.Errorf("Expected 1 AA at level 20, got %d", len(level20AAs))
|
|
}
|
|
|
|
level40AAs := masterList.GetAAsByLevel(40)
|
|
if len(level40AAs) != 2 {
|
|
t.Errorf("Expected 2 AAs at level 40, got %d", len(level40AAs))
|
|
}
|
|
|
|
level60AAs := masterList.GetAAsByLevel(60)
|
|
if len(level60AAs) != 3 {
|
|
t.Errorf("Expected 3 AAs at level 60, got %d", len(level60AAs))
|
|
}
|
|
|
|
level5AAs := masterList.GetAAsByLevel(5)
|
|
if len(level5AAs) != 0 {
|
|
t.Errorf("Expected 0 AAs at level 5, got %d", len(level5AAs))
|
|
}
|
|
})
|
|
|
|
t.Run("SortAAsByGroup", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add AAs in random order
|
|
aa1 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "AA1",
|
|
Group: AA_CLASS,
|
|
Row: 2,
|
|
Col: 3,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa2 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "AA2",
|
|
Group: AA_CLASS,
|
|
Row: 1,
|
|
Col: 5,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa3 := &AltAdvanceData{
|
|
SpellID: 102,
|
|
NodeID: 202,
|
|
Name: "AA3",
|
|
Group: AA_CLASS,
|
|
Row: 1,
|
|
Col: 2,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa1)
|
|
masterList.AddAltAdvancement(aa2)
|
|
masterList.AddAltAdvancement(aa3)
|
|
|
|
// Sort and verify order
|
|
masterList.SortAAsByGroup()
|
|
|
|
classAAs := masterList.GetAAsByGroup(AA_CLASS)
|
|
if len(classAAs) != 3 {
|
|
t.Fatal("Expected 3 AAs")
|
|
}
|
|
|
|
// Should be sorted by row then column
|
|
// aa3 (1,2), aa2 (1,5), aa1 (2,3)
|
|
if classAAs[0].Name != "AA3" || classAAs[1].Name != "AA2" || classAAs[2].Name != "AA1" {
|
|
t.Error("AAs not sorted correctly by row and column")
|
|
}
|
|
})
|
|
|
|
t.Run("GetGroups", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add AAs to different groups
|
|
aa1 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "AA1",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa2 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "AA2",
|
|
Group: AA_HEROIC,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa3 := &AltAdvanceData{
|
|
SpellID: 102,
|
|
NodeID: 202,
|
|
Name: "AA3",
|
|
Group: AA_TRADESKILL,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa1)
|
|
masterList.AddAltAdvancement(aa2)
|
|
masterList.AddAltAdvancement(aa3)
|
|
|
|
groups := masterList.GetGroups()
|
|
if len(groups) != 3 {
|
|
t.Errorf("Expected 3 groups, got %d", len(groups))
|
|
}
|
|
|
|
// Check that all expected groups are present
|
|
groupMap := make(map[int8]bool)
|
|
for _, g := range groups {
|
|
groupMap[g] = true
|
|
}
|
|
|
|
if !groupMap[AA_CLASS] || !groupMap[AA_HEROIC] || !groupMap[AA_TRADESKILL] {
|
|
t.Error("Not all expected groups were returned")
|
|
}
|
|
})
|
|
|
|
t.Run("DestroyAltAdvancements", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add some AAs
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa)
|
|
|
|
if masterList.Size() != 1 {
|
|
t.Error("Expected size to be 1 before destroy")
|
|
}
|
|
|
|
// Destroy all AAs
|
|
masterList.DestroyAltAdvancements()
|
|
|
|
if masterList.Size() != 0 {
|
|
t.Error("Expected size to be 0 after destroy")
|
|
}
|
|
|
|
// Verify maps are cleared
|
|
if len(masterList.aaBySpellID) != 0 || len(masterList.aaByNodeID) != 0 || len(masterList.aaByGroup) != 0 {
|
|
t.Error("Expected all maps to be cleared after destroy")
|
|
}
|
|
})
|
|
|
|
t.Run("ConcurrentAccess", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add initial AAs
|
|
for i := int32(1); i <= 10; i++ {
|
|
aa := &AltAdvanceData{
|
|
SpellID: i * 100,
|
|
NodeID: i * 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
masterList.AddAltAdvancement(aa)
|
|
}
|
|
|
|
// Concurrent reads and writes
|
|
var wg sync.WaitGroup
|
|
errors := make(chan error, 100)
|
|
|
|
// Multiple readers
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < 100; j++ {
|
|
masterList.GetAltAdvancement(100)
|
|
masterList.GetAAsByGroup(AA_CLASS)
|
|
masterList.Size()
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Multiple writers (trying to add new unique AAs)
|
|
for i := 0; i < 5; i++ {
|
|
wg.Add(1)
|
|
go func(id int32) {
|
|
defer wg.Done()
|
|
aa := &AltAdvanceData{
|
|
SpellID: 1000 + id*100,
|
|
NodeID: 2000 + id*100,
|
|
Name: "Concurrent AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
if err := masterList.AddAltAdvancement(aa); err != nil {
|
|
errors <- err
|
|
}
|
|
}(int32(i))
|
|
}
|
|
|
|
wg.Wait()
|
|
close(errors)
|
|
|
|
// Check for unexpected errors
|
|
errorCount := 0
|
|
for err := range errors {
|
|
errorCount++
|
|
t.Logf("Concurrent write error (expected for duplicates): %v", err)
|
|
}
|
|
|
|
// Check that we have expected number of AAs
|
|
finalSize := masterList.Size()
|
|
if finalSize < 10 || finalSize > 15 { // 10 initial + 0-5 concurrent (some may fail due to race)
|
|
t.Errorf("Expected between 10-15 AAs after concurrent operations, got %d", finalSize)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test MasterAANodeList functionality
|
|
func TestMasterAANodeList(t *testing.T) {
|
|
t.Run("NewMasterAANodeList", func(t *testing.T) {
|
|
nodeList := NewMasterAANodeList()
|
|
if nodeList == nil {
|
|
t.Fatal("NewMasterAANodeList returned nil")
|
|
}
|
|
|
|
if nodeList.Size() != 0 {
|
|
t.Error("Expected empty list to have size 0")
|
|
}
|
|
})
|
|
|
|
t.Run("AddTreeNode", func(t *testing.T) {
|
|
nodeList := NewMasterAANodeList()
|
|
|
|
// Test adding nil
|
|
err := nodeList.AddTreeNode(nil)
|
|
if err == nil {
|
|
t.Error("Expected error when adding nil node")
|
|
}
|
|
|
|
// Test adding valid node
|
|
node := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 100,
|
|
AATreeID: 200,
|
|
}
|
|
|
|
err = nodeList.AddTreeNode(node)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error adding valid node: %v", err)
|
|
}
|
|
|
|
if nodeList.Size() != 1 {
|
|
t.Error("Expected size to be 1 after adding node")
|
|
}
|
|
|
|
// Test duplicate tree ID
|
|
dupNode := &TreeNodeData{
|
|
ClassID: 2,
|
|
TreeID: 100, // Same tree ID
|
|
AATreeID: 201,
|
|
}
|
|
|
|
err = nodeList.AddTreeNode(dupNode)
|
|
if err == nil {
|
|
t.Error("Expected error when adding duplicate tree ID")
|
|
}
|
|
})
|
|
|
|
t.Run("GetTreeNodeByTreeID", func(t *testing.T) {
|
|
nodeList := NewMasterAANodeList()
|
|
|
|
node := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 100,
|
|
AATreeID: 200,
|
|
}
|
|
|
|
nodeList.AddTreeNode(node)
|
|
|
|
// Test getting existing node
|
|
retrieved := nodeList.GetTreeNode(100)
|
|
if retrieved == nil {
|
|
t.Fatal("Expected to retrieve node by tree ID")
|
|
}
|
|
|
|
if retrieved == node {
|
|
t.Error("GetTreeNode should return a copy, not the original")
|
|
}
|
|
|
|
if retrieved.TreeID != node.TreeID {
|
|
t.Error("Retrieved node should have same tree ID")
|
|
}
|
|
|
|
// Test getting non-existent node
|
|
notFound := nodeList.GetTreeNode(999)
|
|
if notFound != nil {
|
|
t.Error("Expected nil for non-existent tree ID")
|
|
}
|
|
})
|
|
|
|
t.Run("GetTreeNodesByClass", func(t *testing.T) {
|
|
nodeList := NewMasterAANodeList()
|
|
|
|
// Add nodes for different classes
|
|
node1 := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 100,
|
|
AATreeID: 200,
|
|
}
|
|
|
|
node2 := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 101,
|
|
AATreeID: 201,
|
|
}
|
|
|
|
node3 := &TreeNodeData{
|
|
ClassID: 2,
|
|
TreeID: 102,
|
|
AATreeID: 202,
|
|
}
|
|
|
|
nodeList.AddTreeNode(node1)
|
|
nodeList.AddTreeNode(node2)
|
|
nodeList.AddTreeNode(node3)
|
|
|
|
// Test getting nodes by class
|
|
class1Nodes := nodeList.GetTreeNodesByClass(1)
|
|
if len(class1Nodes) != 2 {
|
|
t.Errorf("Expected 2 nodes for class 1, got %d", len(class1Nodes))
|
|
}
|
|
|
|
class2Nodes := nodeList.GetTreeNodesByClass(2)
|
|
if len(class2Nodes) != 1 {
|
|
t.Errorf("Expected 1 node for class 2, got %d", len(class2Nodes))
|
|
}
|
|
|
|
// Test getting empty class
|
|
emptyClass := nodeList.GetTreeNodesByClass(999)
|
|
if len(emptyClass) != 0 {
|
|
t.Error("Expected empty slice for class with no nodes")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test AATemplate functionality
|
|
func TestAATemplate(t *testing.T) {
|
|
t.Run("NewAATemplate", func(t *testing.T) {
|
|
// Test personal template
|
|
template := NewAATemplate(AA_TEMPLATE_PERSONAL_1, "Personal Template 1")
|
|
if template == nil {
|
|
t.Fatal("NewAATemplate returned nil")
|
|
}
|
|
|
|
if template.TemplateID != AA_TEMPLATE_PERSONAL_1 {
|
|
t.Error("Expected template ID to match")
|
|
}
|
|
|
|
if template.Name != "Personal Template 1" {
|
|
t.Error("Expected template name to match")
|
|
}
|
|
|
|
if !template.IsPersonal {
|
|
t.Error("Expected IsPersonal to be true for template ID 1")
|
|
}
|
|
|
|
if template.IsServer {
|
|
t.Error("Expected IsServer to be false for template ID 1")
|
|
}
|
|
|
|
if template.IsCurrent {
|
|
t.Error("Expected IsCurrent to be false for template ID 1")
|
|
}
|
|
|
|
// Test server template
|
|
serverTemplate := NewAATemplate(AA_TEMPLATE_SERVER_1, "Server Template 1")
|
|
if !serverTemplate.IsServer {
|
|
t.Error("Expected IsServer to be true for template ID 4")
|
|
}
|
|
|
|
// Test current template
|
|
currentTemplate := NewAATemplate(AA_TEMPLATE_CURRENT, "Current")
|
|
if !currentTemplate.IsCurrent {
|
|
t.Error("Expected IsCurrent to be true for template ID 7")
|
|
}
|
|
})
|
|
|
|
t.Run("AddEntry", func(t *testing.T) {
|
|
template := NewAATemplate(AA_TEMPLATE_PERSONAL_1, "Test Template")
|
|
|
|
entry := &AAEntry{
|
|
TemplateID: AA_TEMPLATE_PERSONAL_1,
|
|
TabID: AA_CLASS,
|
|
AAID: 100,
|
|
Order: 1,
|
|
TreeID: 1,
|
|
}
|
|
|
|
// Add entry
|
|
template.AddEntry(entry)
|
|
|
|
if len(template.Entries) != 1 {
|
|
t.Error("Expected 1 entry after adding")
|
|
}
|
|
|
|
if template.Entries[0] != entry {
|
|
t.Error("Expected entry to be added to template")
|
|
}
|
|
})
|
|
|
|
t.Run("GetEntry", func(t *testing.T) {
|
|
template := NewAATemplate(AA_TEMPLATE_PERSONAL_1, "Test Template")
|
|
|
|
entry1 := &AAEntry{
|
|
TemplateID: AA_TEMPLATE_PERSONAL_1,
|
|
TabID: AA_CLASS,
|
|
AAID: 100,
|
|
Order: 1,
|
|
TreeID: 1,
|
|
}
|
|
|
|
entry2 := &AAEntry{
|
|
TemplateID: AA_TEMPLATE_PERSONAL_1,
|
|
TabID: AA_CLASS,
|
|
AAID: 101,
|
|
Order: 2,
|
|
TreeID: 2,
|
|
}
|
|
|
|
template.AddEntry(entry1)
|
|
template.AddEntry(entry2)
|
|
|
|
// Test getting existing entry
|
|
found := template.GetEntry(100)
|
|
if found == nil {
|
|
t.Error("Expected to find entry with AAID 100")
|
|
}
|
|
|
|
if found != entry1 {
|
|
t.Error("Expected to get correct entry")
|
|
}
|
|
|
|
// Test getting non-existent entry
|
|
notFound := template.GetEntry(999)
|
|
if notFound != nil {
|
|
t.Error("Expected nil for non-existent AAID")
|
|
}
|
|
})
|
|
|
|
t.Run("RemoveEntry", func(t *testing.T) {
|
|
template := NewAATemplate(AA_TEMPLATE_PERSONAL_1, "Test Template")
|
|
|
|
entry := &AAEntry{
|
|
TemplateID: AA_TEMPLATE_PERSONAL_1,
|
|
TabID: AA_CLASS,
|
|
AAID: 100,
|
|
Order: 1,
|
|
TreeID: 1,
|
|
}
|
|
|
|
template.AddEntry(entry)
|
|
|
|
// Remove entry
|
|
removed := template.RemoveEntry(100)
|
|
if !removed {
|
|
t.Error("Expected RemoveEntry to return true")
|
|
}
|
|
|
|
if len(template.Entries) != 0 {
|
|
t.Error("Expected 0 entries after removing")
|
|
}
|
|
|
|
// Try to remove non-existent entry
|
|
removed = template.RemoveEntry(999)
|
|
if removed {
|
|
t.Error("Expected RemoveEntry to return false for non-existent entry")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test AAPlayerState functionality
|
|
func TestAAPlayerState(t *testing.T) {
|
|
t.Run("NewAAPlayerState", func(t *testing.T) {
|
|
playerState := NewAAPlayerState(123)
|
|
if playerState == nil {
|
|
t.Fatal("NewAAPlayerState returned nil")
|
|
}
|
|
|
|
if playerState.CharacterID != 123 {
|
|
t.Error("Expected character ID to be 123")
|
|
}
|
|
|
|
if playerState.TotalPoints != 0 {
|
|
t.Error("Expected initial total points to be 0")
|
|
}
|
|
|
|
if playerState.ActiveTemplate != AA_TEMPLATE_CURRENT {
|
|
t.Error("Expected active template to be current template")
|
|
}
|
|
|
|
if playerState.Templates == nil || playerState.Tabs == nil || playerState.AAProgress == nil {
|
|
t.Error("Expected maps to be initialized")
|
|
}
|
|
})
|
|
|
|
t.Run("AddAAProgress", func(t *testing.T) {
|
|
playerState := NewAAPlayerState(123)
|
|
|
|
progress := &PlayerAAData{
|
|
CharacterID: 123,
|
|
NodeID: 100,
|
|
CurrentRank: 1,
|
|
PointsSpent: 2,
|
|
TemplateID: AA_TEMPLATE_CURRENT,
|
|
TabID: AA_CLASS,
|
|
}
|
|
|
|
playerState.AddAAProgress(progress)
|
|
|
|
if len(playerState.AAProgress) != 1 {
|
|
t.Error("Expected 1 AA progress entry")
|
|
}
|
|
|
|
if playerState.AAProgress[100] != progress {
|
|
t.Error("Expected progress to be added correctly")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAAProgress", func(t *testing.T) {
|
|
playerState := NewAAPlayerState(123)
|
|
|
|
progress := &PlayerAAData{
|
|
CharacterID: 123,
|
|
NodeID: 100,
|
|
CurrentRank: 1,
|
|
PointsSpent: 2,
|
|
}
|
|
|
|
playerState.AddAAProgress(progress)
|
|
|
|
// Test getting existing progress
|
|
found := playerState.GetAAProgress(100)
|
|
if found == nil {
|
|
t.Error("Expected to find AA progress")
|
|
}
|
|
|
|
if found != progress {
|
|
t.Error("Expected to get correct progress")
|
|
}
|
|
|
|
// Test getting non-existent progress
|
|
notFound := playerState.GetAAProgress(999)
|
|
if notFound != nil {
|
|
t.Error("Expected nil for non-existent node ID")
|
|
}
|
|
})
|
|
|
|
t.Run("CalculateSpentPoints", func(t *testing.T) {
|
|
playerState := NewAAPlayerState(123)
|
|
|
|
// Add multiple progress entries
|
|
progress1 := &PlayerAAData{
|
|
CharacterID: 123,
|
|
NodeID: 100,
|
|
CurrentRank: 2,
|
|
PointsSpent: 4,
|
|
}
|
|
|
|
progress2 := &PlayerAAData{
|
|
CharacterID: 123,
|
|
NodeID: 101,
|
|
CurrentRank: 3,
|
|
PointsSpent: 6,
|
|
}
|
|
|
|
playerState.AddAAProgress(progress1)
|
|
playerState.AddAAProgress(progress2)
|
|
|
|
total := playerState.CalculateSpentPoints()
|
|
if total != 10 {
|
|
t.Errorf("Expected total spent points to be 10, got %d", total)
|
|
}
|
|
})
|
|
|
|
t.Run("UpdatePoints", func(t *testing.T) {
|
|
playerState := NewAAPlayerState(123)
|
|
|
|
// Update points
|
|
playerState.UpdatePoints(100, 20, 5)
|
|
|
|
if playerState.TotalPoints != 100 {
|
|
t.Error("Expected total points to be 100")
|
|
}
|
|
|
|
if playerState.SpentPoints != 20 {
|
|
t.Error("Expected spent points to be 20")
|
|
}
|
|
|
|
if playerState.BankedPoints != 5 {
|
|
t.Error("Expected banked points to be 5")
|
|
}
|
|
|
|
if playerState.AvailablePoints != 80 {
|
|
t.Error("Expected available points to be 80 (100 - 20)")
|
|
}
|
|
})
|
|
|
|
t.Run("SetActiveTemplate", func(t *testing.T) {
|
|
playerState := NewAAPlayerState(123)
|
|
|
|
// Create and add a template
|
|
template := NewAATemplate(AA_TEMPLATE_PERSONAL_1, "Personal 1")
|
|
playerState.Templates[AA_TEMPLATE_PERSONAL_1] = template
|
|
|
|
// Set active template
|
|
success := playerState.SetActiveTemplate(AA_TEMPLATE_PERSONAL_1)
|
|
if !success {
|
|
t.Error("Expected SetActiveTemplate to succeed")
|
|
}
|
|
|
|
if playerState.ActiveTemplate != AA_TEMPLATE_PERSONAL_1 {
|
|
t.Error("Expected active template to be updated")
|
|
}
|
|
|
|
// Try to set non-existent template
|
|
success = playerState.SetActiveTemplate(AA_TEMPLATE_PERSONAL_2)
|
|
if success {
|
|
t.Error("Expected SetActiveTemplate to fail for non-existent template")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test utility functions
|
|
func TestUtilityFunctions(t *testing.T) {
|
|
t.Run("GetMaxAAForTab", func(t *testing.T) {
|
|
tests := []struct {
|
|
group int8
|
|
expected int32
|
|
}{
|
|
{AA_CLASS, MAX_CLASS_AA},
|
|
{AA_SUBCLASS, MAX_SUBCLASS_AA},
|
|
{AA_SHADOW, MAX_SHADOWS_AA},
|
|
{AA_HEROIC, MAX_HEROIC_AA},
|
|
{AA_TRADESKILL, MAX_TRADESKILL_AA},
|
|
{AA_PRESTIGE, MAX_PRESTIGE_AA},
|
|
{AA_TRADESKILL_PRESTIGE, MAX_TRADESKILL_PRESTIGE_AA},
|
|
{AA_DRAGON, MAX_DRAGON_AA},
|
|
{AA_DRAGONCLASS, MAX_DRAGONCLASS_AA},
|
|
{AA_FARSEAS, MAX_FARSEAS_AA},
|
|
{99, 100}, // Unknown group
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(GetTabName(tt.group), func(t *testing.T) {
|
|
result := GetMaxAAForTab(tt.group)
|
|
if result != tt.expected {
|
|
t.Errorf("GetMaxAAForTab(%d) = %d, want %d", tt.group, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetTabName", func(t *testing.T) {
|
|
tests := []struct {
|
|
group int8
|
|
expected string
|
|
}{
|
|
{AA_CLASS, "Class"},
|
|
{AA_SUBCLASS, "Subclass"},
|
|
{AA_SHADOW, "Shadows"},
|
|
{AA_HEROIC, "Heroic"},
|
|
{AA_TRADESKILL, "Tradeskill"},
|
|
{99, "Unknown"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
result := GetTabName(tt.group)
|
|
if result != tt.expected {
|
|
t.Errorf("GetTabName(%d) = %s, want %s", tt.group, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("GetTemplateName", func(t *testing.T) {
|
|
tests := []struct {
|
|
templateID int8
|
|
expected string
|
|
}{
|
|
{AA_TEMPLATE_PERSONAL_1, "Personal 1"},
|
|
{AA_TEMPLATE_PERSONAL_2, "Personal 2"},
|
|
{AA_TEMPLATE_SERVER_1, "Server 1"},
|
|
{AA_TEMPLATE_CURRENT, "Current"},
|
|
{99, "Unknown"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
result := GetTemplateName(tt.templateID)
|
|
if result != tt.expected {
|
|
t.Errorf("GetTemplateName(%d) = %s, want %s", tt.templateID, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("IsExpansionRequired", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
flags int8
|
|
expansion int8
|
|
expected bool
|
|
}{
|
|
{"No expansion required", EXPANSION_NONE, EXPANSION_KOS, false},
|
|
{"KOS required - has KOS", EXPANSION_KOS, EXPANSION_KOS, true},
|
|
{"KOS required - no KOS", EXPANSION_EOF, EXPANSION_KOS, false},
|
|
{"Multiple expansions - has one", EXPANSION_KOS | EXPANSION_EOF, EXPANSION_KOS, true},
|
|
{"Multiple expansions - has different", EXPANSION_KOS | EXPANSION_EOF, EXPANSION_ROK, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := IsExpansionRequired(tt.flags, tt.expansion)
|
|
if result != tt.expected {
|
|
t.Errorf("IsExpansionRequired(%d, %d) = %v, want %v", tt.flags, tt.expansion, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test DefaultAAManagerConfig
|
|
func TestDefaultAAManagerConfig(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
|
|
if !config.EnableAASystem {
|
|
t.Error("Expected EnableAASystem to be true by default")
|
|
}
|
|
|
|
if !config.EnableCaching {
|
|
t.Error("Expected EnableCaching to be true by default")
|
|
}
|
|
|
|
if config.AAPointsPerLevel != DEFAULT_AA_POINTS_PER_LEVEL {
|
|
t.Errorf("Expected AAPointsPerLevel to be %d, got %d", DEFAULT_AA_POINTS_PER_LEVEL, config.AAPointsPerLevel)
|
|
}
|
|
|
|
if config.MaxBankedPoints != DEFAULT_AA_MAX_BANKED_POINTS {
|
|
t.Errorf("Expected MaxBankedPoints to be %d, got %d", DEFAULT_AA_MAX_BANKED_POINTS, config.MaxBankedPoints)
|
|
}
|
|
|
|
if config.CacheSize != AA_CACHE_SIZE {
|
|
t.Errorf("Expected CacheSize to be %d, got %d", AA_CACHE_SIZE, config.CacheSize)
|
|
}
|
|
}
|
|
|
|
// Test AAManager basic functionality
|
|
func TestAAManagerBasics(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
|
|
manager := NewAAManager(config)
|
|
if manager == nil {
|
|
t.Fatal("NewAAManager returned nil")
|
|
}
|
|
|
|
// Test configuration
|
|
currentConfig := manager.GetConfig()
|
|
if currentConfig.UpdateInterval != config.UpdateInterval {
|
|
t.Error("Expected config to match")
|
|
}
|
|
|
|
// Test stats
|
|
stats := manager.GetSystemStats()
|
|
if stats == nil {
|
|
t.Error("Expected valid stats")
|
|
}
|
|
|
|
// Test that manager is initialized
|
|
if manager.masterAAList == nil {
|
|
t.Error("Expected master AA list to be initialized")
|
|
}
|
|
|
|
if manager.masterNodeList == nil {
|
|
t.Error("Expected master node list to be initialized")
|
|
}
|
|
}
|
|
|
|
// Helper functions for AATemplate
|
|
func (t *AATemplate) AddEntry(entry *AAEntry) {
|
|
t.Entries = append(t.Entries, entry)
|
|
t.UpdatedAt = time.Now()
|
|
}
|
|
|
|
func (t *AATemplate) GetEntry(aaID int32) *AAEntry {
|
|
for _, entry := range t.Entries {
|
|
if entry.AAID == aaID {
|
|
return entry
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *AATemplate) RemoveEntry(aaID int32) bool {
|
|
for i, entry := range t.Entries {
|
|
if entry.AAID == aaID {
|
|
t.Entries = append(t.Entries[:i], t.Entries[i+1:]...)
|
|
t.UpdatedAt = time.Now()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Helper functions for AAPlayerState
|
|
func (ps *AAPlayerState) AddAAProgress(progress *PlayerAAData) {
|
|
ps.mutex.Lock()
|
|
defer ps.mutex.Unlock()
|
|
|
|
ps.AAProgress[progress.NodeID] = progress
|
|
ps.needsSync = true
|
|
ps.lastUpdate = time.Now()
|
|
}
|
|
|
|
func (ps *AAPlayerState) GetAAProgress(nodeID int32) *PlayerAAData {
|
|
ps.mutex.RLock()
|
|
defer ps.mutex.RUnlock()
|
|
|
|
return ps.AAProgress[nodeID]
|
|
}
|
|
|
|
func (ps *AAPlayerState) CalculateSpentPoints() int32 {
|
|
ps.mutex.RLock()
|
|
defer ps.mutex.RUnlock()
|
|
|
|
var total int32
|
|
for _, progress := range ps.AAProgress {
|
|
total += progress.PointsSpent
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (ps *AAPlayerState) UpdatePoints(total, spent, banked int32) {
|
|
ps.mutex.Lock()
|
|
defer ps.mutex.Unlock()
|
|
|
|
ps.TotalPoints = total
|
|
ps.SpentPoints = spent
|
|
ps.BankedPoints = banked
|
|
ps.AvailablePoints = total - spent
|
|
ps.needsSync = true
|
|
ps.lastUpdate = time.Now()
|
|
}
|
|
|
|
func (ps *AAPlayerState) SetActiveTemplate(templateID int8) bool {
|
|
ps.mutex.Lock()
|
|
defer ps.mutex.Unlock()
|
|
|
|
if _, exists := ps.Templates[templateID]; exists {
|
|
ps.ActiveTemplate = templateID
|
|
ps.needsSync = true
|
|
ps.lastUpdate = time.Now()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Helper functions for TreeNodeData
|
|
func (tnd *TreeNodeData) Copy() *TreeNodeData {
|
|
copy := *tnd
|
|
return ©
|
|
}
|
|
|
|
// Test AAManager more comprehensively
|
|
func TestAAManager(t *testing.T) {
|
|
t.Run("LoadAAData", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Test without database
|
|
err := manager.LoadAAData()
|
|
if err == nil {
|
|
t.Error("Expected error when loading without database")
|
|
}
|
|
|
|
// Test with mock database
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
err = manager.LoadAAData()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error loading AA data: %v", err)
|
|
}
|
|
|
|
if !mockDB.loadAAsCalled || !mockDB.loadNodesCalled {
|
|
t.Error("Expected database methods to be called")
|
|
}
|
|
})
|
|
|
|
t.Run("GetPlayerAAState", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Set up mock database that returns no existing data
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test getting non-existent player
|
|
state, err := manager.GetPlayerAAState(123)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if state == nil {
|
|
t.Error("Expected new player state to be created")
|
|
}
|
|
|
|
if state != nil && state.CharacterID != 123 {
|
|
t.Error("Expected character ID to match")
|
|
}
|
|
|
|
// Test getting existing player (should be cached)
|
|
state2, err := manager.GetPlayerAAState(123)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if state != nil && state2 != nil && state != state2 {
|
|
t.Error("Expected same state instance for same character from cache")
|
|
}
|
|
})
|
|
|
|
t.Run("PurchaseAA", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Set up mock database
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Add a test AA to the master list
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
MinLevel: 10,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
// Create a player with points
|
|
state, err := manager.GetPlayerAAState(123)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get player state: %v", err)
|
|
}
|
|
if state == nil {
|
|
t.Fatal("Player state is nil")
|
|
}
|
|
state.TotalPoints = 10
|
|
state.AvailablePoints = 10
|
|
|
|
// Test purchasing AA
|
|
err = manager.PurchaseAA(123, 200, 1)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error purchasing AA: %v", err)
|
|
}
|
|
|
|
// Verify purchase
|
|
progress := state.GetAAProgress(200)
|
|
if progress == nil {
|
|
t.Fatal("Expected AA progress to exist")
|
|
}
|
|
|
|
if progress.CurrentRank != 1 {
|
|
t.Error("Expected rank to be 1")
|
|
}
|
|
|
|
if state.AvailablePoints != 8 {
|
|
t.Error("Expected available points to be reduced by 2")
|
|
}
|
|
|
|
// Test purchasing non-existent AA
|
|
err = manager.PurchaseAA(123, 999, 1)
|
|
if err == nil {
|
|
t.Error("Expected error when purchasing non-existent AA")
|
|
}
|
|
})
|
|
|
|
t.Run("AwardAAPoints", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Set up mock database
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Award points to player
|
|
err := manager.AwardAAPoints(123, 50, "Level up")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error awarding points: %v", err)
|
|
}
|
|
|
|
// Verify points
|
|
total, spent, available, err := manager.GetAAPoints(123)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error getting points: %v", err)
|
|
}
|
|
|
|
if total != 50 {
|
|
t.Errorf("Expected total points to be 50, got %d", total)
|
|
}
|
|
|
|
if spent != 0 {
|
|
t.Errorf("Expected spent points to be 0, got %d", spent)
|
|
}
|
|
|
|
if available != 50 {
|
|
t.Errorf("Expected available points to be 50, got %d", available)
|
|
}
|
|
})
|
|
|
|
t.Run("GetAA", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Add test AA
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
// Test getting by node ID
|
|
retrieved, err := manager.GetAA(200)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if retrieved == nil {
|
|
t.Fatal("Expected to get AA")
|
|
}
|
|
|
|
if retrieved.NodeID != 200 {
|
|
t.Error("Expected node ID to match")
|
|
}
|
|
|
|
// Test getting by spell ID
|
|
retrieved, err = manager.GetAABySpellID(100)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if retrieved == nil {
|
|
t.Fatal("Expected to get AA by spell ID")
|
|
}
|
|
|
|
if retrieved.SpellID != 100 {
|
|
t.Error("Expected spell ID to match")
|
|
}
|
|
})
|
|
|
|
t.Run("StartStop", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
config.UpdateInterval = 10 * time.Millisecond
|
|
config.SaveInterval = 10 * time.Millisecond
|
|
config.AutoSave = true
|
|
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Start manager
|
|
err := manager.Start()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error starting: %v", err)
|
|
}
|
|
|
|
if !manager.IsRunning() {
|
|
t.Error("Expected manager to be running")
|
|
}
|
|
|
|
// Let background processes run briefly
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Stop manager
|
|
err = manager.Stop()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error stopping: %v", err)
|
|
}
|
|
|
|
if manager.IsRunning() {
|
|
t.Error("Expected manager to be stopped")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Mock implementations for testing
|
|
type mockAADatabase struct {
|
|
loadAAsCalled bool
|
|
loadNodesCalled bool
|
|
savePlayerCalled bool
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (m *mockAADatabase) LoadAltAdvancements() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.loadAAsCalled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAADatabase) LoadTreeNodes() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.loadNodesCalled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAADatabase) LoadPlayerAA(characterID int32) (*AAPlayerState, error) {
|
|
// Simulate creating a new player state when none exists
|
|
return NewAAPlayerState(characterID), nil
|
|
}
|
|
|
|
func (m *mockAADatabase) SavePlayerAA(playerState *AAPlayerState) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.savePlayerCalled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAADatabase) DeletePlayerAA(characterID int32) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAADatabase) LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error) {
|
|
return make(map[int8][]*AAEntry), nil
|
|
}
|
|
|
|
func (m *mockAADatabase) GetAAStatistics() (map[string]interface{}, error) {
|
|
return make(map[string]interface{}), nil
|
|
}
|
|
|
|
// Test mock event handler
|
|
type mockAAEventHandler struct {
|
|
events []string
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnAAPurchased(characterID int32, nodeID int32, newRank int8, pointsSpent int32) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "purchased")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnAARefunded(characterID int32, nodeID int32, oldRank int8, pointsRefunded int32) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "refunded")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnAATemplateChanged(characterID int32, oldTemplate, newTemplate int8) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "template_changed")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnAATemplateCreated(characterID int32, templateID int8, name string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "template_created")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnAASystemLoaded(totalAAs int32, totalNodes int32) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "system_loaded")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnAADataReloaded() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "data_reloaded")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnPlayerAALoaded(characterID int32, playerState *AAPlayerState) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "player_loaded")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) OnPlayerAAPointsChanged(characterID int32, oldPoints, newPoints int32) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.events = append(m.events, "points_changed")
|
|
return nil
|
|
}
|
|
|
|
func (m *mockAAEventHandler) LogEvent(message string) {
|
|
log.Println("[MockEvent]", message)
|
|
}
|
|
|
|
// Test event handler integration
|
|
func TestAAEventHandling(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Add mock event handler
|
|
mockHandler := &mockAAEventHandler{}
|
|
manager.SetEventHandler(mockHandler)
|
|
|
|
// Add mock database
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test system loaded event
|
|
err := manager.LoadAAData()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Wait a bit for goroutines to complete
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Check that event was fired
|
|
mockHandler.mu.Lock()
|
|
hasSystemLoaded := false
|
|
for _, event := range mockHandler.events {
|
|
if event == "system_loaded" {
|
|
hasSystemLoaded = true
|
|
break
|
|
}
|
|
}
|
|
if !hasSystemLoaded {
|
|
t.Error("Expected system_loaded event")
|
|
}
|
|
mockHandler.mu.Unlock()
|
|
|
|
// Test purchase event
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
MinLevel: 1,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
state, _ := manager.GetPlayerAAState(123)
|
|
state.TotalPoints = 10
|
|
state.AvailablePoints = 10
|
|
|
|
err = manager.PurchaseAA(123, 200, 1)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Wait a bit for goroutines to complete
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Check that purchase event was fired
|
|
mockHandler.mu.Lock()
|
|
found := false
|
|
for _, event := range mockHandler.events {
|
|
if event == "purchased" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Expected purchased event")
|
|
}
|
|
mockHandler.mu.Unlock()
|
|
|
|
// Test points changed event
|
|
err = manager.AwardAAPoints(123, 50, "Test award")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Wait a bit for goroutines to complete
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
mockHandler.mu.Lock()
|
|
found = false
|
|
for _, event := range mockHandler.events {
|
|
if event == "points_changed" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Expected points_changed event")
|
|
}
|
|
mockHandler.mu.Unlock()
|
|
}
|
|
|
|
// Test Interface Implementations and Adapters
|
|
func TestInterfacesAndAdapters(t *testing.T) {
|
|
t.Run("AAAdapter", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
adapter := NewAAAdapter(manager, 123)
|
|
if adapter == nil {
|
|
t.Fatal("NewAAAdapter returned nil")
|
|
}
|
|
|
|
if adapter.GetCharacterID() != 123 {
|
|
t.Error("Expected character ID to match")
|
|
}
|
|
|
|
if adapter.GetManager() != manager {
|
|
t.Error("Expected manager to match")
|
|
}
|
|
|
|
// Test AwardPoints
|
|
err := adapter.AwardPoints(100, "Test")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Test GetAAPoints
|
|
total, _, _, err := adapter.GetAAPoints()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if total != 100 {
|
|
t.Errorf("Expected total 100, got %d", total)
|
|
}
|
|
|
|
// Test GetAAState
|
|
state, err := adapter.GetAAState()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if state == nil {
|
|
t.Error("Expected state to be returned")
|
|
}
|
|
|
|
// Test SaveAAState
|
|
err = adapter.SaveAAState()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Test GetTemplates
|
|
templates, err := adapter.GetTemplates()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if templates == nil {
|
|
t.Error("Expected templates map")
|
|
}
|
|
|
|
stats := adapter.GetPlayerStats()
|
|
if stats == nil {
|
|
t.Error("Expected stats map")
|
|
}
|
|
})
|
|
|
|
t.Run("PlayerAAAdapter", func(t *testing.T) {
|
|
mockPlayer := &mockPlayer{
|
|
characterID: 123,
|
|
level: 50,
|
|
class: 1,
|
|
adventureClass: 1,
|
|
race: 1,
|
|
name: "TestPlayer",
|
|
}
|
|
|
|
adapter := NewPlayerAAAdapter(mockPlayer)
|
|
if adapter == nil {
|
|
t.Fatal("NewPlayerAAAdapter returned nil")
|
|
}
|
|
|
|
if adapter.GetPlayer() != mockPlayer {
|
|
t.Error("Expected player to match")
|
|
}
|
|
|
|
if adapter.GetCharacterID() != 123 {
|
|
t.Error("Expected character ID to match")
|
|
}
|
|
|
|
if adapter.GetLevel() != 50 {
|
|
t.Error("Expected level to match")
|
|
}
|
|
|
|
if adapter.GetClass() != 1 {
|
|
t.Error("Expected class to match")
|
|
}
|
|
|
|
if adapter.GetAdventureClass() != 1 {
|
|
t.Error("Expected adventure class to match")
|
|
}
|
|
|
|
if adapter.GetRace() != 1 {
|
|
t.Error("Expected race to match")
|
|
}
|
|
|
|
if adapter.GetName() != "TestPlayer" {
|
|
t.Error("Expected name to match")
|
|
}
|
|
|
|
if !adapter.HasExpansion(EXPANSION_NONE) {
|
|
t.Error("Expected expansion check to work")
|
|
}
|
|
})
|
|
|
|
t.Run("ClientAAAdapter", func(t *testing.T) {
|
|
mockClient := &mockClient{
|
|
characterID: 123,
|
|
player: &mockPlayer{
|
|
characterID: 123,
|
|
name: "TestPlayer",
|
|
},
|
|
version: 1096,
|
|
}
|
|
|
|
adapter := NewClientAAAdapter(mockClient)
|
|
if adapter == nil {
|
|
t.Fatal("NewClientAAAdapter returned nil")
|
|
}
|
|
|
|
if adapter.GetClient() != mockClient {
|
|
t.Error("Expected client to match")
|
|
}
|
|
|
|
if adapter.GetCharacterID() != 123 {
|
|
t.Error("Expected character ID to match")
|
|
}
|
|
|
|
if adapter.GetPlayer() != mockClient.player {
|
|
t.Error("Expected player to match")
|
|
}
|
|
|
|
if adapter.GetClientVersion() != 1096 {
|
|
t.Error("Expected client version to match")
|
|
}
|
|
|
|
// Test SendPacket
|
|
err := adapter.SendPacket([]byte("test"))
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("SimpleAACache", func(t *testing.T) {
|
|
cache := NewSimpleAACache(10)
|
|
if cache == nil {
|
|
t.Fatal("NewSimpleAACache returned nil")
|
|
}
|
|
|
|
// Test AA caching
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
// Test miss
|
|
cached, found := cache.GetAA(200)
|
|
if found || cached != nil {
|
|
t.Error("Expected cache miss")
|
|
}
|
|
|
|
// Set and get
|
|
cache.SetAA(200, aa)
|
|
cached, found = cache.GetAA(200)
|
|
if !found || cached == nil {
|
|
t.Error("Expected cache hit")
|
|
}
|
|
|
|
if cached == aa {
|
|
t.Error("Expected cached copy, not original")
|
|
}
|
|
|
|
if cached.NodeID != aa.NodeID {
|
|
t.Error("Expected cached data to match")
|
|
}
|
|
|
|
// Test invalidation
|
|
cache.InvalidateAA(200)
|
|
cached, found = cache.GetAA(200)
|
|
if found || cached != nil {
|
|
t.Error("Expected cache miss after invalidation")
|
|
}
|
|
|
|
// Test player state caching
|
|
playerState := NewAAPlayerState(123)
|
|
|
|
// Test miss
|
|
cachedState, found := cache.GetPlayerState(123)
|
|
if found || cachedState != nil {
|
|
t.Error("Expected cache miss")
|
|
}
|
|
|
|
// Set and get
|
|
cache.SetPlayerState(123, playerState)
|
|
cachedState, found = cache.GetPlayerState(123)
|
|
if !found || cachedState == nil {
|
|
t.Error("Expected cache hit")
|
|
}
|
|
|
|
if cachedState != playerState {
|
|
t.Error("Expected same player state instance")
|
|
}
|
|
|
|
// Test tree node caching
|
|
node := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 100,
|
|
AATreeID: 200,
|
|
}
|
|
|
|
// Test miss
|
|
cachedNode, found := cache.GetTreeNode(100)
|
|
if found || cachedNode != nil {
|
|
t.Error("Expected cache miss")
|
|
}
|
|
|
|
// Set and get
|
|
cache.SetTreeNode(100, node)
|
|
cachedNode, found = cache.GetTreeNode(100)
|
|
if !found || cachedNode == nil {
|
|
t.Error("Expected cache hit")
|
|
}
|
|
|
|
if cachedNode == node {
|
|
t.Error("Expected cached copy, not original")
|
|
}
|
|
|
|
if cachedNode.TreeID != node.TreeID {
|
|
t.Error("Expected cached data to match")
|
|
}
|
|
|
|
// Test stats
|
|
stats := cache.GetStats()
|
|
if stats == nil {
|
|
t.Error("Expected stats map")
|
|
}
|
|
|
|
// Test max size
|
|
cache.SetMaxSize(20)
|
|
if cache.maxSize != 20 {
|
|
t.Error("Expected max size to be updated")
|
|
}
|
|
|
|
// Test clear
|
|
cache.Clear()
|
|
_, found = cache.GetAA(200)
|
|
if found {
|
|
t.Error("Expected cache to be cleared")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test Edge Cases and Error Conditions
|
|
func TestEdgeCases(t *testing.T) {
|
|
t.Run("AAManagerWithoutDatabase", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Test operations without database
|
|
err := manager.LoadAAData()
|
|
if err == nil {
|
|
t.Error("Expected error without database")
|
|
}
|
|
|
|
err = manager.SavePlayerAA(123)
|
|
if err == nil {
|
|
t.Error("Expected error without database")
|
|
}
|
|
|
|
_, err = manager.GetPlayerAAState(123)
|
|
if err == nil {
|
|
t.Error("Expected error without database")
|
|
}
|
|
})
|
|
|
|
t.Run("AAManagerErrorPaths", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Set up mock database that returns errors
|
|
mockDB := &mockAADatabaseWithErrors{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test load errors
|
|
err := manager.LoadAAData()
|
|
if err == nil {
|
|
t.Error("Expected error from database")
|
|
}
|
|
|
|
err = manager.ReloadAAData()
|
|
if err == nil {
|
|
t.Error("Expected error from database")
|
|
}
|
|
})
|
|
|
|
t.Run("PurchaseAAErrorCases", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test with insufficient points
|
|
state, _ := manager.GetPlayerAAState(123)
|
|
state.TotalPoints = 1
|
|
state.AvailablePoints = 1
|
|
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 10, // More than available
|
|
MinLevel: 1,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
err := manager.PurchaseAA(123, 200, 1)
|
|
if err == nil {
|
|
t.Error("Expected error due to insufficient points")
|
|
}
|
|
|
|
// Test purchasing non-existent AA
|
|
err = manager.PurchaseAA(123, 999, 1)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent AA")
|
|
}
|
|
})
|
|
|
|
t.Run("RefundAAErrorCases", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test refunding non-existent AA
|
|
err := manager.RefundAA(123, 999)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent AA")
|
|
}
|
|
|
|
// Test refunding from player without state
|
|
err = manager.RefundAA(999, 200)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent player")
|
|
}
|
|
})
|
|
|
|
t.Run("TemplateOperations", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test with non-existent player
|
|
err := manager.ChangeAATemplate(999, AA_TEMPLATE_PERSONAL_1)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent player")
|
|
}
|
|
|
|
templates, err := manager.GetAATemplates(999)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent player")
|
|
}
|
|
|
|
if templates != nil {
|
|
t.Error("Expected nil templates for non-existent player")
|
|
}
|
|
|
|
err = manager.SaveAATemplate(999, AA_TEMPLATE_PERSONAL_1, "Test")
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent player")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAvailableAAsErrorCases", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Test with non-existent player
|
|
aas, err := manager.GetAvailableAAs(999, AA_CLASS)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent player")
|
|
}
|
|
|
|
if aas != nil {
|
|
t.Error("Expected nil AAs for non-existent player")
|
|
}
|
|
})
|
|
|
|
t.Run("DataValidation", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Test adding AA with missing required fields
|
|
invalidAAs := []*AltAdvanceData{
|
|
{NodeID: 200, Name: "Test", MaxRank: 5, RankCost: 2}, // Missing SpellID
|
|
{SpellID: 100, Name: "Test", MaxRank: 5, RankCost: 2}, // Missing NodeID
|
|
{SpellID: 100, NodeID: 200, MaxRank: 5, RankCost: 2}, // Missing Name
|
|
{SpellID: 100, NodeID: 200, Name: "Test", RankCost: 2}, // Missing MaxRank
|
|
{SpellID: 100, NodeID: 200, Name: "Test", MaxRank: 5}, // Missing RankCost
|
|
}
|
|
|
|
for i, aa := range invalidAAs {
|
|
err := masterList.AddAltAdvancement(aa)
|
|
if err == nil {
|
|
t.Errorf("Expected error for invalid AA %d", i)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test Manager Lifecycle
|
|
func TestManagerLifecycle(t *testing.T) {
|
|
t.Run("StartStopCycle", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
config.UpdateInterval = 5 * time.Millisecond
|
|
config.SaveInterval = 5 * time.Millisecond
|
|
config.AutoSave = true
|
|
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Start manager
|
|
err := manager.Start()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error starting: %v", err)
|
|
}
|
|
|
|
if !manager.IsRunning() {
|
|
t.Error("Expected manager to be running after start")
|
|
}
|
|
|
|
// Let it run briefly
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
// Stop manager
|
|
err = manager.Stop()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error stopping: %v", err)
|
|
}
|
|
|
|
// The IsRunning check consumes the channel close signal, so we can't test it reliably
|
|
// Just verify that stopping doesn't cause errors
|
|
})
|
|
|
|
t.Run("ReloadData", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Add some data
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
// Create player state
|
|
_, err := manager.GetPlayerAAState(123)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get player state: %v", err)
|
|
}
|
|
|
|
// Verify data exists
|
|
if manager.masterAAList.Size() != 1 {
|
|
t.Error("Expected 1 AA before reload")
|
|
}
|
|
|
|
// Reload data
|
|
err = manager.ReloadAAData()
|
|
if err != nil {
|
|
t.Errorf("Unexpected error reloading: %v", err)
|
|
}
|
|
|
|
// Verify data was cleared and reloaded
|
|
if manager.masterAAList.Size() != 0 {
|
|
t.Error("Expected AAs to be cleared after reload")
|
|
}
|
|
|
|
// Player states should be cleared
|
|
if len(manager.playerStates) != 0 {
|
|
t.Error("Expected player states to be cleared after reload")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Mock implementations for testing
|
|
type mockPlayer struct {
|
|
characterID int32
|
|
level int8
|
|
class int8
|
|
adventureClass int8
|
|
race int8
|
|
name string
|
|
}
|
|
|
|
func (m *mockPlayer) GetCharacterID() int32 { return m.characterID }
|
|
func (m *mockPlayer) GetLevel() int8 { return m.level }
|
|
func (m *mockPlayer) GetClass() int8 { return m.class }
|
|
func (m *mockPlayer) GetRace() int8 { return m.race }
|
|
func (m *mockPlayer) GetName() string { return m.name }
|
|
func (m *mockPlayer) GetAdventureClass() int8 { return m.adventureClass }
|
|
func (m *mockPlayer) HasExpansion(expansionFlag int8) bool { return expansionFlag == EXPANSION_NONE }
|
|
|
|
type mockClient struct {
|
|
characterID int32
|
|
player Player
|
|
version int16
|
|
}
|
|
|
|
func (m *mockClient) GetCharacterID() int32 { return m.characterID }
|
|
func (m *mockClient) GetPlayer() Player { return m.player }
|
|
func (m *mockClient) SendPacket(data []byte) error { return nil }
|
|
func (m *mockClient) GetClientVersion() int16 { return m.version }
|
|
|
|
type mockAADatabaseWithErrors struct{}
|
|
|
|
func (m *mockAADatabaseWithErrors) LoadAltAdvancements() error { return fmt.Errorf("load AA error") }
|
|
func (m *mockAADatabaseWithErrors) LoadTreeNodes() error { return fmt.Errorf("load nodes error") }
|
|
func (m *mockAADatabaseWithErrors) LoadPlayerAA(characterID int32) (*AAPlayerState, error) { return nil, fmt.Errorf("load player error") }
|
|
func (m *mockAADatabaseWithErrors) SavePlayerAA(playerState *AAPlayerState) error { return fmt.Errorf("save player error") }
|
|
func (m *mockAADatabaseWithErrors) DeletePlayerAA(characterID int32) error { return fmt.Errorf("delete player error") }
|
|
func (m *mockAADatabaseWithErrors) LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error) { return nil, fmt.Errorf("load defaults error") }
|
|
func (m *mockAADatabaseWithErrors) GetAAStatistics() (map[string]interface{}, error) { return nil, fmt.Errorf("stats error") }
|
|
|
|
// Test more adapter methods
|
|
func TestAdapterMethods(t *testing.T) {
|
|
t.Run("AAAdapterMethods", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Add test AA
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
MinLevel: 1,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
adapter := NewAAAdapter(manager, 123)
|
|
|
|
// Set up player with points
|
|
state, err := manager.GetPlayerAAState(123)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get player state: %v", err)
|
|
}
|
|
state.TotalPoints = 10
|
|
state.AvailablePoints = 10
|
|
|
|
// Test PurchaseAA
|
|
err = adapter.PurchaseAA(200, 1)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Test RefundAA
|
|
err = adapter.RefundAA(200)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Test GetAvailableAAs
|
|
aas, err := adapter.GetAvailableAAs(AA_CLASS)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if aas == nil {
|
|
t.Error("Expected AAs list")
|
|
}
|
|
|
|
// Test ChangeTemplate (should work even without existing template since no validator is set)
|
|
err = adapter.ChangeTemplate(AA_TEMPLATE_PERSONAL_1)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Create template and test again
|
|
template := NewAATemplate(AA_TEMPLATE_PERSONAL_1, "Test Template")
|
|
state.Templates[AA_TEMPLATE_PERSONAL_1] = template
|
|
|
|
err = adapter.ChangeTemplate(AA_TEMPLATE_PERSONAL_2)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test database implementation constructor
|
|
func TestDatabaseImpl(t *testing.T) {
|
|
t.Run("NewDatabaseImpl", func(t *testing.T) {
|
|
masterAAList := NewMasterAAList()
|
|
masterNodeList := NewMasterAANodeList()
|
|
logger := log.New(&testLogWriter{}, "test", 0)
|
|
|
|
dbImpl := NewDatabaseImpl(nil, masterAAList, masterNodeList, logger)
|
|
if dbImpl == nil {
|
|
t.Fatal("NewDatabaseImpl returned nil")
|
|
}
|
|
|
|
if dbImpl.masterAAList != masterAAList {
|
|
t.Error("Expected master AA list to match")
|
|
}
|
|
|
|
if dbImpl.masterNodeList != masterNodeList {
|
|
t.Error("Expected master node list to match")
|
|
}
|
|
|
|
if dbImpl.logger != logger {
|
|
t.Error("Expected logger to match")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test more edge cases and missing functionality
|
|
func TestAdditionalEdgeCases(t *testing.T) {
|
|
t.Run("AAManagerRefundFunctionality", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Add test AA
|
|
aa := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
MinLevel: 1,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
|
|
// Create player and purchase AA first
|
|
state, _ := manager.GetPlayerAAState(123)
|
|
state.TotalPoints = 10
|
|
state.AvailablePoints = 10
|
|
|
|
// Purchase AA
|
|
err := manager.PurchaseAA(123, 200, 1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to purchase AA: %v", err)
|
|
}
|
|
|
|
// Verify purchase
|
|
progress := state.GetAAProgress(200)
|
|
if progress == nil {
|
|
t.Fatal("Expected AA progress")
|
|
}
|
|
|
|
if progress.CurrentRank != 1 {
|
|
t.Error("Expected rank 1")
|
|
}
|
|
|
|
// Test refund
|
|
err = manager.RefundAA(123, 200)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error refunding: %v", err)
|
|
}
|
|
|
|
// Verify refund
|
|
progress = state.GetAAProgress(200)
|
|
if progress != nil {
|
|
t.Error("Expected AA progress to be removed after refund")
|
|
}
|
|
|
|
// Test refunding AA that's not purchased
|
|
err = manager.RefundAA(123, 200)
|
|
if err == nil {
|
|
t.Error("Expected error refunding unpurchased AA")
|
|
}
|
|
})
|
|
|
|
t.Run("TemplateManagement", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Create player
|
|
state, _ := manager.GetPlayerAAState(123)
|
|
|
|
// Test saving template
|
|
err := manager.SaveAATemplate(123, AA_TEMPLATE_PERSONAL_1, "My Template")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error saving template: %v", err)
|
|
}
|
|
|
|
// Verify template was created
|
|
template := state.Templates[AA_TEMPLATE_PERSONAL_1]
|
|
if template == nil {
|
|
t.Error("Expected template to be created")
|
|
}
|
|
|
|
if template.Name != "My Template" {
|
|
t.Error("Expected correct template name")
|
|
}
|
|
|
|
// Test changing template
|
|
err = manager.ChangeAATemplate(123, AA_TEMPLATE_PERSONAL_1)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error changing template: %v", err)
|
|
}
|
|
|
|
if state.ActiveTemplate != AA_TEMPLATE_PERSONAL_1 {
|
|
t.Error("Expected active template to be changed")
|
|
}
|
|
|
|
// Test getting templates
|
|
templates, err := manager.GetAATemplates(123)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error getting templates: %v", err)
|
|
}
|
|
|
|
if len(templates) != 1 {
|
|
t.Error("Expected 1 template")
|
|
}
|
|
|
|
if templates[AA_TEMPLATE_PERSONAL_1] == nil {
|
|
t.Error("Expected template to be in map")
|
|
}
|
|
})
|
|
|
|
t.Run("GetAAsByMethods", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Add test AAs
|
|
aa1 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA 1",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa2 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Test AA 2",
|
|
Group: AA_HEROIC,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
manager.masterAAList.AddAltAdvancement(aa1)
|
|
manager.masterAAList.AddAltAdvancement(aa2)
|
|
|
|
// Test GetAAsByGroup
|
|
classAAs, err := manager.GetAAsByGroup(AA_CLASS)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if len(classAAs) != 1 {
|
|
t.Errorf("Expected 1 class AA, got %d", len(classAAs))
|
|
}
|
|
|
|
// Test GetAAsByClass
|
|
allClassAAs, err := manager.GetAAsByClass(0) // All classes
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if len(allClassAAs) != 2 {
|
|
t.Errorf("Expected 2 AAs for all classes, got %d", len(allClassAAs))
|
|
}
|
|
})
|
|
|
|
t.Run("AAValidation", func(t *testing.T) {
|
|
// Test IsAAAvailable method indirectly through GetAvailableAAs
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Add AA with prerequisites
|
|
prereqAA := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Prerequisite AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 1,
|
|
MinLevel: 1,
|
|
}
|
|
|
|
dependentAA := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Dependent AA",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
MinLevel: 1,
|
|
RankPrereqID: 200,
|
|
RankPrereq: 1,
|
|
}
|
|
|
|
manager.masterAAList.AddAltAdvancement(prereqAA)
|
|
manager.masterAAList.AddAltAdvancement(dependentAA)
|
|
|
|
// Create player
|
|
state, _ := manager.GetPlayerAAState(123)
|
|
|
|
// Get available AAs - dependent AA should not be available without prereq
|
|
availableAAs, err := manager.GetAvailableAAs(123, AA_CLASS)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Should only have prerequisite AA available
|
|
hasPrereq := false
|
|
hasDependent := false
|
|
for _, aa := range availableAAs {
|
|
if aa.NodeID == 200 {
|
|
hasPrereq = true
|
|
}
|
|
if aa.NodeID == 201 {
|
|
hasDependent = true
|
|
}
|
|
}
|
|
|
|
if !hasPrereq {
|
|
t.Error("Expected prerequisite AA to be available")
|
|
}
|
|
|
|
if hasDependent {
|
|
t.Error("Expected dependent AA to not be available without prerequisite")
|
|
}
|
|
|
|
// Purchase prerequisite
|
|
state.TotalPoints = 10
|
|
state.AvailablePoints = 10
|
|
err = manager.PurchaseAA(123, 200, 1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to purchase prerequisite: %v", err)
|
|
}
|
|
|
|
// Now dependent AA should be available
|
|
availableAAs, err = manager.GetAvailableAAs(123, AA_CLASS)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
hasDependent = false
|
|
for _, aa := range availableAAs {
|
|
if aa.NodeID == 201 {
|
|
hasDependent = true
|
|
}
|
|
}
|
|
|
|
if !hasDependent {
|
|
t.Error("Expected dependent AA to be available after prerequisite purchased")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Helper type for database test
|
|
type testLogWriter struct{}
|
|
|
|
func (w *testLogWriter) Write(p []byte) (n int, err error) {
|
|
return len(p), nil
|
|
}
|
|
|
|
// Test for cache eviction and other missing cache functionality
|
|
func TestCacheEviction(t *testing.T) {
|
|
t.Run("CacheEviction", func(t *testing.T) {
|
|
cache := NewSimpleAACache(2) // Small cache for testing eviction
|
|
|
|
// Add first AA
|
|
aa1 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA 1",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
cache.SetAA(200, aa1)
|
|
|
|
// Add second AA
|
|
aa2 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Test AA 2",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
cache.SetAA(201, aa2)
|
|
|
|
// Add third AA (should evict one)
|
|
aa3 := &AltAdvanceData{
|
|
SpellID: 102,
|
|
NodeID: 202,
|
|
Name: "Test AA 3",
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
cache.SetAA(202, aa3)
|
|
|
|
// Check that only 2 items remain
|
|
stats := cache.GetStats()
|
|
if stats["aa_data_count"].(int) != 2 {
|
|
t.Errorf("Expected 2 cached AAs after eviction, got %d", stats["aa_data_count"].(int))
|
|
}
|
|
|
|
// Test player state eviction
|
|
state1 := NewAAPlayerState(123)
|
|
state2 := NewAAPlayerState(124)
|
|
state3 := NewAAPlayerState(125)
|
|
|
|
cache.SetPlayerState(123, state1)
|
|
cache.SetPlayerState(124, state2)
|
|
cache.SetPlayerState(125, state3) // Should evict one
|
|
|
|
stats = cache.GetStats()
|
|
if stats["player_count"].(int) != 2 {
|
|
t.Errorf("Expected 2 cached player states after eviction, got %d", stats["player_count"].(int))
|
|
}
|
|
|
|
// Test tree node eviction
|
|
node1 := &TreeNodeData{ClassID: 1, TreeID: 100, AATreeID: 200}
|
|
node2 := &TreeNodeData{ClassID: 2, TreeID: 101, AATreeID: 201}
|
|
node3 := &TreeNodeData{ClassID: 3, TreeID: 102, AATreeID: 202}
|
|
|
|
cache.SetTreeNode(100, node1)
|
|
cache.SetTreeNode(101, node2)
|
|
cache.SetTreeNode(102, node3) // Should evict one
|
|
|
|
stats = cache.GetStats()
|
|
if stats["tree_node_count"].(int) != 2 {
|
|
t.Errorf("Expected 2 cached tree nodes after eviction, got %d", stats["tree_node_count"].(int))
|
|
}
|
|
|
|
// Test some get operations to generate statistics
|
|
cache.GetAA(999) // Should generate a miss
|
|
cache.GetAA(200) // Should generate a hit
|
|
|
|
// Test invalidate
|
|
cache.InvalidatePlayerState(123)
|
|
cache.InvalidateTreeNode(100)
|
|
|
|
// Get final stats
|
|
finalStats := cache.GetStats()
|
|
|
|
// Verify hits and misses are tracked (should have at least some activity)
|
|
hits := finalStats["hits"].(int64)
|
|
misses := finalStats["misses"].(int64)
|
|
if hits == 0 && misses == 0 {
|
|
t.Error("Expected some cache statistics to be tracked")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test missing MasterAAList functions
|
|
func TestMasterAAListAdditionalMethods(t *testing.T) {
|
|
t.Run("GetAllAAs", func(t *testing.T) {
|
|
masterList := NewMasterAAList()
|
|
|
|
// Add test AAs
|
|
aa1 := &AltAdvanceData{
|
|
SpellID: 100,
|
|
NodeID: 200,
|
|
Name: "Test AA 1",
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
aa2 := &AltAdvanceData{
|
|
SpellID: 101,
|
|
NodeID: 201,
|
|
Name: "Test AA 2",
|
|
Group: AA_HEROIC,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
}
|
|
|
|
masterList.AddAltAdvancement(aa1)
|
|
masterList.AddAltAdvancement(aa2)
|
|
|
|
// Test GetAllAAs
|
|
allAAs := masterList.GetAllAAs()
|
|
if len(allAAs) != 2 {
|
|
t.Errorf("Expected 2 AAs, got %d", len(allAAs))
|
|
}
|
|
|
|
// Verify they are copies (different pointers but same content)
|
|
if allAAs[0] == aa1 || allAAs[1] == aa1 {
|
|
t.Error("GetAllAAs should return copies, not originals")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test MasterAANodeList additional methods
|
|
func TestMasterAANodeListAdditionalMethods(t *testing.T) {
|
|
t.Run("GetTreeNodes", func(t *testing.T) {
|
|
nodeList := NewMasterAANodeList()
|
|
|
|
// Add test nodes
|
|
node1 := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 100,
|
|
AATreeID: 200,
|
|
}
|
|
|
|
node2 := &TreeNodeData{
|
|
ClassID: 2,
|
|
TreeID: 101,
|
|
AATreeID: 201,
|
|
}
|
|
|
|
nodeList.AddTreeNode(node1)
|
|
nodeList.AddTreeNode(node2)
|
|
|
|
// Test GetTreeNodes
|
|
allNodes := nodeList.GetTreeNodes()
|
|
if len(allNodes) != 2 {
|
|
t.Errorf("Expected 2 nodes, got %d", len(allNodes))
|
|
}
|
|
|
|
// Verify they are copies (different pointers but same content)
|
|
if allNodes[0] == node1 || allNodes[1] == node1 {
|
|
t.Error("GetTreeNodes should return copies, not originals")
|
|
}
|
|
})
|
|
|
|
t.Run("DestroyTreeNodes", func(t *testing.T) {
|
|
nodeList := NewMasterAANodeList()
|
|
|
|
// Add test node
|
|
node := &TreeNodeData{
|
|
ClassID: 1,
|
|
TreeID: 100,
|
|
AATreeID: 200,
|
|
}
|
|
|
|
nodeList.AddTreeNode(node)
|
|
|
|
if nodeList.Size() != 1 {
|
|
t.Error("Expected 1 node before destroy")
|
|
}
|
|
|
|
// Destroy all nodes
|
|
nodeList.DestroyTreeNodes()
|
|
|
|
if nodeList.Size() != 0 {
|
|
t.Error("Expected 0 nodes after destroy")
|
|
}
|
|
|
|
// Verify maps are cleared
|
|
if len(nodeList.nodesByTree) != 0 || len(nodeList.nodesByClass) != 0 || len(nodeList.nodeList) != 0 {
|
|
t.Error("Expected all maps to be cleared after destroy")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test configuration validation
|
|
func TestConfigValidation(t *testing.T) {
|
|
t.Run("ConfigUpdate", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
|
|
// Test SetConfig
|
|
newConfig := AAManagerConfig{
|
|
EnableAASystem: false,
|
|
AAPointsPerLevel: 10,
|
|
MaxBankedPoints: 2000,
|
|
EnableCaching: false,
|
|
CacheSize: 200,
|
|
UpdateInterval: 30 * time.Second,
|
|
SaveInterval: 120 * time.Second,
|
|
AutoSave: false,
|
|
}
|
|
|
|
err := manager.SetConfig(newConfig)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error setting config: %v", err)
|
|
}
|
|
|
|
retrievedConfig := manager.GetConfig()
|
|
if retrievedConfig.EnableAASystem != false {
|
|
t.Error("Expected EnableAASystem to be updated")
|
|
}
|
|
|
|
if retrievedConfig.AAPointsPerLevel != 10 {
|
|
t.Error("Expected AAPointsPerLevel to be updated")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test more complex scenarios
|
|
func TestComplexScenarios(t *testing.T) {
|
|
t.Run("MultiplePlayersMultipleAAs", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Add multiple AAs
|
|
for i := 1; i <= 5; i++ {
|
|
aa := &AltAdvanceData{
|
|
SpellID: int32(100 + i),
|
|
NodeID: int32(200 + i),
|
|
Name: fmt.Sprintf("Test AA %d", i),
|
|
Group: AA_CLASS,
|
|
MaxRank: 5,
|
|
RankCost: 2,
|
|
MinLevel: 1,
|
|
}
|
|
manager.masterAAList.AddAltAdvancement(aa)
|
|
}
|
|
|
|
// Create multiple players
|
|
players := []int32{123, 124, 125}
|
|
for _, playerID := range players {
|
|
state, err := manager.GetPlayerAAState(playerID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get player state: %v", err)
|
|
}
|
|
|
|
state.TotalPoints = 20
|
|
state.AvailablePoints = 20
|
|
|
|
// Each player purchases different AAs
|
|
nodeID := int32(200 + int(playerID-122))
|
|
err = manager.PurchaseAA(playerID, nodeID, 1)
|
|
if err != nil {
|
|
t.Errorf("Failed to purchase AA for player %d: %v", playerID, err)
|
|
}
|
|
}
|
|
|
|
// Verify each player has their purchase
|
|
for _, playerID := range players {
|
|
state := manager.getPlayerState(playerID)
|
|
if state == nil {
|
|
t.Errorf("Player state not found for %d", playerID)
|
|
continue
|
|
}
|
|
|
|
if len(state.AAProgress) != 1 {
|
|
t.Errorf("Expected 1 AA progress for player %d, got %d", playerID, len(state.AAProgress))
|
|
}
|
|
}
|
|
|
|
// Manually update statistics since background processes aren't running
|
|
manager.updateStatistics()
|
|
|
|
// Test system stats
|
|
stats := manager.GetSystemStats()
|
|
if stats.ActivePlayers != 3 {
|
|
t.Errorf("Expected 3 active players, got %d", stats.ActivePlayers)
|
|
}
|
|
})
|
|
|
|
t.Run("TemplateOperationsWithMultipleTemplates", func(t *testing.T) {
|
|
config := DefaultAAManagerConfig()
|
|
manager := NewAAManager(config)
|
|
mockDB := &mockAADatabase{}
|
|
manager.SetDatabase(mockDB)
|
|
|
|
// Create player
|
|
state, _ := manager.GetPlayerAAState(123)
|
|
|
|
// Create multiple templates
|
|
templateNames := []string{"Build 1", "Build 2", "Build 3"}
|
|
templateIDs := []int8{AA_TEMPLATE_PERSONAL_1, AA_TEMPLATE_PERSONAL_2, AA_TEMPLATE_PERSONAL_3}
|
|
|
|
for i, templateID := range templateIDs {
|
|
err := manager.SaveAATemplate(123, templateID, templateNames[i])
|
|
if err != nil {
|
|
t.Errorf("Failed to save template %d: %v", templateID, err)
|
|
}
|
|
}
|
|
|
|
// Verify all templates were created
|
|
templates, err := manager.GetAATemplates(123)
|
|
if err != nil {
|
|
t.Errorf("Failed to get templates: %v", err)
|
|
}
|
|
|
|
if len(templates) != 3 {
|
|
t.Errorf("Expected 3 templates, got %d", len(templates))
|
|
}
|
|
|
|
for i, templateID := range templateIDs {
|
|
template := templates[templateID]
|
|
if template == nil {
|
|
t.Errorf("Template %d not found", templateID)
|
|
continue
|
|
}
|
|
|
|
if template.Name != templateNames[i] {
|
|
t.Errorf("Expected template name %s, got %s", templateNames[i], template.Name)
|
|
}
|
|
}
|
|
|
|
// Test changing between templates
|
|
for _, templateID := range templateIDs {
|
|
err := manager.ChangeAATemplate(123, templateID)
|
|
if err != nil {
|
|
t.Errorf("Failed to change to template %d: %v", templateID, err)
|
|
}
|
|
|
|
if state.ActiveTemplate != templateID {
|
|
t.Errorf("Expected active template %d, got %d", templateID, state.ActiveTemplate)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|