eq2go/internal/factions/factions_test.go

734 lines
19 KiB
Go

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