diff --git a/optimizely/client/client.go b/optimizely/client/client.go index c0e5c7e7e..611295c42 100644 --- a/optimizely/client/client.go +++ b/optimizely/client/client.go @@ -133,6 +133,37 @@ func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext) return enabledFeatures, nil } +// Track take and event key with event tags and if the event is part of the config, send to events backend. +func (o *OptimizelyClient) Track(eventKey string, userContext entities.UserContext, eventTags map[string]interface{}) (err error) { + if !o.isValid { + errorMessage := "Optimizely instance is not valid. Failing GetEnabledFeatures." + err = errors.New(errorMessage) + logger.Error(errorMessage, err) + return err + } + + defer func() { + if r := recover(); r != nil { + errorMessage := fmt.Sprintf(`Optimizely SDK is panicking with the error "%s"`, string(debug.Stack())) + err = errors.New(errorMessage) + logger.Error(errorMessage, err) + } + }() + + configEvent, eventError := o.configManager.GetConfig().GetEventByKey(eventKey) + + if eventError == nil { + userEvent := event.CreateConversionUserEvent(o.configManager.GetConfig(), configEvent, userContext, eventTags) + o.eventProcessor.ProcessEvent(userEvent) + } else { + errorMessage := fmt.Sprintf(`Optimizely SDK track: error getting event with key "%s"`, eventKey) + logger.Error(errorMessage, eventError) + return eventError + } + + return nil +} + // Close closes the Optimizely instance and stops any ongoing tasks from its children components func (o *OptimizelyClient) Close() { o.cancelFunc() diff --git a/optimizely/client/client_test.go b/optimizely/client/client_test.go index e282b7125..ea904112b 100644 --- a/optimizely/client/client_test.go +++ b/optimizely/client/client_test.go @@ -18,14 +18,13 @@ package client import ( "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/optimizely/go-sdk/optimizely" "github.com/optimizely/go-sdk/optimizely/decision" "github.com/optimizely/go-sdk/optimizely/entities" + "github.com/optimizely/go-sdk/optimizely/event" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "testing" ) type MockProjectConfig struct { @@ -43,6 +42,11 @@ func (c *MockProjectConfig) GetFeatureList() []entities.Feature { return args.Get(0).([]entities.Feature) } +func (c *MockProjectConfig) GetEventByKey(string) (entities.Event, error) { + args := c.Called() + return args.Get(0).(entities.Event), args.Error(1) +} + type MockProjectConfigManager struct { mock.Mock } @@ -62,6 +66,123 @@ func (m *MockDecisionService) GetFeatureDecision(decisionContext decision.Featur return args.Get(0).(decision.FeatureDecision), args.Error(1) } +type MockProcessor struct { + Events []event.UserEvent +} + +func (f *MockProcessor) ProcessEvent(event event.UserEvent) { + f.Events = append(f.Events, event) +} + +type TestConfig struct { + optimizely.ProjectConfig +} + +func (TestConfig) GetEventByKey(key string) (entities.Event, error) { + if key == "sample_conversion" { + return entities.Event{ExperimentIds: []string{"15402980349"}, ID: "15368860886", Key: "sample_conversion"}, nil + } + + return entities.Event{}, errors.New("No conversion") +} + +func (TestConfig) GetFeatureByKey(string) (entities.Feature, error) { + return entities.Feature{}, nil +} + +func (TestConfig) GetProjectID() string { + return "15389410617" +} +func (TestConfig) GetRevision() string { + return "7" +} +func (TestConfig) GetAccountID() string { + return "8362480420" +} +func (TestConfig) GetAnonymizeIP() bool { + return true +} +func (TestConfig) GetAttributeID(key string) string { // returns "" if there is no id + return "" +} +func (TestConfig) GetBotFiltering() bool { + return false +} +func (TestConfig) GetClientName() string { + return "go-sdk" +} +func (TestConfig) GetClientVersion() string { + return "1.0.0" +} + +func TestTrack(t *testing.T) { + mockProcessor := &MockProcessor{} + + mockConfig := new(TestConfig) + mockConfigManager := new(MockProjectConfigManager) + mockConfigManager.On("GetConfig").Return(mockConfig) + mockDecisionService := new(MockDecisionService) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + eventProcessor: mockProcessor, + isValid: true, + } + + err := client.Track("sample_conversion", entities.UserContext{ID:"1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) + + assert.Nil(t, err) + assert.True(t, len(mockProcessor.Events) == 1) + assert.True(t, mockProcessor.Events[0].VisitorID == "1212121") + assert.True(t, mockProcessor.Events[0].EventContext.ProjectID == "15389410617") + +} + +func TestTrackFail(t *testing.T) { + mockProcessor := &MockProcessor{} + + mockConfig := new(TestConfig) + mockConfigManager := new(MockProjectConfigManager) + mockConfigManager.On("GetConfig").Return(mockConfig) + mockDecisionService := new(MockDecisionService) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + eventProcessor: mockProcessor, + isValid: true, + } + + err := client.Track("bob", entities.UserContext{ID:"1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) + + assert.NotNil(t, err) + assert.True(t, len(mockProcessor.Events) == 0) + +} + +func TestTrackInvalid(t *testing.T) { + mockProcessor := &MockProcessor{} + + mockConfig := new(TestConfig) + mockConfigManager := new(MockProjectConfigManager) + mockConfigManager.On("GetConfig").Return(mockConfig) + mockDecisionService := new(MockDecisionService) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + eventProcessor: mockProcessor, + isValid: false, + } + + err := client.Track("sample_conversion", entities.UserContext{ID:"1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) + + assert.NotNil(t, err) + assert.True(t, len(mockProcessor.Events) == 0) + +} + func TestIsFeatureEnabled(t *testing.T) { testUserContext := entities.UserContext{ID: "test_user_1"} testVariation := entities.Variation{