Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 2 additions & 1 deletion pkg/config/datafileprojectconfig/mappers/condition_trees.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -127,6 +127,7 @@ func createLeafCondition(typedV map[string]interface{}, node *entities.TreeNode)
if err := json.Unmarshal(jsonBody, &condition); err != nil {
return err
}
condition.StringRepresentation = string(jsonBody)
node.Item = condition
return nil
}
Expand Down
27 changes: 15 additions & 12 deletions pkg/config/datafileprojectconfig/mappers/condition_trees_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -111,9 +111,10 @@ func TestBuildConditionTreeUsingDatafileAudienceConditions(t *testing.T) {
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Type: "custom_attribute",
Value: "foo",
Name: "s_foo",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
Expand Down Expand Up @@ -145,10 +146,11 @@ func TestBuildConditionTreeSimpleAudienceCondition(t *testing.T) {
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"match":"exact","name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
Expand All @@ -172,10 +174,11 @@ func TestBuildConditionTreeWithLeafNode(t *testing.T) {
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"match":"exact","name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
Expand Down
45 changes: 0 additions & 45 deletions pkg/decision/evaluator/audience.go

This file was deleted.

35 changes: 29 additions & 6 deletions pkg/decision/evaluator/condition.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -22,6 +22,7 @@ import (

"github.com/optimizely/go-sdk/pkg/decision/evaluator/matchers"
"github.com/optimizely/go-sdk/pkg/entities"
"github.com/optimizely/go-sdk/pkg/logging"
)

const (
Expand All @@ -38,14 +39,23 @@ type ItemEvaluator interface {
}

// CustomAttributeConditionEvaluator evaluates conditions with custom attributes
type CustomAttributeConditionEvaluator struct{}
type CustomAttributeConditionEvaluator struct {
logger logging.OptimizelyLogProducer
}

// NewCustomAttributeConditionEvaluator creates a custom attribute condition
func NewCustomAttributeConditionEvaluator(logger logging.OptimizelyLogProducer) *CustomAttributeConditionEvaluator {
return &CustomAttributeConditionEvaluator{logger: logger}
}

// Evaluate returns true if the given user's attributes match the condition
func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition, condTreeParams *entities.TreeParameters) (bool, error) {
// We should only be evaluating custom attributes

if condition.Type != customAttributeType {
return false, fmt.Errorf(`unable to evaluator condition of type "%s"`, condition.Type)

c.logger.Warning(fmt.Sprintf(logging.UnknownConditionType.String(), condition.StringRepresentation))
return false, fmt.Errorf(`unable to evaluate condition of type "%s"`, condition.Type)
}

var matcher matchers.Matcher
Expand All @@ -57,6 +67,7 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
case exactMatchType:
matcher = matchers.ExactMatcher{
Condition: condition,
Logger: c.logger,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why logger not passed in existsMatchType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No logs were required inside exists.

}
case existsMatchType:
matcher = matchers.ExistsMatcher{
Expand All @@ -65,16 +76,20 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
case ltMatchType:
matcher = matchers.LtMatcher{
Condition: condition,
Logger: c.logger,
}
case gtMatchType:
matcher = matchers.GtMatcher{
Condition: condition,
Logger: c.logger,
}
case substringMatchType:
matcher = matchers.SubstringMatcher{
Condition: condition,
Logger: c.logger,
}
default:
c.logger.Warning(fmt.Sprintf(logging.UnknownMatchType.String(), condition.StringRepresentation))
return false, fmt.Errorf(`invalid Condition matcher "%s"`, condition.Match)
}

Expand All @@ -84,20 +99,28 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
}

// AudienceConditionEvaluator evaluates conditions with audience condition
type AudienceConditionEvaluator struct{}
type AudienceConditionEvaluator struct {
logger logging.OptimizelyLogProducer
}

// NewAudienceConditionEvaluator creates a audience condition evaluator
func NewAudienceConditionEvaluator(logger logging.OptimizelyLogProducer) *AudienceConditionEvaluator {
return &AudienceConditionEvaluator{logger: logger}
}

// Evaluate returns true if the given user's attributes match the condition
func (c AudienceConditionEvaluator) Evaluate(audienceID string, condTreeParams *entities.TreeParameters) (bool, error) {

if audience, ok := condTreeParams.AudienceMap[audienceID]; ok {
c.logger.Debug(fmt.Sprintf(logging.AudienceEvaluationStarted.String(), audienceID))
condTree := audience.ConditionTree
conditionTreeEvaluator := NewMixedTreeEvaluator()
conditionTreeEvaluator := NewMixedTreeEvaluator(c.logger)
retValue, isValid := conditionTreeEvaluator.Evaluate(condTree, condTreeParams)
if !isValid {
return false, fmt.Errorf(`an error occurred while evaluating nested tree for audience ID "%s"`, audienceID)
}
c.logger.Debug(fmt.Sprintf(logging.AudienceEvaluatedTo.String(), audienceID, retValue))
return retValue, nil

}

return false, fmt.Errorf(`unable to evaluate nested tree for audience ID "%s"`, audienceID)
Expand Down
86 changes: 72 additions & 14 deletions pkg/decision/evaluator/condition_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -17,14 +17,26 @@
package evaluator

import (
"fmt"
"testing"

"github.com/optimizely/go-sdk/pkg/entities"
"github.com/stretchr/testify/assert"
"github.com/optimizely/go-sdk/pkg/logging"
"github.com/stretchr/testify/suite"
)

func TestCustomAttributeConditionEvaluator(t *testing.T) {
conditionEvaluator := CustomAttributeConditionEvaluator{}
type ConditionTestSuite struct {
suite.Suite
mockLogger *MockLogger
conditionEvaluator *CustomAttributeConditionEvaluator
}

func (s *ConditionTestSuite) SetupTest() {
s.mockLogger = new(MockLogger)
s.conditionEvaluator = NewCustomAttributeConditionEvaluator(s.mockLogger)
}

func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluator() {
condition := entities.Condition{
Match: "exact",
Value: "foo",
Expand All @@ -40,21 +52,20 @@ func TestCustomAttributeConditionEvaluator(t *testing.T) {
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
result, _ := conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, true)
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, true)

// Test condition fails
user = entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "not_foo",
},
}
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, false)
result, _ = s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
}

func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) {
conditionEvaluator := CustomAttributeConditionEvaluator{}
func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithoutMatchType() {
condition := entities.Condition{
Value: "foo",
Name: "string_foo",
Expand All @@ -69,15 +80,62 @@ func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) {
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
result, _ := conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, true)
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, true)

// Test condition fails
user = entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "not_foo",
},
}
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, false)
result, _ = s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
}

