From 2d9ec680222d4f5a72da4482d9fe48a274bf4dac Mon Sep 17 00:00:00 2001 From: Kavindu Dodanduwa Date: Wed, 12 Jul 2023 12:44:19 -0700 Subject: [PATCH] generic resolving and tests Signed-off-by: Kavindu Dodanduwa --- pkg/openfeature/testing/in_memory_provider.go | 90 +++++++------------ .../testing/in_memory_provider_test.go | 80 +++++++++++++++++ 2 files changed, 114 insertions(+), 56 deletions(-) diff --git a/pkg/openfeature/testing/in_memory_provider.go b/pkg/openfeature/testing/in_memory_provider.go index 28c6e940..0be33ac7 100644 --- a/pkg/openfeature/testing/in_memory_provider.go +++ b/pkg/openfeature/testing/in_memory_provider.go @@ -39,17 +39,8 @@ func (i InMemoryProvider) BooleanEvaluation(ctx context.Context, flag string, de } } - resolveFlag, detail := memoryFlag.Resolve(evalCtx) - - var result bool - res, ok := resolveFlag.(bool) - if ok { - result = res - } else { - result = defaultValue - detail.Reason = openfeature.ErrorReason - detail.ResolutionError = openfeature.NewTypeMismatchResolutionError("incorrect type association") - } + resolveFlag, detail := memoryFlag.Resolve(defaultValue, evalCtx) + result := genericResolve[bool](resolveFlag, defaultValue, &detail) return openfeature.BoolResolutionDetail{ Value: result, @@ -69,17 +60,8 @@ func (i InMemoryProvider) StringEvaluation(ctx context.Context, flag string, def } } - resolveFlag, detail := memoryFlag.Resolve(evalCtx) - - var result string - res, ok := resolveFlag.(string) - if ok { - result = res - } else { - result = defaultValue - detail.Reason = openfeature.ErrorReason - detail.ResolutionError = openfeature.NewTypeMismatchResolutionError("incorrect type association") - } + resolveFlag, detail := memoryFlag.Resolve(defaultValue, evalCtx) + result := genericResolve[string](resolveFlag, defaultValue, &detail) return openfeature.StringResolutionDetail{ Value: result, @@ -99,17 +81,8 @@ func (i InMemoryProvider) FloatEvaluation(ctx context.Context, flag string, defa } } - resolveFlag, detail := memoryFlag.Resolve(evalCtx) - - var result float64 - res, ok := resolveFlag.(float64) - if ok { - result = res - } else { - result = defaultValue - detail.Reason = openfeature.ErrorReason - detail.ResolutionError = openfeature.NewTypeMismatchResolutionError("incorrect type association") - } + resolveFlag, detail := memoryFlag.Resolve(defaultValue, evalCtx) + result := genericResolve[float64](resolveFlag, defaultValue, &detail) return openfeature.FloatResolutionDetail{ Value: result, @@ -129,20 +102,11 @@ func (i InMemoryProvider) IntEvaluation(ctx context.Context, flag string, defaul } } - resolveFlag, detail := memoryFlag.Resolve(evalCtx) - - var result int64 - res, ok := resolveFlag.(int) - if ok { - result = int64(res) - } else { - result = defaultValue - detail.Reason = openfeature.ErrorReason - detail.ResolutionError = openfeature.NewTypeMismatchResolutionError("incorrect type association") - } + resolveFlag, detail := memoryFlag.Resolve(defaultValue, evalCtx) + result := genericResolve[int](resolveFlag, int(defaultValue), &detail) return openfeature.IntResolutionDetail{ - Value: result, + Value: int64(result), ProviderResolutionDetail: detail, } } @@ -159,7 +123,7 @@ func (i InMemoryProvider) ObjectEvaluation(ctx context.Context, flag string, def } } - resolveFlag, detail := memoryFlag.Resolve(evalCtx) + resolveFlag, detail := memoryFlag.Resolve(defaultValue, evalCtx) var result interface{} if resolveFlag != nil { @@ -177,10 +141,24 @@ func (i InMemoryProvider) ObjectEvaluation(ctx context.Context, flag string, def } func (i InMemoryProvider) Hooks() []openfeature.Hook { - //TODO implement some hooks return []openfeature.Hook{} } +// helpers + +// genericResolve is a helper to extract type verified evaluation and fill openfeature.ProviderResolutionDetail +func genericResolve[T comparable](value interface{}, defaultValue T, detail *openfeature.ProviderResolutionDetail) T { + v, ok := value.(T) + + if ok { + return v + } + + detail.Reason = openfeature.ErrorReason + detail.ResolutionError = openfeature.NewTypeMismatchResolutionError("incorrect type association") + return defaultValue +} + // Type Definitions for InMemoryProvider flag // State of the feature flag @@ -199,24 +177,24 @@ type InMemoryFlag struct { ContextEvaluator ContextEvaluator } -func (flag *InMemoryFlag) Resolve(evalCtx openfeature.FlattenedContext) ( +func (flag *InMemoryFlag) Resolve(defaultValue interface{}, evalCtx openfeature.FlattenedContext) ( interface{}, openfeature.ProviderResolutionDetail) { - // first resolve from context callback - if flag.ContextEvaluator != nil { - return (*flag.ContextEvaluator)(*flag, evalCtx) - } - - // fallback to evaluation - // check the state if flag.State == Disabled { - return nil, openfeature.ProviderResolutionDetail{ + return defaultValue, openfeature.ProviderResolutionDetail{ ResolutionError: openfeature.NewGeneralResolutionError("flag is disabled"), Reason: openfeature.DisabledReason, } } + // first resolve from context callback + if flag.ContextEvaluator != nil { + return (*flag.ContextEvaluator)(*flag, evalCtx) + } + + // fallback to evaluation + return flag.Variants[flag.DefaultVariant], openfeature.ProviderResolutionDetail{ Reason: openfeature.StaticReason, Variant: flag.DefaultVariant, diff --git a/pkg/openfeature/testing/in_memory_provider_test.go b/pkg/openfeature/testing/in_memory_provider_test.go index 54e9d4db..a2d09de7 100644 --- a/pkg/openfeature/testing/in_memory_provider_test.go +++ b/pkg/openfeature/testing/in_memory_provider_test.go @@ -165,3 +165,83 @@ func TestInMemoryProvider_WithContext(t *testing.T) { } }) } + +func TestInMemoryProvider_MissingFlag(t *testing.T) { + memoryProvider := NewInMemoryProvider(map[string]InMemoryFlag{}) + + ctx := context.Background() + + t.Run("test missing flag", func(t *testing.T) { + evaluation := memoryProvider.StringEvaluation(ctx, "missing-flag", "GoodBye", nil) + + if evaluation.Value != "GoodBye" { + t.Errorf("incorect evaluation, expected %v, got %v", "SomeResult", evaluation.Value) + } + + if evaluation.Reason != openfeature.ErrorReason { + t.Errorf("incorect reason, expected %v, got %v", openfeature.ErrorReason, evaluation.Reason) + } + + if evaluation.ResolutionDetail().ErrorCode != openfeature.FlagNotFoundCode { + t.Errorf("incorect reason, expected %v, got %v", openfeature.ErrorReason, evaluation.ResolutionDetail().ErrorCode) + } + }) +} + +func TestInMemoryProvider_TypeMismatch(t *testing.T) { + memoryProvider := NewInMemoryProvider(map[string]InMemoryFlag{ + "boolFlag": { + Key: "boolFlag", + State: Enabled, + DefaultVariant: "true", + Variants: map[string]interface{}{ + "true": true, + "false": false, + }, + ContextEvaluator: nil, + }, + }) + + ctx := context.Background() + + t.Run("test missing flag", func(t *testing.T) { + evaluation := memoryProvider.StringEvaluation(ctx, "boolFlag", "GoodBye", nil) + + if evaluation.Value != "GoodBye" { + t.Errorf("incorect evaluation, expected %v, got %v", "SomeResult", evaluation.Value) + } + + if evaluation.ResolutionDetail().ErrorCode != openfeature.TypeMismatchCode { + t.Errorf("incorect reason, expected %v, got %v", openfeature.ErrorReason, evaluation.Reason) + } + }) +} + +func TestInMemoryProvider_Disabled(t *testing.T) { + memoryProvider := NewInMemoryProvider(map[string]InMemoryFlag{ + "boolFlag": { + Key: "boolFlag", + State: Disabled, + DefaultVariant: "true", + Variants: map[string]interface{}{ + "true": true, + "false": false, + }, + ContextEvaluator: nil, + }, + }) + + ctx := context.Background() + + t.Run("test missing flag", func(t *testing.T) { + evaluation := memoryProvider.BooleanEvaluation(ctx, "boolFlag", false, nil) + + if evaluation.Value != false { + t.Errorf("incorect evaluation, expected %v, got %v", false, evaluation.Value) + } + + if evaluation.Reason != openfeature.DisabledReason { + t.Errorf("incorect reason, expected %v, got %v", openfeature.ErrorReason, evaluation.Reason) + } + }) +}