Skip to content

Commit ba6072b

Browse files
authored
fix(composite services): Don't use decision returned from inner service when it returns an error (#138)
Summary: - In CompositeExperimentService and CompositeFeatureService GetDecision methods, when the inner service returns an error, skip that decision and move to the next inner service - Return an error from CompositeExperimentService GetDecision when no decision was made Test Plan: Updated & added unit tests
1 parent 02ccb79 commit ba6072b

File tree

4 files changed

+84
-5
lines changed

4 files changed

+84
-5
lines changed

pkg/decision/composite_experiment_service.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@
1818
package decision
1919

2020
import (
21+
"errors"
22+
"fmt"
23+
2124
"github.com/optimizely/go-sdk/pkg/entities"
25+
"github.com/optimizely/go-sdk/pkg/logging"
2226
)
2327

28+
var ceLogger = logging.GetLogger("CompositeExperimentService")
29+
2430
// CompositeExperimentService bridges together the various experiment decision services that ship by default with the SDK
2531
type CompositeExperimentService struct {
2632
experimentServices []ExperimentService
@@ -47,10 +53,14 @@ func (s CompositeExperimentService) GetDecision(decisionContext ExperimentDecisi
4753

4854
// Run through the various decision services until we get a decision
4955
for _, experimentService := range s.experimentServices {
50-
if decision, err := experimentService.GetDecision(decisionContext, userContext); decision.Variation != nil {
56+
decision, err := experimentService.GetDecision(decisionContext, userContext)
57+
if err != nil {
58+
ceLogger.Debug(fmt.Sprintf("%v", err))
59+
}
60+
if decision.Variation != nil && err == nil {
5161
return decision, err
5262
}
5363
}
5464

55-
return experimentDecision, nil
65+
return experimentDecision, errors.New("no decision was made")
5666
}

pkg/decision/composite_experiment_service_test.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package decision
1818

1919
import (
20+
"errors"
2021
"testing"
2122

2223
"github.com/stretchr/testify/suite"
@@ -109,12 +110,46 @@ func (s *CompositeExperimentTestSuite) TestGetDecisionNoDecisionsMade() {
109110
}
110111
decision, err := compositeExperimentService.GetDecision(s.testDecisionContext, testUserContext)
111112

112-
s.NoError(err)
113+
s.Error(err)
113114
s.Equal(expectedExperimentDecision2, decision)
114115
s.mockExperimentService.AssertExpectations(s.T())
115116
s.mockExperimentService2.AssertExpectations(s.T())
116117
}
117118

119+
func (s *CompositeExperimentTestSuite) TestGetDecisionReturnsError() {
120+
// Assert that we continue to the next inner service when an inner service GetDecision returns an error
121+
testUserContext := entities.UserContext{
122+
ID: "test_user_1",
123+
}
124+
125+
testDecisionContext := ExperimentDecisionContext{
126+
Experiment: &testExp1114,
127+
ProjectConfig: s.mockConfig,
128+
}
129+
130+
shouldBeIgnoredDecision := ExperimentDecision{
131+
Variation: &testExp1114Var2225,
132+
}
133+
s.mockExperimentService.On("GetDecision", testDecisionContext, testUserContext).Return(shouldBeIgnoredDecision, errors.New("Error making decision"))
134+
135+
expectedDecision := ExperimentDecision{
136+
Variation: &testExp1114Var2226,
137+
}
138+
s.mockExperimentService2.On("GetDecision", testDecisionContext, testUserContext).Return(expectedDecision, nil)
139+
140+
compositeExperimentService := &CompositeExperimentService{
141+
experimentServices: []ExperimentService{
142+
s.mockExperimentService,
143+
s.mockExperimentService2,
144+
},
145+
}
146+
decision, err := compositeExperimentService.GetDecision(testDecisionContext, testUserContext)
147+
s.Equal(expectedDecision, decision)
148+
s.NoError(err)
149+
s.mockExperimentService.AssertExpectations(s.T())
150+
s.mockExperimentService2.AssertExpectations(s.T())
151+
}
152+
118153
func (s *CompositeExperimentTestSuite) TestNewCompositeExperimentService() {
119154
// Assert that the service is instantiated with the correct child services in the right order
120155
compositeExperimentService := NewCompositeExperimentService()

pkg/decision/composite_feature_service.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package decision
1919

2020
import (
21+
"errors"
2122
"fmt"
2223

2324
"github.com/optimizely/go-sdk/pkg/entities"
@@ -45,10 +46,13 @@ func NewCompositeFeatureService(compositeExperimentService ExperimentService) *C
4546
func (f CompositeFeatureService) GetDecision(decisionContext FeatureDecisionContext, userContext entities.UserContext) (FeatureDecision, error) {
4647
for _, featureDecisionService := range f.featureServices {
4748
featureDecision, err := featureDecisionService.GetDecision(decisionContext, userContext)
48-
if featureDecision.Variation != nil {
49+
if err != nil {
50+
cfLogger.Debug(fmt.Sprintf("%v", err))
51+
}
52+
if featureDecision.Variation != nil && err == nil {
4953
return featureDecision, err
5054
}
5155
}
5256

53-
return FeatureDecision{}, fmt.Errorf("no decision was made for feature %s", decisionContext.Feature.Key)
57+
return FeatureDecision{}, errors.New("no decision was made")
5458
}

pkg/decision/composite_feature_service_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package decision
1818

1919
import (
20+
"errors"
2021
"testing"
2122

2223
"github.com/optimizely/go-sdk/pkg/decision/reasons"
@@ -98,6 +99,35 @@ func (s *CompositeFeatureServiceTestSuite) TestGetDecisionFallthrough() {
9899
s.mockFeatureService2.AssertExpectations(s.T())
99100
}
100101

102+
func (s *CompositeFeatureServiceTestSuite) TestGetDecisionReturnsError() {
103+
// test that we move onto the next decision service if an inner service returns an error
104+
testUserContext := entities.UserContext{
105+
ID: "test_user_1",
106+
}
107+
108+
shouldBeIgnoredDecision := FeatureDecision{
109+
Variation: &testExp1113Var2223,
110+
}
111+
s.mockFeatureService.On("GetDecision", s.testFeatureDecisionContext, testUserContext).Return(shouldBeIgnoredDecision, errors.New("Error making decision"))
112+
113+
expectedDecision := FeatureDecision{
114+
Variation: &testExp1113Var2224,
115+
}
116+
s.mockFeatureService2.On("GetDecision", s.testFeatureDecisionContext, testUserContext).Return(expectedDecision, nil)
117+
118+
compositeFeatureService := &CompositeFeatureService{
119+
featureServices: []FeatureService{
120+
s.mockFeatureService,
121+
s.mockFeatureService2,
122+
},
123+
}
124+
decision, err := compositeFeatureService.GetDecision(s.testFeatureDecisionContext, testUserContext)
125+
s.Equal(expectedDecision, decision)
126+
s.NoError(err)
127+
s.mockFeatureService.AssertExpectations(s.T())
128+
s.mockFeatureService2.AssertExpectations(s.T())
129+
}
130+
101131
func (s *CompositeFeatureServiceTestSuite) TestNewCompositeFeatureService() {
102132
// Assert that the service is instantiated with the correct child services in the right order
103133
compositeExperimentService := NewCompositeExperimentService()

0 commit comments

Comments
 (0)