Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions engine/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,33 @@ func (e *Evaluator) EvaluateDependency(key string, bucketingKey *string, feature
res := e.EvaluateFeature(key, bucketingKey, featureFlag, attributes)
return res.Treatment
}

// EvaluateDefault returns the default treatment and its configuration for a given definition name.
// This method is designed for cases where no target (key) is provided.
func (e *Evaluator) EvaluateDefault(definitionName string) *Result {
definition := e.splitStorage.Split(definitionName)

if definition == nil {
label := impressionlabels.SplitNotFound
fallbackTratment := e.fallbackTratmentCalculator.Resolve(definitionName, &label)
e.logger.Warning(fmt.Sprintf("Definition %s not found, returning fallback treatment.", definitionName))
return &Result{
Treatment: *fallbackTratment.Treatment,
Config: nil,
Label: *fallbackTratment.Label(),
}
}

var config *string
if definition.Configurations != nil {
if val, ok := definition.Configurations[definition.DefaultTreatment]; ok {
config = &val
}
}

return &Result{
Treatment: definition.DefaultTreatment,
Config: config,
Label: impressionlabels.NoConditionMatched,
}
}
242 changes: 242 additions & 0 deletions engine/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,245 @@ func TestEvaluationByFlagSetsASetEmpty(t *testing.T) {
t.Error("evaluations size should be 0")
}
}

func TestEvaluateDefaultDefinitionNotFound(t *testing.T) {
logger := logging.NewLogger(nil)

// Setup fallback treatment calculator
fallbackTreatment := "control"
fallbackTreatmentConfig := dtos.FallbackTreatmentConfig{
GlobalFallbackTreatment: &dtos.FallbackTreatment{
Treatment: &fallbackTreatment,
},
}

// Mock storage that returns nil for unknown definition
mockedStorage := mocks.MockSplitStorage{
SplitCall: func(splitName string) *dtos.SplitDTO {
return nil
},
}

evaluator := NewEvaluator(
mockedStorage,
nil,
nil,
nil,
nil,
logger,
syncProxyFeatureFlagsRules,
syncProxyRuleBasedSegmentRules,
dtos.NewFallbackTreatmentCalculatorImp(&fallbackTreatmentConfig))

result := evaluator.EvaluateDefault("nonexistent_definition")

// Verify fallback treatment is returned
assert.Equal(t, "control", result.Treatment, "Should return fallback treatment")
assert.Equal(t, "fallback - "+impressionlabels.SplitNotFound, result.Label, "Should return prefixed SplitNotFound label")
assert.Nil(t, result.Config, "Config should be nil for definition not found")
}

func TestEvaluateDefaultDefinitionExistsWithConfig(t *testing.T) {
logger := logging.NewLogger(nil)

// Create a definition with config for default treatment
definitionWithConfig := &dtos.SplitDTO{
Name: "definition_with_config",
DefaultTreatment: "on",
Configurations: map[string]string{
"on": "{\"color\": \"red\", \"size\": 15}",
"off": "{\"color\": \"blue\", \"size\": 10}",
},
Status: "ACTIVE",
}

mockedStorage := mocks.MockSplitStorage{
SplitCall: func(splitName string) *dtos.SplitDTO {
if splitName == "definition_with_config" {
return definitionWithConfig
}
return nil
},
}

evaluator := NewEvaluator(
mockedStorage,
nil,
nil,
nil,
nil,
logger,
syncProxyFeatureFlagsRules,
syncProxyRuleBasedSegmentRules,
nil)

result := evaluator.EvaluateDefault("definition_with_config")

// Verify default treatment and config are returned
assert.Equal(t, "on", result.Treatment, "Should return default treatment")
assert.Equal(t, impressionlabels.NoConditionMatched, result.Label, "Should return NoConditionMatched label")
assert.NotNil(t, result.Config, "Config should not be nil")
assert.Equal(t, "{\"color\": \"red\", \"size\": 15}", *result.Config, "Should return config for default treatment")
}

