From 680f920e1c1cf468d4bd03be494f8a439fbf877d Mon Sep 17 00:00:00 2001 From: Nadia Mayor Date: Mon, 13 May 2024 16:34:20 -0300 Subject: [PATCH] Add test cases for semver and unsupported matcher --- go.mod | 2 +- go.sum | 2 + splitio/client/client_test.go | 270 ++++++++++++++++++++++++++++ splitio/version.go | 2 +- testdata/splits_mock_3.json | 319 ++++++++++++++++++++++++++++++++++ 5 files changed, 593 insertions(+), 2 deletions(-) create mode 100644 testdata/splits_mock_3.json diff --git a/go.mod b/go.mod index 7b9d9b7..dcb5619 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/splitio/go-client/v6 go 1.18 require ( - github.com/splitio/go-split-commons/v5 v5.2.2-0.20240510211202-3c3300ae55ce + github.com/splitio/go-split-commons/v5 v5.2.2-0.20240513192200-9e60c1ddc0f3 github.com/splitio/go-toolkit/v5 v5.4.0 ) diff --git a/go.sum b/go.sum index e489fe0..2de0acc 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvkt github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/splitio/go-split-commons/v5 v5.2.2-0.20240510211202-3c3300ae55ce h1:aZ+p/vi2sOD0NsWCny93qQl1uJC30b/wnpMcUVt7WEI= github.com/splitio/go-split-commons/v5 v5.2.2-0.20240510211202-3c3300ae55ce/go.mod h1:344KP05ULARzjRfnC4VtGSyu5l3kmIM375WUIzrURs0= +github.com/splitio/go-split-commons/v5 v5.2.2-0.20240513192200-9e60c1ddc0f3 h1:ARgFL9kuUOYX5U1afUbWSMp3fvB7LeWsTDNWWDT9Hl8= +github.com/splitio/go-split-commons/v5 v5.2.2-0.20240513192200-9e60c1ddc0f3/go.mod h1:344KP05ULARzjRfnC4VtGSyu5l3kmIM375WUIzrURs0= github.com/splitio/go-toolkit/v5 v5.4.0 h1:g5WFpRhQomnXCmvfsNOWV4s5AuUrWIZ+amM68G8NBKM= github.com/splitio/go-toolkit/v5 v5.4.0/go.mod h1:xYhUvV1gga9/1029Wbp5pjnR6Cy8nvBpjw99wAbsMko= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= diff --git a/splitio/client/client_test.go b/splitio/client/client_test.go index bccdc16..f00a23c 100644 --- a/splitio/client/client_test.go +++ b/splitio/client/client_test.go @@ -2114,6 +2114,122 @@ func TestClientDebug(t *testing.T) { } } +func TestUnsupportedMatcherAndSemver(t *testing.T) { + var isDestroyCalled = false + var splitsMock, _ = ioutil.ReadFile("../../testdata/splits_mock_3.json") + + postChannel := make(chan string, 1) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v2/auth": + if r.URL.Query().Get("s") != "1.1" { + t.Error("should be parameter s, for flags spec") + } + fmt.Fprintln(w, "{\"pushEnabled\": false, \"token\": \"token\"}") + return + case "/splitChanges": + fmt.Fprintln(w, string(splitsMock)) + return + case "/testImpressions/bulk": + if r.Header.Get("SplitSDKImpressionsMode") != commonsCfg.ImpressionsModeOptimized { + t.Error("Wrong header") + } + + if isDestroyCalled { + rBody, _ := ioutil.ReadAll(r.Body) + var dataInPost []map[string]interface{} + err := json.Unmarshal(rBody, &dataInPost) + if err != nil { + t.Error(err) + return + } + if len(dataInPost) != 6 { + t.Error("It should send two impressions in optimized mode") + } + for _, ki := range dataInPost { + if asISlice, ok := ki["i"].([]interface{}); !ok || len(asISlice) != 1 { + t.Error("It should send only one impression per featureName", dataInPost) + } + if ki["f"] == "unsupported" { + message := ki["i"].([]interface{})[0].(map[string]interface{})["r"] + if message != "targeting rule type unsupported by sdk" { + t.Error("message sould be: targeting rule type unsupported by sdk") + } + } + } + } + + fmt.Fprintln(w, "ok") + postChannel <- "finished" + case "/testImpressions/count": + fallthrough + case "/keys/ss": + fallthrough + case "/events/bulk": + fallthrough + case "/segmentChanges": + fallthrough + default: + fmt.Fprintln(w, "ok") + } + })) + defer ts.Close() + + cfg := conf.Default() + cfg.Advanced.AuthServiceURL = ts.URL + cfg.Advanced.EventsURL = ts.URL + cfg.Advanced.SdkURL = ts.URL + cfg.Advanced.TelemetryServiceURL = ts.URL + + factory, _ := NewSplitFactory("test", cfg) + client := factory.Client() + client.BlockUntilReady(2) + + // Calls treatments to generate one valid impression + time.Sleep(300 * time.Millisecond) // Let's wait until first call of recorders have finished + attributes := make(map[string]interface{}) + attributes["version"] = "1.22.9" + evaluation := client.Treatment("user1", "semver", attributes) + if evaluation != "on" { + t.Error("evaluation for semver should be on") + } + attributes["version"] = "2.0.0" + evaluation = client.Treatment("user1", "semver1", attributes) + if evaluation != "on" { + t.Error("evaluation for semver should be on") + } + evaluation = client.Treatment("user1", "semver2", attributes) + if evaluation != "on" { + t.Error("evaluation for semver should be on") + } + attributes["version"] = "1.0.0" + evaluation = client.Treatment("user1", "semver3", attributes) + if evaluation != "on" { + t.Error("evaluation for semver should be on") + } + attributes["version"] = "2.1.0" + evaluation = client.Treatment("user1", "semver4", attributes) + if evaluation != "on" { + t.Error("evaluation for semver should be on") + } + evaluation = client.Treatment("user1", "unsupported", nil) + if evaluation != "control" { + t.Error("evaluation for unsupported should be control") + } + + isDestroyCalled = true + client.Destroy() + + select { + case <-postChannel: + return + case <-time.After(4 * time.Second): + t.Error("The test couldn't send impressions to check headers") + return + } +} + func TestTelemetryMemory(t *testing.T) { factoryInstances = make(map[string]int64) var metricsInitCalled int64 @@ -2633,3 +2749,157 @@ func TestClientDebugRedis(t *testing.T) { prefixedClient.Del(k) } } + +var semver string = "3.4.5" +var attribute string = "version" + +var splitSemver = &dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 1494593336752, + DefaultTreatment: "off", + Killed: false, + Name: "semver", + Seed: -1992295819, + Status: "ACTIVE", + TrafficAllocation: 100, + TrafficAllocationSeed: -285565213, + TrafficTypeName: "user", + Configurations: map[string]string{"on": "{\"color\": \"blue\",\"size\": 13}"}, + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "default rule", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{ + { + KeySelector: &dtos.KeySelectorDTO{ + TrafficType: "user", + Attribute: &attribute, + }, + MatcherType: "EQUAL_TO_SEMVER", + String: &semver, + Whitelist: nil, + Negate: false, + }, + }, + }, + Partitions: []dtos.PartitionDTO{ + { + Size: 100, + Treatment: "on", + }, + { + Size: 0, + Treatment: "off", + }, + }, + }, + }, +} + +var splitUnsupported = &dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 1494593336752, + DefaultTreatment: "off", + Killed: false, + Name: "unsupported", + Seed: -1992295819, + Status: "ACTIVE", + TrafficAllocation: 100, + TrafficAllocationSeed: -285565213, + TrafficTypeName: "user", + Configurations: map[string]string{"on": "{\"color\": \"blue\",\"size\": 13}"}, + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "default rule", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{ + { + KeySelector: &dtos.KeySelectorDTO{ + TrafficType: "user", + Attribute: nil, + }, + MatcherType: "UNSUPPORTED", + Whitelist: nil, + Negate: false, + }, + }, + }, + Partitions: []dtos.PartitionDTO{ + { + Size: 100, + Treatment: "on", + }, + }, + }, + }, +} + +func TestUnsupportedandSemverMatcherRedis(t *testing.T) { + redisConfig := &commonsCfg.RedisConfig{ + Host: "localhost", + Port: 6379, + Password: "", + Prefix: "test-prefix-semver", + } + + prefixedClient, _ := redis.NewRedisClient(redisConfig, logging.NewLogger(&logging.LoggerOptions{})) + raw, _ := json.Marshal(*splitSemver) + prefixedClient.Set("SPLITIO.split.semver", raw, 0) + raw, _ = json.Marshal(*splitUnsupported) + prefixedClient.Set("SPLITIO.split.unsupported", raw, 0) + + impTest := &ImpressionListenerTest{} + cfg := conf.Default() + cfg.LabelsEnabled = true + cfg.Advanced.ImpressionListener = impTest + cfg.ImpressionsMode = commonsCfg.ImpressionsModeOptimized + cfg.OperationMode = conf.RedisConsumer + cfg.Redis = *redisConfig + + factory, _ := NewSplitFactory("test", cfg) + client := factory.Client() + client.BlockUntilReady(2) + + // Calls treatments to generate one valid impression + time.Sleep(300 * time.Millisecond) // Let's wait until first call of recorders have finished + attributes := make(map[string]interface{}) + attributes["version"] = "3.4.5" + evaluation := client.Treatment("user1", "semver", attributes) + if evaluation != "on" { + t.Error("evaluation for semver should be on") + } + evaluation = client.Treatment("user2", "unsupported", nil) + if evaluation != "control" { + t.Error("evaluation for unsupported should be control") + } + client.Destroy() + + // Validate impressions + impressions, _ := prefixedClient.LRange("SPLITIO.impressions", 0, -1) + + if len(impressions) != 2 { + t.Error("Impression length shold be 2") + } + + for _, imp := range impressions { + var imprObject dtos.ImpressionQueueObject + _ = json.Unmarshal([]byte(imp), &imprObject) + + if imprObject.Impression.KeyName == "user1" && imprObject.Impression.FeatureName == "semver" && imprObject.Impression.Pt != 0 { + t.Error("Pt should be 0.") + } + if imprObject.Impression.KeyName == "user2" && imprObject.Impression.FeatureName == "unsupported" && imprObject.Impression.Pt != 0 { + t.Error("Pt should be 0.") + } + } + + // Clean redis + keys, _ := prefixedClient.Keys("SPLITIO*") + for _, k := range keys { + prefixedClient.Del(k) + } +} diff --git a/splitio/version.go b/splitio/version.go index 485b795..0c541f5 100644 --- a/splitio/version.go +++ b/splitio/version.go @@ -1,4 +1,4 @@ package splitio // Version contains a string with the split sdk version -const Version = "6.6.0-rc1" +const Version = "6.6.0-rc2" diff --git a/testdata/splits_mock_3.json b/testdata/splits_mock_3.json new file mode 100644 index 0000000..48a2abc --- /dev/null +++ b/testdata/splits_mock_3.json @@ -0,0 +1,319 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "semver", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"color\": \"blue\",\"size\": 13}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType":"EQUAL_TO_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":"1.22.9" + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "of", + "size": 0 + } + ], + "label": "in segment all" + } + ] + }, + { + "trafficTypeName": "user", + "name": "semver1", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"color\": \"blue\",\"size\": 13}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType":"BETWEEN_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":null, + "betweenStringMatcherData":{ + "start":"1.22.9", + "end":"2.1.0" + } + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "of", + "size": 0 + } + ], + "label": "in segment all" + } + ] + }, + { + "trafficTypeName": "user", + "name": "semver2", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"color\": \"blue\",\"size\": 13}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType":"GREATER_THAN_OR_EQUAL_TO_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":"1.22.9" + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "of", + "size": 0 + } + ], + "label": "in segment all" + } + ] + }, + { + "trafficTypeName": "user", + "name": "semver3", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"color\": \"blue\",\"size\": 13}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType":"LESS_THAN_OR_EQUAL_TO_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":null, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":"1.22.9" + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "of", + "size": 0 + } + ], + "label": "in segment all" + } + ] + }, + { + "trafficTypeName": "user", + "name": "semver4", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"color\": \"blue\",\"size\": 13}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "version" + }, + "matcherType":"IN_LIST_SEMVER", + "negate":false, + "userDefinedSegmentMatcherData":null, + "whitelistMatcherData":{ + "whitelist":[ + "1.22.9", + "2.1.0" + ] + }, + "unaryNumericMatcherData":null, + "betweenMatcherData":null, + "dependencyMatcherData":null, + "booleanMatcherData":null, + "stringMatcherData":null, + "betweenStringMatcherData":null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 100 + }, + { + "treatment": "of", + "size": 0 + } + ], + "label": "in segment all" + } + ] + }, + { + "trafficTypeName": "user", + "name": "unsupported", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"color\": \"blue\",\"size\": 13}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "UNSUPPORTED", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "of", + "size": 100 + } + ], + "label": "in segment all" + } + ] + } + ], + "since": 1491244291288, + "till": 1491244291288 + } \ No newline at end of file