package rules import ( "testing" "zombiezen.com/go/sqlite" ) // Test Rule creation and basic functionality func TestNewRule(t *testing.T) { rule := NewRule() if rule == nil { t.Fatal("NewRule() returned nil") } if rule.GetCategory() != 0 { t.Errorf("Expected category 0, got %d", rule.GetCategory()) } if rule.GetType() != 0 { t.Errorf("Expected type 0, got %d", rule.GetType()) } if rule.GetValue() != "" { t.Errorf("Expected empty value, got %s", rule.GetValue()) } if rule.GetCombined() != "NONE" { t.Errorf("Expected combined 'NONE', got %s", rule.GetCombined()) } } func TestNewRuleWithValues(t *testing.T) { rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") if rule == nil { t.Fatal("NewRuleWithValues() returned nil") } if rule.GetCategory() != CategoryPlayer { t.Errorf("Expected category %d, got %d", CategoryPlayer, rule.GetCategory()) } if rule.GetType() != PlayerMaxLevel { t.Errorf("Expected type %d, got %d", PlayerMaxLevel, rule.GetType()) } if rule.GetValue() != "50" { t.Errorf("Expected value '50', got %s", rule.GetValue()) } if rule.GetCombined() != "Player:MaxLevel" { t.Errorf("Expected combined 'Player:MaxLevel', got %s", rule.GetCombined()) } } func TestNewRuleFromRule(t *testing.T) { // Test with nil source rule := NewRuleFromRule(nil) if rule == nil { t.Error("NewRuleFromRule(nil) should return default rule, not nil") } // Test with valid source original := NewRuleWithValues(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") copy := NewRuleFromRule(original) if copy == nil { t.Fatal("NewRuleFromRule() returned nil") } if copy.GetCategory() != original.GetCategory() { t.Errorf("Copy category mismatch: expected %d, got %d", original.GetCategory(), copy.GetCategory()) } if copy.GetType() != original.GetType() { t.Errorf("Copy type mismatch: expected %d, got %d", original.GetType(), copy.GetType()) } if copy.GetValue() != original.GetValue() { t.Errorf("Copy value mismatch: expected %s, got %s", original.GetValue(), copy.GetValue()) } if copy.GetCombined() != original.GetCombined() { t.Errorf("Copy combined mismatch: expected %s, got %s", original.GetCombined(), copy.GetCombined()) } // Test that they are independent copies copy.SetValue("8.0") if original.GetValue() == copy.GetValue() { t.Error("Original and copy are not independent after modification") } } func TestRuleSetValue(t *testing.T) { rule := NewRule() rule.SetValue("test") if rule.GetValue() != "test" { t.Errorf("SetValue failed: expected 'test', got %s", rule.GetValue()) } } func TestRuleTypeConversions(t *testing.T) { rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") // Test integer conversions if rule.GetInt8() != 50 { t.Errorf("Expected int8 50, got %d", rule.GetInt8()) } if rule.GetInt16() != 50 { t.Errorf("Expected int16 50, got %d", rule.GetInt16()) } if rule.GetInt32() != 50 { t.Errorf("Expected int32 50, got %d", rule.GetInt32()) } if rule.GetInt64() != 50 { t.Errorf("Expected int64 50, got %d", rule.GetInt64()) } // Test unsigned integer conversions if rule.GetUInt8() != 50 { t.Errorf("Expected uint8 50, got %d", rule.GetUInt8()) } if rule.GetUInt16() != 50 { t.Errorf("Expected uint16 50, got %d", rule.GetUInt16()) } if rule.GetUInt32() != 50 { t.Errorf("Expected uint32 50, got %d", rule.GetUInt32()) } if rule.GetUInt64() != 50 { t.Errorf("Expected uint64 50, got %d", rule.GetUInt64()) } // Test boolean conversion (> 0 = true) if !rule.GetBool() { t.Error("Expected bool true for value '50'") } // Test float conversions rule.SetValue("3.14") if rule.GetFloat32() != 3.14 { t.Errorf("Expected float32 3.14, got %f", rule.GetFloat32()) } if rule.GetFloat64() != 3.14 { t.Errorf("Expected float64 3.14, got %f", rule.GetFloat64()) } // Test character conversion rule.SetValue("Hello") if rule.GetChar() != 'H' { t.Errorf("Expected char 'H', got %c", rule.GetChar()) } // Test string conversion if rule.GetString() != "Hello" { t.Errorf("Expected string 'Hello', got %s", rule.GetString()) } } func TestRuleTypeConversionsInvalid(t *testing.T) { rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "invalid", "Player:MaxLevel") // Invalid conversions should return zero values if rule.GetInt32() != 0 { t.Errorf("Expected int32 0 for invalid value, got %d", rule.GetInt32()) } if rule.GetFloat64() != 0.0 { t.Errorf("Expected float64 0.0 for invalid value, got %f", rule.GetFloat64()) } if rule.GetBool() != false { t.Error("Expected bool false for invalid value") } } func TestRuleIsValid(t *testing.T) { // Test invalid rule (default) rule := NewRule() if rule.IsValid() { t.Error("Default rule should not be valid") } // Test valid rule validRule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") if !validRule.IsValid() { t.Error("Rule with valid data should be valid") } } func TestRuleString(t *testing.T) { rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") str := rule.String() expected := "Rule{Player:MaxLevel: 50}" if str != expected { t.Errorf("Expected string %s, got %s", expected, str) } } // Test RuleSet creation and operations func TestNewRuleSet(t *testing.T) { ruleSet := NewRuleSet() if ruleSet == nil { t.Fatal("NewRuleSet() returned nil") } if ruleSet.GetID() != 0 { t.Errorf("Expected ID 0, got %d", ruleSet.GetID()) } if ruleSet.GetName() != "" { t.Errorf("Expected empty name, got %s", ruleSet.GetName()) } if ruleSet.Size() != 0 { t.Errorf("Expected size 0, got %d", ruleSet.Size()) } } func TestRuleSetIDAndName(t *testing.T) { ruleSet := NewRuleSet() // Test SetID and SetName ruleSet.SetID(1) ruleSet.SetName("Test Rule Set") if ruleSet.GetID() != 1 { t.Errorf("Expected ID 1, got %d", ruleSet.GetID()) } if ruleSet.GetName() != "Test Rule Set" { t.Errorf("Expected name 'Test Rule Set', got %s", ruleSet.GetName()) } } func TestRuleSetAddRule(t *testing.T) { ruleSet := NewRuleSet() // Test adding nil rule ruleSet.AddRule(nil) // Should not crash if ruleSet.Size() != 0 { t.Error("Adding nil rule should not increase size") } // Test adding valid rules rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") rule2 := NewRuleWithValues(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") ruleSet.AddRule(rule1) ruleSet.AddRule(rule2) if ruleSet.Size() != 2 { t.Errorf("Expected size 2, got %d", ruleSet.Size()) } } func TestRuleSetGetRule(t *testing.T) { ruleSet := NewRuleSet() rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") ruleSet.AddRule(rule1) // Test rule retrieval retrievedRule := ruleSet.GetRule(CategoryPlayer, PlayerMaxLevel) if retrievedRule == nil { t.Fatal("GetRule() returned nil for added rule") } if retrievedRule.GetValue() != "50" { t.Errorf("Retrieved rule value mismatch: expected '50', got %s", retrievedRule.GetValue()) } // Test non-existent rule nonExistentRule := ruleSet.GetRule(CategorySpawn, SpawnSpeedMultiplier) if nonExistentRule != nil { t.Error("GetRule() should return nil for non-existent rule") } } func TestRuleSetGetRuleByName(t *testing.T) { ruleSet := NewRuleSet() rule := NewRuleWithValues(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") ruleSet.AddRule(rule) // Test rule retrieval by name retrievedRule := ruleSet.GetRuleByName("Combat", "MaxCombatRange") if retrievedRule == nil { t.Fatal("GetRuleByName() returned nil for added rule") } if retrievedRule.GetValue() != "4.0" { t.Errorf("Retrieved rule value mismatch: expected '4.0', got %s", retrievedRule.GetValue()) } // Test non-existent rule nonExistentRule := ruleSet.GetRuleByName("NonExistent", "Rule") if nonExistentRule != nil { t.Error("GetRuleByName() should return nil for non-existent rule") } } func TestRuleSetHasRule(t *testing.T) { ruleSet := NewRuleSet() rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") ruleSet.AddRule(rule) // Test HasRule if !ruleSet.HasRule(CategoryPlayer, PlayerMaxLevel) { t.Error("HasRule() returned false for existing rule") } if ruleSet.HasRule(CategorySpawn, SpawnSpeedMultiplier) { t.Error("HasRule() returned true for non-existing rule") } } func TestRuleSetGetRulesByCategory(t *testing.T) { ruleSet := NewRuleSet() // Add multiple player rules rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") rule2 := NewRuleWithValues(CategoryPlayer, PlayerVitalityAmount, "0.5", "Player:VitalityAmount") rule3 := NewRuleWithValues(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") ruleSet.AddRule(rule1) ruleSet.AddRule(rule2) ruleSet.AddRule(rule3) // Test getting rules by category playerRules := ruleSet.GetRulesByCategory(CategoryPlayer) if len(playerRules) != 2 { t.Errorf("Expected 2 player rules, got %d", len(playerRules)) } // Test empty category spawnRules := ruleSet.GetRulesByCategory(CategorySpawn) if len(spawnRules) != 0 { t.Errorf("Expected 0 spawn rules, got %d", len(spawnRules)) } } func TestRuleSetClearRules(t *testing.T) { ruleSet := NewRuleSet() rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") ruleSet.AddRule(rule) if ruleSet.Size() != 1 { t.Error("Rule set should have 1 rule before clearing") } ruleSet.ClearRules() if ruleSet.Size() != 0 { t.Error("Rule set should be empty after clearing") } } func TestRuleSetCopyRulesInto(t *testing.T) { source := NewRuleSet() source.SetID(1) source.SetName("Source") rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") rule2 := NewRuleWithValues(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") source.AddRule(rule1) source.AddRule(rule2) target := NewRuleSet() target.SetID(2) target.SetName("Target") // Test copying rules target.CopyRulesInto(source) if target.Size() != 2 { t.Errorf("Target should have 2 rules after copying, got %d", target.Size()) } // Test that target still has its own ID and name if target.GetID() != 2 { t.Errorf("Target ID should remain 2, got %d", target.GetID()) } if target.GetName() != "Target" { t.Errorf("Target name should remain 'Target', got %s", target.GetName()) } } func TestNewRuleSetFromRuleSet(t *testing.T) { // Test with nil source nilCopy := NewRuleSetFromRuleSet(nil) if nilCopy == nil { t.Error("NewRuleSetFromRuleSet(nil) should return empty rule set, not nil") } // Test with valid source original := NewRuleSet() original.SetID(1) original.SetName("Original") rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") rule2 := NewRuleWithValues(CategoryCombat, CombatMaxRange, "4.0", "Combat:MaxCombatRange") original.AddRule(rule1) original.AddRule(rule2) // Test copy constructor copy := NewRuleSetFromRuleSet(original) if copy == nil { t.Fatal("NewRuleSetFromRuleSet() returned nil") } if copy.GetID() != original.GetID() { t.Errorf("Copy ID mismatch: expected %d, got %d", original.GetID(), copy.GetID()) } if copy.GetName() != original.GetName() { t.Errorf("Copy name mismatch: expected %s, got %s", original.GetName(), copy.GetName()) } if copy.Size() != original.Size() { t.Errorf("Copy size mismatch: expected %d, got %d", original.Size(), copy.Size()) } // Test that rules were copied correctly copyRule := copy.GetRule(CategoryPlayer, PlayerMaxLevel) if copyRule == nil { t.Fatal("Copied rule set is missing expected rule") } if copyRule.GetValue() != "50" { t.Errorf("Copied rule value mismatch: expected '50', got %s", copyRule.GetValue()) } } func TestRuleSetString(t *testing.T) { ruleSet := NewRuleSet() ruleSet.SetID(1) ruleSet.SetName("Test Set") rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "50", "Player:MaxLevel") ruleSet.AddRule(rule) str := ruleSet.String() expected := "RuleSet{ID: 1, Name: Test Set, Rules: 1}" if str != expected { t.Errorf("Expected string %s, got %s", expected, str) } } // Test RuleManager initialization and operations func TestNewRuleManager(t *testing.T) { ruleManager := NewRuleManager() if ruleManager == nil { t.Fatal("NewRuleManager() returned nil") } if !ruleManager.IsInitialized() { t.Error("RuleManager should be initialized after creation") } } func TestRuleManagerGetGlobalRule(t *testing.T) { ruleManager := NewRuleManager() // Test getting a default rule that should be initialized rule := ruleManager.GetGlobalRule(CategoryPlayer, PlayerMaxLevel) if rule == nil { t.Fatal("GetGlobalRule() returned nil for default rule") } // Should return the default value from Init() if rule.GetValue() != "50" { t.Errorf("Expected default value '50', got %s", rule.GetValue()) } // Test getting rule by name rule2 := ruleManager.GetGlobalRuleByName("Player", "MaxLevel") if rule2 == nil { t.Fatal("GetGlobalRuleByName() returned nil for default rule") } if rule2.GetValue() != "50" { t.Errorf("Expected default value '50', got %s", rule2.GetValue()) } } func TestRuleManagerBlankRule(t *testing.T) { ruleManager := NewRuleManager() // Test blank rule for non-existent rule blankRule := ruleManager.GetGlobalRule(9999, 9999) if blankRule == nil { t.Fatal("GetGlobalRule() should return blank rule for non-existent rule") } if blankRule.IsValid() { t.Error("Blank rule should not be valid") } // Test GetBlankRule method blankRule2 := ruleManager.GetBlankRule() if blankRule2 == nil { t.Fatal("GetBlankRule() should not return nil") } if blankRule2.IsValid() { t.Error("Blank rule should not be valid") } } func TestRuleManagerRuleSetOperations(t *testing.T) { ruleManager := NewRuleManager() // Create a test rule set ruleSet := NewRuleSet() ruleSet.SetID(1) ruleSet.SetName("Test Set") // Add some rules to it rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "60", "Player:MaxLevel") ruleSet.AddRule(rule1) // Add the rule set to the manager if !ruleManager.AddRuleSet(ruleSet) { t.Fatal("AddRuleSet() returned false") } // Test getting the rule set retrievedRuleSet := ruleManager.GetRuleSet(1) if retrievedRuleSet == nil { t.Fatal("GetRuleSet() returned nil") } if retrievedRuleSet.GetName() != "Test Set" { t.Errorf("Retrieved rule set name mismatch: expected 'Test Set', got %s", retrievedRuleSet.GetName()) } // Test duplicate rule set duplicateRuleSet := NewRuleSet() duplicateRuleSet.SetID(1) duplicateRuleSet.SetName("Duplicate") if ruleManager.AddRuleSet(duplicateRuleSet) { t.Error("AddRuleSet() should return false for duplicate ID") } // Test GetNumRuleSets if ruleManager.GetNumRuleSets() != 1 { t.Errorf("Expected 1 rule set, got %d", ruleManager.GetNumRuleSets()) } } func TestRuleManagerGlobalRuleSet(t *testing.T) { ruleManager := NewRuleManager() // Create a test rule set with modified rules ruleSet := NewRuleSet() ruleSet.SetID(1) ruleSet.SetName("Test Set") // Load coded defaults first, then modify ruleManager.LoadCodedDefaultsIntoRuleSet(ruleSet) // Override a rule playerMaxLevelRule := ruleSet.GetRule(CategoryPlayer, PlayerMaxLevel) if playerMaxLevelRule != nil { playerMaxLevelRule.SetValue("60") } ruleManager.AddRuleSet(ruleSet) // Test setting global rule set if !ruleManager.SetGlobalRuleSet(1) { t.Fatal("SetGlobalRuleSet() returned false") } // Test that global rule now returns the overridden value globalRule := ruleManager.GetGlobalRule(CategoryPlayer, PlayerMaxLevel) if globalRule == nil { t.Fatal("GetGlobalRule() returned nil after setting global rule set") } if globalRule.GetValue() != "60" { t.Errorf("Expected overridden value '60', got %s", globalRule.GetValue()) } } func TestRuleManagerZoneRules(t *testing.T) { ruleManager := NewRuleManager() // Create a zone-specific rule set zoneRuleSet := NewRuleSet() zoneRuleSet.SetID(100) zoneRuleSet.SetName("Zone Rules") // Load coded defaults and modify a rule ruleManager.LoadCodedDefaultsIntoRuleSet(zoneRuleSet) playerMaxLevelRule := zoneRuleSet.GetRule(CategoryPlayer, PlayerMaxLevel) if playerMaxLevelRule != nil { playerMaxLevelRule.SetValue("70") } ruleManager.AddRuleSet(zoneRuleSet) // Set zone rule set zoneID := int32(1) if !ruleManager.SetZoneRuleSet(zoneID, 100) { t.Fatal("SetZoneRuleSet() returned false") } // Test zone rule lookup zoneRule := ruleManager.GetZoneRule(zoneID, CategoryPlayer, PlayerMaxLevel) if zoneRule == nil { t.Fatal("GetZoneRule() returned nil") } if zoneRule.GetValue() != "70" { t.Errorf("Expected zone rule value '70', got %s", zoneRule.GetValue()) } // Test fallback to global rule for non-zone rule globalFallback := ruleManager.GetZoneRule(999, CategoryPlayer, PlayerMaxLevel) if globalFallback == nil { t.Fatal("GetZoneRule() should fallback to global rule") } } func TestRuleManagerFlush(t *testing.T) { ruleManager := NewRuleManager() // Add a rule set ruleSet := NewRuleSet() ruleSet.SetID(1) ruleSet.SetName("Test Set") ruleManager.AddRuleSet(ruleSet) // Test flush with reinit ruleManager.Flush(true) if ruleManager.GetNumRuleSets() != 0 { t.Error("Rule sets should be cleared after flush") } if !ruleManager.IsInitialized() { t.Error("Rule manager should be initialized after flush(true)") } // Test flush without reinit ruleManager.Flush(false) if ruleManager.IsInitialized() { t.Error("Rule manager should not be initialized after flush(false)") } } func TestRuleManagerClearOperations(t *testing.T) { ruleManager := NewRuleManager() // Add rule sets ruleSet1 := NewRuleSet() ruleSet1.SetID(1) ruleManager.AddRuleSet(ruleSet1) ruleSet2 := NewRuleSet() ruleSet2.SetID(2) ruleManager.AddRuleSet(ruleSet2) // Set zone rule set ruleManager.SetZoneRuleSet(1, 1) // Test ClearRuleSets ruleManager.ClearRuleSets() if ruleManager.GetNumRuleSets() != 0 { t.Error("Rule sets should be cleared") } // Test ClearZoneRuleSets ruleManager.ClearZoneRuleSets() zoneRule := ruleManager.GetZoneRule(1, CategoryPlayer, PlayerMaxLevel) // Should fallback to global rule, not zone rule if zoneRule == nil { t.Error("Should fallback to global rule after clearing zone rule sets") } } func TestRuleManagerGetAllRuleSets(t *testing.T) { ruleManager := NewRuleManager() // Add multiple rule sets for i := 1; i <= 3; i++ { ruleSet := NewRuleSet() ruleSet.SetID(int32(i)) ruleSet.SetName("Rule Set " + string(rune(i+'0'))) ruleManager.AddRuleSet(ruleSet) } allRuleSets := ruleManager.GetAllRuleSets() if len(allRuleSets) != 3 { t.Errorf("Expected 3 rule sets, got %d", len(allRuleSets)) } } func TestRuleManagerValidateRule(t *testing.T) { ruleManager := NewRuleManager() // Test valid rule err := ruleManager.ValidateRule(CategoryPlayer, PlayerMaxLevel, "50") if err != nil { t.Errorf("ValidateRule() returned error for valid rule: %v", err) } // Test rule value too long longValue := make([]byte, MaxRuleValueLength+1) for i := range longValue { longValue[i] = 'a' } err = ruleManager.ValidateRule(CategoryPlayer, PlayerMaxLevel, string(longValue)) if err != ErrRuleValueTooLong { t.Error("ValidateRule() should return ErrRuleValueTooLong for long value") } } func TestRuleManagerGetRuleInfo(t *testing.T) { ruleManager := NewRuleManager() info := ruleManager.GetRuleInfo(CategoryPlayer, PlayerMaxLevel) expected := "Rule: Player:MaxLevel = 50" if info != expected { t.Errorf("Expected rule info %s, got %s", expected, info) } // Test non-existent rule info = ruleManager.GetRuleInfo(9999, 9999) if info != "Rule not found" { t.Errorf("Expected 'Rule not found', got %s", info) } } func TestRuleManagerStatistics(t *testing.T) { ruleManager := NewRuleManager() // Get initial stats stats := ruleManager.GetStats() // Add rule sets and perform operations to change stats ruleSet := NewRuleSet() ruleSet.SetID(1) ruleManager.AddRuleSet(ruleSet) // Get rule to increment lookup counter ruleManager.GetGlobalRule(CategoryPlayer, PlayerMaxLevel) // Get updated stats newStats := ruleManager.GetStats() if newStats.TotalRuleSets != 1 { t.Errorf("Expected 1 total rule set, got %d", newStats.TotalRuleSets) } if newStats.RuleGetOperations <= stats.RuleGetOperations { t.Error("Rule get operations should have increased") } // Test reset stats ruleManager.ResetStats() resetStats := ruleManager.GetStats() if resetStats.RuleGetOperations != 0 { t.Error("Stats should be reset to 0") } } func TestRuleManagerString(t *testing.T) { ruleManager := NewRuleManager() str := ruleManager.String() // Should contain basic information about rule sets, rules, etc. if str == "" { t.Error("String() should return non-empty string") } } // Test category name functions func TestCategoryNames(t *testing.T) { // Test GetCategoryName name := GetCategoryName(CategoryPlayer) if name != "Player" { t.Errorf("Expected category name 'Player', got %s", name) } // Test unknown category unknownName := GetCategoryName(9999) if unknownName != "Unknown" { t.Errorf("Expected 'Unknown' for invalid category, got %s", unknownName) } // Test GetCategoryByName category, exists := GetCategoryByName("Player") if !exists { t.Error("GetCategoryByName() should find 'Player' category") } if category != CategoryPlayer { t.Errorf("Expected category %d, got %d", CategoryPlayer, category) } // Test unknown category name _, exists = GetCategoryByName("NonExistent") if exists { t.Error("GetCategoryByName() should not find non-existent category") } } // Test RuleManagerAdapter func TestRuleManagerAdapter(t *testing.T) { ruleManager := NewRuleManager() adapter := NewRuleManagerAdapter(ruleManager, 0) if adapter == nil { t.Fatal("NewRuleManagerAdapter() returned nil") } // Test basic rule access rule := adapter.GetRule(CategoryPlayer, PlayerMaxLevel) if rule == nil { t.Fatal("Adapter GetRule() returned nil") } if rule.GetValue() != "50" { t.Errorf("Expected value '50', got %s", rule.GetValue()) } // Test convenience methods intValue := adapter.GetInt32(CategoryPlayer, PlayerMaxLevel) if intValue != 50 { t.Errorf("Expected int32 50, got %d", intValue) } boolValue := adapter.GetBool(CategoryPlayer, PlayerMaxLevel) if !boolValue { t.Error("Expected bool true for value '50'") } stringValue := adapter.GetString(CategoryPlayer, PlayerMaxLevel) if stringValue != "50" { t.Errorf("Expected string '50', got %s", stringValue) } floatValue := adapter.GetFloat64(CategoryPlayer, PlayerMaxLevel) if floatValue != 50.0 { t.Errorf("Expected float64 50.0, got %f", floatValue) } // Test zone ID if adapter.GetZoneID() != 0 { t.Errorf("Expected zone ID 0, got %d", adapter.GetZoneID()) } adapter.SetZoneID(100) if adapter.GetZoneID() != 100 { t.Errorf("Expected zone ID 100 after SetZoneID, got %d", adapter.GetZoneID()) } } func TestRuleManagerAdapterWithZone(t *testing.T) { ruleManager := NewRuleManager() // Create zone-specific rule set zoneRuleSet := NewRuleSet() zoneRuleSet.SetID(100) ruleManager.LoadCodedDefaultsIntoRuleSet(zoneRuleSet) // Override a rule if rule := zoneRuleSet.GetRule(CategoryPlayer, PlayerMaxLevel); rule != nil { rule.SetValue("70") } ruleManager.AddRuleSet(zoneRuleSet) ruleManager.SetZoneRuleSet(1, 100) // Test adapter with zone adapter := NewRuleManagerAdapter(ruleManager, 1) intValue := adapter.GetInt32(CategoryPlayer, PlayerMaxLevel) if intValue != 70 { t.Errorf("Expected zone-specific value 70, got %d", intValue) } } // Test RuleManagerStats operations func TestRuleManagerStats(t *testing.T) { stats := &RuleManagerStats{} // Test increment operations initialGets := stats.RuleGetOperations stats.IncrementRuleGetOperations() if stats.RuleGetOperations != initialGets+1 { t.Error("IncrementRuleGetOperations() did not increment correctly") } initialSets := stats.RuleSetOperations stats.IncrementRuleSetOperations() if stats.RuleSetOperations != initialSets+1 { t.Error("IncrementRuleSetOperations() did not increment correctly") } initialDB := stats.DatabaseOperations stats.IncrementDatabaseOperations() if stats.DatabaseOperations != initialDB+1 { t.Error("IncrementDatabaseOperations() did not increment correctly") } // Test snapshot snapshot := stats.GetSnapshot() if snapshot.RuleGetOperations != stats.RuleGetOperations { t.Error("Snapshot should match current stats") } // Test reset stats.Reset() if stats.RuleGetOperations != 0 || stats.RuleSetOperations != 0 || stats.DatabaseOperations != 0 { t.Error("Reset() should zero all counters") } } // Test error constants func TestErrorConstants(t *testing.T) { errors := []error{ ErrRuleNotFound, ErrRuleSetNotFound, ErrInvalidRuleCategory, ErrInvalidRuleType, ErrInvalidRuleValue, ErrDuplicateRuleSet, ErrRuleSetNotActive, ErrGlobalRuleSetNotSet, ErrZoneRuleSetNotFound, ErrRuleValueTooLong, ErrRuleNameTooLong, } for _, err := range errors { if err == nil { t.Error("Error constant should not be nil") } if err.Error() == "" { t.Error("Error message should not be empty") } } } // Test constants func TestConstants(t *testing.T) { // Test some rule categories if CategoryClient != 0 { t.Errorf("CategoryClient should be 0, got %d", CategoryClient) } if CategoryPlayer != 3 { t.Errorf("CategoryPlayer should be 3, got %d", CategoryPlayer) } // Test some rule types if PlayerMaxLevel != 0 { t.Errorf("PlayerMaxLevel should be 0, got %d", PlayerMaxLevel) } // Test validation constants if MaxRuleValueLength != 1024 { t.Errorf("MaxRuleValueLength should be 1024, got %d", MaxRuleValueLength) } if MaxRuleCombinedLength != 2048 { t.Errorf("MaxRuleCombinedLength should be 2048, got %d", MaxRuleCombinedLength) } // Test database constants if TableRuleSets != "rulesets" { t.Errorf("TableRuleSets should be 'rulesets', got %s", TableRuleSets) } if DefaultRuleSetIDVar != "default_ruleset_id" { t.Errorf("DefaultRuleSetIDVar should be 'default_ruleset_id', got %s", DefaultRuleSetIDVar) } } // Test DatabaseService with in-memory SQLite func TestDatabaseService(t *testing.T) { // Create in-memory database conn, err := sqlite.OpenConn(":memory:", 0) if err != nil { t.Fatalf("Failed to create in-memory database: %v", err) } defer conn.Close() ds := NewDatabaseService(conn) if ds == nil { t.Fatal("NewDatabaseService() returned nil") } // Test CreateRulesTables err = ds.CreateRulesTables() if err != nil { t.Fatalf("CreateRulesTables() failed: %v", err) } // Test ValidateDatabase err = ds.ValidateDatabase() if err != nil { t.Fatalf("ValidateDatabase() failed after creating tables: %v", err) } // Test SetDefaultRuleSet and GetDefaultRuleSetID testRuleSetID := int32(42) err = ds.SetDefaultRuleSet(testRuleSetID) if err != nil { t.Fatalf("SetDefaultRuleSet() failed: %v", err) } retrievedID, err := ds.GetDefaultRuleSetID() if err != nil { t.Fatalf("GetDefaultRuleSetID() failed: %v", err) } if retrievedID != testRuleSetID { t.Errorf("Expected rule set ID %d, got %d", testRuleSetID, retrievedID) } } func TestDatabaseServiceRuleSetOperations(t *testing.T) { // Create in-memory database conn, err := sqlite.OpenConn(":memory:", 0) if err != nil { t.Fatalf("Failed to create in-memory database: %v", err) } defer conn.Close() ds := NewDatabaseService(conn) ds.CreateRulesTables() // Create a test rule set ruleSet := NewRuleSet() ruleSet.SetID(1) ruleSet.SetName("Test Rule Set") // Add some rules rule1 := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "60", "Player:MaxLevel") rule2 := NewRuleWithValues(CategoryCombat, CombatMaxRange, "5.0", "Combat:MaxCombatRange") ruleSet.AddRule(rule1) ruleSet.AddRule(rule2) // Test SaveRuleSet err = ds.SaveRuleSet(ruleSet) if err != nil { t.Fatalf("SaveRuleSet() failed: %v", err) } // Test GetRuleSetList ruleSets, err := ds.GetRuleSetList() if err != nil { t.Fatalf("GetRuleSetList() failed: %v", err) } if len(ruleSets) != 1 { t.Errorf("Expected 1 rule set, got %d", len(ruleSets)) } if ruleSets[0].ID != 1 { t.Errorf("Expected rule set ID 1, got %d", ruleSets[0].ID) } if ruleSets[0].Name != "Test Rule Set" { t.Errorf("Expected rule set name 'Test Rule Set', got %s", ruleSets[0].Name) } // Test DeleteRuleSet err = ds.DeleteRuleSet(1) if err != nil { t.Fatalf("DeleteRuleSet() failed: %v", err) } // Verify deletion ruleSets, err = ds.GetRuleSetList() if err != nil { t.Fatalf("GetRuleSetList() failed after deletion: %v", err) } if len(ruleSets) != 0 { t.Errorf("Expected 0 rule sets after deletion, got %d", len(ruleSets)) } } // Test RuleService functionality func TestRuleService(t *testing.T) { config := RuleServiceConfig{ DatabaseEnabled: false, CacheEnabled: false, CacheTTL: 3600, MaxCacheSize: 1024 * 1024, } service := NewRuleService(config) if service == nil { t.Fatal("NewRuleService() returned nil") } err := service.Initialize() if err != nil { t.Fatalf("Initialize() failed: %v", err) } ruleManager := service.GetRuleManager() if ruleManager == nil { t.Fatal("GetRuleManager() returned nil") } adapter := service.GetAdapter(0) if adapter == nil { t.Fatal("GetAdapter() returned nil") } err = service.Shutdown() if err != nil { t.Fatalf("Shutdown() failed: %v", err) } } // Test splitCombined helper function func TestSplitCombined(t *testing.T) { // Test valid combined string parts := splitCombined("Player:MaxLevel") if len(parts) != 2 { t.Errorf("Expected 2 parts, got %d", len(parts)) } if parts[0] != "Player" || parts[1] != "MaxLevel" { t.Errorf("Expected ['Player', 'MaxLevel'], got %v", parts) } // Test invalid combined string (no colon) parts = splitCombined("PlayerMaxLevel") if len(parts) != 1 { t.Errorf("Expected 1 part for invalid string, got %d", len(parts)) } if parts[0] != "PlayerMaxLevel" { t.Errorf("Expected ['PlayerMaxLevel'], got %v", parts) } // Test empty string parts = splitCombined("") if len(parts) != 1 { t.Errorf("Expected 1 part for empty string, got %d", len(parts)) } if parts[0] != "" { t.Errorf("Expected [''], got %v", parts) } } // Benchmark tests func BenchmarkRuleAccess(b *testing.B) { ruleManager := NewRuleManager() b.ResetTimer() for i := 0; i < b.N; i++ { _ = ruleManager.GetGlobalRule(CategoryPlayer, PlayerMaxLevel) } } func BenchmarkRuleManagerCreation(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = NewRuleManager() } } func BenchmarkRuleSetAddRule(b *testing.B) { ruleSet := NewRuleSet() rules := make([]*Rule, b.N) // Pre-create rules for i := 0; i < b.N; i++ { rules[i] = NewRuleWithValues(CategoryPlayer, PlayerMaxLevel+RuleType(i), "50", "Player:MaxLevel") } b.ResetTimer() for i := 0; i < b.N; i++ { ruleSet.AddRule(rules[i]) } } func BenchmarkRuleTypeConversion(b *testing.B) { rule := NewRuleWithValues(CategoryPlayer, PlayerMaxLevel, "12345", "Player:MaxLevel") b.ResetTimer() for i := 0; i < b.N; i++ { _ = rule.GetInt32() } } func BenchmarkRuleManagerAdapter(b *testing.B) { ruleManager := NewRuleManager() adapter := NewRuleManagerAdapter(ruleManager, 0) b.ResetTimer() for i := 0; i < b.N; i++ { _ = adapter.GetInt32(CategoryPlayer, PlayerMaxLevel) } }