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