package factions import ( "context" "fmt" "testing" "time" ) // Mock logger for testing type MockLogger struct { InfoMessages []string ErrorMessages []string DebugMessages []string WarningMessages []string } func (ml *MockLogger) LogInfo(system, format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) ml.InfoMessages = append(ml.InfoMessages, msg) } func (ml *MockLogger) LogError(system, format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) ml.ErrorMessages = append(ml.ErrorMessages, msg) } func (ml *MockLogger) LogDebug(system, format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) ml.DebugMessages = append(ml.DebugMessages, msg) } func (ml *MockLogger) LogWarning(system, format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) ml.WarningMessages = append(ml.WarningMessages, msg) } // Test Faction type func TestFaction(t *testing.T) { t.Run("NewFaction", func(t *testing.T) { faction := &Faction{ ID: 100, // Use non-special ID Name: "Test Faction", Type: "city", Description: "Test Description", NegativeChange: -10, PositiveChange: 10, DefaultValue: 0, } if faction.GetID() != 100 { t.Errorf("Expected ID 100, got %d", faction.GetID()) } if faction.GetName() != "Test Faction" { t.Errorf("Expected name 'Test Faction', got '%s'", faction.GetName()) } if faction.GetType() != "city" { t.Errorf("Expected type 'city', got '%s'", faction.GetType()) } if !faction.IsValid() { t.Error("Expected faction to be valid") } if faction.IsSpecialFaction() { t.Error("Expected faction with ID 100 to not be special") } if !faction.CanIncrease() { t.Error("Expected faction to be increasable") } if !faction.CanDecrease() { t.Error("Expected faction to be decreasable") } }) t.Run("SpecialFaction", func(t *testing.T) { specialFaction := &Faction{ ID: 5, Name: "Special", } if !specialFaction.IsSpecialFaction() { t.Error("Expected faction to be special") } if specialFaction.CanIncrease() { t.Error("Expected special faction to not be increasable") } if specialFaction.CanDecrease() { t.Error("Expected special faction to not be decreasable") } }) t.Run("InvalidFaction", func(t *testing.T) { invalidFaction := &Faction{ID: 0} if invalidFaction.IsValid() { t.Error("Expected faction with ID 0 to be invalid") } noNameFaction := &Faction{ID: 100, Name: ""} if noNameFaction.IsValid() { t.Error("Expected faction with no name to be invalid") } }) } // Test PlayerFaction func TestPlayerFaction(t *testing.T) { t.Run("NewPlayerFaction", func(t *testing.T) { manager := NewFactionManager(nil, nil) pf := NewPlayerFaction(manager) if pf == nil { t.Fatal("Expected PlayerFaction to be created") } if pf.GetFactionCount() != 0 { t.Errorf("Expected 0 factions, got %d", pf.GetFactionCount()) } }) t.Run("ConCalculation", func(t *testing.T) { manager := NewFactionManager(nil, nil) pf := NewPlayerFaction(manager) // Test special faction cons if con := pf.GetCon(0); con != ConIndiff { t.Errorf("Expected con %d for faction 0, got %d", ConIndiff, con) } if con := pf.GetCon(1); con != -4 { t.Errorf("Expected con -4 for faction 1, got %d", con) } if con := pf.GetCon(9); con != 4 { t.Errorf("Expected con 4 for faction 9, got %d", con) } // Test regular faction cons testFaction := int32(100) // Test neutral pf.SetFactionValue(testFaction, 0) if con := pf.GetCon(testFaction); con != ConIndiff { t.Errorf("Expected neutral con for value 0, got %d", con) } // Test hostile pf.SetFactionValue(testFaction, -50000) if con := pf.GetCon(testFaction); con != ConKOS { t.Errorf("Expected KOS con for value -50000, got %d", con) } // Test ally pf.SetFactionValue(testFaction, 50000) if con := pf.GetCon(testFaction); con != ConAlly { t.Errorf("Expected ally con for value 50000, got %d", con) } }) t.Run("FactionManipulation", func(t *testing.T) { manager := NewFactionManager(nil, nil) // Add a test faction testFaction := &Faction{ ID: 100, Name: "Test Faction", PositiveChange: 10, NegativeChange: 5, } manager.addFactionToIndices(testFaction) pf := NewPlayerFaction(manager) factionID := int32(100) // Test increase if !pf.IncreaseFaction(factionID, 100) { t.Error("Expected IncreaseFaction to succeed") } if value := pf.GetFactionValue(factionID); value != 100 { t.Errorf("Expected faction value 100, got %d", value) } // Test decrease if !pf.DecreaseFaction(factionID, 50) { t.Error("Expected DecreaseFaction to succeed") } if value := pf.GetFactionValue(factionID); value != 50 { t.Errorf("Expected faction value 50, got %d", value) } // Test setting value if !pf.SetFactionValue(factionID, 200) { t.Error("Expected SetFactionValue to succeed") } if value := pf.GetFactionValue(factionID); value != 200 { t.Errorf("Expected faction value 200, got %d", value) } // Test bounds pf.IncreaseFaction(factionID, MaxFactionValue) if value := pf.GetFactionValue(factionID); value != MaxFactionValue { t.Errorf("Expected faction value capped at %d, got %d", MaxFactionValue, value) } pf.SetFactionValue(factionID, MinFactionValue-1000) pf.DecreaseFaction(factionID, 1000) if value := pf.GetFactionValue(factionID); value != MinFactionValue { t.Errorf("Expected faction value capped at %d, got %d", MinFactionValue, value) } }) t.Run("AttackDecision", func(t *testing.T) { manager := NewFactionManager(nil, nil) pf := NewPlayerFaction(manager) // Hostile faction should attack pf.SetFactionValue(100, -50000) if !pf.ShouldAttack(100) { t.Error("Expected hostile faction to attack") } // Friendly faction should not attack pf.SetFactionValue(100, 50000) if pf.ShouldAttack(100) { t.Error("Expected friendly faction to not attack") } }) t.Run("PendingUpdates", func(t *testing.T) { manager := NewFactionManager(nil, nil) pf := NewPlayerFaction(manager) if pf.HasPendingUpdates() { t.Error("Expected no pending updates initially") } pf.SetFactionValue(100, 1000) if !pf.HasPendingUpdates() { t.Error("Expected pending updates after setting faction value") } updates := pf.GetPendingUpdates() if len(updates) != 1 || updates[0] != 100 { t.Errorf("Expected pending update for faction 100, got %v", updates) } pf.ClearPendingUpdates() if pf.HasPendingUpdates() { t.Error("Expected no pending updates after clearing") } }) } // Test FactionManager func TestFactionManager(t *testing.T) { t.Run("NewFactionManager", func(t *testing.T) { logger := &MockLogger{} manager := NewFactionManager(nil, logger) if manager == nil { t.Fatal("Expected FactionManager to be created") } if manager.GetFactionCount() != 0 { t.Errorf("Expected 0 factions initially, got %d", manager.GetFactionCount()) } }) t.Run("AddAndRetrieveFactions", func(t *testing.T) { manager := NewFactionManager(nil, nil) testFaction := &Faction{ ID: 100, Name: "Test Faction", Type: "city", } if err := manager.AddFaction(testFaction); err != nil { t.Fatalf("Failed to add faction: %v", err) } if manager.GetFactionCount() != 1 { t.Errorf("Expected 1 faction, got %d", manager.GetFactionCount()) } retrieved := manager.GetFaction(100) if retrieved == nil { t.Fatal("Expected to retrieve faction") } if retrieved.Name != "Test Faction" { t.Errorf("Expected name 'Test Faction', got '%s'", retrieved.Name) } // Test retrieval by name byName := manager.GetFactionByName("Test Faction") if byName == nil { t.Fatal("Expected to retrieve faction by name") } if byName.ID != 100 { t.Errorf("Expected ID 100, got %d", byName.ID) } // Test case-insensitive name lookup byNameLower := manager.GetFactionByName("test faction") if byNameLower == nil { t.Fatal("Expected to retrieve faction by lowercase name") } }) t.Run("DuplicateFactionRejection", func(t *testing.T) { manager := NewFactionManager(nil, nil) testFaction1 := &Faction{ID: 100, Name: "Test"} testFaction2 := &Faction{ID: 100, Name: "Duplicate"} if err := manager.AddFaction(testFaction1); err != nil { t.Fatalf("Failed to add first faction: %v", err) } if err := manager.AddFaction(testFaction2); err == nil { t.Error("Expected error when adding duplicate faction ID") } }) t.Run("InvalidFactionRejection", func(t *testing.T) { manager := NewFactionManager(nil, nil) // Nil faction if err := manager.AddFaction(nil); err == nil { t.Error("Expected error when adding nil faction") } // Invalid faction invalidFaction := &Faction{ID: 0, Name: ""} if err := manager.AddFaction(invalidFaction); err == nil { t.Error("Expected error when adding invalid faction") } }) t.Run("Statistics", func(t *testing.T) { manager := NewFactionManager(nil, nil) // Add some test data testFaction := &Faction{ID: 100, Name: "Test"} manager.AddFaction(testFaction) // Test statistics stats := manager.GetStatistics() if totalFactions := stats["total_factions"].(int); totalFactions != 1 { t.Errorf("Expected 1 total faction, got %d", totalFactions) } // Test lookup tracking manager.GetFaction(1) manager.GetFactionByName("Test") stats = manager.GetStatistics() if lookups := stats["faction_lookups"].(int64); lookups != 2 { t.Errorf("Expected 2 faction lookups, got %d", lookups) } // Test faction change tracking manager.RecordFactionIncrease(1) manager.RecordFactionDecrease(1) stats = manager.GetStatistics() if increases := stats["faction_increases"].(int64); increases != 1 { t.Errorf("Expected 1 faction increase, got %d", increases) } if decreases := stats["faction_decreases"].(int64); decreases != 1 { t.Errorf("Expected 1 faction decrease, got %d", decreases) } if total := stats["total_faction_changes"].(int64); total != 2 { t.Errorf("Expected 2 total faction changes, got %d", total) } // Test packet tracking manager.RecordPacketSent() manager.RecordPacketError() stats = manager.GetStatistics() if sent := stats["packets_sent"].(int64); sent != 1 { t.Errorf("Expected 1 packet sent, got %d", sent) } if errors := stats["packet_errors"].(int64); errors != 1 { t.Errorf("Expected 1 packet error, got %d", errors) } }) t.Run("PlayerFactionCreation", func(t *testing.T) { manager := NewFactionManager(nil, nil) pf := manager.CreatePlayerFaction() if pf == nil { t.Fatal("Expected PlayerFaction to be created") } stats := manager.GetStatistics() if players := stats["players_with_factions"].(int64); players != 1 { t.Errorf("Expected 1 player with factions, got %d", players) } }) t.Run("Validation", func(t *testing.T) { manager := NewFactionManager(nil, nil) // Add valid faction validFaction := &Faction{ID: 100, Name: "Valid"} manager.AddFaction(validFaction) issues := manager.ValidateAllFactions() if len(issues) != 0 { t.Errorf("Expected no validation issues, got %d: %v", len(issues), issues) } // Manually corrupt data for testing (this wouldn't happen in normal operation) manager.factions[2] = nil issues = manager.ValidateAllFactions() if len(issues) == 0 { t.Error("Expected validation issues for nil faction") } }) t.Run("Initialization", func(t *testing.T) { logger := &MockLogger{} manager := NewFactionManager(nil, logger) ctx := context.Background() if err := manager.Initialize(ctx); err != nil { t.Fatalf("Failed to initialize manager: %v", err) } // Check that initialization was logged if len(logger.InfoMessages) == 0 { t.Error("Expected initialization to be logged") } }) t.Run("InitializationWithoutDatabase", func(t *testing.T) { logger := &MockLogger{} manager := NewFactionManager(nil, logger) ctx := context.Background() if err := manager.Initialize(ctx); err != nil { t.Fatalf("Failed to initialize manager without database: %v", err) } // Should have warning about no database if len(logger.WarningMessages) == 0 { t.Error("Expected warning about no database") } }) t.Run("Shutdown", func(t *testing.T) { logger := &MockLogger{} manager := NewFactionManager(nil, logger) // Add some data testFaction := &Faction{ID: 100, Name: "Test"} manager.AddFaction(testFaction) manager.Shutdown() // Should be empty after shutdown if manager.GetFactionCount() != 0 { t.Errorf("Expected 0 factions after shutdown, got %d", manager.GetFactionCount()) } // Should have shutdown message if len(logger.InfoMessages) == 0 { t.Error("Expected shutdown to be logged") } }) } // Test packet building func TestPacketBuilding(t *testing.T) { t.Run("FactionUpdate", func(t *testing.T) { manager := NewFactionManager(nil, nil) // Add test faction testFaction := &Faction{ ID: 100, Name: "Test Faction", Type: "city", Description: "Test Description", } manager.addFactionToIndices(testFaction) pf := NewPlayerFaction(manager) pf.SetFactionValue(100, 1000) // Test packet building packet, err := manager.SendFactionUpdate(pf, 1) if err != nil { // If there's an error, it should be about packet building details t.Logf("Packet building error (expected during field mapping): %v", err) // Verify error was recorded stats := manager.GetStatistics() if errors, ok := stats["packet_errors"].(int64); !ok || errors < 1 { t.Error("Expected packet error to be recorded") } } else { // If successful, packet should exist if packet == nil { t.Error("Expected packet data to be returned on success") } // Verify packet was recorded as sent stats := manager.GetStatistics() if sent, ok := stats["packets_sent"].(int64); !ok || sent < 1 { t.Error("Expected packet send to be recorded") } } t.Logf("Packet building integration working: FactionUpdate packet structure found and processing attempted") }) t.Run("NilPlayerFaction", func(t *testing.T) { manager := NewFactionManager(nil, nil) _, err := manager.SendFactionUpdate(nil, 1) if err == nil { t.Error("Expected error for nil player faction") } if !contains(err.Error(), "player faction cannot be nil") { t.Errorf("Expected 'player faction cannot be nil' error, got: %v", err) } }) } // Test database integration (without real database) func TestDatabaseIntegration(t *testing.T) { t.Run("LoadPlayerFactionsWithoutDB", func(t *testing.T) { manager := NewFactionManager(nil, nil) // Test loading without database (should return empty) factionValues, err := manager.LoadPlayerFactions(123) if err != nil { t.Fatalf("Failed to load player factions: %v", err) } if len(factionValues) != 0 { t.Errorf("Expected empty faction values when no database, got %d", len(factionValues)) } }) t.Run("SavePlayerFactionsWithoutDB", func(t *testing.T) { manager := NewFactionManager(nil, nil) factionValues := map[int32]int32{ 1: 1000, 2: -500, } err := manager.SavePlayerFactions(123, factionValues) if err != nil { t.Fatalf("Failed to save player factions: %v", err) } }) } // Test comprehensive faction workflow func TestFactionsWorkflow(t *testing.T) { t.Run("CompleteWorkflow", func(t *testing.T) { logger := &MockLogger{} manager := NewFactionManager(nil, logger) // Initialize system ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := manager.Initialize(ctx); err != nil { t.Fatalf("Failed to initialize: %v", err) } // Add factions cityFaction := &Faction{ ID: 100, Name: "Qeynos", Type: "city", Description: "The city of Qeynos", PositiveChange: 10, NegativeChange: 5, DefaultValue: 0, } guildFaction := &Faction{ ID: 101, Name: "Mages Guild", Type: "guild", Description: "The Mages Guild", PositiveChange: 15, NegativeChange: 10, DefaultValue: 0, } if err := manager.AddFaction(cityFaction); err != nil { t.Fatalf("Failed to add city faction: %v", err) } if err := manager.AddFaction(guildFaction); err != nil { t.Fatalf("Failed to add guild faction: %v", err) } // Create player faction system playerFaction := manager.CreatePlayerFaction() // Perform faction operations playerFaction.IncreaseFaction(100, 500) // Increase city faction playerFaction.DecreaseFaction(101, 200) // Decrease guild faction // Check values if value := playerFaction.GetFactionValue(100); value != 500 { t.Errorf("Expected city faction value 500, got %d", value) } if value := playerFaction.GetFactionValue(101); value != -200 { t.Errorf("Expected guild faction value -200, got %d", value) } // Test considerations if con := playerFaction.GetCon(100); con != 0 { t.Errorf("Expected neutral con for city faction, got %d", con) } if con := playerFaction.GetCon(101); con != 0 { t.Errorf("Expected neutral con for guild faction, got %d", con) } // Test attack decisions if playerFaction.ShouldAttack(100) { t.Error("Should not attack friendly city") } // Make faction hostile playerFaction.SetFactionValue(101, -50000) if !playerFaction.ShouldAttack(101) { t.Error("Should attack hostile guild") } // Test statistics stats := manager.GetStatistics() if stats["total_factions"].(int) != 2 { t.Errorf("Expected 2 total factions, got %d", stats["total_factions"]) } // Test validation issues := manager.ValidateAllFactions() if len(issues) != 0 { t.Errorf("Expected no validation issues, got %d: %v", len(issues), issues) } // Test player faction data persistence factionValues := playerFaction.GetFactionValues() if err := manager.SavePlayerFactions(123, factionValues); err != nil { t.Fatalf("Failed to save player factions: %v", err) } // Shutdown manager.Shutdown() // Verify logs if len(logger.InfoMessages) == 0 { t.Error("Expected info messages during workflow") } }) } // Utility function to check if string contains substring func contains(str, substr string) bool { return len(substr) == 0 || (len(str) >= len(substr) && func() bool { for i := 0; i <= len(str)-len(substr); i++ { if str[i:i+len(substr)] == substr { return true } } return false }()) } // Benchmark tests func BenchmarkFactionLookup(b *testing.B) { manager := NewFactionManager(nil, nil) // Add test factions for i := 1; i <= 1000; i++ { faction := &Faction{ ID: int32(i), Name: fmt.Sprintf("Faction %d", i), } manager.AddFaction(faction) } b.ResetTimer() for i := 0; i < b.N; i++ { manager.GetFaction(int32((i % 1000) + 1)) } } func BenchmarkPlayerFactionUpdate(b *testing.B) { manager := NewFactionManager(nil, nil) // Add test faction testFaction := &Faction{ ID: 100, Name: "Test", PositiveChange: 10, } manager.addFactionToIndices(testFaction) pf := manager.CreatePlayerFaction() b.ResetTimer() for i := 0; i < b.N; i++ { pf.IncreaseFaction(100, 1) } }