grafana/pkg/services/featuremgmt/openfeature_test.go

137 lines
4.3 KiB
Go

package featuremgmt
import (
"context"
"errors"
"net/url"
"testing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/clientauth/middleware"
"github.com/grafana/grafana/pkg/setting"
authlib "github.com/grafana/authlib/authn"
gofeatureflag "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg"
"github.com/open-feature/go-sdk/openfeature"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestCreateProvider(t *testing.T) {
u, err := url.Parse("http://localhost:10333")
require.NoError(t, err)
testCases := []struct {
name string
cfg setting.OpenFeatureSettings
expectedProvider string
expectExchangeRequest *authlib.TokenExchangeRequest
failSigning bool
}{
{
name: "static provider",
expectedProvider: setting.StaticProviderType,
},
{
name: "goff provider",
cfg: setting.OpenFeatureSettings{
ProviderType: setting.GOFFProviderType,
URL: u,
TargetingKey: "grafana",
},
expectExchangeRequest: &authlib.TokenExchangeRequest{
Namespace: "*",
Audiences: []string{"features.grafana.app"},
},
expectedProvider: setting.GOFFProviderType,
},
{
name: "goff provider with failing token exchange",
cfg: setting.OpenFeatureSettings{
ProviderType: setting.GOFFProviderType,
URL: u,
TargetingKey: "grafana",
},
expectExchangeRequest: &authlib.TokenExchangeRequest{
Namespace: "*",
Audiences: []string{"features.grafana.app"},
},
expectedProvider: setting.GOFFProviderType,
failSigning: true,
},
{
name: "invalid provider",
cfg: setting.OpenFeatureSettings{
ProviderType: "some_provider",
},
expectedProvider: setting.StaticProviderType,
},
}
require.NoError(t, err)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.OpenFeature = tc.cfg
var tokenExchangeClient *fakeTokenExchangeClient
if tc.expectExchangeRequest != nil {
tokenExchangeClient = &fakeTokenExchangeClient{
Mock: &mock.Mock{
ExpectedCalls: []*mock.Call{
{
Method: "Exchange",
Arguments: mock.Arguments{mock.Anything, *tc.expectExchangeRequest},
},
},
},
}
if tc.failSigning {
tokenExchangeClient.expectedErr = errors.New("failed signing access token")
}
}
tokenExchangeMiddleware := middleware.TestingTokenExchangeMiddleware(tokenExchangeClient)
goffClient, err := goffHTTPClient(tokenExchangeMiddleware)
require.NoError(t, err, "failed to create goff http client")
provider, err := createProvider(tc.cfg.ProviderType, tc.cfg.URL, nil, goffClient)
require.NoError(t, err)
err = openfeature.SetProviderAndWait(provider)
require.NoError(t, err, "failed to set provider")
if tc.expectedProvider == setting.GOFFProviderType {
_, ok := provider.(*gofeatureflag.Provider)
assert.True(t, ok, "expected provider to be of type goff.Provider")
testGoFFProvider(t, tc.failSigning)
} else {
_, ok := provider.(*inMemoryBulkProvider)
assert.True(t, ok, "expected provider to be of type memprovider.InMemoryProvider")
}
})
}
}
func testGoFFProvider(t *testing.T, failSigning bool) {
// this tests with a fake identity with * namespace access, but in any case, it proves what the requester
// is scoped to is what is used to sign the token with
ctx, _ := identity.WithServiceIdentity(context.Background(), 1)
// Test that the flag evaluation can be attempted (though it will fail due to non-existent service)
// The important thing is that the authentication middleware is properly integrated
_, err := openfeature.NewDefaultClient().BooleanValueDetails(ctx, "test", false, openfeature.NewEvaluationContext("test", map[string]interface{}{"test": "test"}))
// Error related to the token exchange should be returned if signing fails
// otherwise, it should return a connection refused error since the goff URL is not set
if failSigning {
assert.ErrorContains(t, err, "failed to exchange token: error signing token", "should return an error when signing fails")
} else {
assert.ErrorContains(t, err, "connect: connection refused", "should return an error when goff url is not set")
}
}