diff --git a/providers/statsig/README.md b/providers/statsig/README.md index e724f21a6..88afdac5b 100644 --- a/providers/statsig/README.md +++ b/providers/statsig/README.md @@ -4,7 +4,7 @@ # Installation -To use the provider, you'll need to install [Statsig Go client](github.com/statsig-io/go-sdk) and Statsig provider. You can install the packages using the following command +To use the provider, you'll need to install [Statsig Go client](https://github.com/statsig-io/go-sdk) and Statsig provider. You can install the packages using the following command ```shell go get github.com/statsig-io/go-sdk @@ -53,7 +53,7 @@ featureConfig := statsigProvider.FeatureConfig{ evalCtx := of.NewEvaluationContext( "", map[string]interface{}{ - "UserID": "123", + "UserID": "123", // can use "UserID" or of.TargetingKey ("targetingKey") "Email": "testuser1@statsig.com", "feature_config": featureConfig, }, diff --git a/providers/statsig/pkg/provider.go b/providers/statsig/pkg/provider.go index 2ad2398a8..c4cb281f5 100644 --- a/providers/statsig/pkg/provider.go +++ b/providers/statsig/pkg/provider.go @@ -76,7 +76,7 @@ func (p *Provider) BooleanEvaluation(ctx context.Context, flag string, defaultVa } } - statsigUser, err := toStatsigUser(evalCtx) + statsigUser, err := ToStatsigUser(evalCtx) if err != nil { return of.BoolResolutionDetail{ Value: defaultValue, @@ -191,7 +191,7 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flag string, defaultVal } } - statsigUser, err := toStatsigUser(evalCtx) + statsigUser, err := ToStatsigUser(evalCtx) if err != nil { return of.InterfaceResolutionDetail{ Value: defaultValue, @@ -312,7 +312,7 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flag string, defaultVal } } -func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) { +func ToStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) { if len(evalCtx) == 0 { return &statsig.User{}, nil } @@ -320,7 +320,7 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) { statsigUser := statsig.User{} for key, origVal := range evalCtx { switch key { - case "UserID": + case of.TargetingKey, "UserID": val, ok := toStr(origVal) if !ok { return nil, fmt.Errorf("key `%s` can not be converted to string", key) @@ -364,7 +364,13 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) { statsigUser.AppVersion = val case "Custom": if val, ok := origVal.(map[string]interface{}); ok { - statsigUser.Custom = val + if statsigUser.Custom == nil { + statsigUser.Custom = val + } else { + for k, v := range val { + statsigUser.Custom[k] = v + } + } } else { return nil, fmt.Errorf("key `%s` can not be converted to map", key) } @@ -388,9 +394,15 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) { } case featureConfigKey: default: - return nil, fmt.Errorf("key `%s` is not mapped", key) + if statsigUser.Custom == nil { + statsigUser.Custom = make(map[string]interface{}) + } + statsigUser.Custom[key] = origVal } } + if statsigUser.UserID == "" { + return nil, of.NewTargetingKeyMissingResolutionError("UserID/targetingKey is missing") + } return &statsigUser, nil } diff --git a/providers/statsig/pkg/provider_test.go b/providers/statsig/pkg/provider_test.go index 3f0fc12cc..219bb096e 100644 --- a/providers/statsig/pkg/provider_test.go +++ b/providers/statsig/pkg/provider_test.go @@ -7,6 +7,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" statsigProvider "github.com/open-feature/go-sdk-contrib/providers/statsig/pkg" @@ -88,6 +89,88 @@ func TestBoolLayerEvaluation(t *testing.T) { } } +func TestConvertsValidUserIDToString(t *testing.T) { + evalCtx := of.FlattenedContext{ + "UserID": "test_user", + } + + user, err := statsigProvider.ToStatsigUser(evalCtx) + assert.NoError(t, err) + assert.Equal(t, "test_user", user.UserID) +} + +// Converts valid EvaluationContext with all fields correctly to statsig.User +func TestConvertsValidEvaluationContextToStatsigUser(t *testing.T) { + evalCtx := of.FlattenedContext{ + of.TargetingKey: "test-key", + "Email": "user@example.com", + "IpAddress": "192.168.1.1", + "UserAgent": "Mozilla/5.0", + "Country": "US", + "Locale": "en-US", + "AppVersion": "1.0.0", + "Custom": map[string]interface{}{"customKey": "customValue"}, + "PrivateAttributes": map[string]interface{}{"privateKey": "privateValue"}, + "StatsigEnvironment": map[string]string{"envKey": "envValue"}, + "CustomIDs": map[string]string{"customIDKey": "customIDValue"}, + "custom-key": "custom-value", + } + + user, err := statsigProvider.ToStatsigUser(evalCtx) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if user.UserID != "test-key" { + t.Errorf("expected UserID to be 'test-key', got %v", user.UserID) + } + if user.Email != "user@example.com" { + t.Errorf("expected Email to be 'user@example.com', got %v", user.Email) + } + if user.IpAddress != "192.168.1.1" { + t.Errorf("expected IpAddress to be '192.168.1.1', got %v", user.IpAddress) + } + if user.UserAgent != "Mozilla/5.0" { + t.Errorf("expected UserAgent to be 'Mozilla/5.0', got %v", user.UserAgent) + } + if user.Country != "US" { + t.Errorf("expected Country to be 'US', got %v", user.Country) + } + if user.Locale != "en-US" { + t.Errorf("expected Locale to be 'en-US', got %v", user.Locale) + } + if user.AppVersion != "1.0.0" { + t.Errorf("expected AppVersion to be '1.0.0', got %v", user.AppVersion) + } + if user.Custom["customKey"] != "customValue" { + t.Errorf("expected Custom['customKey'] to be 'customValue', got %v", user.Custom["customKey"]) + } + if user.PrivateAttributes["privateKey"] != "privateValue" { + t.Errorf("expected PrivateAttributes['privateKey'] to be 'privateValue', got %v", user.PrivateAttributes["privateKey"]) + } + if user.StatsigEnvironment["envKey"] != "envValue" { + t.Errorf("expected StatsigEnvironment['envKey'] to be 'envValue', got %v", user.StatsigEnvironment["envKey"]) + } + if user.CustomIDs["customIDKey"] != "customIDValue" { + t.Errorf("expected CustomIDs['customIDKey'] to be 'customIDValue', got %v", user.CustomIDs["customIDKey"]) + } + if user.Custom["custom-key"] != "custom-value" { + t.Errorf("expected CustomIDs['custom-key'] to be 'custom_value', got %v", user.Custom["custom-key"]) + } +} + +// Handles missing TargetingKey by checking for "key" in EvaluationContext +func TestHandlesMissingTargetingKey(t *testing.T) { + evalCtx := of.FlattenedContext{ + "dummy-key": "test-key", + } + + _, err := statsigProvider.ToStatsigUser(evalCtx) + if err == nil { + t.Fatalf("expected error") + } +} + func cleanup() { provider.Shutdown() }