Make it possible to compare transformation pipelines in unit tests
This commit is contained in:
parent
c771328bb1
commit
b89e6d9d93
@ -160,6 +160,7 @@ func (t *UsernameTransformation) compile(transformer *CELTransformer, consts *Tr
|
||||
baseCompiledTransformation: &baseCompiledTransformation{
|
||||
program: program,
|
||||
consts: consts,
|
||||
sourceExpr: t,
|
||||
maxExpressionRuntime: transformer.maxExpressionRuntime,
|
||||
},
|
||||
}, nil
|
||||
@ -174,6 +175,7 @@ func (t *GroupsTransformation) compile(transformer *CELTransformer, consts *Tran
|
||||
baseCompiledTransformation: &baseCompiledTransformation{
|
||||
program: program,
|
||||
consts: consts,
|
||||
sourceExpr: t,
|
||||
maxExpressionRuntime: transformer.maxExpressionRuntime,
|
||||
},
|
||||
}, nil
|
||||
@ -188,6 +190,7 @@ func (t *AllowAuthenticationPolicy) compile(transformer *CELTransformer, consts
|
||||
baseCompiledTransformation: &baseCompiledTransformation{
|
||||
program: program,
|
||||
consts: consts,
|
||||
sourceExpr: t,
|
||||
maxExpressionRuntime: transformer.maxExpressionRuntime,
|
||||
},
|
||||
rejectedAuthenticationMessage: t.RejectedAuthenticationMessage,
|
||||
@ -198,6 +201,7 @@ func (t *AllowAuthenticationPolicy) compile(transformer *CELTransformer, consts
|
||||
type baseCompiledTransformation struct {
|
||||
program cel.Program
|
||||
consts *TransformationConstants
|
||||
sourceExpr CELTransformation
|
||||
maxExpressionRuntime time.Duration
|
||||
}
|
||||
|
||||
@ -302,6 +306,23 @@ func (c *compiledAllowAuthenticationPolicy) Evaluate(ctx context.Context, userna
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type CELTransformationSource struct {
|
||||
Expr CELTransformation
|
||||
Consts *TransformationConstants
|
||||
}
|
||||
|
||||
func (c *compiledUsernameTransformation) Source() interface{} {
|
||||
return &CELTransformationSource{Expr: c.sourceExpr, Consts: c.consts}
|
||||
}
|
||||
|
||||
func (c *compiledGroupsTransformation) Source() interface{} {
|
||||
return &CELTransformationSource{Expr: c.sourceExpr, Consts: c.consts}
|
||||
}
|
||||
|
||||
func (c *compiledAllowAuthenticationPolicy) Source() interface{} {
|
||||
return &CELTransformationSource{Expr: c.sourceExpr, Consts: c.consts}
|
||||
}
|
||||
|
||||
func newEnv() (*cel.Env, error) {
|
||||
// Note that Kubernetes uses CEL in several places, which are helpful to see as an example of
|
||||
// how to configure the CEL compiler for production usage. Examples:
|
||||
|
@ -765,6 +765,7 @@ func TestTransformer(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
pipeline := idtransform.NewTransformationPipeline()
|
||||
expectedPipelineSource := []interface{}{}
|
||||
|
||||
for _, transform := range tt.transforms {
|
||||
compiledTransform, err := transformer.CompileTransformation(transform, tt.consts)
|
||||
@ -774,6 +775,15 @@ func TestTransformer(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, err, "got an unexpected compile error")
|
||||
pipeline.AppendTransformation(compiledTransform)
|
||||
|
||||
expectedTransformSource := &CELTransformationSource{
|
||||
Expr: transform,
|
||||
Consts: tt.consts,
|
||||
}
|
||||
if expectedTransformSource.Consts == nil {
|
||||
expectedTransformSource.Consts = &TransformationConstants{}
|
||||
}
|
||||
expectedPipelineSource = append(expectedPipelineSource, expectedTransformSource)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
@ -792,6 +802,8 @@ func TestTransformer(t *testing.T) {
|
||||
require.Equal(t, tt.wantGroups, result.Groups)
|
||||
require.Equal(t, !tt.wantAuthRejected, result.AuthenticationAllowed, "AuthenticationAllowed had unexpected value")
|
||||
require.Equal(t, tt.wantAuthRejectedMessage, result.RejectedAuthenticationMessage)
|
||||
|
||||
require.Equal(t, expectedPipelineSource, pipeline.Source())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
coretesting "k8s.io/client-go/testing"
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
"k8s.io/utils/pointer"
|
||||
@ -25,6 +26,7 @@ import (
|
||||
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
||||
pinnipedfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake"
|
||||
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
|
||||
"go.pinniped.dev/internal/celtransformer"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/federationdomain/federationdomainproviders"
|
||||
"go.pinniped.dev/internal/here"
|
||||
@ -1282,7 +1284,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
|
||||
Transforms: configv1alpha1.FederationDomainTransforms{
|
||||
Constants: []configv1alpha1.FederationDomainTransformsConstant{
|
||||
{Name: "duplicate1", Type: "string", StringValue: "abc"},
|
||||
{Name: "duplicate1", Type: "string", StringValue: "def"},
|
||||
{Name: "duplicate1", Type: "stringList", StringListValue: []string{"def"}},
|
||||
{Name: "duplicate1", Type: "string", StringValue: "efg"},
|
||||
{Name: "duplicate2", Type: "string", StringValue: "123"},
|
||||
{Name: "duplicate2", Type: "string", StringValue: "456"},
|
||||
@ -1612,6 +1614,133 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "the federation domain has valid IDPs and transformations and examples",
|
||||
inputObjects: []runtime.Object{
|
||||
oidcIdentityProvider,
|
||||
ldapIdentityProvider,
|
||||
&configv1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123},
|
||||
Spec: configv1alpha1.FederationDomainSpec{
|
||||
Issuer: "https://issuer1.com",
|
||||
IdentityProviders: []configv1alpha1.FederationDomainIdentityProvider{
|
||||
{
|
||||
DisplayName: "name1",
|
||||
ObjectRef: corev1.TypedLocalObjectReference{
|
||||
APIGroup: pointer.String(apiGroupSupervisor),
|
||||
Kind: "OIDCIdentityProvider",
|
||||
Name: oidcIdentityProvider.Name,
|
||||
},
|
||||
Transforms: configv1alpha1.FederationDomainTransforms{
|
||||
Expressions: []configv1alpha1.FederationDomainTransformsExpression{
|
||||
{Type: "policy/v1", Expression: `username == "ryan" || username == "rejectMeWithDefaultMessage"`, Message: "only ryan allowed"},
|
||||
{Type: "policy/v1", Expression: `username != "rejectMeWithDefaultMessage"`}, // no message specified
|
||||
{Type: "username/v1", Expression: `"pre:" + username`},
|
||||
{Type: "groups/v1", Expression: `groups.map(g, "pre:" + g)`},
|
||||
},
|
||||
Constants: []configv1alpha1.FederationDomainTransformsConstant{
|
||||
{Name: "str", Type: "string", StringValue: "abc"},
|
||||
{Name: "strL", Type: "stringList", StringListValue: []string{"def"}},
|
||||
},
|
||||
Examples: []configv1alpha1.FederationDomainTransformsExample{
|
||||
{
|
||||
Username: "ryan",
|
||||
Groups: []string{"a", "b"},
|
||||
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
|
||||
Username: "pre:ryan",
|
||||
Groups: []string{"pre:b", "pre:a"},
|
||||
Rejected: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Username: "other",
|
||||
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
|
||||
Rejected: true,
|
||||
Message: "only ryan allowed",
|
||||
},
|
||||
},
|
||||
{
|
||||
Username: "rejectMeWithDefaultMessage",
|
||||
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
|
||||
Rejected: true,
|
||||
// Not specifying message is the same as expecting the default message.
|
||||
},
|
||||
},
|
||||
{
|
||||
Username: "rejectMeWithDefaultMessage",
|
||||
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
|
||||
Rejected: true,
|
||||
Message: "Authentication was rejected by a configured policy", // this is the default message
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DisplayName: "name2",
|
||||
ObjectRef: corev1.TypedLocalObjectReference{
|
||||
APIGroup: pointer.String(apiGroupSupervisor),
|
||||
Kind: "LDAPIdentityProvider",
|
||||
Name: ldapIdentityProvider.Name,
|
||||
},
|
||||
Transforms: configv1alpha1.FederationDomainTransforms{
|
||||
Expressions: []configv1alpha1.FederationDomainTransformsExpression{
|
||||
{Type: "username/v1", Expression: `"pre:" + username`},
|
||||
},
|
||||
Examples: []configv1alpha1.FederationDomainTransformsExample{
|
||||
{
|
||||
Username: "ryan",
|
||||
Groups: []string{"a", "b"},
|
||||
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
|
||||
Username: "pre:ryan",
|
||||
Groups: []string{"b", "a"},
|
||||
Rejected: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{
|
||||
federationDomainIssuerWithIDPs(t, "https://issuer1.com", []*federationdomainproviders.FederationDomainIdentityProvider{
|
||||
{
|
||||
DisplayName: "name1",
|
||||
UID: oidcIdentityProvider.UID,
|
||||
Transforms: newTransformationPipeline(t, &celtransformer.TransformationConstants{
|
||||
StringConstants: map[string]string{"str": "abc"},
|
||||
StringListConstants: map[string][]string{"strL": {"def"}},
|
||||
},
|
||||
&celtransformer.AllowAuthenticationPolicy{
|
||||
Expression: `username == "ryan" || username == "rejectMeWithDefaultMessage"`,
|
||||
RejectedAuthenticationMessage: "only ryan allowed",
|
||||
},
|
||||
&celtransformer.AllowAuthenticationPolicy{Expression: `username != "rejectMeWithDefaultMessage"`},
|
||||
&celtransformer.UsernameTransformation{Expression: `"pre:" + username`},
|
||||
&celtransformer.GroupsTransformation{Expression: `groups.map(g, "pre:" + g)`},
|
||||
),
|
||||
},
|
||||
{
|
||||
DisplayName: "name2",
|
||||
UID: ldapIdentityProvider.UID,
|
||||
Transforms: newTransformationPipeline(t, &celtransformer.TransformationConstants{},
|
||||
&celtransformer.UsernameTransformation{Expression: `"pre:" + username`},
|
||||
),
|
||||
},
|
||||
}),
|
||||
},
|
||||
wantStatusUpdates: []*configv1alpha1.FederationDomain{
|
||||
expectedFederationDomainStatusUpdate(
|
||||
&configv1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123},
|
||||
},
|
||||
configv1alpha1.FederationDomainPhaseReady,
|
||||
allHappyConditionsSuccess("https://issuer1.com", frozenMetav1Now, 123),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "the federation domain specifies illegal const type, which shouldn't really happen since the CRD validates it",
|
||||
inputObjects: []runtime.Object{
|
||||
@ -1719,7 +1848,12 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
|
||||
|
||||
if tt.wantFDIssuers != nil {
|
||||
require.True(t, federationDomainsSetter.SetFederationDomainsWasCalled)
|
||||
require.ElementsMatch(t, tt.wantFDIssuers, federationDomainsSetter.FederationDomainsReceived)
|
||||
// This is ugly, but we cannot test equality on compiled identity transformations because cel.Program
|
||||
// cannot be compared for equality. This converts them to a type which can be tested for equality,
|
||||
// which should be good enough for the purposes of this test.
|
||||
require.ElementsMatch(t,
|
||||
convertToComparableType(tt.wantFDIssuers),
|
||||
convertToComparableType(federationDomainsSetter.FederationDomainsReceived))
|
||||
} else {
|
||||
require.False(t, federationDomainsSetter.SetFederationDomainsWasCalled)
|
||||
}
|
||||
@ -1743,6 +1877,46 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type comparableFederationDomainIssuer struct {
|
||||
issuer string
|
||||
identityProviders []*comparableFederationDomainIdentityProvider
|
||||
defaultIdentityProvider *comparableFederationDomainIdentityProvider
|
||||
}
|
||||
|
||||
type comparableFederationDomainIdentityProvider struct {
|
||||
DisplayName string
|
||||
UID types.UID
|
||||
TransformsSource []interface{}
|
||||
}
|
||||
|
||||
func makeFederationDomainIdentityProviderComparable(fdi *federationdomainproviders.FederationDomainIdentityProvider) *comparableFederationDomainIdentityProvider {
|
||||
if fdi == nil {
|
||||
return nil
|
||||
}
|
||||
return &comparableFederationDomainIdentityProvider{
|
||||
DisplayName: fdi.DisplayName,
|
||||
UID: fdi.UID,
|
||||
TransformsSource: fdi.Transforms.Source(),
|
||||
}
|
||||
}
|
||||
|
||||
func convertToComparableType(fdis []*federationdomainproviders.FederationDomainIssuer) []*comparableFederationDomainIssuer {
|
||||
result := []*comparableFederationDomainIssuer{}
|
||||
for _, fdi := range fdis {
|
||||
comparableFDIs := make([]*comparableFederationDomainIdentityProvider, len(fdi.IdentityProviders()))
|
||||
for _, idp := range fdi.IdentityProviders() {
|
||||
comparableFDIs = append(comparableFDIs, makeFederationDomainIdentityProviderComparable(idp))
|
||||
}
|
||||
converted := &comparableFederationDomainIssuer{
|
||||
issuer: fdi.Issuer(),
|
||||
identityProviders: comparableFDIs,
|
||||
defaultIdentityProvider: makeFederationDomainIdentityProviderComparable(fdi.DefaultIdentityProvider()),
|
||||
}
|
||||
result = append(result, converted)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func expectedFederationDomainStatusUpdate(
|
||||
fd *configv1alpha1.FederationDomain,
|
||||
phase configv1alpha1.FederationDomainPhase,
|
||||
@ -1789,3 +1963,116 @@ func sortFederationDomainsByName(federationDomains []*configv1alpha1.FederationD
|
||||
return federationDomains[a].GetName() < federationDomains[b].GetName()
|
||||
})
|
||||
}
|
||||
|
||||
func newTransformationPipeline(
|
||||
t *testing.T,
|
||||
consts *celtransformer.TransformationConstants,
|
||||
transformations ...celtransformer.CELTransformation,
|
||||
) *idtransform.TransformationPipeline {
|
||||
pipeline := idtransform.NewTransformationPipeline()
|
||||
|
||||
compiler, err := celtransformer.NewCELTransformer(celTransformerMaxExpressionRuntime)
|
||||
require.NoError(t, err)
|
||||
|
||||
if consts.StringConstants == nil {
|
||||
consts.StringConstants = map[string]string{}
|
||||
}
|
||||
if consts.StringListConstants == nil {
|
||||
consts.StringListConstants = map[string][]string{}
|
||||
}
|
||||
|
||||
for _, transform := range transformations {
|
||||
compiledTransform, err := compiler.CompileTransformation(transform, consts)
|
||||
require.NoError(t, err)
|
||||
pipeline.AppendTransformation(compiledTransform)
|
||||
}
|
||||
|
||||
return pipeline
|
||||
}
|
||||
|
||||
func TestTransformationPipelinesCanBeTestedForEqualityUsingSourceToMakeTestingEasier(t *testing.T) {
|
||||
compiler, err := celtransformer.NewCELTransformer(5 * time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
transforms := []celtransformer.CELTransformation{
|
||||
&celtransformer.AllowAuthenticationPolicy{
|
||||
Expression: `username == "ryan" || username == "rejectMeWithDefaultMessage"`,
|
||||
RejectedAuthenticationMessage: "only ryan allowed",
|
||||
},
|
||||
&celtransformer.UsernameTransformation{Expression: `"pre:" + username`},
|
||||
&celtransformer.GroupsTransformation{Expression: `groups.map(g, "pre:" + g)`},
|
||||
}
|
||||
|
||||
differentTransforms := []celtransformer.CELTransformation{
|
||||
&celtransformer.AllowAuthenticationPolicy{
|
||||
Expression: `username == "ryan" || username == "different"`,
|
||||
RejectedAuthenticationMessage: "different",
|
||||
},
|
||||
&celtransformer.UsernameTransformation{Expression: `"different:" + username`},
|
||||
&celtransformer.GroupsTransformation{Expression: `groups.map(g, "different:" + g)`},
|
||||
}
|
||||
|
||||
consts := &celtransformer.TransformationConstants{
|
||||
StringConstants: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "bat",
|
||||
},
|
||||
StringListConstants: map[string][]string{
|
||||
"foo": {"a", "b"},
|
||||
"bar": {"c", "d"},
|
||||
},
|
||||
}
|
||||
|
||||
differentConsts := &celtransformer.TransformationConstants{
|
||||
StringConstants: map[string]string{
|
||||
"foo": "barDifferent",
|
||||
"baz": "bat",
|
||||
},
|
||||
StringListConstants: map[string][]string{
|
||||
"foo": {"aDifferent", "b"},
|
||||
"bar": {"c", "d"},
|
||||
},
|
||||
}
|
||||
|
||||
pipeline := idtransform.NewTransformationPipeline()
|
||||
equalPipeline := idtransform.NewTransformationPipeline()
|
||||
differentPipeline1 := idtransform.NewTransformationPipeline()
|
||||
differentPipeline2 := idtransform.NewTransformationPipeline()
|
||||
expectedSourceList := []interface{}{}
|
||||
|
||||
for i, transform := range transforms {
|
||||
// Compile and append to a pipeline.
|
||||
compiledTransform1, err := compiler.CompileTransformation(transform, consts)
|
||||
require.NoError(t, err)
|
||||
pipeline.AppendTransformation(compiledTransform1)
|
||||
|
||||
// Recompile the same thing and append it to another pipeline.
|
||||
// This pipeline should end up being equal to the first one.
|
||||
compiledTransform2, err := compiler.CompileTransformation(transform, consts)
|
||||
require.NoError(t, err)
|
||||
equalPipeline.AppendTransformation(compiledTransform2)
|
||||
|
||||
// Build up a test expectation value.
|
||||
expectedSourceList = append(expectedSourceList, &celtransformer.CELTransformationSource{Expr: transform, Consts: consts})
|
||||
|
||||
// Compile a different expression using the same constants and append it to a different pipeline.
|
||||
// This should not be equal to the other pipelines.
|
||||
compiledDifferentExpressionSameConsts, err := compiler.CompileTransformation(differentTransforms[i], consts)
|
||||
require.NoError(t, err)
|
||||
differentPipeline1.AppendTransformation(compiledDifferentExpressionSameConsts)
|
||||
|
||||
// Compile the same expression using the different constants and append it to a different pipeline.
|
||||
// This should not be equal to the other pipelines.
|
||||
compiledSameExpressionDifferentConsts, err := compiler.CompileTransformation(transform, differentConsts)
|
||||
require.NoError(t, err)
|
||||
differentPipeline2.AppendTransformation(compiledSameExpressionDifferentConsts)
|
||||
}
|
||||
|
||||
require.Equal(t, expectedSourceList, pipeline.Source())
|
||||
require.Equal(t, expectedSourceList, equalPipeline.Source())
|
||||
|
||||
// The source of compiled pipelines can be compared to each other in this way for testing purposes.
|
||||
require.Equal(t, pipeline.Source(), equalPipeline.Source())
|
||||
require.NotEqual(t, pipeline.Source(), differentPipeline1.Source())
|
||||
require.NotEqual(t, pipeline.Source(), differentPipeline2.Source())
|
||||
}
|
||||
|
@ -25,6 +25,10 @@ type TransformationResult struct {
|
||||
// IdentityTransformation is an individual identity transformation which can be evaluated.
|
||||
type IdentityTransformation interface {
|
||||
Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error)
|
||||
|
||||
// Source returns some representation of the original source code of the transformation, which is
|
||||
// useful for tests to be able to check that a compiled transformation came from the right source.
|
||||
Source() interface{}
|
||||
}
|
||||
|
||||
// TransformationPipeline is a list of identity transforms, which can be evaluated in order against some given input
|
||||
@ -85,6 +89,14 @@ func (p *TransformationPipeline) Evaluate(ctx context.Context, username string,
|
||||
return accumulatedResult, nil
|
||||
}
|
||||
|
||||
func (p *TransformationPipeline) Source() []interface{} {
|
||||
result := []interface{}{}
|
||||
for _, transform := range p.transforms {
|
||||
result = append(result, transform.Source())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func sortAndUniq(s []string) []string {
|
||||
unique := sets.New(s...).UnsortedList()
|
||||
sort.Strings(unique)
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type FakeNoopTransformer struct{}
|
||||
type fakeNoopTransformer struct{}
|
||||
|
||||
func (a FakeNoopTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
func (a fakeNoopTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
return &TransformationResult{
|
||||
Username: username,
|
||||
Groups: groups,
|
||||
@ -22,9 +22,13 @@ func (a FakeNoopTransformer) Evaluate(ctx context.Context, username string, grou
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FakeNilGroupTransformer struct{}
|
||||
func (a fakeNoopTransformer) Source() interface{} {
|
||||
return nil // not needed for this test
|
||||
}
|
||||
|
||||
func (a FakeNilGroupTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
type fakeNilGroupTransformer struct{}
|
||||
|
||||
func (a fakeNilGroupTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
return &TransformationResult{
|
||||
Username: username,
|
||||
Groups: nil,
|
||||
@ -33,9 +37,13 @@ func (a FakeNilGroupTransformer) Evaluate(ctx context.Context, username string,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FakeAppendStringTransformer struct{}
|
||||
func (a fakeNilGroupTransformer) Source() interface{} {
|
||||
return nil // not needed for this test
|
||||
}
|
||||
|
||||
func (a FakeAppendStringTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
type fakeAppendStringTransformer struct{}
|
||||
|
||||
func (a fakeAppendStringTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
newGroups := []string{}
|
||||
for _, group := range groups {
|
||||
newGroups = append(newGroups, group+":transformed")
|
||||
@ -48,9 +56,13 @@ func (a FakeAppendStringTransformer) Evaluate(ctx context.Context, username stri
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FakeDeleteUsernameAndGroupsTransformer struct{}
|
||||
func (a fakeAppendStringTransformer) Source() interface{} {
|
||||
return nil // not needed for this test
|
||||
}
|
||||
|
||||
func (d FakeDeleteUsernameAndGroupsTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
type fakeDeleteUsernameAndGroupsTransformer struct{}
|
||||
|
||||
func (a fakeDeleteUsernameAndGroupsTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
return &TransformationResult{
|
||||
Username: "",
|
||||
Groups: []string{},
|
||||
@ -59,9 +71,13 @@ func (d FakeDeleteUsernameAndGroupsTransformer) Evaluate(ctx context.Context, us
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FakeAuthenticationDisallowedTransformer struct{}
|
||||
func (a fakeDeleteUsernameAndGroupsTransformer) Source() interface{} {
|
||||
return nil // not needed for this test
|
||||
}
|
||||
|
||||
func (d FakeAuthenticationDisallowedTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
type fakeAuthenticationDisallowedTransformer struct{}
|
||||
|
||||
func (a fakeAuthenticationDisallowedTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
newGroups := []string{}
|
||||
for _, group := range groups {
|
||||
newGroups = append(newGroups, group+":disallowed")
|
||||
@ -74,13 +90,33 @@ func (d FakeAuthenticationDisallowedTransformer) Evaluate(ctx context.Context, u
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FakeErrorTransformer struct{}
|
||||
func (a fakeAuthenticationDisallowedTransformer) Source() interface{} {
|
||||
return nil // not needed for this test
|
||||
}
|
||||
|
||||
func (d FakeErrorTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
type fakeErrorTransformer struct{}
|
||||
|
||||
func (a fakeErrorTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
return &TransformationResult{}, errors.New("unexpected catastrophic error")
|
||||
}
|
||||
|
||||
func TestTransformationPipeline(t *testing.T) {
|
||||
func (a fakeErrorTransformer) Source() interface{} {
|
||||
return nil // not needed for this test
|
||||
}
|
||||
|
||||
type fakeTransformerWithSource struct {
|
||||
source string
|
||||
}
|
||||
|
||||
func (a fakeTransformerWithSource) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
||||
return nil, nil // not needed for this test
|
||||
}
|
||||
|
||||
func (a fakeTransformerWithSource) Source() interface{} {
|
||||
return a.source
|
||||
}
|
||||
|
||||
func TestTransformationPipelineEvaluation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
@ -95,7 +131,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
{
|
||||
name: "single transformation applied successfully",
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
},
|
||||
username: "foo",
|
||||
groups: []string{
|
||||
@ -113,7 +149,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
{
|
||||
name: "group results are sorted and made unique",
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
},
|
||||
username: "foo",
|
||||
groups: []string{
|
||||
@ -141,8 +177,8 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
"foobaz",
|
||||
},
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAppendStringTransformer{},
|
||||
FakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
},
|
||||
wantUsername: "foo:transformed:transformed",
|
||||
wantGroups: []string{
|
||||
@ -159,7 +195,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
"foobar",
|
||||
},
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAuthenticationDisallowedTransformer{},
|
||||
fakeAuthenticationDisallowedTransformer{},
|
||||
},
|
||||
wantUsername: "foo:disallowed",
|
||||
wantGroups: []string{"foobar:disallowed"},
|
||||
@ -173,10 +209,10 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
"foobar",
|
||||
},
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAppendStringTransformer{},
|
||||
FakeAuthenticationDisallowedTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
fakeAuthenticationDisallowedTransformer{},
|
||||
// this transformation will not be run because the previous exits the pipeline
|
||||
FakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
},
|
||||
wantUsername: "foo:transformed:disallowed",
|
||||
wantGroups: []string{"foobar:transformed:disallowed"},
|
||||
@ -190,9 +226,9 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
"foobar",
|
||||
},
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAppendStringTransformer{},
|
||||
FakeErrorTransformer{},
|
||||
FakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
fakeErrorTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
},
|
||||
wantError: "identity transformation at index 1: unexpected catastrophic error",
|
||||
},
|
||||
@ -200,7 +236,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
name: "empty username not allowed",
|
||||
username: "foo",
|
||||
transforms: []IdentityTransformation{
|
||||
FakeDeleteUsernameAndGroupsTransformer{},
|
||||
fakeDeleteUsernameAndGroupsTransformer{},
|
||||
},
|
||||
wantError: "identity transformation returned an empty username, which is not allowed",
|
||||
},
|
||||
@ -208,7 +244,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
name: "whitespace username not allowed",
|
||||
username: " \t\n\r ",
|
||||
transforms: []IdentityTransformation{
|
||||
FakeNoopTransformer{},
|
||||
fakeNoopTransformer{},
|
||||
},
|
||||
wantError: "identity transformation returned an empty username, which is not allowed",
|
||||
},
|
||||
@ -217,7 +253,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
username: "foo",
|
||||
groups: []string{},
|
||||
transforms: []IdentityTransformation{
|
||||
FakeAppendStringTransformer{},
|
||||
fakeAppendStringTransformer{},
|
||||
},
|
||||
wantUsername: "foo:transformed",
|
||||
wantGroups: []string{},
|
||||
@ -229,7 +265,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
username: "foo",
|
||||
groups: nil,
|
||||
transforms: []IdentityTransformation{
|
||||
FakeNoopTransformer{},
|
||||
fakeNoopTransformer{},
|
||||
},
|
||||
wantUsername: "foo",
|
||||
wantGroups: []string{},
|
||||
@ -243,7 +279,7 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
"these.will.be.converted.to.nil",
|
||||
},
|
||||
transforms: []IdentityTransformation{
|
||||
FakeNilGroupTransformer{},
|
||||
fakeNilGroupTransformer{},
|
||||
},
|
||||
wantError: "identity transformation returned a null list of groups, which is not allowed",
|
||||
},
|
||||
@ -287,3 +323,18 @@ func TestTransformationPipeline(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransformationSource(t *testing.T) {
|
||||
pipeline := NewTransformationPipeline()
|
||||
|
||||
for _, transform := range []IdentityTransformation{
|
||||
&fakeTransformerWithSource{source: "foo"},
|
||||
&fakeTransformerWithSource{source: "bar"},
|
||||
&fakeTransformerWithSource{source: "baz"},
|
||||
} {
|
||||
pipeline.AppendTransformation(transform)
|
||||
}
|
||||
|
||||
require.Equal(t, []interface{}{"foo", "bar", "baz"}, pipeline.Source())
|
||||
require.NotEqual(t, []interface{}{"foo", "something-else", "baz"}, pipeline.Source())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user