...

Source file src/github.com/growthbook/growthbook-golang/features_test.go

Documentation: github.com/growthbook/growthbook-golang

     1  package growthbook
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  )
     7  
     8  func TestContextMalformedJSON(t *testing.T) {
     9  	SetLogger(&testLog)
    10  
    11  	contextJSON := []string{
    12  		`{"enabled": 1}`,
    13  		`{"attributes": 1}`,
    14  		`{"url": 1}`,
    15  		`{"features": 1}`,
    16  		`{"forcedVariations": 1}`,
    17  		`{"forcedVariations": {"abc": 1, "def": "bad"}}`,
    18  		`{"qaMode": 1}`,
    19  		`{"devMode": 1}`,
    20  		`{"userAttributes": 1}`,
    21  		`{"groups": 1}`,
    22  		`{"groups": {"abc": true, "def": "bad"}}`,
    23  		`{"apiHost": 1}`,
    24  		`{"clientKey": 1}`,
    25  		`{"decryptionKey": 1}`,
    26  		`{"overrides": 1}`,
    27  		`{"unknownKey": "some data"}`,
    28  	}
    29  
    30  	for _, json := range contextJSON {
    31  		testLog.reset()
    32  		ParseContext([]byte(json)) // discarding result...
    33  		if len(testLog.warnings) != 1 {
    34  			t.Errorf("expected warning from Context JSON parser for: %s", json)
    35  		}
    36  	}
    37  }
    38  
    39  func TestFeaturesCanSetFeatures(t *testing.T) {
    40  	context := NewContext().
    41  		WithAttributes(Attributes{"id": "123"})
    42  	gb := New(context).
    43  		WithFeatures(FeatureMap{"feature": &Feature{DefaultValue: 0}})
    44  
    45  	result := gb.Feature("feature")
    46  	expected := FeatureResult{
    47  		Value:  0,
    48  		On:     false,
    49  		Off:    true,
    50  		Source: DefaultValueResultSource,
    51  	}
    52  
    53  	if result == nil || !reflect.DeepEqual(*result, expected) {
    54  		t.Errorf("unexpected result: %v", result)
    55  	}
    56  }
    57  
    58  func TestFeaturesCanSetEncryptedFeatures(t *testing.T) {
    59  	gb := New(nil)
    60  
    61  	keyString := "Ns04T5n9+59rl2x3SlNHtQ=="
    62  	encrypedFeatures :=
    63  		"vMSg2Bj/IurObDsWVmvkUg==.L6qtQkIzKDoE2Dix6IAKDcVel8PHUnzJ7JjmLjFZFQDqidRIoCxKmvxvUj2kTuHFTQ3/NJ3D6XhxhXXv2+dsXpw5woQf0eAgqrcxHrbtFORs18tRXRZza7zqgzwvcznx"
    64  
    65  	_, err := gb.WithEncryptedFeatures(encrypedFeatures, keyString)
    66  	if err != nil {
    67  		t.Error("unexpected error: ", err)
    68  	}
    69  
    70  	expectedJson := `{
    71      "testfeature1": {
    72        "defaultValue": true,
    73        "rules": [{"condition": { "id": "1234" }, "force": false}]
    74      }
    75    }`
    76  	expected := ParseFeatureMap([]byte(expectedJson))
    77  	actual := gb.Features()
    78  
    79  	if !reflect.DeepEqual(actual, expected) {
    80  		t.Error("unexpected features value: ", actual)
    81  	}
    82  }
    83  
    84  func TestFeaturesDecryptFeaturesWithInvalidKey(t *testing.T) {
    85  	gb := New(nil)
    86  
    87  	keyString := "fakeT5n9+59rl2x3SlNHtQ=="
    88  	encrypedFeatures :=
    89  		"vMSg2Bj/IurObDsWVmvkUg==.L6qtQkIzKDoE2Dix6IAKDcVel8PHUnzJ7JjmLjFZFQDqidRIoCxKmvxvUj2kTuHFTQ3/NJ3D6XhxhXXv2+dsXpw5woQf0eAgqrcxHrbtFORs18tRXRZza7zqgzwvcznx"
    90  
    91  	_, err := gb.WithEncryptedFeatures(encrypedFeatures, keyString)
    92  	if err == nil {
    93  		t.Error("unexpected lack of error")
    94  	}
    95  }
    96  
    97  func TestFeaturesDecryptFeaturesWithInvalidCiphertext(t *testing.T) {
    98  	gb := New(nil)
    99  
   100  	keyString := "Ns04T5n9+59rl2x3SlNHtQ=="
   101  	encrypedFeatures :=
   102  		"FAKE2Bj/IurObDsWVmvkUg==.L6qtQkIzKDoE2Dix6IAKDcVel8PHUnzJ7JjmLjFZFQDqidRIoCxKmvxvUj2kTuHFTQ3/NJ3D6XhxhXXv2+dsXpw5woQf0eAgqrcxHrbtFORs18tRXRZza7zqgzwvcznx"
   103  
   104  	_, err := gb.WithEncryptedFeatures(encrypedFeatures, keyString)
   105  	if err == nil {
   106  		t.Error("unexpected lack of error")
   107  	}
   108  }
   109  
   110  func TestFeaturesReturnsRuleID(t *testing.T) {
   111  	featuresJson := `{
   112      "feature": {"defaultValue": 0, "rules": [{"force": 1, "id": "foo"}]}
   113    }`
   114  	gb := New(nil).
   115  		WithFeatures(ParseFeatureMap([]byte(featuresJson)))
   116  	result := gb.EvalFeature("feature")
   117  	if result.RuleID != "foo" {
   118  		t.Errorf("expected rule ID to be foo, got: %v", result.RuleID)
   119  	}
   120  }
   121  
   122  func TestFeaturesUpdatesAttributes(t *testing.T) {
   123  	context := NewContext().
   124  		WithAttributes(Attributes{"foo": 1, "bar": 2})
   125  	gb := New(context).
   126  		WithAttributes(Attributes{"foo": 2, "baz": 3})
   127  
   128  	result := context.Attributes
   129  	expected := Attributes{"foo": 2, "baz": 3}
   130  
   131  	if !reflect.DeepEqual(result, expected) {
   132  		t.Errorf("unexpected result: %v", result)
   133  	}
   134  	if !reflect.DeepEqual(gb.Attributes(), expected) {
   135  		t.Errorf("unexpected result: %v", result)
   136  	}
   137  }
   138  
   139  func TestFeaturesUsesAttributeOverrides(t *testing.T) {
   140  	context := NewContext().
   141  		WithAttributes(Attributes{"id": "123", "foo": "bar"})
   142  	gb := New(context).
   143  		WithAttributeOverrides(Attributes{"foo": "baz"})
   144  
   145  	if !reflect.DeepEqual(gb.Attributes(),
   146  		Attributes{"id": "123", "foo": "baz"}) {
   147  		t.Errorf("unexpected value for gb.Attributes(): %v\n",
   148  			gb.Attributes())
   149  	}
   150  
   151  	exp1 := NewExperiment("my-test").WithVariations(0, 1).WithHashAttribute("foo")
   152  	result := gb.Run(exp1)
   153  	if result.HashValue != "baz" {
   154  		t.Errorf("unexpected experiment result: %v\n", result.HashValue)
   155  	}
   156  
   157  	gb = gb.WithAttributeOverrides(nil)
   158  
   159  	if !reflect.DeepEqual(gb.Attributes(),
   160  		Attributes{"id": "123", "foo": "bar"}) {
   161  		t.Errorf("unexpected value for gb.Attributes(): %v\n",
   162  			gb.Attributes())
   163  	}
   164  
   165  	result = gb.Run(exp1)
   166  	if result.HashValue != "bar" {
   167  		t.Errorf("unexpected experiment result: %v\n", result.HashValue)
   168  	}
   169  }
   170  
   171  func TestFeaturesUsesForcedFeatureValues(t *testing.T) {
   172  	featuresJson := `{
   173      "feature1": {"defaultValue": 0},
   174      "feature2": {"defaultValue": 0}
   175    }`
   176  	gb := New(nil).
   177  		WithFeatures(ParseFeatureMap([]byte(featuresJson))).
   178  		WithForcedFeatures(map[string]interface{}{
   179  			"feature2": 1.0,
   180  			"feature3": 1.0,
   181  		})
   182  
   183  	check := func(icase int, feature string, value interface{}) {
   184  		result := gb.EvalFeature(feature)
   185  		if !reflect.DeepEqual(result.Value, value) {
   186  			t.Errorf("%d: result from EvalFeature: expected %v, got %v",
   187  				icase, value, result.Value)
   188  		}
   189  	}
   190  
   191  	check(1, "feature1", 0.0)
   192  	check(2, "feature2", 1.0)
   193  	check(3, "feature3", 1.0)
   194  
   195  	gb = gb.WithForcedFeatures(nil)
   196  
   197  	check(4, "feature1", 0.0)
   198  	check(5, "feature2", 0.0)
   199  	check(6, "feature3", nil)
   200  }
   201  
   202  func TestFeaturesGetsFeatures(t *testing.T) {
   203  	featuresJson := `{ "feature1": { "defaultValue": 0 } }`
   204  	features := ParseFeatureMap([]byte(featuresJson))
   205  	gb := New(nil).WithFeatures(features)
   206  
   207  	if !reflect.DeepEqual(gb.Features(), features) {
   208  		t.Error("expected features to match")
   209  	}
   210  }
   211  
   212  func TestFeaturesFeatureUsageWhenAssignedValueChanges(t *testing.T) {
   213  	featuresJson := `{
   214      "feature": {
   215        "defaultValue": 0,
   216        "rules": [{"condition": {"color": "blue"}, "force": 1}]
   217      }
   218    }`
   219  	context := NewContext().
   220  		WithAttributes(Attributes{"color": "green"}).
   221  		WithFeatures(ParseFeatureMap([]byte(featuresJson)))
   222  
   223  	type featureCall struct {
   224  		key    string
   225  		result *FeatureResult
   226  	}
   227  	calls := []featureCall{}
   228  	callback := func(key string, result *FeatureResult) {
   229  		calls = append(calls, featureCall{key, result})
   230  	}
   231  	gb := New(context).WithFeatureUsageCallback(callback)
   232  
   233  	// Fires for regular features
   234  	res1 := gb.EvalFeature("feature")
   235  	if res1.Value != 0.0 {
   236  		t.Errorf("expected value 0, got %#v", res1.Value)
   237  	}
   238  
   239  	// Fires when the assigned value changes
   240  	gb = gb.WithAttributes(Attributes{"color": "blue"})
   241  	res2 := gb.EvalFeature("feature")
   242  	if res2.Value != 1.0 {
   243  		t.Errorf("expected value 1, got %#v", res2.Value)
   244  	}
   245  
   246  	if len(calls) != 2 {
   247  		t.Errorf("expected 2 calls to feature usage callback, got %d", len(calls))
   248  	} else {
   249  		if !reflect.DeepEqual(calls[0], featureCall{"feature", res1}) {
   250  			t.Errorf("unexpected callback result")
   251  		}
   252  		if !reflect.DeepEqual(calls[1], featureCall{"feature", res2}) {
   253  			t.Errorf("unexpected callback result")
   254  		}
   255  	}
   256  }
   257  
   258  func TestFeaturesUsesFallbacksForGetFeatureValue(t *testing.T) {
   259  	gb := New(nil).WithFeatures(ParseFeatureMap(
   260  		[]byte(`{"feature": {"defaultValue": "blue"}}`)))
   261  
   262  	res := gb.GetFeatureValue("feature", "green")
   263  	if res != "blue" {
   264  		t.Error("1: unexpected return from GetFeatureValue: ", res)
   265  	}
   266  	res = gb.GetFeatureValue("unknown", "green")
   267  	if res != "green" {
   268  		t.Error("2: unexpected return from GetFeatureValue: ", res)
   269  	}
   270  	res = gb.GetFeatureValue("testing", nil)
   271  	if res != nil {
   272  		t.Error("3: unexpected return from GetFeatureValue: ", res)
   273  	}
   274  }
   275  
   276  func TestFeaturesUsageTracking(t *testing.T) {
   277  	called := false
   278  	cb := func(key string, result *FeatureResult) {
   279  		called = true
   280  	}
   281  
   282  	context := NewContext().
   283  		WithAttributes(Attributes{"id": "123"}).
   284  		WithFeatureUsageCallback(cb)
   285  	gb := New(context).
   286  		WithFeatures(FeatureMap{"feature": &Feature{DefaultValue: 0}})
   287  
   288  	result := gb.Feature("feature")
   289  	expected := FeatureResult{
   290  		Value:  0,
   291  		On:     false,
   292  		Off:    true,
   293  		Source: DefaultValueResultSource,
   294  	}
   295  
   296  	if result == nil || !reflect.DeepEqual(*result, expected) {
   297  		t.Errorf("unexpected result: %v", result)
   298  	}
   299  	if !called {
   300  		t.Errorf("expected feature tracking callback to be called")
   301  	}
   302  }
   303  

View as plain text