func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithInvalidMatchType() {
condition := entities.Condition{
Value: "foo",
Name: "string_foo",
Type: "custom_attribute",
Match: "invalid",
}

// Test condition fails
user := entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "foo",
},
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
s.mockLogger.On("Warning", fmt.Sprintf(logging.UnknownMatchType.String(), ""))
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
s.mockLogger.AssertExpectations(s.T())
}

func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithUnknownType() {
condition := entities.Condition{
Value: "foo",
Name: "string_foo",
Type: "",
}

// Test condition fails
user := entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "foo",
},
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
s.mockLogger.On("Warning", fmt.Sprintf(logging.UnknownConditionType.String(), ""))
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
s.mockLogger.AssertExpectations(s.T())
}

func TestConditionTestSuite(t *testing.T) {
suite.Run(t, new(ConditionTestSuite))
}
12 changes: 7 additions & 5 deletions pkg/decision/evaluator/condition_tree.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -21,6 +21,7 @@ import (
"fmt"

"github.com/optimizely/go-sdk/pkg/entities"
"github.com/optimizely/go-sdk/pkg/logging"
)

const customAttributeType = "custom_attribute"
Expand All @@ -41,11 +42,12 @@ type TreeEvaluator interface {

// MixedTreeEvaluator evaluates a tree of mixed node types (condition node or audience nodes)
type MixedTreeEvaluator struct {
logger logging.OptimizelyLogProducer
}

// NewMixedTreeEvaluator creates a condition tree evaluator with the out-of-the-box condition evaluators
func NewMixedTreeEvaluator() *MixedTreeEvaluator {
return &MixedTreeEvaluator{}
func NewMixedTreeEvaluator(logger logging.OptimizelyLogProducer) *MixedTreeEvaluator {
return &MixedTreeEvaluator{logger: logger}
}

// Evaluate returns whether the userAttributes satisfy the given condition tree and the evaluation of the condition is valid or not (to handle null bubbling)
Expand All @@ -66,10 +68,10 @@ func (c MixedTreeEvaluator) Evaluate(node *entities.TreeNode, condTreeParams *en
var err error
switch v := node.Item.(type) {
case entities.Condition:
evaluator := CustomAttributeConditionEvaluator{}
evaluator := NewCustomAttributeConditionEvaluator(c.logger)
result, err = evaluator.Evaluate(node.Item.(entities.Condition), condTreeParams)
case string:
evaluator := AudienceConditionEvaluator{}
evaluator := NewAudienceConditionEvaluator(c.logger)
result, err = evaluator.Evaluate(node.Item.(string), condTreeParams)
default:
fmt.Printf("I don't know about type %T!\n", v)
Expand Down
Loading