From f52a6bf88eff30cd19876fc7e22d46e4c47fb40a Mon Sep 17 00:00:00 2001 From: grambbledook Date: Wed, 10 Dec 2025 20:55:55 +0100 Subject: [PATCH] more tests added --- pkg/services/featuremgmt/openfeature.go | 4 +- pkg/services/featuremgmt/service.go | 2 +- pkg/services/featuremgmt/static_provider.go | 49 ++++------ .../featuremgmt/static_provider_test.go | 95 ++++++++++++++++++- 4 files changed, 116 insertions(+), 34 deletions(-) diff --git a/pkg/services/featuremgmt/openfeature.go b/pkg/services/featuremgmt/openfeature.go index 9e516c06ed7..44176d8bfbe 100644 --- a/pkg/services/featuremgmt/openfeature.go +++ b/pkg/services/featuremgmt/openfeature.go @@ -26,7 +26,7 @@ type OpenFeatureConfig struct { // HTTPClient is a pre-configured HTTP client (optional, used by features-service + OFREP providers) HTTPClient *http.Client // StaticFlags are the feature flags to use with static provider - StaticFlags map[string]bool + StaticFlags map[string]setting.FeatureToggle // TargetingKey is used for evaluation context TargetingKey string // ContextAttrs are additional attributes for evaluation context @@ -100,7 +100,7 @@ func InitOpenFeatureWithCfg(cfg *setting.Cfg) error { func createProvider( providerType string, u *url.URL, - staticFlags map[string]bool, + StaticFlags map[string]setting.FeatureToggle, httpClient *http.Client, ) (openfeature.FeatureProvider, error) { if providerType == setting.FeaturesServiceProviderType || providerType == setting.OFREPProviderType { diff --git a/pkg/services/featuremgmt/service.go b/pkg/services/featuremgmt/service.go index 2769a75d788..fd7ddbd91cf 100644 --- a/pkg/services/featuremgmt/service.go +++ b/pkg/services/featuremgmt/service.go @@ -47,7 +47,7 @@ func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) { } mgmt.warnings[key] = "unknown flag in config" } - mgmt.startup[key] = val + mgmt.startup[key] = val.Value == true } // update the values diff --git a/pkg/services/featuremgmt/static_provider.go b/pkg/services/featuremgmt/static_provider.go index 183cc29bf23..3a1b5a1465f 100644 --- a/pkg/services/featuremgmt/static_provider.go +++ b/pkg/services/featuremgmt/static_provider.go @@ -3,6 +3,7 @@ package featuremgmt import ( "encoding/json" "fmt" + "github.com/grafana/grafana/pkg/setting" "github.com/open-feature/go-sdk/openfeature" "github.com/open-feature/go-sdk/openfeature/memprovider" "strconv" @@ -31,51 +32,43 @@ func (p *inMemoryBulkProvider) ListFlags() ([]string, error) { return keys, nil } -func newStaticProvider(confFlags map[string]bool, standardFlags []FeatureFlag) (openfeature.FeatureProvider, error) { +func newStaticProvider(confFlags map[string]setting.FeatureToggle, standardFlags []FeatureFlag) (openfeature.FeatureProvider, error) { flags := make(map[string]memprovider.InMemoryFlag, len(standardFlags)) - - // Add flags from config.ini file - for name, value := range confFlags { - flags[name] = createInMemoryFlag(name, value) - } - + index := make(map[string]FeatureFlag, len(standardFlags)) // Add standard flags for _, flag := range standardFlags { - _, exists := flags[flag.Name] - - // Fail fast if a flag is declared with a mismatched type - if exists && flag.Type != Boolean { - return nil, fmt.Errorf("flag %s already declared as boolean", flag.Name) - } - - // config.ini take precedence over code - if exists { - continue - } - inMemFlag, err := createTypedFlag(flag) if err != nil { return nil, err } flags[flag.Name] = inMemFlag + index[flag.Name] = flag + } + + // Add flags from config.ini file + for name, flag := range confFlags { + standard, exists := index[flag.Name] + + // Fail fast if a flag is declared with a mismatched type + if exists && standard.Type.String() != string(flag.Type) { + return nil, fmt.Errorf("type mismatch for flag '%s' detected", flag.Name) + } + + flags[name] = createInMemoryFlag(flag) } return newInMemoryBulkProvider(flags), nil } -func createInMemoryFlag(name string, enabled bool) memprovider.InMemoryFlag { - variant := "disabled" - if enabled { - variant = "enabled" - } +func createInMemoryFlag(flag setting.FeatureToggle) memprovider.InMemoryFlag { + variant := "default" return memprovider.InMemoryFlag{ - Key: name, + Key: flag.Name, DefaultVariant: variant, - Variants: map[string]interface{}{ - "enabled": true, - "disabled": false, + Variants: map[string]any{ + variant: flag.Value, }, } } diff --git a/pkg/services/featuremgmt/static_provider_test.go b/pkg/services/featuremgmt/static_provider_test.go index ec1bf1b940d..7a21f83697b 100644 --- a/pkg/services/featuremgmt/static_provider_test.go +++ b/pkg/services/featuremgmt/static_provider_test.go @@ -95,15 +95,19 @@ ABCD = true } func Test_StaticProvider_FailfastOnMismatchedType(t *testing.T) { - staticFlags := map[string]bool{"oldBooleanFlag": true} + staticFlags := map[string]setting.FeatureToggle{"oldBooleanFlag": { + Type: setting.Boolean, + Name: "oldBooleanFlag", + Value: true, + }} flag := FeatureFlag{ Name: "oldBooleanFlag", Expression: "1.0", - Type: Integer, + Type: Float, } _, err := newStaticProvider(staticFlags, []FeatureFlag{flag}) - assert.EqualError(t, err, "flag oldBooleanFlag already declared as boolean") + assert.EqualError(t, err, "type mismatch for flag 'oldBooleanFlag' detected") } func Test_StaticProvider_TypedFlags(t *testing.T) { @@ -180,3 +184,88 @@ func Test_StaticProvider_TypedFlags(t *testing.T) { assert.Equal(t, tt.expectedValue, result) } } +func Test_StaticProvider_ConfigOverride(t *testing.T) { + tests := []struct { + name string + typ FeatureFlagType + originalValue string + configValue any + }{ + { + name: "bool", + typ: Boolean, + originalValue: "false", + configValue: true, + }, + { + name: "int", + typ: Integer, + originalValue: "0", + configValue: int64(1), + }, + { + name: "float", + typ: Float, + originalValue: "0.0", + configValue: 1.0, + }, + { + name: "string", + typ: String, + originalValue: "foo", + configValue: "bar", + }, + { + name: "structure", + typ: Structure, + originalValue: "{}", + configValue: make(map[string]any), + }, + } + + for _, tt := range tests { + configFlags, standardFlags := makeFlags(tt) + provider, err := newStaticProvider(configFlags, standardFlags) + assert.NoError(t, err) + + var result any + switch tt.typ { + case Boolean: + result = provider.BooleanEvaluation(t.Context(), tt.name, false, openfeature.FlattenedContext{}).Value + case Float: + result = provider.FloatEvaluation(t.Context(), tt.name, 0.0, openfeature.FlattenedContext{}).Value + case String: + result = provider.StringEvaluation(t.Context(), tt.name, "foo", openfeature.FlattenedContext{}).Value + case Integer: + result = provider.IntEvaluation(t.Context(), tt.name, 1, openfeature.FlattenedContext{}).Value + case Structure: + result = provider.ObjectEvaluation(t.Context(), tt.name, make(map[string]any), openfeature.FlattenedContext{}).Value + } + + assert.Equal(t, tt.configValue, result) + } +} + +func makeFlags(tt struct { + name string + typ FeatureFlagType + originalValue string + configValue any +}) (map[string]setting.FeatureToggle, []FeatureFlag) { + orig := FeatureFlag{ + Name: tt.name, + Expression: tt.originalValue, + Type: tt.typ, + } + + config := map[string]setting.FeatureToggle{ + tt.name: { + Name: tt.name, + Type: setting.FeatureToggleType(tt.typ.String()), + Value: tt.configValue, + }, + } + + return config, []FeatureFlag{orig} + +}