// Copyright 2021 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package whoamirequest import ( "context" "errors" "testing" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/user" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" identityapi "go.pinniped.dev/generated/latest/apis/concierge/identity" ) func TestNew(t *testing.T) { r := NewREST(schema.GroupResource{Group: "bears", Resource: "panda"}) require.NotNil(t, r) require.False(t, r.NamespaceScoped()) require.Equal(t, []string{"pinniped"}, r.Categories()) require.IsType(t, &identityapi.WhoAmIRequest{}, r.New()) require.IsType(t, &identityapi.WhoAmIRequestList{}, r.NewList()) ctx := context.Background() // check the simple invariants of our no-op list list, err := r.List(ctx, nil) require.NoError(t, err) require.NotNil(t, list) require.IsType(t, &identityapi.WhoAmIRequestList{}, list) require.Equal(t, "0", list.(*identityapi.WhoAmIRequestList).ResourceVersion) require.NotNil(t, list.(*identityapi.WhoAmIRequestList).Items) require.Len(t, list.(*identityapi.WhoAmIRequestList).Items, 0) // make sure we can turn lists into tables if needed table, err := r.ConvertToTable(ctx, list, nil) require.NoError(t, err) require.NotNil(t, table) require.Equal(t, "0", table.ResourceVersion) require.Nil(t, table.Rows) // exercise group resource - force error by passing a runtime.Object that does not have an embedded object meta _, err = r.ConvertToTable(ctx, &metav1.APIGroup{}, nil) require.Error(t, err, "the resource panda.bears does not support being converted to a Table") } func TestCreate(t *testing.T) { type args struct { ctx context.Context obj runtime.Object createValidation rest.ValidateObjectFunc options *metav1.CreateOptions } tests := []struct { name string args args want runtime.Object wantErr string }{ { name: "wrong type", args: args{ ctx: genericapirequest.NewContext(), obj: &metav1.Status{}, createValidation: nil, options: nil, }, want: nil, wantErr: `not a WhoAmIRequest: &v1.Status{TypeMeta:v1.TypeMeta{Kind:"", APIVersion:""}, ListMeta:v1.ListMeta{SelfLink:"", ResourceVersion:"", Continue:"", RemainingItemCount:(*int64)(nil)}, Status:"", Message:"", Reason:"", Details:(*v1.StatusDetails)(nil), Code:0}`, }, { name: "bad options", args: args{ ctx: genericapirequest.NewContext(), obj: &identityapi.WhoAmIRequest{ TypeMeta: metav1.TypeMeta{ Kind: "SomeKind", }, ObjectMeta: metav1.ObjectMeta{ Name: "some-name", }, }, createValidation: nil, options: &metav1.CreateOptions{DryRun: []string{"stuff"}}, }, want: nil, wantErr: `SomeKind.identity.concierge.pinniped.dev "some-name" is invalid: dryRun: Unsupported value: []string{"stuff"}`, }, { name: "bad namespace", args: args{ ctx: genericapirequest.WithNamespace(genericapirequest.NewContext(), "some-ns"), obj: &identityapi.WhoAmIRequest{}, createValidation: nil, options: nil, }, want: nil, wantErr: `namespace is not allowed on WhoAmIRequest: some-ns`, }, { // if we add fields to spec, we need additional tests to: // - make sure admission cannot mutate it // - the input spec fields are validated correctly name: "create validation failure", args: args{ ctx: genericapirequest.NewContext(), obj: &identityapi.WhoAmIRequest{}, createValidation: func(ctx context.Context, obj runtime.Object) error { return errors.New("some-error-here") }, options: nil, }, want: nil, wantErr: `some-error-here`, }, { name: "no user info", args: args{ ctx: genericapirequest.NewContext(), obj: &identityapi.WhoAmIRequest{}, createValidation: nil, options: nil, }, want: nil, wantErr: `Internal error occurred: no user info on request`, }, { name: "with user info, no auds", args: args{ ctx: genericapirequest.WithUser(genericapirequest.NewContext(), &user.DefaultInfo{ Name: "bond", UID: "007", Groups: []string{"agents", "ops"}, Extra: map[string][]string{ "fan-of": {"pandas", "twizzlers"}, "needs": {"sleep"}, }, }), obj: &identityapi.WhoAmIRequest{}, createValidation: nil, options: nil, }, want: &identityapi.WhoAmIRequest{ Status: identityapi.WhoAmIRequestStatus{ KubernetesUserInfo: identityapi.KubernetesUserInfo{ User: identityapi.UserInfo{ Username: "bond", UID: "007", Groups: []string{"agents", "ops"}, Extra: map[string]identityapi.ExtraValue{ "fan-of": {"pandas", "twizzlers"}, "needs": {"sleep"}, }, }, Audiences: nil, }, }, }, wantErr: ``, }, { name: "with user info and auds", args: args{ ctx: authenticator.WithAudiences( genericapirequest.WithUser(genericapirequest.NewContext(), &user.DefaultInfo{ Name: "panda", }), authenticator.Audiences{"gitlab", "aws"}, ), obj: &identityapi.WhoAmIRequest{}, createValidation: nil, options: nil, }, want: &identityapi.WhoAmIRequest{ Status: identityapi.WhoAmIRequestStatus{ KubernetesUserInfo: identityapi.KubernetesUserInfo{ User: identityapi.UserInfo{ Username: "panda", }, Audiences: []string{"gitlab", "aws"}, }, }, }, wantErr: ``, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { r := &REST{} got, err := r.Create(tt.args.ctx, tt.args.obj, tt.args.createValidation, tt.args.options) require.Equal(t, tt.wantErr, errString(err)) require.Equal(t, tt.want, got) }) } } func errString(err error) string { if err == nil { return "" } return err.Error() }