Skip to content

Commit 8e0578f

Browse files
yasirfolio3Michael Ng
authored andcommitted
fix(fsc): Fixes to test is_feature_enabled with fsc. (#47)
1 parent 308b1e4 commit 8e0578f

File tree

10 files changed

+355
-26
lines changed

10 files changed

+355
-26
lines changed

optimizely/config/datafileProjectConfig/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ func NewDatafileProjectConfig(jsonDatafile []byte) (*DatafileProjectConfig, erro
7878

7979
experiments, experimentKeyMap := mappers.MapExperiments(datafile.Experiments)
8080
rolloutMap := mappers.MapRollouts(datafile.Rollouts)
81+
mergedAudiences := append(datafile.TypedAudiences, datafile.Audiences...)
8182
config := &DatafileProjectConfig{
82-
audienceMap: mappers.MapAudiences(datafile.TypedAudiences),
83+
audienceMap: mappers.MapAudiences(mergedAudiences),
8384
experimentMap: experiments,
8485
experimentKeyToIDMap: experimentKeyMap,
8586
rolloutMap: rolloutMap,

optimizely/config/datafileProjectConfig/mappers/audience.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ func MapAudiences(audiences []datafileEntities.Audience) map[string]entities.Aud
2626

2727
audienceMap := make(map[string]entities.Audience)
2828
for _, audience := range audiences {
29-
conditionTree, err := buildConditionTree(audience.Conditions)
30-
if err != nil {
31-
// @TODO: handle error
32-
}
33-
audienceMap[audience.ID] = entities.Audience{
34-
ID: audience.ID,
35-
Name: audience.Name,
36-
ConditionTree: conditionTree,
29+
_, ok := audienceMap[audience.ID]
30+
if !ok {
31+
conditionTree, err := buildConditionTree(audience.Conditions)
32+
if err != nil {
33+
// @TODO: handle error
34+
}
35+
audienceMap[audience.ID] = entities.Audience{
36+
ID: audience.ID,
37+
Name: audience.Name,
38+
ConditionTree: conditionTree,
39+
}
3740
}
3841
}
3942
return audienceMap

optimizely/config/datafileProjectConfig/mappers/condition_trees.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ var ErrEmptyTree = errors.New("Empty Tree")
2828

2929
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
3030
func buildConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, retErr error) {
31-
value := reflect.ValueOf(conditions)
31+
32+
var parsedConditions interface{}
33+
switch v := conditions.(type) {
34+
case string:
35+
json.Unmarshal([]byte(v), &parsedConditions)
36+
default:
37+
parsedConditions = conditions
38+
}
39+
40+
value := reflect.ValueOf(parsedConditions)
3241
visited := make(map[interface{}]bool)
3342

3443
conditionTree = &entities.TreeNode{}

optimizely/config/datafileProjectConfig/mappers/condition_trees_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"testing"
2222

23+
datafileConfig "github.com/optimizely/go-sdk/optimizely/config/datafileProjectConfig/entities"
2324
"github.com/optimizely/go-sdk/optimizely/entities"
2425
"github.com/stretchr/testify/assert"
2526
)
@@ -60,6 +61,44 @@ func TestBuildAudienceConditionTreeSimpleAudienceCondition(t *testing.T) {
6061
assert.Equal(t, expectedConditionTree, conditionTree)
6162
}
6263

64+
func TestBuildConditionTreeUsingDatafileAudienceConditions(t *testing.T) {
65+
66+
audience := datafileConfig.Audience{
67+
ID: "12567320080",
68+
Name: "message",
69+
Conditions: "[\"and\", [\"or\", [\"or\", {\"name\": \"s_foo\", \"type\": \"custom_attribute\", \"value\": \"foo\"}]]]",
70+
}
71+
72+
conditionTree, err := buildConditionTree(audience.Conditions)
73+
if err != nil {
74+
assert.Fail(t, err.Error())
75+
}
76+
77+
expectedConditionTree := &entities.TreeNode{
78+
Operator: "and",
79+
Nodes: []*entities.TreeNode{
80+
{
81+
Operator: "or",
82+
Nodes: []*entities.TreeNode{
83+
{
84+
Operator: "or",
85+
Nodes: []*entities.TreeNode{
86+
{
87+
Item: entities.Condition{
88+
Name: "s_foo",
89+
Type: "custom_attribute",
90+
Value: "foo",
91+
},
92+
},
93+
},
94+
},
95+
},
96+
},
97+
},
98+
}
99+
assert.Equal(t, expectedConditionTree, conditionTree)
100+
}
101+
63102
func TestBuildConditionTreeSimpleAudienceCondition(t *testing.T) {
64103
conditionString := "[ \"and\", [ \"or\", [ \"or\", { \"type\": \"custom_attribute\", \"name\": \"s_foo\", \"match\": \"exact\", \"value\": \"foo\" } ] ] ]"
65104
var conditions interface{}

optimizely/config/polling_manager_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestNewPollingProjectConfigManager(t *testing.T) {
4343
newConfig := configManager.GetConfig()
4444

4545
assert.Equal(t, "", newConfig.GetAccountID())
46-
assert.Equal(t, 3, len(newConfig.GetAudienceMap()))
46+
assert.Equal(t, 4, len(newConfig.GetAudienceMap()))
4747
assert.Equal(t, "", configManager.GetMetrics())
4848

4949
}

optimizely/decision/evaluator/condition.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package evaluator
1818

1919
import (
2020
"fmt"
21+
2122
"github.com/optimizely/go-sdk/optimizely/decision/evaluator/matchers"
2223
"github.com/optimizely/go-sdk/optimizely/entities"
2324
)
@@ -47,6 +48,9 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
4748

4849
var matcher matchers.Matcher
4950
matchType := condition.Match
51+
if matchType == "" {
52+
matchType = exactMatchType
53+
}
5054
switch matchType {
5155
case exactMatchType:
5256
matcher = matchers.ExactMatcher{

optimizely/decision/evaluator/condition_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,32 @@ func TestCustomAttributeConditionEvaluator(t *testing.T) {
5252
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
5353
assert.Equal(t, result, false)
5454
}
55+
56+
func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) {
57+
conditionEvaluator := CustomAttributeConditionEvaluator{}
58+
condition := entities.Condition{
59+
Value: "foo",
60+
Name: "string_foo",
61+
Type: "custom_attribute",
62+
}
63+
64+
// Test condition passes
65+
user := entities.UserContext{
66+
Attributes: map[string]interface{}{
67+
"string_foo": "foo",
68+
},
69+
}
70+
71+
condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
72+
result, _ := conditionEvaluator.Evaluate(condition, condTreeParams)
73+
assert.Equal(t, result, true)
74+
75+
// Test condition fails
76+
user = entities.UserContext{
77+
Attributes: map[string]interface{}{
78+
"string_foo": "not_foo",
79+
},
80+
}
81+
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
82+
assert.Equal(t, result, false)
83+
}

optimizely/decision/experiment_bucketer_service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (s ExperimentBucketerService) GetDecision(decisionContext ExperimentDecisio
5252
// bucket user into a variation
5353
bucketingID, err := userContext.GetBucketingID()
5454
if err != nil {
55-
return experimentDecision, fmt.Errorf(`Error computing bucketing ID for experiment "%s": "%s"`, experiment.Key, err.Error())
55+
bLogger.Debug(fmt.Sprintf(`Error computing bucketing ID for experiment "%s": "%s"`, experiment.Key, err.Error()))
5656
}
5757

5858
bLogger.Debug(fmt.Sprintf(`Using bucketing ID: "%s"`, bucketingID))

optimizely/utils/types.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,41 +26,50 @@ var intType = reflect.TypeOf(int64(0))
2626

2727
// GetBoolValue will attempt to convert the given value to a bool
2828
func GetBoolValue(value interface{}) (bool, error) {
29-
v := reflect.ValueOf(value)
30-
if v.Type().String() == "bool" {
31-
return v.Bool(), nil
29+
if value != nil {
30+
v := reflect.ValueOf(value)
31+
if v.Type().String() == "bool" {
32+
return v.Bool(), nil
33+
}
3234
}
3335

3436
return false, fmt.Errorf(`Value "%v" could not be converted to bool`, value)
3537
}
3638

3739
// GetFloatValue will attempt to convert the given value to a float64
3840
func GetFloatValue(value interface{}) (float64, error) {
39-
v := reflect.ValueOf(value)
40-
if v.Type().String() == "float64" || v.Type().ConvertibleTo(floatType) {
41-
floatValue := v.Convert(floatType).Float()
42-
return floatValue, nil
41+
if value != nil {
42+
v := reflect.ValueOf(value)
43+
v = reflect.Indirect(v)
44+
if v.Type().ConvertibleTo(floatType) {
45+
fv := v.Convert(floatType)
46+
return fv.Float(), nil
47+
}
4348
}
4449

4550
return 0, fmt.Errorf(`Value "%v" could not be converted to float`, value)
4651
}
4752

4853
// GetIntValue will attempt to convert the given value to an int64
4954
func GetIntValue(value interface{}) (int64, error) {
50-
v := reflect.ValueOf(value)
51-
if v.Type().String() == "int64" || v.Type().ConvertibleTo(intType) {
52-
intValue := v.Convert(intType).Int()
53-
return intValue, nil
55+
if value != nil {
56+
v := reflect.ValueOf(value)
57+
if v.Type().String() == "int64" || v.Type().ConvertibleTo(intType) {
58+
intValue := v.Convert(intType).Int()
59+
return intValue, nil
60+
}
5461
}
5562

5663
return 0, fmt.Errorf(`Value "%v" could not be converted to int`, value)
5764
}
5865

5966
// GetStringValue will attempt to convert the given value to a string
6067
func GetStringValue(value interface{}) (string, error) {
61-
v := reflect.ValueOf(value)
62-
if v.Type().String() == "string" {
63-
return v.String(), nil
68+
if value != nil {
69+
v := reflect.ValueOf(value)
70+
if v.Type().String() == "string" {
71+
return v.String(), nil
72+
}
6473
}
6574

6675
return "", fmt.Errorf(`Value "%v" could not be converted to string`, value)

0 commit comments

Comments
 (0)