Skip to content

Commit

Permalink
Support multiple cohorts in local evaluation
Browse files Browse the repository at this point in the history
> 3. There's more than one cohort in the feature flag definition.

This restriction is annoying. And it seems it could be resolved rather
easily. Copying what NodeJS SDK does.

Ref: https://posthog.com/docs/feature-flags/local-evaluation
  • Loading branch information
zaynetro committed Feb 14, 2024
1 parent abe6962 commit c67cf25
Show file tree
Hide file tree
Showing 6 changed files with 428 additions and 35 deletions.
110 changes: 98 additions & 12 deletions feature_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func TestMatchPropertyValue(t *testing.T) {
property := Property{
property := FlagProperty{
Key: "Browser",
Value: "Chrome",
Operator: "exact",
Expand All @@ -28,9 +28,8 @@ func TestMatchPropertyValue(t *testing.T) {

}


func TestMatchPropertyInvalidOperator(t *testing.T) {
property := Property{
property := FlagProperty{
Key: "Browser",
Value: "Chrome",
Operator: "is_unknown",
Expand All @@ -51,7 +50,7 @@ func TestMatchPropertyInvalidOperator(t *testing.T) {
}
func TestMatchPropertySlice(t *testing.T) {

property := Property{
property := FlagProperty{
Key: "Browser",
Value: []interface{}{"Chrome"},
Operator: "exact",
Expand All @@ -67,7 +66,7 @@ func TestMatchPropertySlice(t *testing.T) {
}

func TestMatchPropertyNumber(t *testing.T) {
property := Property{
property := FlagProperty{
Key: "Number",
Value: 5,
Operator: "gt",
Expand All @@ -85,7 +84,7 @@ func TestMatchPropertyNumber(t *testing.T) {
t.Error("Value is not a match")
}

property = Property{
property = FlagProperty{
Key: "Number",
Value: 5,
Operator: "lt",
Expand All @@ -103,7 +102,7 @@ func TestMatchPropertyNumber(t *testing.T) {
t.Error("Value is not a match")
}

property = Property{
property = FlagProperty{
Key: "Number",
Value: 5,
Operator: "gte",
Expand All @@ -121,7 +120,7 @@ func TestMatchPropertyNumber(t *testing.T) {
t.Error("Value is not a match")
}

property = Property{
property = FlagProperty{
Key: "Number",
Value: 5,
Operator: "lte",
Expand All @@ -144,7 +143,7 @@ func TestMatchPropertyRegex(t *testing.T) {

shouldMatch := []interface{}{"value.com", "value2.com"}

property := Property{
property := FlagProperty{
Key: "key",
Value: "\\.com$",
Operator: "regex",
Expand Down Expand Up @@ -175,7 +174,7 @@ func TestMatchPropertyRegex(t *testing.T) {
}

// invalid regex
property = Property{
property = FlagProperty{
Key: "key",
Value: "?*",
Operator: "regex",
Expand All @@ -195,7 +194,7 @@ func TestMatchPropertyRegex(t *testing.T) {

// non string value

property = Property{
property = FlagProperty{
Key: "key",
Value: 4,
Operator: "regex",
Expand All @@ -217,7 +216,7 @@ func TestMatchPropertyRegex(t *testing.T) {
func TestMatchPropertyContains(t *testing.T) {
shouldMatch := []interface{}{"value", "value2", "value3", "value4", "343tfvalue5"}

property := Property{
property := FlagProperty{
Key: "key",
Value: "valUe",
Operator: "icontains",
Expand Down Expand Up @@ -3146,3 +3145,90 @@ func TestMultivariateFlagConsistency(t *testing.T) {
}
}
}

func TestComplexCohortsLocally(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fixture("feature_flag/test-complex-cohorts-locally.json"))) // 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()

payload := FeatureFlagPayload{
Key: "beta-feature",
DistinctId: "some-distinct-id",
PersonProperties: NewProperties().Set("region", "UK"),
}

isMatch, err := client.IsFeatureEnabled(payload)
if err != nil {
t.Fatal(err)
}
if isMatch != false {
t.Error("Should not match")
}

payload.PersonProperties = NewProperties().Set("region", "USA").Set("other", "thing")
isMatch, _ = client.IsFeatureEnabled(payload)
if isMatch != true {
t.Error("Should match")
}

// even though 'other' property is not present, the cohort should still match since it's an OR condition
payload.PersonProperties = NewProperties().Set("region", "USA").Set("nation", "UK")
isMatch, _ = client.IsFeatureEnabled(payload)
if isMatch != true {
t.Error("Should match")
}
}

func TestComplexCohortsWithNegationLocally(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fixture("feature_flag/test-complex-cohorts-negation-locally.json"))) // 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()

payload := FeatureFlagPayload{
Key: "beta-feature",
DistinctId: "some-distinct-id",
PersonProperties: NewProperties().Set("region", "UK"),
}

isMatch, err := client.IsFeatureEnabled(payload)
if err != nil {
t.Fatal(err)
}
if isMatch != false {
t.Error("Should not match")
}

// even though 'other' property is not present, the cohort should still match since it's an OR condition
payload.PersonProperties = NewProperties().Set("region", "USA").Set("nation", "UK")
isMatch, _ = client.IsFeatureEnabled(payload)
if isMatch != true {
t.Error("Should match")
}

// # since 'other' is negated, we return False. Since 'nation' is not present, we can't tell whether the flag should be true or false, so go to decide
payload.PersonProperties = NewProperties().Set("region", "USA").Set("other", "thing")
_, err = client.IsFeatureEnabled(payload)
if err != nil {
t.Error("Expected to fail")
}

payload.PersonProperties = NewProperties().Set("region", "USA").Set("other", "thing2")
isMatch, _ = client.IsFeatureEnabled(payload)
if isMatch != true {
t.Error("Should match")
}
}
Loading

0 comments on commit c67cf25

Please sign in to comment.