mirror of
https://github.com/hanzoai/insights-go.git
synced 2026-09-30 22:46:12 +00:00
5273 lines
97 KiB
Go
5273 lines
97 KiB
Go
package insights
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
json "github.com/goccy/go-json"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Note: Property matching tests (TestMatchProperty*) have been moved to feature_flags_matching_test.go
|
|
|
|
func TestFlagPersonProperty(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag-person-prop.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "simple-flag",
|
|
DistinctId: "some-distinct-id",
|
|
PersonProperties: NewProperties().Set("region", "USA"),
|
|
},
|
|
)
|
|
|
|
if isMatch != true {
|
|
t.Error("Should match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "simple-flag",
|
|
DistinctId: "some-distinct-id",
|
|
PersonProperties: NewProperties().Set("region", "Canada"),
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
}
|
|
|
|
func TestFlagGroup(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
decoder := json.NewDecoder(r.Body)
|
|
decoder.DisallowUnknownFields()
|
|
var reqBody FlagsRequestData
|
|
err := decoder.Decode(&reqBody)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
groupsEquality := reflect.DeepEqual(reqBody.Groups, Groups{"company": "abc"})
|
|
if !groupsEquality {
|
|
t.Errorf("Expected groups to be map[company:abc], got %s", reqBody.Groups)
|
|
}
|
|
|
|
distinctIdEquality := reflect.DeepEqual(reqBody.DistinctId, "-")
|
|
if !distinctIdEquality {
|
|
t.Errorf("Expected distinctId to be -, got %s", reqBody.DistinctId)
|
|
}
|
|
|
|
apiKeyEquality := reflect.DeepEqual(reqBody.ApiKey, "Csyjlnlun3OzyNJAafdlv")
|
|
if !apiKeyEquality {
|
|
t.Errorf("Expected apiKey to be Csyjlnlun3OzyNJAafdlv, got %s", reqBody.ApiKey)
|
|
}
|
|
|
|
personPropertiesEquality := reflect.DeepEqual(reqBody.PersonProperties, Properties{"region": "Canada"})
|
|
if !personPropertiesEquality {
|
|
t.Errorf("Expected personProperties to be map[region:Canada], got %s", reqBody.PersonProperties)
|
|
}
|
|
|
|
groupPropertiesEquality := reflect.DeepEqual(reqBody.GroupProperties, map[string]Properties{"company": {"name": "Project Name 1"}})
|
|
if !groupPropertiesEquality {
|
|
t.Errorf("Expected groupProperties to be map[company:map[name:Project Name 1]], got %s", reqBody.GroupProperties)
|
|
}
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-flag-group-properties.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// Ignore batch requests
|
|
} else {
|
|
t.Error("Unknown request made by library")
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "unknown-flag",
|
|
DistinctId: "-",
|
|
Groups: Groups{"company": "abc"},
|
|
PersonProperties: NewProperties().Set("region", "Canada"),
|
|
GroupProperties: map[string]Properties{"company": NewProperties().Set("name", "Project Name 1")},
|
|
OnlyEvaluateLocally: false,
|
|
},
|
|
)
|
|
|
|
if isMatch != false {
|
|
t.Error("Unknown flag shouldn't match known flags")
|
|
}
|
|
}
|
|
|
|
func TestFlagGroupProperty(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(fixture("feature_flag/test-flag-group-properties.json")))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "group-flag",
|
|
DistinctId: "some-distinct-id",
|
|
GroupProperties: map[string]Properties{"company": NewProperties().Set("name", "Project Name 1")},
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "group-flag",
|
|
DistinctId: "some-distinct-id",
|
|
GroupProperties: map[string]Properties{"company": NewProperties().Set("name", "Project Name 2")},
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "group-flag",
|
|
DistinctId: "some-distinct-id",
|
|
Groups: Groups{"company": "amazon_without_rollout"},
|
|
GroupProperties: map[string]Properties{"company": NewProperties().Set("name", "Project Name 1")},
|
|
},
|
|
)
|
|
|
|
if isMatch != true {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestComplexDefinition(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-complex-definition.json"))) // Don't return anything for local eval
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
|
|
isMatch, err := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "complex-flag",
|
|
DistinctId: "some-distinct-id",
|
|
PersonProperties: NewProperties().Set("region", "USA").Set("name", "Aloha"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, isMatch)
|
|
|
|
isMatch, err = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "complex-flag",
|
|
DistinctId: "some-distinct-id_within_rollout_3",
|
|
PersonProperties: NewProperties().Set("region", "USA").Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, isMatch)
|
|
}
|
|
|
|
func TestFallbackToFlags(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte("{}")) // Don't return anything for local eval
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "simple-flag",
|
|
DistinctId: "some-distinct-id",
|
|
},
|
|
)
|
|
|
|
if isMatch != true {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestFeatureFlagsDontFallbackToFlagsWhenOnlyLocalEvaluationIsTrue(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte("test-flags-v3.json"))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-feature-flags-dont-fallback-to-decide-when-only-local-evaluation-is-true.json")))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
matchedPayload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "some-distinct-id",
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
if matchedPayload != "" {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
matchedVariant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "some-distinct-id",
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
if matchedVariant != nil {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "some-distinct-id",
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
matchedVariant, _ = client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature2",
|
|
DistinctId: "some-distinct-id",
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
if matchedVariant != nil {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature2",
|
|
DistinctId: "some-distinct-id",
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
}
|
|
|
|
func TestFeatureFlagDefaultsDontHinderEvaluation(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-false.json")))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "false-flag",
|
|
DistinctId: "some-distinct-id",
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "false-flag",
|
|
DistinctId: "some-distinct-id",
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "false-flag-2",
|
|
DistinctId: "some-distinct-id",
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "false-flag-2",
|
|
DistinctId: "some-distinct-id",
|
|
},
|
|
)
|
|
|
|
if isMatch == true {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
}
|
|
|
|
func TestFeatureFlagNullComeIntoPlayOnlyWhenFlagsErrorsOut(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("{ads}"))
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
matchedPayload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "test-get-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if matchedPayload != "" {
|
|
t.Error("Should not match")
|
|
}
|
|
|
|
isMatch, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "test-get-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if isMatch != nil {
|
|
t.Error("Should be nil")
|
|
}
|
|
|
|
isMatch, _ = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "test-get-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if isMatch != nil {
|
|
t.Error("Should be nil")
|
|
}
|
|
}
|
|
|
|
func TestExperienceContinuityOverride(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
featureVariant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if featureVariant != "decide-fallback-value" {
|
|
t.Error("Should be decide-fallback-value")
|
|
}
|
|
|
|
payload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if payload != "{\"foo\": \"bar\"}" {
|
|
t.Error(`Should be "{"foo": "bar"}"`)
|
|
}
|
|
}
|
|
|
|
func TestGetAllFlags(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-multiple-flags.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
featureVariants, _ := client.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "distinct-id",
|
|
})
|
|
|
|
if featureVariants["beta-feature"] != "decide-fallback-value" || featureVariants["beta-feature2"] != "variant-2" {
|
|
t.Error("Should match decide values")
|
|
}
|
|
}
|
|
|
|
func TestGetAllFlagsEmptyLocal(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte("{}"))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, err := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
|
|
featureVariants, err := client.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "distinct-id",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
if featureVariants["beta-feature"] != "decide-fallback-value" || featureVariants["beta-feature2"] != "variant-2" {
|
|
t.Error("Should match decide values")
|
|
}
|
|
}
|
|
|
|
func TestGetAllFlagsNoRemoteFallback(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-multiple-flags-valid.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
featureVariants, _ := client.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "distinct-id",
|
|
})
|
|
|
|
if featureVariants["beta-feature"] != true || featureVariants["disabled-feature"] != false {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestGetAllFlagsOnlyLocalEvaluationSet(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-get-all-flags-with-fallback-but-only-local-evaluation-set.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
featureVariants, _ := client.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "distinct-id",
|
|
OnlyEvaluateLocally: true,
|
|
})
|
|
|
|
if featureVariants["beta-feature"] != true || featureVariants["disabled-feature"] != false || featureVariants["beta-feature2"] != nil {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestComputeInactiveFlagsLocally(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-compute-inactive-flags-locally.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
featureVariants, _ := client.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "distinct-id",
|
|
})
|
|
|
|
if featureVariants["beta-feature"] != true || featureVariants["disabled-feature"] != false {
|
|
t.Error("Should match")
|
|
}
|
|
|
|
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-compute-inactive-flags-locally-2.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ = NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
featureVariants, _ = client.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "distinct-id",
|
|
})
|
|
|
|
if featureVariants["beta-feature"] != false || featureVariants["disabled-feature"] != true {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestFeatureFlagWithDependencies(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-flag-with-dependencies.json")))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("test-api-key", Config{
|
|
PersonalApiKey: "test-personal-api-key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
// Test that flag evaluation handles flag dependencies properly
|
|
// The flag has a dependency on "beta-feature" which doesn't exist locally
|
|
// Since OnlyEvaluateLocally is true and the dependency can't be resolved locally,
|
|
// this should return an error
|
|
_, err := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "flag-with-dependencies",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
// Should return an error since dependency can't be evaluated locally
|
|
if err == nil {
|
|
t.Error("Should return error when flag dependencies can't be evaluated locally")
|
|
}
|
|
|
|
// Test with email that doesn't match - should also error due to missing dependency
|
|
_, err = client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "flag-with-dependencies",
|
|
DistinctId: "test-user-2",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
OnlyEvaluateLocally: true,
|
|
},
|
|
)
|
|
|
|
if err == nil {
|
|
t.Error("Should return error when flag dependencies can't be evaluated locally")
|
|
}
|
|
}
|
|
|
|
func TestFeatureEnabledSimpleIsTrueWhenRolloutUndefined(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag-without-rollout.json")))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "simple-flag",
|
|
DistinctId: "distinct-id",
|
|
},
|
|
)
|
|
if isMatch != true {
|
|
t.Error("Should be enabled")
|
|
}
|
|
}
|
|
|
|
func TestGetFeatureFlag(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag-person-prop.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
var capturedEvent *CaptureInApi
|
|
var mu sync.Mutex
|
|
eventCaptured := make(chan struct{}, 1)
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
BatchSize: 1,
|
|
Callback: testCallback{
|
|
func(m APIMessage) {
|
|
if capture, ok := m.(CaptureInApi); ok {
|
|
mu.Lock()
|
|
capturedEvent = &capture
|
|
mu.Unlock()
|
|
select {
|
|
case eventCaptured <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
},
|
|
func(m APIMessage, e error) {},
|
|
},
|
|
})
|
|
defer client.Close()
|
|
|
|
variant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "test-get-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if variant != "variant-1" {
|
|
t.Error("Should match")
|
|
}
|
|
|
|
select {
|
|
case <-eventCaptured:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Timed out waiting for captured event")
|
|
}
|
|
|
|
mu.Lock()
|
|
lastEvent := capturedEvent
|
|
mu.Unlock()
|
|
|
|
if lastEvent == nil || lastEvent.Event != "$feature_flag_called" {
|
|
t.Errorf("Expected a $feature_flag_called event, got: %v", lastEvent)
|
|
}
|
|
|
|
if lastEvent != nil {
|
|
if lastEvent.Properties["$feature_flag"] != "test-get-feature" {
|
|
t.Errorf("Expected feature flag key 'test-get-feature', got: %v", lastEvent.Properties["$feature_flag"])
|
|
}
|
|
if lastEvent.Properties["$feature_flag_response"] != "variant-1" {
|
|
t.Errorf("Expected feature flag response 'variant-1', got: %v", lastEvent.Properties["$feature_flag_response"])
|
|
}
|
|
if lastEvent.Properties["locally_evaluated"] != false {
|
|
t.Errorf("Expected locally_evaluated to be false (test-get-feature is only in /flags response), got: %v", lastEvent.Properties["locally_evaluated"])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetFeatureFlagLocallyEvaluated(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag-person-prop.json")))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
var capturedEvent *CaptureInApi
|
|
var mu sync.Mutex
|
|
eventCaptured := make(chan struct{}, 1)
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
BatchSize: 1,
|
|
Callback: testCallback{
|
|
func(m APIMessage) {
|
|
if capture, ok := m.(CaptureInApi); ok {
|
|
mu.Lock()
|
|
capturedEvent = &capture
|
|
mu.Unlock()
|
|
select {
|
|
case eventCaptured <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
},
|
|
func(m APIMessage, e error) {},
|
|
},
|
|
})
|
|
defer client.Close()
|
|
|
|
value, err := client.GetFeatureFlag(FeatureFlagPayload{
|
|
Key: "simple-flag",
|
|
DistinctId: "distinct_id",
|
|
PersonProperties: NewProperties().Set("region", "USA"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if value != true {
|
|
t.Errorf("Expected simple-flag to evaluate to true, got: %v", value)
|
|
}
|
|
|
|
select {
|
|
case <-eventCaptured:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Timed out waiting for captured event")
|
|
}
|
|
|
|
mu.Lock()
|
|
lastEvent := capturedEvent
|
|
mu.Unlock()
|
|
|
|
if lastEvent == nil || lastEvent.Event != "$feature_flag_called" {
|
|
t.Fatalf("Expected a $feature_flag_called event, got: %v", lastEvent)
|
|
}
|
|
if lastEvent.Properties["locally_evaluated"] != true {
|
|
t.Errorf("Expected locally_evaluated to be true for local evaluation, got: %v", lastEvent.Properties["locally_evaluated"])
|
|
}
|
|
}
|
|
|
|
func TestGetFeatureFlagPayload(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag-person-prop.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
variant, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "test-get-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if variant != "this is a string" {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestGetRemoteConfigPayload(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(fixture("test-remote-config.json")))
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
payload, _ := client.GetRemoteConfigPayload("flag_key")
|
|
|
|
var payloadMap map[string]interface{}
|
|
err := json.Unmarshal([]byte(payload), &payloadMap)
|
|
if err != nil {
|
|
t.Error("Failed to decode payload")
|
|
}
|
|
|
|
if payloadMap["foo"] != "bar" || payloadMap["baz"] != float64(42) {
|
|
t.Error("Should match")
|
|
}
|
|
}
|
|
|
|
func TestFlagWithVariantOverrides(t *testing.T) {
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-variant-override.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
variant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "test_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "second-variant" {
|
|
t.Error("Should match", variant, "second-variant")
|
|
}
|
|
|
|
payload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "test_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if payload != "{\"test\": 2}" {
|
|
t.Error("Should match", payload, "{\"test\": 2}")
|
|
}
|
|
|
|
variant, _ = client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "example_id",
|
|
},
|
|
)
|
|
|
|
if variant != "first-variant" {
|
|
t.Error("Should match", variant, "first-variant")
|
|
}
|
|
|
|
payload, _ = client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "example_id",
|
|
},
|
|
)
|
|
|
|
if payload != "{\"test\": 1}" {
|
|
t.Error("Should match", payload, "{\"test\": 1}")
|
|
}
|
|
}
|
|
|
|
func TestFlagWithClashingVariantOverrides(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-variant-override-clashing.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
variant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "test_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "second-variant" {
|
|
t.Error("Should match", variant, "second-variant")
|
|
}
|
|
|
|
payload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "test_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if payload != "{\"test\": 2}" {
|
|
t.Error("Should match", payload, "{\"test\": 2}")
|
|
}
|
|
|
|
variant, _ = client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "example_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "second-variant" {
|
|
t.Error("Should match", variant, "second-variant")
|
|
}
|
|
|
|
payload, _ = client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "example_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if payload != "{\"test\": 2}" {
|
|
t.Error("Should match", payload, "{\"test\": 2}")
|
|
}
|
|
}
|
|
|
|
func TestFlagWithInvalidVariantOverrides(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-variant-override-invalid.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
variant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "test_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "third-variant" {
|
|
t.Error("Should match", variant, "third-variant")
|
|
}
|
|
|
|
payload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "test_id",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if payload != "{\"test\": 3}" {
|
|
t.Error("Should match", payload, "{\"test\": 3}")
|
|
}
|
|
|
|
variant, _ = client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "example_id",
|
|
},
|
|
)
|
|
|
|
if variant != "second-variant" {
|
|
t.Error("Should match", variant, "second-variant")
|
|
}
|
|
|
|
payload, _ = client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "beta-feature",
|
|
DistinctId: "example_id",
|
|
},
|
|
)
|
|
|
|
if payload != "{\"test\": 2}" {
|
|
t.Error("Should match", payload, "{\"test\": 2}")
|
|
}
|
|
}
|
|
|
|
func TestConditionsEvaluatedInOrder(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-condition-order.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
// Test 1: User with @vip.com email should get "first-condition" variant
|
|
// because the first condition (100% rollout) matches before the VIP condition
|
|
variant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "order-test-flag",
|
|
DistinctId: "vip-user",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "first-condition" {
|
|
t.Error("Expected first-condition variant for VIP user, but got", variant)
|
|
}
|
|
|
|
payload, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "order-test-flag",
|
|
DistinctId: "vip-user",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if payload != "{\"order\": 1}" {
|
|
t.Error("Expected payload {\"order\": 1} for VIP user, but got", payload)
|
|
}
|
|
|
|
// Test 2: User with [email protected] should also get "first-condition" variant
|
|
// because the first condition matches before the specific email condition
|
|
variant, _ = client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "order-test-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "first-condition" {
|
|
t.Error("Expected first-condition variant for [email protected] user, but got", variant)
|
|
}
|
|
|
|
// Test 3: Any other user should also get "first-condition" variant
|
|
variant, _ = client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "order-test-flag",
|
|
DistinctId: "random-user",
|
|
PersonProperties: NewProperties().Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
|
|
if variant != "first-condition" {
|
|
t.Error("Expected first-condition variant for random user, but got", variant)
|
|
}
|
|
}
|
|
|
|
func TestCaptureIsCalled(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag-person-prop.json")))
|
|
}
|
|
}))
|
|
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
variant, _ := client.GetFeatureFlag(
|
|
|
|
FeatureFlagPayload{
|
|
Key: "test-get-feature",
|
|
DistinctId: "distinct_id",
|
|
},
|
|
)
|
|
|
|
if variant != "variant-1" {
|
|
t.Error("Should match")
|
|
}
|
|
|
|
}
|
|
|
|
func TestSimpleFlagConsistency(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag.json")))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
results := []bool{
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
}
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
isMatch, _ := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "simple-flag",
|
|
DistinctId: fmt.Sprintf("%s%d", "distinct_id_", i),
|
|
},
|
|
)
|
|
if results[i] != isMatch {
|
|
t.Error("Match result is not consistent")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultivariateFlagConsistency(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(fixture("feature_flag/test-multivariate-flag.json")))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
results := []interface{}{
|
|
"second-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"fourth-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"fifth-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
"third-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fifth-variant",
|
|
"second-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"fifth-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"second-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"fifth-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"fifth-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"fourth-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"fifth-variant",
|
|
false,
|
|
"first-variant",
|
|
"fifth-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"fifth-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"second-variant",
|
|
"fifth-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"fifth-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"fourth-variant",
|
|
"fifth-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
"fourth-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"fourth-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"fourth-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"fifth-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"fourth-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"fifth-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"fifth-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"fifth-variant",
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fifth-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
"fifth-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"fourth-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"third-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"fifth-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
"fifth-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"second-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"fifth-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"fourth-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"fifth-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
"second-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
false,
|
|
false,
|
|
"first-variant",
|
|
"third-variant",
|
|
"second-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
"first-variant",
|
|
false,
|
|
"third-variant",
|
|
"second-variant",
|
|
"third-variant",
|
|
false,
|
|
false,
|
|
"third-variant",
|
|
"first-variant",
|
|
false,
|
|
"first-variant",
|
|
}
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
variant, _ := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-flag",
|
|
DistinctId: fmt.Sprintf("%s%d", "distinct_id_", i),
|
|
},
|
|
)
|
|
if results[i] != variant {
|
|
t.Error("Match result is not consistent")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultivariateFlagConsistencyPayload(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(fixture("feature_flag/test-multivariate-flag.json")))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
results := []string{
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 4}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 5}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 2}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"",
|
|
"",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 2}",
|
|
"{\"test\": 3}",
|
|
"",
|
|
"",
|
|
"{\"test\": 3}",
|
|
"{\"test\": 1}",
|
|
"",
|
|
"{\"test\": 1}",
|
|
}
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
variant, _ := client.GetFeatureFlagPayload(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-flag",
|
|
DistinctId: fmt.Sprintf("%s%d", "distinct_id_", i),
|
|
},
|
|
)
|
|
if results[i] != variant {
|
|
t.Errorf("Match result is not consistent, expected %s, got %s", results[i], variant)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: Cohort tests (TestComplexCohorts*) have been moved to feature_flags_cohorts_test.go
|
|
|
|
func TestFlagsFetchFail(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// ignore batch requests
|
|
} else {
|
|
t.Errorf("Unknown request made by library: %s", r.URL.String())
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
FeatureFlagRequestTimeout: 10 * time.Millisecond,
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
_, err = client.GetFeatureFlag(FeatureFlagPayload{
|
|
Key: "enabled-flag",
|
|
DistinctId: "123",
|
|
OnlyEvaluateLocally: true,
|
|
})
|
|
require.EqualError(t, err, "flags were not successfully fetched yet")
|
|
}
|
|
|
|
func TestFlagWithTimeoutExceeded(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
time.Sleep(1 * time.Second)
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-flag-group-properties.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// Ignore batch requests
|
|
} else {
|
|
t.Error("Unknown request made by library")
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, err := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
FeatureFlagRequestTimeout: 10 * time.Millisecond,
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
|
|
isMatch, err := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "enabled-flag",
|
|
DistinctId: "-",
|
|
},
|
|
)
|
|
|
|
require.Error(t, err)
|
|
if !strings.Contains(err.Error(), "context deadline exceeded") {
|
|
t.Error("Expected context deadline exceeded error")
|
|
}
|
|
require.Nil(t, isMatch)
|
|
|
|
// get all flags with no local evaluation possible
|
|
variants, err := client.GetAllFlags(
|
|
FeatureFlagPayloadNoKey{
|
|
DistinctId: "-",
|
|
Groups: Groups{"company": "posthog1"},
|
|
},
|
|
)
|
|
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, "context deadline exceeded")
|
|
|
|
require.Empty(t, variants)
|
|
|
|
// get all flags with partial local evaluation possible
|
|
variants, err = client.GetAllFlags(
|
|
FeatureFlagPayloadNoKey{
|
|
DistinctId: "-",
|
|
Groups: Groups{"company": "posthog1"},
|
|
PersonProperties: NewProperties().Set("region", "USA"),
|
|
},
|
|
)
|
|
|
|
require.Error(t, err)
|
|
if !strings.Contains(err.Error(), "context deadline exceeded") {
|
|
t.Error("Expected context deadline exceeded error")
|
|
}
|
|
require.EqualValues(t, map[string]interface{}{"simple-flag": true}, variants)
|
|
|
|
// get all flags with full local evaluation possible
|
|
variants, err = client.GetAllFlags(
|
|
FeatureFlagPayloadNoKey{
|
|
DistinctId: "-",
|
|
Groups: Groups{"company": "posthog1"},
|
|
PersonProperties: NewProperties().Set("region", "USA"),
|
|
GroupProperties: map[string]Properties{"company": NewProperties().Set("name", "Project Name 1")},
|
|
},
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
fmt.Println(variants)
|
|
require.EqualValues(t, map[string]interface{}{"simple-flag": true, "group-flag": true}, variants)
|
|
}
|
|
|
|
func TestFlagDefinitionsWithTimeoutExceeded(t *testing.T) {
|
|
// Not run in parallel - timeout-based tests are sensitive to system load
|
|
// create buffer to write logs to
|
|
var buf bytes.Buffer
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
w.Write([]byte(fixture("test-flags-v3.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
// Sleep longer than client timeout (100ms) to trigger timeout
|
|
time.Sleep(1 * time.Second)
|
|
w.Write([]byte(fixture("feature_flag/test-flag-group-properties.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// Ignore batch requests
|
|
} else {
|
|
t.Error("Unknown request made by library")
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, clientErr := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
FeatureFlagRequestTimeout: 100 * time.Millisecond,
|
|
Logger: StdLogger(log.New(&buf, "insights-test", log.LstdFlags), false),
|
|
})
|
|
if clientErr != nil {
|
|
t.Fatalf("Client creation failed: %v", clientErr)
|
|
}
|
|
defer client.Close()
|
|
|
|
_, err := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "enabled-flag",
|
|
DistinctId: "-",
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Error("Unexpected error")
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.Contains(output, "Unable to fetch feature flags") {
|
|
t.Errorf("Expected error fetching flags, got: %q", output)
|
|
}
|
|
|
|
if !strings.Contains(output, "context deadline exceeded") {
|
|
t.Errorf("Expected timeout error fetching flags, got: %q", output)
|
|
}
|
|
}
|
|
|
|
func TestFetchFlagsFails(t *testing.T) {
|
|
// This test verifies that even in presence of HTTP errors flags continue to be fetched.
|
|
var called uint32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if atomic.LoadUint32(&called) == 0 {
|
|
// Load initial flags successfully
|
|
w.Write([]byte(fixture("feature_flag/test-simple-flag.json")))
|
|
} else {
|
|
// Fail all next requests
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
atomic.AddUint32(&called, 1)
|
|
|
|
}))
|
|
defer server.Close()
|
|
|
|
cli, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer cli.Close()
|
|
|
|
_, err := cli.(*client).featureFlagsPoller.GetFeatureFlags()
|
|
if err != nil {
|
|
t.Error("Should not fail", err)
|
|
}
|
|
cli.ReloadFeatureFlags()
|
|
cli.ReloadFeatureFlags()
|
|
|
|
_, err = cli.GetAllFlags(FeatureFlagPayloadNoKey{
|
|
DistinctId: "my-id",
|
|
})
|
|
if err != nil {
|
|
t.Error("Should not fail", err)
|
|
}
|
|
|
|
// Wait for the last request to complete
|
|
<-time.After(50 * time.Millisecond)
|
|
|
|
const expectedCalls = 3
|
|
actualCalls := atomic.LoadUint32(&called)
|
|
if actualCalls != expectedCalls {
|
|
t.Error("Expected to be called", expectedCalls, "times but got", actualCalls)
|
|
}
|
|
}
|
|
|
|
// The reference implementation of overrides may be found in Rust feature-flag implementation:
|
|
//
|
|
// Check link: https://github.com/PostHog/posthog/blob/0bb3ed063c37f5be280e4283a0d2a6a6683a9534/rust/feature-flags/src/api/request_handler.rs#L1412
|
|
func TestFeatureFlagWithOverrides(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-group-props.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// ignore
|
|
} else {
|
|
t.Errorf("Unknown request made by library: %s", r.URL.String())
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
cases := []struct {
|
|
name string
|
|
req FeatureFlagPayload
|
|
want bool
|
|
err error
|
|
}{
|
|
{
|
|
"matches group_key",
|
|
FeatureFlagPayload{
|
|
Key: "flag-with-groups",
|
|
DistinctId: "test-id",
|
|
Groups: Groups{"company": "1"},
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"distinct id does not match",
|
|
FeatureFlagPayload{
|
|
Key: "flag-with-groups",
|
|
DistinctId: "test-id",
|
|
Groups: Groups{"company": "2"},
|
|
},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
"matching group_key property is not overridden",
|
|
FeatureFlagPayload{
|
|
Key: "flag-with-groups",
|
|
DistinctId: "test-id",
|
|
Groups: Groups{"company": "2", "project": "7"},
|
|
GroupProperties: map[string]Properties{"company": {
|
|
"$group_key": "1",
|
|
}},
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"no match group_key property is not overridden",
|
|
FeatureFlagPayload{
|
|
Key: "flag-with-groups",
|
|
DistinctId: "test-id",
|
|
Groups: Groups{"company": "1", "project": "7"},
|
|
GroupProperties: map[string]Properties{"company": {
|
|
"$group_key": "2",
|
|
}},
|
|
},
|
|
false,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(tt *testing.T) {
|
|
enabled, err := client.GetFeatureFlag(c.req)
|
|
require.Equal(tt, c.err, err)
|
|
require.Equal(tt, c.want, enabled)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFeatureFlagDistinctIDOverride(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-distinct-id-local.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// ignore
|
|
} else {
|
|
t.Errorf("Unknown request made by library: %s", r.URL.String())
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
cases := []struct {
|
|
name string
|
|
req FeatureFlagPayload
|
|
want bool
|
|
err error
|
|
}{
|
|
{
|
|
"matches distinct id",
|
|
FeatureFlagPayload{
|
|
Key: "test-boolean-flag-with-rollout-conditions",
|
|
DistinctId: "1",
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"distinct id does not match",
|
|
FeatureFlagPayload{
|
|
Key: "test-boolean-flag-with-rollout-conditions",
|
|
DistinctId: "2",
|
|
},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
"distinct id property is not override",
|
|
FeatureFlagPayload{
|
|
Key: "test-boolean-flag-with-rollout-conditions",
|
|
DistinctId: "2",
|
|
PersonProperties: NewProperties().Set("distinct_id", "1"),
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(tt *testing.T) {
|
|
enabled, err := client.GetFeatureFlag(c.req)
|
|
require.Equal(tt, c.err, err)
|
|
require.Equal(tt, c.want, enabled)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFeatureFlagDeviceIDBucketingLocalEvaluation(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-device-id-bucketing.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// ignore
|
|
} else {
|
|
t.Errorf("Unknown request made by library: %s", r.URL.String())
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
distinctId := "distinct-123"
|
|
deviceId := "device-456"
|
|
expectedDevice := checkIfSimpleFlagEnabled("device-bucket-flag", deviceId, 50)
|
|
expectedDistinct := checkIfSimpleFlagEnabled("device-bucket-flag", distinctId, 50)
|
|
require.NotEqual(t, expectedDistinct, expectedDevice)
|
|
|
|
enabled, err := client.GetFeatureFlag(FeatureFlagPayload{
|
|
Key: "device-bucket-flag",
|
|
DistinctId: distinctId,
|
|
DeviceId: &deviceId,
|
|
OnlyEvaluateLocally: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedDevice, enabled)
|
|
}
|
|
|
|
func TestFeatureFlagWithFalseVariant(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
w.Write([]byte(fixture("feature_flag/test-false-variant.json")))
|
|
} else if strings.HasPrefix(r.URL.Path, "/batch/") {
|
|
// ignore
|
|
} else {
|
|
t.Errorf("Unknown request made by library: %s", r.URL.String())
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
|
|
PersonalApiKey: "some very secret key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
// Test GetFeatureFlag - should return the variant name "false"
|
|
variant, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "test-flag",
|
|
DistinctId: "test-id",
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "false", variant)
|
|
|
|
// Test IsFeatureEnabled - should return the variant name "false"
|
|
// This will fail with the current implementation because it incorrectly converts "false" to false
|
|
isEnabled, err := client.IsFeatureEnabled(
|
|
FeatureFlagPayload{
|
|
Key: "test-flag",
|
|
DistinctId: "test-id",
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "false", isEnabled)
|
|
}
|
|
|
|
func TestBasicEmailPropertyMatching(t *testing.T) {
|
|
// Test basic property matching for icontains
|
|
property := FlagProperty{
|
|
Key: "email",
|
|
Operator: "icontains",
|
|
Value: "@example.com",
|
|
Type: "person",
|
|
}
|
|
|
|
// Should match
|
|
properties1 := NewProperties().Set("email", "[email protected]")
|
|
match1, err1 := matchProperty(property, properties1)
|
|
require.NoError(t, err1)
|
|
require.True(t, match1, "Should match email containing @example.com")
|
|
|
|
// Should not match
|
|
properties2 := NewProperties().Set("email", "[email protected]")
|
|
match2, err2 := matchProperty(property, properties2)
|
|
require.NoError(t, err2)
|
|
require.False(t, match2, "Should not match email not containing @example.com")
|
|
}
|
|
|
|
// Note: Dependency tests (TestFlagDependencies*, TestMultiLevelMultivariateDependencyChain) have been moved to feature_flags_dependencies_test.go
|
|
|
|
func TestProductionStyleMultivariateDependencyChain(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
response := `{
|
|
"flags": [
|
|
{
|
|
"id": 451,
|
|
"key": "multivariate-leaf-flag",
|
|
"name": "Multivariate Leaf Flag (Base)",
|
|
"active": true,
|
|
"deleted": false,
|
|
"is_simple_flag": false,
|
|
"rollout_percentage": null,
|
|
"filters": {
|
|
"multivariate": {
|
|
"variants": [
|
|
{
|
|
"key": "pineapple",
|
|
"rollout_percentage": 25
|
|
},
|
|
{
|
|
"key": "mango",
|
|
"rollout_percentage": 25
|
|
},
|
|
{
|
|
"key": "papaya",
|
|
"rollout_percentage": 25
|
|
},
|
|
{
|
|
"key": "kiwi",
|
|
"rollout_percentage": 25
|
|
}
|
|
]
|
|
},
|
|
"groups": [
|
|
{
|
|
"variant": "pineapple",
|
|
"properties": [
|
|
{
|
|
"key": "email",
|
|
"type": "person",
|
|
"value": ["[email protected]"],
|
|
"operator": "exact"
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"variant": "mango",
|
|
"properties": [
|
|
{
|
|
"key": "email",
|
|
"type": "person",
|
|
"value": ["[email protected]"],
|
|
"operator": "exact"
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"variant": "papaya",
|
|
"properties": [
|
|
{
|
|
"key": "email",
|
|
"type": "person",
|
|
"value": ["[email protected]"],
|
|
"operator": "exact"
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"variant": "kiwi",
|
|
"properties": [
|
|
{
|
|
"key": "email",
|
|
"type": "person",
|
|
"value": ["[email protected]"],
|
|
"operator": "exact"
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"properties": [],
|
|
"rollout_percentage": 0
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"id": 467,
|
|
"key": "multivariate-intermediate-flag",
|
|
"name": "Multivariate Intermediate Flag (Depends on fruit)",
|
|
"active": true,
|
|
"deleted": false,
|
|
"is_simple_flag": false,
|
|
"rollout_percentage": null,
|
|
"filters": {
|
|
"multivariate": {
|
|
"variants": [
|
|
{
|
|
"key": "blue",
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"key": "red",
|
|
"rollout_percentage": 0
|
|
},
|
|
{
|
|
"key": "green",
|
|
"rollout_percentage": 0
|
|
},
|
|
{
|
|
"key": "black",
|
|
"rollout_percentage": 0
|
|
}
|
|
]
|
|
},
|
|
"groups": [
|
|
{
|
|
"variant": "blue",
|
|
"properties": [
|
|
{
|
|
"key": "multivariate-leaf-flag",
|
|
"type": "flag",
|
|
"value": "pineapple",
|
|
"operator": "flag_evaluates_to",
|
|
"dependency_chain": ["multivariate-leaf-flag"]
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"variant": "red",
|
|
"properties": [
|
|
{
|
|
"key": "multivariate-leaf-flag",
|
|
"type": "flag",
|
|
"value": "mango",
|
|
"operator": "flag_evaluates_to",
|
|
"dependency_chain": ["multivariate-leaf-flag"]
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"id": 468,
|
|
"key": "multivariate-root-flag",
|
|
"name": "Multivariate Root Flag (Depends on color)",
|
|
"active": true,
|
|
"deleted": false,
|
|
"is_simple_flag": false,
|
|
"rollout_percentage": null,
|
|
"filters": {
|
|
"multivariate": {
|
|
"variants": [
|
|
{
|
|
"key": "breaking-bad",
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"key": "the-wire",
|
|
"rollout_percentage": 0
|
|
},
|
|
{
|
|
"key": "game-of-thrones",
|
|
"rollout_percentage": 0
|
|
},
|
|
{
|
|
"key": "the-expanse",
|
|
"rollout_percentage": 0
|
|
}
|
|
]
|
|
},
|
|
"groups": [
|
|
{
|
|
"variant": "breaking-bad",
|
|
"properties": [
|
|
{
|
|
"key": "multivariate-intermediate-flag",
|
|
"type": "flag",
|
|
"value": "blue",
|
|
"operator": "flag_evaluates_to",
|
|
"dependency_chain": ["multivariate-leaf-flag", "multivariate-intermediate-flag"]
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
},
|
|
{
|
|
"variant": "the-wire",
|
|
"properties": [
|
|
{
|
|
"key": "multivariate-intermediate-flag",
|
|
"type": "flag",
|
|
"value": "red",
|
|
"operator": "flag_evaluates_to",
|
|
"dependency_chain": ["multivariate-leaf-flag", "multivariate-intermediate-flag"]
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
w.Write([]byte(response))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("test-api-key", Config{
|
|
PersonalApiKey: "test-personal-key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
// Test successful pineapple -> blue -> breaking-bad chain
|
|
leafResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-leaf-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "pineapple", leafResult)
|
|
|
|
intermediateResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-intermediate-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "blue", intermediateResult)
|
|
|
|
rootResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-root-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "breaking-bad", rootResult)
|
|
|
|
// Test successful mango -> red -> the-wire chain
|
|
mangoLeafResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-leaf-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "mango", mangoLeafResult)
|
|
|
|
mangoIntermediateResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-intermediate-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "red", mangoIntermediateResult)
|
|
|
|
mangoRootResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-root-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "the-wire", mangoRootResult)
|
|
|
|
// Test broken chain - user without matching email gets default/false results
|
|
unknownLeafResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-leaf-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, unknownLeafResult) // No matching email -> null variant -> false
|
|
|
|
unknownIntermediateResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-intermediate-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, unknownIntermediateResult) // Dependency not satisfied
|
|
|
|
unknownRootResult, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "multivariate-root-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("email", "[email protected]"),
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, unknownRootResult) // Dependency chain broken
|
|
}
|
|
|
|
func TestFallbackToAPIWhenFlagHasStaticCohortInMultiCondition(t *testing.T) {
|
|
// When a flag has multiple conditions and one contains a static cohort,
|
|
// the SDK should fallback to API for the entire flag, not just skip that
|
|
// condition and evaluate the next one locally.
|
|
//
|
|
// This prevents returning wrong variants when later conditions could match
|
|
// locally but the user is actually in the static cohort.
|
|
|
|
var flagsAPICalled bool
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
// Return API response indicating user is in the static cohort
|
|
flagsAPICalled = true
|
|
w.Write([]byte(`{"featureFlags": {"multi-condition-flag": "set-1"}}`))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
// Return local evaluation data WITHOUT cohort 999 (making it a static cohort)
|
|
w.Write([]byte(`{
|
|
"flags": [
|
|
{
|
|
"id": 1,
|
|
"key": "multi-condition-flag",
|
|
"active": true,
|
|
"filters": {
|
|
"groups": [
|
|
{
|
|
"properties": [
|
|
{"key": "id", "value": 999, "type": "cohort"}
|
|
],
|
|
"rollout_percentage": 100,
|
|
"variant": "set-1"
|
|
},
|
|
{
|
|
"properties": [
|
|
{
|
|
"key": "$geoip_country_code",
|
|
"operator": "exact",
|
|
"value": ["DE"],
|
|
"type": "person"
|
|
}
|
|
],
|
|
"rollout_percentage": 100,
|
|
"variant": "set-8"
|
|
}
|
|
],
|
|
"multivariate": {
|
|
"variants": [
|
|
{"key": "set-1", "rollout_percentage": 50},
|
|
{"key": "set-8", "rollout_percentage": 50}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"cohorts": {}
|
|
}`))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("test_api_key", Config{
|
|
Endpoint: server.URL,
|
|
PersonalApiKey: "test_personal_api_key",
|
|
})
|
|
defer client.Close()
|
|
|
|
// Wait for local evaluation to load
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := client.GetFeatureFlag(FeatureFlagPayload{
|
|
Key: "multi-condition-flag",
|
|
DistinctId: "test-user",
|
|
PersonProperties: NewProperties().
|
|
Set("$geoip_country_code", "DE"),
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("GetFeatureFlag returned error: %v", err)
|
|
}
|
|
|
|
// Should return API result (set-1), not local evaluation result (set-8)
|
|
if !flagsAPICalled {
|
|
t.Errorf("FLAGS API was not called - local evaluation did not fallback")
|
|
}
|
|
|
|
if result != "set-1" {
|
|
t.Errorf("Expected 'set-1' from API, got %v", result)
|
|
}
|
|
}
|
|
|
|
func TestGetFeatureFlagPayloadFallbackToAPIWhenFlagHasStaticCohort(t *testing.T) {
|
|
// When a flag has a static cohort and GetFeatureFlagPayload is called,
|
|
// the SDK should fallback to API and return the API payload, not the local payload.
|
|
//
|
|
// This test verifies the fix for the bug where GetFeatureFlagPayload
|
|
// didn't properly handle static cohort errors like GetFeatureFlag did.
|
|
|
|
var flagsAPICalled bool
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/flags" || r.URL.Path == "/flags/" {
|
|
// Return API response with payload
|
|
flagsAPICalled = true
|
|
w.Write([]byte(`{"featureFlags": {"flag-with-cohort": "variant-a"}, "featureFlagPayloads": {"flag-with-cohort": "{\"message\": \"from-api\"}"}}`))
|
|
} else if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
// Return local evaluation data WITHOUT cohort 999 (making it a static cohort)
|
|
w.Write([]byte(`{
|
|
"flags": [
|
|
{
|
|
"id": 1,
|
|
"key": "flag-with-cohort",
|
|
"active": true,
|
|
"filters": {
|
|
"groups": [
|
|
{
|
|
"properties": [
|
|
{"key": "id", "value": 999, "type": "cohort"}
|
|
],
|
|
"rollout_percentage": 100,
|
|
"variant": "variant-a"
|
|
}
|
|
],
|
|
"multivariate": {
|
|
"variants": [
|
|
{"key": "variant-a", "rollout_percentage": 100}
|
|
]
|
|
},
|
|
"payloads": {
|
|
"variant-a": "{\"message\": \"from-local\"}"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"cohorts": {}
|
|
}`))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("test_api_key", Config{
|
|
Endpoint: server.URL,
|
|
PersonalApiKey: "test_personal_api_key",
|
|
})
|
|
defer client.Close()
|
|
|
|
// Wait for local evaluation to load
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
result, err := client.GetFeatureFlagPayload(FeatureFlagPayload{
|
|
Key: "flag-with-cohort",
|
|
DistinctId: "test-user",
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("GetFeatureFlagPayload returned error: %v", err)
|
|
}
|
|
|
|
// Should call FLAGS API since cohort is static (not in local cohorts map)
|
|
if !flagsAPICalled {
|
|
t.Errorf("FLAGS API was not called - local evaluation did not fallback")
|
|
}
|
|
|
|
// Should return API payload, not local payload
|
|
expectedPayload := "{\"message\": \"from-api\"}"
|
|
if result != expectedPayload {
|
|
t.Errorf("Expected payload '%s' from API, got '%s'", expectedPayload, result)
|
|
}
|
|
}
|
|
|
|
func TestDateBeforeOperatorAbsolute(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, "/flags/definitions") {
|
|
response := `{
|
|
"flags": [
|
|
{
|
|
"id": 1,
|
|
"key": "test-flag",
|
|
"active": true,
|
|
"filters": {
|
|
"groups": [
|
|
{
|
|
"properties": [
|
|
{
|
|
"key": "created_at",
|
|
"value": "2024-12-31T23:59:59Z",
|
|
"operator": "is_date_before",
|
|
"type": "person"
|
|
}
|
|
],
|
|
"rollout_percentage": 100
|
|
}
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"group_type_mapping": {},
|
|
"cohorts": {}
|
|
}`
|
|
w.Write([]byte(response))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client, _ := NewWithConfig("test-api-key", Config{
|
|
PersonalApiKey: "test-personal-key",
|
|
Endpoint: server.URL,
|
|
})
|
|
defer client.Close()
|
|
|
|
// Person has created_at "2024-01-01T00:00:00Z" which is before "2024-12-31T23:59:59Z"
|
|
result, err := client.GetFeatureFlag(
|
|
FeatureFlagPayload{
|
|
Key: "test-flag",
|
|
DistinctId: "user-123",
|
|
PersonProperties: NewProperties().Set("created_at", "2024-01-01T00:00:00Z"),
|
|
OnlyEvaluateLocally: true,
|
|
SendFeatureFlagEvents: nil,
|
|
},
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, result)
|
|
}
|