func TestEvaluateDefaultDefinitionExistsWithoutConfig(t *testing.T) {
logger := logging.NewLogger(nil)

// Create a definition without any configurations
definitionWithoutConfig := &dtos.SplitDTO{
Name: "definition_without_config",
DefaultTreatment: "off",
Configurations: nil,
Status: "ACTIVE",
}

mockedStorage := mocks.MockSplitStorage{
SplitCall: func(splitName string) *dtos.SplitDTO {
if splitName == "definition_without_config" {
return definitionWithoutConfig
}
return nil
},
}

evaluator := NewEvaluator(
mockedStorage,
nil,
nil,
nil,
nil,
logger,
syncProxyFeatureFlagsRules,
syncProxyRuleBasedSegmentRules,
nil)

result := evaluator.EvaluateDefault("definition_without_config")

// Verify default treatment is returned without config
assert.Equal(t, "off", result.Treatment, "Should return default treatment")
assert.Equal(t, impressionlabels.NoConditionMatched, result.Label, "Should return NoConditionMatched label")
assert.Nil(t, result.Config, "Config should be nil when Configurations map is nil")
}

func TestEvaluateDefaultDefinitionExistsConfigMapMissingDefaultTreatmentKey(t *testing.T) {
logger := logging.NewLogger(nil)

// Create a definition with configs but missing the default treatment key
definitionMissingKey := &dtos.SplitDTO{
Name: "definition_missing_key",
DefaultTreatment: "default",
Configurations: map[string]string{
"on": "{\"color\": \"green\", \"size\": 20}",
"off": "{\"color\": \"yellow\", \"size\": 5}",
},
Status: "ACTIVE",
}

mockedStorage := mocks.MockSplitStorage{
SplitCall: func(splitName string) *dtos.SplitDTO {
if splitName == "definition_missing_key" {
return definitionMissingKey
}
return nil
},
}

evaluator := NewEvaluator(
mockedStorage,
nil,
nil,
nil,
nil,
logger,
syncProxyFeatureFlagsRules,
syncProxyRuleBasedSegmentRules,
nil)

result := evaluator.EvaluateDefault("definition_missing_key")

// Verify default treatment is returned without config
assert.Equal(t, "default", result.Treatment, "Should return default treatment")
assert.Equal(t, impressionlabels.NoConditionMatched, result.Label, "Should return NoConditionMatched label")
assert.Nil(t, result.Config, "Config should be nil when default treatment key is not in Configurations map")
}

func TestEvaluateDefaultDefinitionExistsEmptyConfigMap(t *testing.T) {
logger := logging.NewLogger(nil)

// Create a definition with empty configurations map
definitionEmptyMap := &dtos.SplitDTO{
Name: "definition_empty_map",
DefaultTreatment: "control",
Configurations: map[string]string{},
Status: "ACTIVE",
}

mockedStorage := mocks.MockSplitStorage{
SplitCall: func(splitName string) *dtos.SplitDTO {
if splitName == "definition_empty_map" {
return definitionEmptyMap
}
return nil
},
}

evaluator := NewEvaluator(
mockedStorage,
nil,
nil,
nil,
nil,
logger,
syncProxyFeatureFlagsRules,
syncProxyRuleBasedSegmentRules,
nil)

result := evaluator.EvaluateDefault("definition_empty_map")

// Verify default treatment is returned without config
assert.Equal(t, "control", result.Treatment, "Should return default treatment")
assert.Equal(t, impressionlabels.NoConditionMatched, result.Label, "Should return NoConditionMatched label")
assert.Nil(t, result.Config, "Config should be nil when Configurations map is empty")
}

func TestEvaluateDefaultWithPerFlagFallback(t *testing.T) {
logger := logging.NewLogger(nil)

// Setup fallback treatment calculator with per-flag fallback
globalFallback := "control"
perFlagFallback := "special_fallback"
fallbackTreatmentConfig := dtos.FallbackTreatmentConfig{
GlobalFallbackTreatment: &dtos.FallbackTreatment{
Treatment: &globalFallback,
},
ByFlagFallbackTreatment: map[string]dtos.FallbackTreatment{
"special_definition": {
Treatment: &perFlagFallback,
},
},
}

mockedStorage := mocks.MockSplitStorage{
SplitCall: func(splitName string) *dtos.SplitDTO {
return nil
},
}

evaluator := NewEvaluator(
mockedStorage,
nil,
nil,
nil,
nil,
logger,
syncProxyFeatureFlagsRules,
syncProxyRuleBasedSegmentRules,
dtos.NewFallbackTreatmentCalculatorImp(&fallbackTreatmentConfig))

result := evaluator.EvaluateDefault("special_definition")

// Verify per-flag fallback is used
assert.Equal(t, "special_fallback", result.Treatment, "Should return per-flag fallback treatment")
assert.Equal(t, "fallback - "+impressionlabels.SplitNotFound, result.Label, "Should return prefixed SplitNotFound label")
assert.Nil(t, result.Config, "Config should be nil for definition not found")
}
Loading