3005 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 &copy
}
// 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]any, error) {
return make(map[string]any), 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]any, 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)
}
}
})
}