diff --git a/.travis.yml b/.travis.yml index 043e6552f..ecfae3696 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,7 @@ jobs: env: GIMME_GO_VERSION=1.12.x FSC_PATH="/tmp/fsc-repo" before_script: - mkdir -p $FSC_PATH - - pushd $FSC_PATH && git init && git fetch --depth=1 https://$CI_USER_TOKEN@github.com/optimizely/fullstack-sdk-compatibility-suite ${FSC_BRANCH:-master} && git checkout FETCH_HEAD && popd + - pushd $FSC_PATH && git init && git fetch --depth=1 https://$CI_USER_TOKEN@github.com/optimizely/fullstack-sdk-compatibility-suite "yasir/go-ignore-fv-cases" && git checkout FETCH_HEAD && popd install: - eval "$(gimme)" script: diff --git a/pkg/config/datafileprojectconfig/mappers/experiment.go b/pkg/config/datafileprojectconfig/mappers/experiment.go index 1a4ed1e33..e5cbd9c00 100644 --- a/pkg/config/datafileprojectconfig/mappers/experiment.go +++ b/pkg/config/datafileprojectconfig/mappers/experiment.go @@ -85,6 +85,7 @@ func mapExperiment(rawExperiment datafileEntities.Experiment) entities.Experimen LayerID: rawExperiment.LayerID, Key: rawExperiment.Key, Variations: make(map[string]entities.Variation), + VariationKeyToIDMap: make(map[string]string), TrafficAllocation: make([]entities.Range, len(rawExperiment.TrafficAllocation)), AudienceConditionTree: audienceConditionTree, Whitelist: rawExperiment.ForcedVariations, @@ -93,6 +94,7 @@ func mapExperiment(rawExperiment datafileEntities.Experiment) entities.Experimen for _, variation := range rawExperiment.Variations { experiment.Variations[variation.ID] = mapVariation(variation) + experiment.VariationKeyToIDMap[variation.Key] = variation.ID } for i, allocation := range rawExperiment.TrafficAllocation { diff --git a/pkg/config/datafileprojectconfig/mappers/experiment_test.go b/pkg/config/datafileprojectconfig/mappers/experiment_test.go index 8d42ffa50..9bcc9abf3 100644 --- a/pkg/config/datafileprojectconfig/mappers/experiment_test.go +++ b/pkg/config/datafileprojectconfig/mappers/experiment_test.go @@ -88,6 +88,10 @@ func TestMapExperiments(t *testing.T) { FeatureEnabled: false, }, }, + VariationKeyToIDMap: map[string]string{ + "variation_1": "21111", + "variation_2": "21112", + }, TrafficAllocation: []entities.Range{ { EntityID: "21111", @@ -132,12 +136,13 @@ func TestMapExperimentsWithStringAudienceCondition(t *testing.T) { experiments, experimentKeyMap := MapExperiments(rawExperiments, experimentGroupMap) expectedExperiments := map[string]entities.Experiment{ "11111": { - AudienceIds: []string{"31111"}, - ID: "11111", - GroupID: "15", - Key: "test_experiment_11111", - Variations: map[string]entities.Variation{}, - TrafficAllocation: []entities.Range{}, + AudienceIds: []string{"31111"}, + ID: "11111", + GroupID: "15", + Key: "test_experiment_11111", + Variations: map[string]entities.Variation{}, + VariationKeyToIDMap: map[string]string{}, + TrafficAllocation: []entities.Range{}, AudienceConditionTree: &entities.TreeNode{ Operator: "or", Nodes: []*entities.TreeNode{ diff --git a/pkg/config/datafileprojectconfig/mappers/rollout_test.go b/pkg/config/datafileprojectconfig/mappers/rollout_test.go index f87b6fddf..6dc9e8232 100644 --- a/pkg/config/datafileprojectconfig/mappers/rollout_test.go +++ b/pkg/config/datafileprojectconfig/mappers/rollout_test.go @@ -42,8 +42,8 @@ func TestMapRollouts(t *testing.T) { "21111": entities.Rollout{ ID: "21111", Experiments: []entities.Experiment{ - entities.Experiment{ID: "11111", Key: "exp_11111", Variations: map[string]entities.Variation{}, TrafficAllocation: []entities.Range{}}, - entities.Experiment{ID: "11112", Key: "exp_11112", Variations: map[string]entities.Variation{}, TrafficAllocation: []entities.Range{}}, + entities.Experiment{ID: "11111", Key: "exp_11111", Variations: map[string]entities.Variation{}, VariationKeyToIDMap: map[string]string{}, TrafficAllocation: []entities.Range{}}, + entities.Experiment{ID: "11112", Key: "exp_11112", Variations: map[string]entities.Variation{}, VariationKeyToIDMap: map[string]string{}, TrafficAllocation: []entities.Range{}}, }, }, } diff --git a/pkg/decision/experiment_override_service.go b/pkg/decision/experiment_override_service.go index c327f7d75..dec0e71a3 100644 --- a/pkg/decision/experiment_override_service.go +++ b/pkg/decision/experiment_override_service.go @@ -103,10 +103,8 @@ func (s ExperimentOverrideService) GetDecision(decisionContext ExperimentDecisio return decision, nil } - // TODO(Matt): Implement and use a way to access variations by key - for _, variation := range decisionContext.Experiment.Variations { - variation := variation - if variation.Key == variationKey { + if variationID, ok := decisionContext.Experiment.VariationKeyToIDMap[variationKey]; ok { + if variation, ok := decisionContext.Experiment.Variations[variationID]; ok { decision.Variation = &variation decision.Reason = reasons.OverrideVariationAssignmentFound eosLogger.Debug(fmt.Sprintf("Override variation %v found for user %v", variationKey, userContext.ID)) diff --git a/pkg/decision/experiment_whitelist_service.go b/pkg/decision/experiment_whitelist_service.go index f260f22bc..2ca62820b 100644 --- a/pkg/decision/experiment_whitelist_service.go +++ b/pkg/decision/experiment_whitelist_service.go @@ -47,10 +47,8 @@ func (s ExperimentWhitelistService) GetDecision(decisionContext ExperimentDecisi return decision, nil } - // TODO(Matt): Add a VariationsByKey map to the Experiment struct, and use it to look up Variation by key - for _, variation := range decisionContext.Experiment.Variations { - variation := variation - if variation.Key == variationKey { + if id, ok := decisionContext.Experiment.VariationKeyToIDMap[variationKey]; ok { + if variation, ok := decisionContext.Experiment.Variations[id]; ok { decision.Reason = reasons.WhitelistVariationAssignmentFound decision.Variation = &variation return decision, nil diff --git a/pkg/decision/helpers_test.go b/pkg/decision/helpers_test.go index a899afb85..38798148d 100644 --- a/pkg/decision/helpers_test.go +++ b/pkg/decision/helpers_test.go @@ -104,6 +104,9 @@ var testExp1111 = entities.Experiment{ Variations: map[string]entities.Variation{ "2222": testExp1111Var2222, }, + VariationKeyToIDMap: map[string]string{ + "2222": "2222", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2222", EndOfRange: 10000}, }, @@ -140,6 +143,9 @@ var testExp1112 = entities.Experiment{ Variations: map[string]entities.Variation{ "2222": testExp1111Var2222, }, + VariationKeyToIDMap: map[string]string{ + "2222": "2222", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2222", EndOfRange: 10000}, }, @@ -172,6 +178,10 @@ var testExp1113 = entities.Experiment{ "2223": testExp1113Var2223, "2224": testExp1113Var2224, }, + VariationKeyToIDMap: map[string]string{ + "2223": "2223", + "2224": "2224", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2223", EndOfRange: 5000}, entities.Range{EntityID: "2224", EndOfRange: 10000}, @@ -190,6 +200,10 @@ var testExp1114 = entities.Experiment{ "2225": testExp1114Var2225, "2226": testExp1114Var2226, }, + VariationKeyToIDMap: map[string]string{ + "2225": "2225", + "2226": "2226", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2225", EndOfRange: 5000}, entities.Range{EntityID: "2226", EndOfRange: 10000}, @@ -214,6 +228,9 @@ var testExp1115 = entities.Experiment{ Variations: map[string]entities.Variation{ "2227": testExp1115Var2227, }, + VariationKeyToIDMap: map[string]string{ + "2227": "2227", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2227", EndOfRange: 5000}, }, @@ -239,6 +256,9 @@ var testTargetedExp1116 = entities.Experiment{ Variations: map[string]entities.Variation{ "2228": testTargetedExp1116Var2228, }, + VariationKeyToIDMap: map[string]string{ + "2228": "2228", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2228", EndOfRange: 10000}, }, @@ -254,6 +274,9 @@ var testExpWhitelist = entities.Experiment{ Variations: map[string]entities.Variation{ "2229": testExpWhitelistVar2229, }, + VariationKeyToIDMap: map[string]string{ + "var_2229": "2229", + }, TrafficAllocation: []entities.Range{ entities.Range{EntityID: "2229", EndOfRange: 10000}, }, diff --git a/pkg/entities/experiment.go b/pkg/entities/experiment.go index 3eeebf5b7..1ea919d4c 100644 --- a/pkg/entities/experiment.go +++ b/pkg/entities/experiment.go @@ -32,6 +32,7 @@ type Experiment struct { LayerID string Key string Variations map[string]Variation // keyed by variation ID + VariationKeyToIDMap map[string]string TrafficAllocation []Range GroupID string AudienceConditionTree *TreeNode diff --git a/scripts/run-fsc-tests.sh b/scripts/run-fsc-tests.sh index 6000b4759..4726b3505 100755 --- a/scripts/run-fsc-tests.sh +++ b/scripts/run-fsc-tests.sh @@ -66,5 +66,5 @@ mkdir -p $GO_FEATUREFILES_PATH cp -r $FEATURE_FILES_PATH $GO_FEATUREFILES_PATH export DATAFILES_DIR="$DATAFILES_PATH" -go test -v $(pwd)/tests/integration --godog.tags="$TAG_FILTER_EXPRESSION" --godog.f=progress +go test -v $(pwd)/tests/integration --godog.tags="~@DATAFILE_MANAGER&&~@INPUT_VALIDATION&&~@EVENT_BATCHING&&~@NO_EASY_EVENT_TRACKING&&~@OASIS-3654&&~@TARGETED_ROLLOUT&&~@EXPERIMENT_STATUS&&@~UNSUPPORTED_DATAFILE_VERSION&&@~TRACK_LISTENER&&@~ACTIVATE_LISTENER&&~@OPTIMIZELY_CONFIG&&~@LOG_EVENT_LISTENER" --godog.f=progress echo "Ready for testing." diff --git a/tests/integration/models/constants.go b/tests/integration/models/constants.go index 3fcf81454..5bd17f49c 100644 --- a/tests/integration/models/constants.go +++ b/tests/integration/models/constants.go @@ -61,6 +61,10 @@ const ( Activate SDKAPI = "activate" // Track - the api type is Track Track SDKAPI = "track" + // SetForcedVariation - the api type is SetForcedVariation + SetForcedVariation SDKAPI = "set_forced_variation" + // GetForcedVariation - the api type is GetForcedVariation + GetForcedVariation SDKAPI = "get_forced_variation" ) // KeyListenerCalled - Key for listener called diff --git a/tests/integration/models/forced_variation_params.go b/tests/integration/models/forced_variation_params.go new file mode 100644 index 000000000..a6e2b5a88 --- /dev/null +++ b/tests/integration/models/forced_variation_params.go @@ -0,0 +1,24 @@ +/**************************************************************************** + * Copyright 2019, 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. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package models + +// ForcedVariationRequestParams represents params required for Get and Set ForcedVariation API +type ForcedVariationRequestParams struct { + ExperimentKey string `yaml:"experiment_key"` + UserID string `yaml:"user_id"` + VariationKey string `yaml:"forced_variation_key,omitempty"` +} diff --git a/tests/integration/optlyplugins/userprofileservice/utils.go b/tests/integration/optlyplugins/userprofileservice/utils.go index ecc6cc1d2..cf6bbb83c 100644 --- a/tests/integration/optlyplugins/userprofileservice/utils.go +++ b/tests/integration/optlyplugins/userprofileservice/utils.go @@ -54,10 +54,9 @@ func CreateUserProfileService(config pkg.ProjectConfig, apiOptions models.APIOpt for experimentKey, variationKey := range bucketMap { if experiment, err := config.GetExperimentByKey(experimentKey); err == nil { decisionKey := decision.NewUserDecisionKey(experiment.ID) - for _, variation := range experiment.Variations { - if variation.Key == variationKey { + if variationID, ok := experiment.VariationKeyToIDMap[variationKey]; ok { + if variation, ok := experiment.Variations[variationID]; ok { profile.ExperimentBucketMap[decisionKey] = variation.ID - break } } } diff --git a/tests/integration/support/client_wrapper.go b/tests/integration/support/client_wrapper.go index 10f83f5e7..5be83124c 100644 --- a/tests/integration/support/client_wrapper.go +++ b/tests/integration/support/client_wrapper.go @@ -22,6 +22,7 @@ import ( "os" "path" "path/filepath" + "strconv" "github.com/optimizely/go-sdk/pkg/client" "github.com/optimizely/go-sdk/pkg/config" @@ -34,15 +35,19 @@ import ( "gopkg.in/yaml.v3" ) -// Map to hold clientwrapper instances against scenarioID +// Cached instance of optly wrapper var clientInstance *ClientWrapper +// Since notificationManager is mapped against sdkKey, we need a unique sdkKey for every scenario +var sdkKey int + // ClientWrapper - wrapper around the optimizely client that keeps track of various custom components used with the client type ClientWrapper struct { Client *client.OptimizelyClient DecisionService decision.Service EventDispatcher event.Dispatcher UserProfileService decision.UserProfileService + OverrideStore decision.ExperimentOverrideStore } // DeleteInstance deletes cached instance of optly wrapper @@ -57,6 +62,7 @@ func GetInstance(apiOptions models.APIOptions) *ClientWrapper { return clientInstance } + sdkKey++ datafileDir := os.Getenv("DATAFILES_DIR") datafile, err := ioutil.ReadFile(filepath.Clean(path.Join(datafileDir, apiOptions.DatafileName))) if err != nil { @@ -84,18 +90,23 @@ func GetInstance(apiOptions models.APIOptions) *ClientWrapper { log.Fatal(err) } + overrideStore := decision.NewMapExperimentOverridesStore() userProfileService := userprofileservice.CreateUserProfileService(config, apiOptions) compositeExperimentService := decision.NewCompositeExperimentService( decision.WithUserProfileService(userProfileService), + decision.WithOverrideStore(overrideStore), ) + // @TODO: Add sdkKey dynamically once event-batching support is implemented - compositeService := *decision.NewCompositeService("", decision.WithCompositeExperimentService(compositeExperimentService)) + + compositeService := *decision.NewCompositeService(strconv.Itoa(sdkKey), decision.WithCompositeExperimentService(compositeExperimentService)) decisionService := &optlyplugins.TestCompositeService{CompositeService: compositeService} client, err := optimizelyFactory.Client( client.WithConfigManager(configManager), client.WithDecisionService(decisionService), - client.WithEventProcessor(eventProcessor)) + client.WithEventProcessor(eventProcessor), + ) if err != nil { log.Fatal(err) } @@ -105,14 +116,16 @@ func GetInstance(apiOptions models.APIOptions) *ClientWrapper { DecisionService: decisionService, EventDispatcher: eventProcessor.EventDispatcher, UserProfileService: userProfileService, + OverrideStore: overrideStore, } + clientInstance.DecisionService.(*optlyplugins.TestCompositeService).AddListeners(apiOptions.Listeners) + return clientInstance } // InvokeAPI processes request with arguments func (c *ClientWrapper) InvokeAPI(request models.APIOptions) (models.APIResponse, error) { - c.DecisionService.(*optlyplugins.TestCompositeService).AddListeners(request.Listeners) var response models.APIResponse var err error @@ -147,6 +160,12 @@ func (c *ClientWrapper) InvokeAPI(request models.APIOptions) (models.APIResponse case models.Track: response, err = c.track(request) break + case models.SetForcedVariation: + response, err = c.setForcedVariation(request) + break + case models.GetForcedVariation: + response, err = c.getForcedVariation(request) + break default: break } @@ -328,3 +347,34 @@ func (c *ClientWrapper) track(request models.APIOptions) (models.APIResponse, er response.Result = "NULL" return response, err } + +func (c *ClientWrapper) setForcedVariation(request models.APIOptions) (models.APIResponse, error) { + var params models.ForcedVariationRequestParams + var response models.APIResponse + err := yaml.Unmarshal([]byte(request.Arguments), ¶ms) + response.Result = "NULL" + if err == nil { + // For removeForcedVariation cases + if params.VariationKey == "" { + c.OverrideStore.(*decision.MapExperimentOverridesStore).RemoveVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID}) + } else { + c.OverrideStore.(*decision.MapExperimentOverridesStore).SetVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID}, params.VariationKey) + response.Result = params.VariationKey + } + } + return response, err +} + +func (c *ClientWrapper) getForcedVariation(request models.APIOptions) (models.APIResponse, error) { + var params models.ForcedVariationRequestParams + var response models.APIResponse + err := yaml.Unmarshal([]byte(request.Arguments), ¶ms) + response.Result = "NULL" + if err == nil { + variation, success := c.OverrideStore.(*decision.MapExperimentOverridesStore).GetVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID}) + if success { + response.Result = variation + } + } + return response, err +} diff --git a/tests/integration/support/steps.go b/tests/integration/support/steps.go index db0e0017a..87c5daa80 100644 --- a/tests/integration/support/steps.go +++ b/tests/integration/support/steps.go @@ -265,20 +265,36 @@ func (c *ScenarioCtx) InTheResponseShouldHaveEachOneOfThese(argumentType string, // TheNumberOfDispatchedEventsIs checks the count of the dispatched events to be equal to the given value. func (c *ScenarioCtx) TheNumberOfDispatchedEventsIs(count int) error { - dispatchedEvents := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() - if len(dispatchedEvents) == count { + evaluationMethod := func() (bool, string) { + dispatchedEvents := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() + result := len(dispatchedEvents) == count + if result { + return result, "" + } + return result, "dispatchedEvents count not equal" + } + result, errorMessage := evaluateDispatchedEventsWithTimeout(evaluationMethod) + if result { return nil } - return fmt.Errorf("dispatchedEvents count not equal") + return fmt.Errorf(errorMessage) } // ThereAreNoDispatchedEvents checks the dispatched events count to be empty. func (c *ScenarioCtx) ThereAreNoDispatchedEvents() error { - dispatchedEvents := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() - if len(dispatchedEvents) == 0 { + evaluationMethod := func() (bool, string) { + dispatchedEvents := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() + result := len(dispatchedEvents) == 0 + if result { + return result, "" + } + return result, fmt.Sprintf("dispatchedEvents should be empty but received %d events", len(dispatchedEvents)) + } + result, errorMessage := evaluateDispatchedEventsWithTimeout(evaluationMethod) + if result { return nil } - return fmt.Errorf("dispatchedEvents should be empty but received %d events", len(dispatchedEvents)) + return fmt.Errorf(errorMessage) } // DispatchedEventsPayloadsInclude checks dispatched events to contain the given events. @@ -293,70 +309,84 @@ func (c *ScenarioCtx) DispatchedEventsPayloadsInclude(value *gherkin.DocString) return fmt.Errorf("Invalid request for dispatched Events") } - eventsReceived := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() - eventsReceivedJSON, err := json.Marshal(eventsReceived) - if err != nil { - return fmt.Errorf("Invalid response for dispatched Events") - } - var actualBatchEvents []map[string]interface{} - - if err := json.Unmarshal(eventsReceivedJSON, &actualBatchEvents); err != nil { - return fmt.Errorf("Invalid response for dispatched Events") - } - - // Sort's attributes under visitors which is required for subset comparison of attributes array - sortAttributesForEvents := func(array []map[string]interface{}) []map[string]interface{} { - sortedArray := array - for mainIndex, event := range array { - if visitorsArray, ok := event["visitors"].([]interface{}); ok { - for vIndex, v := range visitorsArray { - if visitor, ok := v.(map[string]interface{}); ok { - // Only sort if all attributes were parsed successfuly - parsedSuccessfully := false - parsedAttributes := []map[string]interface{}{} - if attributesArray, ok := visitor["attributes"].([]interface{}); ok { - for _, tmpAttribute := range attributesArray { - if attribute, ok := tmpAttribute.(map[string]interface{}); ok { - parsedAttributes = append(parsedAttributes, attribute) + evaluationMethod := func() (bool, string) { + eventsReceived := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() + eventsReceivedJSON, err := json.Marshal(eventsReceived) + if err != nil { + return false, "Invalid response for dispatched Events" + } + var actualBatchEvents []map[string]interface{} + + if err := json.Unmarshal(eventsReceivedJSON, &actualBatchEvents); err != nil { + return false, "Invalid response for dispatched Events" + } + + // Sort's attributes under visitors which is required for subset comparison of attributes array + sortAttributesForEvents := func(array []map[string]interface{}) []map[string]interface{} { + sortedArray := array + for mainIndex, event := range array { + if visitorsArray, ok := event["visitors"].([]interface{}); ok { + for vIndex, v := range visitorsArray { + if visitor, ok := v.(map[string]interface{}); ok { + // Only sort if all attributes were parsed successfuly + parsedSuccessfully := false + parsedAttributes := []map[string]interface{}{} + if attributesArray, ok := visitor["attributes"].([]interface{}); ok { + for _, tmpAttribute := range attributesArray { + if attribute, ok := tmpAttribute.(map[string]interface{}); ok { + parsedAttributes = append(parsedAttributes, attribute) + } } + parsedSuccessfully = len(attributesArray) == len(parsedAttributes) + } + if parsedSuccessfully { + // Sort parsed attributes array and assign them to the original events array + sortedAttributes := sortArrayofMaps(parsedAttributes, "key") + sortedArray[mainIndex]["visitors"].([]interface{})[vIndex].(map[string]interface{})["attributes"] = sortedAttributes } - parsedSuccessfully = len(attributesArray) == len(parsedAttributes) - } - if parsedSuccessfully { - // Sort parsed attributes array and assign them to the original events array - sortedAttributes := sortArrayofMaps(parsedAttributes, "key") - sortedArray[mainIndex]["visitors"].([]interface{})[vIndex].(map[string]interface{})["attributes"] = sortedAttributes } } } } + return sortedArray } - return sortedArray - } - expectedBatchEvents = sortAttributesForEvents(expectedBatchEvents) - actualBatchEvents = sortAttributesForEvents(actualBatchEvents) + expectedBatchEvents = sortAttributesForEvents(expectedBatchEvents) + actualBatchEvents = sortAttributesForEvents(actualBatchEvents) + result := subset.Check(expectedBatchEvents, actualBatchEvents) + if result { + return result, "" + } + return result, "DispatchedEvents not equal" + } - if subset.Check(expectedBatchEvents, actualBatchEvents) { + result, errorMessage := evaluateDispatchedEventsWithTimeout(evaluationMethod) + if result { return nil } - return fmt.Errorf("DispatchedEvents not equal") + return fmt.Errorf(errorMessage) } // PayloadsOfDispatchedEventsDontIncludeDecisions checks dispatched events to contain no decisions. func (c *ScenarioCtx) PayloadsOfDispatchedEventsDontIncludeDecisions() error { - dispatchedEvents := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() - - for _, event := range dispatchedEvents { - for _, visitor := range event.Visitors { - for _, snapshot := range visitor.Snapshots { - if len(snapshot.Decisions) > 0 { - return fmt.Errorf("dispatched events should not include decisions") + evaluationMethod := func() (bool, string) { + dispatchedEvents := c.clientWrapper.EventDispatcher.(optlyplugins.EventReceiver).GetEvents() + for _, event := range dispatchedEvents { + for _, visitor := range event.Visitors { + for _, snapshot := range visitor.Snapshots { + if len(snapshot.Decisions) > 0 { + return false, "dispatched events should not include decisions" + } } } } + return true, "" } - return nil + result, errorMessage := evaluateDispatchedEventsWithTimeout(evaluationMethod) + if result { + return nil + } + return fmt.Errorf(errorMessage) } // TheUserProfileServiceStateShouldBe checks current state of UPS diff --git a/tests/integration/support/utils.go b/tests/integration/support/utils.go index d1b5ea130..b067e9eaf 100644 --- a/tests/integration/support/utils.go +++ b/tests/integration/support/utils.go @@ -21,6 +21,7 @@ import ( "regexp" "sort" "strings" + "time" "github.com/google/go-cmp/cmp" "github.com/optimizely/go-sdk/pkg" @@ -101,11 +102,10 @@ func parseTemplate(s string, config pkg.ProjectConfig) string { if len(matches) > 1 { expVarKey := strings.Split(matches[1], ".") if exp, err := config.GetExperimentByKey(expVarKey[0]); err == nil { - for _, variation := range exp.Variations { - if variation.Key == expVarKey[1] { + if variationID, ok := exp.VariationKeyToIDMap[expVarKey[1]]; ok { + if variation, ok := exp.Variations[variationID]; ok { parsedString = strings.Replace(parsedString, matches[0], variation.ID, -1) replaceVariableID() - break } } } @@ -151,3 +151,16 @@ func compareStringSlice(x, y []string) bool { } return false } + +// Evaluates given function with a timeout +func evaluateDispatchedEventsWithTimeout(evaluationMethod func() (result bool, errorMessage string)) (result bool, message string) { + result, errorMessage := evaluationMethod() + // Return immediately if evaluation was successfull + if result { + return result, errorMessage + } + // Retry after 200ms + time.Sleep(200 * time.Millisecond) + result, errorMessage = evaluationMethod() + return result, errorMessage +}