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
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.

33 changes: 26 additions & 7 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"
)

// ItemEvaluator evaluates a condition against the given user's attributes
Expand All @@ -30,14 +31,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)
}

matchType := condition.Match
Expand All @@ -47,27 +57,36 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition

matcher, ok := matchers.Get(matchType)
if !ok {
c.logger.Warning(fmt.Sprintf(logging.UnknownMatchType.String(), condition.StringRepresentation))
return false, fmt.Errorf(`invalid Condition matcher "%s"`, condition.Match)
}

return matcher(condition, *condTreeParams.User)
return matcher(condition, *condTreeParams.User, c.logger)
}

// 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