eq2go/internal/alt_advancement/alt_advancement_test.go

533 lines
15 KiB
Go

package alt_advancement
import (
"fmt"
"testing"
"time"
)
// Mock logger implementation for tests
type mockLogger struct {
logs []string
}
func (m *mockLogger) LogInfo(system, format string, args ...interface{}) {
m.logs = append(m.logs, fmt.Sprintf("[INFO][%s] "+format, append([]interface{}{system}, args...)...))
}
func (m *mockLogger) LogError(system, format string, args ...interface{}) {
m.logs = append(m.logs, fmt.Sprintf("[ERROR][%s] "+format, append([]interface{}{system}, args...)...))
}
func (m *mockLogger) LogDebug(system, format string, args ...interface{}) {
m.logs = append(m.logs, fmt.Sprintf("[DEBUG][%s] "+format, append([]interface{}{system}, args...)...))
}
func (m *mockLogger) LogWarning(system, format string, args ...interface{}) {
m.logs = append(m.logs, fmt.Sprintf("[WARNING][%s] "+format, append([]interface{}{system}, args...)...))
}
// Mock player manager implementation for tests
type mockPlayerManager struct {
players map[int32]*mockPlayer
}
type mockPlayer struct {
level int8
class int8
totalPoints int32
spentPoints int32
availPoints int32
expansions int32
name string
}
func newMockPlayerManager() *mockPlayerManager {
return &mockPlayerManager{
players: make(map[int32]*mockPlayer),
}
}
func (m *mockPlayerManager) addPlayer(characterID int32, level, class int8, totalPoints, spentPoints, availPoints int32, expansions int32, name string) {
m.players[characterID] = &mockPlayer{
level: level,
class: class,
totalPoints: totalPoints,
spentPoints: spentPoints,
availPoints: availPoints,
expansions: expansions,
name: name,
}
}
func (m *mockPlayerManager) GetPlayerLevel(characterID int32) (int8, error) {
if player, exists := m.players[characterID]; exists {
return player.level, nil
}
return 0, fmt.Errorf("player %d not found", characterID)
}
func (m *mockPlayerManager) GetPlayerClass(characterID int32) (int8, error) {
if player, exists := m.players[characterID]; exists {
return player.class, nil
}
return 0, fmt.Errorf("player %d not found", characterID)
}
func (m *mockPlayerManager) GetPlayerAAPoints(characterID int32) (total, spent, available int32, err error) {
if player, exists := m.players[characterID]; exists {
return player.totalPoints, player.spentPoints, player.availPoints, nil
}
return 0, 0, 0, fmt.Errorf("player %d not found", characterID)
}
func (m *mockPlayerManager) SpendAAPoints(characterID int32, points int32) error {
if player, exists := m.players[characterID]; exists {
if player.availPoints < points {
return fmt.Errorf("insufficient AA points: need %d, have %d", points, player.availPoints)
}
player.availPoints -= points
player.spentPoints += points
return nil
}
return fmt.Errorf("player %d not found", characterID)
}
func (m *mockPlayerManager) AwardAAPoints(characterID int32, points int32, reason string) error {
if player, exists := m.players[characterID]; exists {
player.totalPoints += points
player.availPoints += points
return nil
}
return fmt.Errorf("player %d not found", characterID)
}
func (m *mockPlayerManager) GetPlayerExpansions(characterID int32) (int32, error) {
if player, exists := m.players[characterID]; exists {
return player.expansions, nil
}
return 0, fmt.Errorf("player %d not found", characterID)
}
func (m *mockPlayerManager) GetPlayerName(characterID int32) (string, error) {
if player, exists := m.players[characterID]; exists {
return player.name, nil
}
return "", fmt.Errorf("player %d not found", characterID)
}
// Helper function to set up test manager without database dependencies
func setupTestManager() (*AAManager, *mockLogger) {
logger := &mockLogger{}
config := DefaultAAConfig()
config.AutoSave = false // Disable auto-save for tests
config.DatabaseEnabled = false // Disable database for tests
manager := NewAAManager(nil, logger, config)
return manager, logger
}
// Helper to create test AA data
func createTestAA(nodeID int32, name string, group int8, minLevel int8, maxRank int8, rankCost int8) *AltAdvancement {
return &AltAdvancement{
NodeID: nodeID,
Name: name,
Description: fmt.Sprintf("Test AA: %s", name),
Group: group,
MinLevel: minLevel,
MaxRank: maxRank,
RankCost: rankCost,
Icon: 100,
Col: 1,
Row: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}
func TestSimpleAAManager(t *testing.T) {
// Test basic manager creation
manager, logger := setupTestManager()
if manager == nil {
t.Fatal("Expected manager to be non-nil")
}
// Test adding AAs manually (without database)
aa1 := createTestAA(1, "Test AA", AA_CLASS, 10, 5, 2)
manager.altAdvancements[1] = aa1
manager.byGroup[AA_CLASS] = []*AltAdvancement{aa1}
// Test retrieval
retrieved, exists := manager.GetAltAdvancement(1)
if !exists {
t.Error("Expected to find AA with ID 1")
}
if retrieved.Name != "Test AA" {
t.Errorf("Expected name 'Test AA', got '%s'", retrieved.Name)
}
// Test group lookup
classAAs := manager.GetAltAdvancementsByGroup(AA_CLASS)
if len(classAAs) != 1 {
t.Errorf("Expected 1 class AA, got %d", len(classAAs))
}
// Check logger was used
if len(logger.logs) == 0 {
t.Log("Note: No log entries recorded (this is normal for basic tests)")
}
}
func TestPlayerAAState(t *testing.T) {
characterID := int32(12345)
state := NewPlayerAAState(characterID)
if state == nil {
t.Fatal("Expected state to be non-nil")
}
if state.CharacterID != characterID {
t.Errorf("Expected CharacterID %d, got %d", characterID, state.CharacterID)
}
if state.TotalPoints != 0 {
t.Errorf("Expected TotalPoints 0, got %d", state.TotalPoints)
}
if state.ActiveTemplate != AA_TEMPLATE_CURRENT {
t.Errorf("Expected ActiveTemplate %d, got %d", AA_TEMPLATE_CURRENT, state.ActiveTemplate)
}
if state.Templates == nil {
t.Error("Expected Templates to be non-nil")
}
if state.Tabs == nil {
t.Error("Expected Tabs to be non-nil")
}
if state.AAProgress == nil {
t.Error("Expected AAProgress to be non-nil")
}
}
func TestAATemplate(t *testing.T) {
templateID := int8(AA_TEMPLATE_PERSONAL_1)
name := "Test Template"
template := NewAATemplate(templateID, name)
if template == nil {
t.Fatal("Expected template to be non-nil")
}
if template.TemplateID != templateID {
t.Errorf("Expected TemplateID %d, got %d", templateID, template.TemplateID)
}
if template.Name != name {
t.Errorf("Expected name '%s', got '%s'", name, template.Name)
}
if !template.IsPersonal {
t.Error("Expected template to be personal")
}
if template.IsServer {
t.Error("Expected template not to be server template")
}
if template.Entries == nil {
t.Error("Expected Entries to be non-nil")
}
}
func TestAATab(t *testing.T) {
tabID := int8(AA_CLASS)
group := int8(AA_CLASS)
name := "Class"
tab := NewAATab(tabID, group, name)
if tab == nil {
t.Fatal("Expected tab to be non-nil")
}
if tab.TabID != tabID {
t.Errorf("Expected TabID %d, got %d", tabID, tab.TabID)
}
if tab.Group != group {
t.Errorf("Expected Group %d, got %d", group, tab.Group)
}
if tab.Name != name {
t.Errorf("Expected name '%s', got '%s'", name, tab.Name)
}
if tab.Nodes == nil {
t.Error("Expected Nodes to be non-nil")
}
}
func TestConstants(t *testing.T) {
// Test tab names
className := GetTabName(AA_CLASS)
if className != "Class" {
t.Errorf("Expected 'Class', got '%s'", className)
}
unknownName := GetTabName(99)
if unknownName != "Unknown" {
t.Errorf("Expected 'Unknown', got '%s'", unknownName)
}
// Test template names
personal1Name := GetTemplateName(AA_TEMPLATE_PERSONAL_1)
if personal1Name != "Personal Template 1" {
t.Errorf("Expected 'Personal Template 1', got '%s'", personal1Name)
}
// Test max AA for tabs
classMax := GetMaxAAForTab(AA_CLASS)
if classMax != 100 {
t.Errorf("Expected 100, got %d", classMax)
}
shadowsMax := GetMaxAAForTab(AA_SHADOW)
if shadowsMax != 70 {
t.Errorf("Expected 70, got %d", shadowsMax)
}
// Test expansion checks
combined := EXPANSION_RUINS_OF_KUNARK | EXPANSION_SHADOWS_OF_LUCLIN
if !IsExpansionRequired(combined, EXPANSION_RUINS_OF_KUNARK) {
t.Error("Expected expansion check to return true")
}
if IsExpansionRequired(EXPANSION_RUINS_OF_KUNARK, EXPANSION_SHADOWS_OF_LUCLIN) {
t.Error("Expected expansion check to return false")
}
// Test error messages
successMsg := GetAAErrorMessage(AA_ERROR_NONE)
if successMsg != "Success" {
t.Errorf("Expected 'Success', got '%s'", successMsg)
}
unknownMsg := GetAAErrorMessage(999)
if unknownMsg != "Unknown error" {
t.Errorf("Expected 'Unknown error', got '%s'", unknownMsg)
}
// Test template validation
if !ValidateTemplateID(AA_TEMPLATE_PERSONAL_1) {
t.Error("Expected valid template ID")
}
if ValidateTemplateID(0) {
t.Error("Expected invalid template ID")
}
// Test template type checks
if !IsPersonalTemplate(AA_TEMPLATE_PERSONAL_1) {
t.Error("Expected personal template")
}
if IsServerTemplate(AA_TEMPLATE_PERSONAL_1) {
t.Error("Expected not server template")
}
if !IsServerTemplate(AA_TEMPLATE_SERVER_1) {
t.Error("Expected server template")
}
if !IsCurrentTemplate(AA_TEMPLATE_CURRENT) {
t.Error("Expected current template")
}
// Test AA group validation
if !ValidateAAGroup(AA_CLASS) {
t.Error("Expected valid AA group")
}
if ValidateAAGroup(-1) {
t.Error("Expected invalid AA group")
}
}
func TestExpansionNames(t *testing.T) {
kunarkName := GetExpansionNameByFlag(EXPANSION_RUINS_OF_KUNARK)
if kunarkName != "Ruins of Kunark" {
t.Errorf("Expected 'Ruins of Kunark', got '%s'", kunarkName)
}
unknownExpansion := GetExpansionNameByFlag(999)
if unknownExpansion != "Unknown Expansion" {
t.Errorf("Expected 'Unknown Expansion', got '%s'", unknownExpansion)
}
}
func TestInMemoryOperations(t *testing.T) {
manager, _ := setupTestManager()
// Add test data
aa1 := createTestAA(1, "Fireball", AA_CLASS, 10, 5, 1)
aa2 := createTestAA(2, "Ice Bolt", AA_CLASS, 15, 3, 2)
aa3 := createTestAA(3, "Heal", AA_SUBCLASS, 8, 4, 1)
manager.altAdvancements[1] = aa1
manager.altAdvancements[2] = aa2
manager.altAdvancements[3] = aa3
manager.byGroup[AA_CLASS] = []*AltAdvancement{aa1, aa2}
manager.byGroup[AA_SUBCLASS] = []*AltAdvancement{aa3}
manager.byLevel[10] = []*AltAdvancement{aa1}
manager.byLevel[15] = []*AltAdvancement{aa2}
manager.byLevel[8] = []*AltAdvancement{aa3}
// Test retrievals
classAAs := manager.GetAltAdvancementsByGroup(AA_CLASS)
if len(classAAs) != 2 {
t.Errorf("Expected 2 class AAs, got %d", len(classAAs))
}
level10AAs := manager.GetAltAdvancementsByLevel(10)
if len(level10AAs) != 1 {
t.Errorf("Expected 1 level 10 AA, got %d", len(level10AAs))
}
// Test player state operations
characterID := int32(12345)
state := NewPlayerAAState(characterID)
manager.playerStates[characterID] = state
stats := manager.GetPlayerAAStats(characterID)
if stats == nil {
t.Fatal("Expected stats to be non-nil")
}
if stats["character_id"] != characterID {
t.Errorf("Expected character_id %d, got %v", characterID, stats["character_id"])
}
}
func TestSystemStats(t *testing.T) {
manager, _ := setupTestManager()
// Set some test stats
manager.stats.TotalAAsLoaded = 150
manager.stats.TotalAAPurchases = 25
manager.stats.TotalPointsSpent = 500
// Add some player states
manager.playerStates[1] = NewPlayerAAState(1)
manager.playerStates[2] = NewPlayerAAState(2)
stats := manager.GetSystemStats()
if stats == nil {
t.Fatal("Expected stats to be non-nil")
}
if stats.TotalAAsLoaded != 150 {
t.Errorf("Expected TotalAAsLoaded 150, got %d", stats.TotalAAsLoaded)
}
if stats.ActivePlayers != 2 {
t.Errorf("Expected ActivePlayers 2, got %d", stats.ActivePlayers)
}
if stats.AveragePointsSpent != 20.0 { // 500 / 25
t.Errorf("Expected AveragePointsSpent 20.0, got %f", stats.AveragePointsSpent)
}
}
func TestMockPlayerManager(t *testing.T) {
playerManager := newMockPlayerManager()
characterID := int32(12345)
// Test player not found
_, err := playerManager.GetPlayerLevel(characterID)
if err == nil {
t.Error("Expected error for non-existent player")
}
// Add player and test
playerManager.addPlayer(characterID, 25, 3, 100, 50, 50, EXPANSION_RUINS_OF_KUNARK, "TestPlayer")
level, err := playerManager.GetPlayerLevel(characterID)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if level != 25 {
t.Errorf("Expected level 25, got %d", level)
}
class, err := playerManager.GetPlayerClass(characterID)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if class != 3 {
t.Errorf("Expected class 3, got %d", class)
}
total, spent, avail, err := playerManager.GetPlayerAAPoints(characterID)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if total != 100 || spent != 50 || avail != 50 {
t.Errorf("Expected points 100/50/50, got %d/%d/%d", total, spent, avail)
}
// Test spending points
err = playerManager.SpendAAPoints(characterID, 10)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
player := playerManager.players[characterID]
if player.availPoints != 40 {
t.Errorf("Expected availPoints 40, got %d", player.availPoints)
}
if player.spentPoints != 60 {
t.Errorf("Expected spentPoints 60, got %d", player.spentPoints)
}
// Test insufficient points
err = playerManager.SpendAAPoints(characterID, 100)
if err == nil {
t.Error("Expected error for insufficient points")
}
}
func TestPacketBuilding(t *testing.T) {
manager, _ := setupTestManager()
characterID := int32(12345)
clientVersion := uint32(1142)
// Add some test AAs
manager.altAdvancements[1] = createTestAA(1, "Test AA 1", AA_CLASS, 10, 5, 2)
manager.altAdvancements[2] = createTestAA(2, "Test AA 2", AA_SUBCLASS, 15, 3, 1)
// Set up player state
state := NewPlayerAAState(characterID)
state.TotalPoints = 100
state.SpentPoints = 60
state.AvailablePoints = 40
manager.playerStates[characterID] = state
// Test GetAAListPacket - should find packet but fail on missing fields
_, err := manager.GetAAListPacket(characterID, clientVersion)
if err == nil {
t.Error("Expected error due to missing packet fields")
}
if !contains(err.Error(), "failed to build AA packet") {
t.Errorf("Expected 'failed to build AA packet' error, got: %v", err)
}
// Test DisplayAA
_, err = manager.DisplayAA(characterID, AA_TEMPLATE_CURRENT, 0, clientVersion)
if err == nil {
t.Error("Expected error due to missing packet fields")
}
// Test SendAAListPacket wrapper
_, err = manager.SendAAListPacket(characterID, clientVersion)
if err == nil {
t.Error("Expected error due to missing packet fields")
}
// Verify packet errors are tracked (we successfully found packets but failed to build them)
if manager.stats.PacketErrors < 3 {
t.Errorf("Expected at least 3 packet errors, got %d", manager.stats.PacketErrors)
}
t.Logf("Packet integration working: found AdventureList packet but needs proper field mapping")
}
// Helper function to check if string contains substring
func contains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}