eq2go/internal/rules/rules_test.go
2025-08-05 21:45:03 -05:00

1208 lines
31 KiB
Go

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