From 2014e7a62a6db9c27813098fc73b49aa4d503bce Mon Sep 17 00:00:00 2001 From: Evan Anderson Date: Thu, 25 Apr 2024 07:11:21 -0700 Subject: [PATCH] Introduce fake for openfeature.IClient --- internal/auth/interface_test.go | 2 +- internal/controlplane/handlers_authz_test.go | 17 +- internal/controlplane/server.go | 2 +- internal/flags/flags.go | 2 +- internal/flags/test_client.go | 182 +++++++++++++++++++ 5 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 internal/flags/test_client.go diff --git a/internal/auth/interface_test.go b/internal/auth/interface_test.go index 0ea6331da2..e30f5944cc 100644 --- a/internal/auth/interface_test.go +++ b/internal/auth/interface_test.go @@ -135,7 +135,7 @@ type StaticIDP struct { human string } -var _ IdentityProvider = &StaticIDP{} +var _ IdentityProvider = (*StaticIDP)(nil) // Resolve implements IdentityProvider. func (s *StaticIDP) Resolve(_ context.Context, id string) (*Identity, error) { diff --git a/internal/controlplane/handlers_authz_test.go b/internal/controlplane/handlers_authz_test.go index 306356cbeb..e3532064ae 100644 --- a/internal/controlplane/handlers_authz_test.go +++ b/internal/controlplane/handlers_authz_test.go @@ -23,7 +23,6 @@ import ( "github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/jwt/openid" "github.com/open-feature/go-sdk/openfeature" - "github.com/open-feature/go-sdk/openfeature/memprovider" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -293,9 +292,6 @@ func TestProjectAuthorizationInterceptor(t *testing.T) { } } -// see https://github.com/open-feature/go-sdk/issues/266 -// -//nolint:tparallel,paralleltest // openfeature currently has global singleton state: func TestRoleManagement(t *testing.T) { t.Parallel() @@ -393,6 +389,7 @@ func TestRoleManagement(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + t.Parallel() authzClient := &mock.SimpleClient{ Allowed: []uuid.UUID{}, } @@ -418,16 +415,8 @@ func TestRoleManagement(t *testing.T) { mockStore.EXPECT().GetUserBySubject(gomock.Any(), match).Return(db.User{ID: 1}, nil) } - inMemFlag := memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{ - string(flags.IDPResolver): { - Key: string(flags.IDPResolver), - DefaultVariant: "Fixed", - Variants: map[string]any{"Fixed": tc.idpFlag}, - }, - }) - assert.NoError(t, openfeature.SetProvider(inMemFlag)) - featureClient := openfeature.NewClient(t.Name()) - t.Cleanup(openfeature.Shutdown) + featureClient := &flags.FakeClient{} + featureClient.Data = map[string]any{"idp_resolver": tc.idpFlag} server := Server{ store: mockStore, diff --git a/internal/controlplane/server.go b/internal/controlplane/server.go index 84d0f5738e..3d9cd24eaf 100644 --- a/internal/controlplane/server.go +++ b/internal/controlplane/server.go @@ -90,7 +90,7 @@ type Server struct { idClient auth.Resolver cryptoEngine crypto.Engine restClientCache ratecache.RestClientCache - featureFlags *openfeature.Client + featureFlags openfeature.IClient // We may want to start breaking up the server struct if we use it to // inject more entity-specific interfaces. For example, we may want to // consider having a struct per grpc service diff --git a/internal/flags/flags.go b/internal/flags/flags.go index 3c2b67e414..c06d4deb40 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -48,7 +48,7 @@ func FromContext(ctx context.Context) openfeature.EvaluationContext { } // Bool provides a simple wrapper around client.Boolean to normalize usage for Minder. -func Bool(ctx context.Context, client *openfeature.Client, feature Experiment) bool { +func Bool(ctx context.Context, client openfeature.IClient, feature Experiment) bool { ret := client.Boolean(ctx, string(feature), false, FromContext(ctx)) // TODO: capture in telemetry records return ret diff --git a/internal/flags/test_client.go b/internal/flags/test_client.go new file mode 100644 index 0000000000..2ea77d7673 --- /dev/null +++ b/internal/flags/test_client.go @@ -0,0 +1,182 @@ +// +// Copyright 2023 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package flags + +import ( + "context" + "errors" + + "github.com/open-feature/go-sdk/openfeature" +) + +// FakeClient implements a simple in-memory client for testing. +// +// see https://github.com/open-feature/go-sdk/issues/266 for the proper support. +type FakeClient struct { + Data map[string]any +} + +var _ openfeature.IClient = (*FakeClient)(nil) + +// AddHandler implements openfeature.IClient. +func (_ *FakeClient) AddHandler(_ openfeature.EventType, _ openfeature.EventCallback) { + panic("unimplemented") +} + +// AddHooks implements openfeature.IClient. +func (_ *FakeClient) AddHooks(_ ...openfeature.Hook) { + panic("unimplemented") +} + +// Boolean implements openfeature.IClient. +func (f *FakeClient) Boolean(_ context.Context, flag string, defaultValue bool, + _ openfeature.EvaluationContext, _ ...openfeature.Option) bool { + if v, ok := f.Data[flag]; ok { + return v.(bool) + } + return defaultValue +} + +// BooleanValue implements openfeature.IClient. +func (f *FakeClient) BooleanValue(_ context.Context, flag string, defaultValue bool, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (bool, error) { + if v, ok := f.Data[flag]; ok { + return v.(bool), nil + } + return defaultValue, errors.New("Not found") +} + +// BooleanValueDetails implements openfeature.IClient. +func (_ *FakeClient) BooleanValueDetails(_ context.Context, _ string, _ bool, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (openfeature.BooleanEvaluationDetails, error) { + panic("unimplemented") +} + +// EvaluationContext implements openfeature.IClient. +func (_ *FakeClient) EvaluationContext() openfeature.EvaluationContext { + panic("unimplemented") +} + +// Float implements openfeature.IClient. +func (f *FakeClient) Float(_ context.Context, flag string, defaultValue float64, + _ openfeature.EvaluationContext, _ ...openfeature.Option) float64 { + if v, ok := f.Data[flag]; ok { + return v.(float64) + } + return defaultValue +} + +// FloatValue implements openfeature.IClient. +func (f *FakeClient) FloatValue(_ context.Context, flag string, defaultValue float64, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (float64, error) { + if v, ok := f.Data[flag]; ok { + return v.(float64), nil + } + return defaultValue, errors.New("Not found") +} + +// FloatValueDetails implements openfeature.IClient. +func (_ *FakeClient) FloatValueDetails(_ context.Context, _ string, _ float64, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (openfeature.FloatEvaluationDetails, error) { + panic("unimplemented") +} + +// Int implements openfeature.IClient. +func (f *FakeClient) Int(_ context.Context, flag string, defaultValue int64, + _ openfeature.EvaluationContext, _ ...openfeature.Option) int64 { + if v, ok := f.Data[flag]; ok { + return v.(int64) + } + return defaultValue +} + +// IntValue implements openfeature.IClient. +func (f *FakeClient) IntValue(_ context.Context, flag string, defaultValue int64, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (int64, error) { + if v, ok := f.Data[flag]; ok { + return v.(int64), nil + } + return defaultValue, errors.New("Not found") +} + +// IntValueDetails implements openfeature.IClient. +func (_ *FakeClient) IntValueDetails(_ context.Context, _ string, _ int64, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (openfeature.IntEvaluationDetails, error) { + panic("unimplemented") +} + +// Metadata implements openfeature.IClient. +func (_ *FakeClient) Metadata() openfeature.ClientMetadata { + panic("unimplemented") +} + +// Object implements openfeature.IClient. +func (f *FakeClient) Object(_ context.Context, flag string, defaultValue interface{}, + _ openfeature.EvaluationContext, _ ...openfeature.Option) interface{} { + if v, ok := f.Data[flag]; ok { + return v + } + return defaultValue +} + +// ObjectValue implements openfeature.IClient. +func (f *FakeClient) ObjectValue(_ context.Context, flag string, defaultValue interface{}, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (interface{}, error) { + if v, ok := f.Data[flag]; ok { + return v, nil + } + return defaultValue, errors.New("Not found") +} + +// ObjectValueDetails implements openfeature.IClient. +func (_ *FakeClient) ObjectValueDetails(_ context.Context, _ string, _ interface{}, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (openfeature.InterfaceEvaluationDetails, error) { + panic("unimplemented") +} + +// RemoveHandler implements openfeature.IClient. +func (_ *FakeClient) RemoveHandler(_ openfeature.EventType, _ openfeature.EventCallback) { + panic("unimplemented") +} + +// SetEvaluationContext implements openfeature.IClient. +func (_ *FakeClient) SetEvaluationContext(_ openfeature.EvaluationContext) { + panic("unimplemented") +} + +// String implements openfeature.IClient. +func (f *FakeClient) String(_ context.Context, flag string, defaultValue string, + _ openfeature.EvaluationContext, _ ...openfeature.Option) string { + if v, ok := f.Data[flag]; ok { + return v.(string) + } + return defaultValue +} + +// StringValue implements openfeature.IClient. +func (f *FakeClient) StringValue(_ context.Context, flag string, defaultValue string, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (string, error) { + if v, ok := f.Data[flag]; ok { + return v.(string), nil + } + return defaultValue, errors.New("Not found") +} + +// StringValueDetails implements openfeature.IClient. +func (_ *FakeClient) StringValueDetails(_ context.Context, _ string, _ string, + _ openfeature.EvaluationContext, _ ...openfeature.Option) (openfeature.StringEvaluationDetails, error) { + panic("unimplemented") +}