1
0
mirror of https://github.com/rancher/steve.git synced 2025-07-31 06:23:11 +00:00
steve/pkg/ext/delegate_error_test.go
2024-11-12 12:40:57 -03:00

1098 lines
29 KiB
Go

package ext
import (
"context"
"errors"
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
gomock "go.uber.org/mock/gomock"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
)
func TestConvertError(t *testing.T) {
tests := []struct {
name string
input error
output error
}{
{
name: "api status error",
input: &apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
output: &apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
},
{
name: "generic error",
input: assert.AnError,
output: &apierrors.StatusError{ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusInternalServerError,
Reason: metav1.StatusReasonInternalError,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{{Message: assert.AnError.Error()}},
},
Message: fmt.Sprintf("Internal error occurred: %v", assert.AnError),
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := convertError(tt.input)
assert.Equal(t, tt.output, output)
})
}
}
func TestDelegateError_Watch(t *testing.T) {
type input struct {
ctx context.Context
internaloptions *metainternalversion.ListOptions
}
type output struct {
watch watch.Interface
err error
}
type testCase struct {
name string
input input
expected output
storeSetup func(*MockStore[*TestType, *TestTypeList])
simulateConversionError bool
wantedErr bool
}
tests := []testCase{
{
name: "missing user in context",
input: input{
ctx: context.TODO(),
internaloptions: &metainternalversion.ListOptions{},
},
wantedErr: true,
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
},
{
name: "convert list error",
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
internaloptions: &metainternalversion.ListOptions{},
},
simulateConversionError: true,
wantedErr: true,
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
},
}
for _, tt := range tests {
scheme := runtime.NewScheme()
addToSchemeTest(scheme)
if !tt.simulateConversionError {
scheme.AddConversionFunc(&metainternalversion.ListOptions{}, &metav1.ListOptions{}, convert_internalversion_ListOptions_to_v1_ListOptions)
}
gvk := schema.GroupVersionKind{Group: "ext.cattle.io", Version: "v1", Kind: "TestType"}
gvr := schema.GroupVersionResource{Group: "ext.cattle.io", Version: "v1", Resource: "testtypes"}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := NewMockStore[*TestType, *TestTypeList](ctrl)
testDelegate := &delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{
scheme: scheme,
t: &TestType{},
tList: &TestTypeList{},
gvk: gvk,
gvr: gvr,
store: mockStore,
},
}
watch, err := testDelegate.Watch(tt.input.ctx, tt.input.internaloptions)
if tt.wantedErr {
// check if we have an error
assert.Error(t, err)
// check if error is an api error
_, ok := err.(apierrors.APIStatus)
assert.True(t, ok)
} else {
assert.NoError(t, err)
assert.Equal(t, watch, tt.expected.watch)
}
}
}
func TestDelegateError_Update(t *testing.T) {
type input struct {
parentCtx context.Context
name string
objInfo rest.UpdatedObjectInfo
createValidation rest.ValidateObjectFunc
updateValidation rest.ValidateObjectUpdateFunc
forceAllowCreate bool
options *metav1.UpdateOptions
}
type output struct {
obj runtime.Object
created bool
err error
}
type testCase struct {
name string
setup func(*MockUpdatedObjectInfo, *MockStore[*TestType, *TestTypeList])
input input
expect output
wantErr bool
}
tests := []testCase{
{
name: "working case",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
// objInfo is created in the for loop
forceAllowCreate: false,
options: &metav1.UpdateOptions{},
},
expect: output{
obj: &TestType{},
created: false,
err: nil,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
store.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
wantErr: false,
},
{
name: "missing user in context",
input: input{
parentCtx: context.TODO(),
},
setup: func(muoi *MockUpdatedObjectInfo, ms *MockStore[*TestType, *TestTypeList]) {},
expect: output{
obj: nil,
created: false,
err: errMissingUserInfo,
},
wantErr: true,
},
{
name: "get failed - other error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
wantErr: true,
},
{
name: "get failed - not found - updated object error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil,
&apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
wantErr: true,
},
{
name: "get failed - not found - create succeeded",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error { return nil },
},
expect: output{
obj: &TestType{},
created: true,
err: nil,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil,
&apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
store.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
wantErr: false,
},
{
name: "get failed - not found - create validation error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return assert.AnError
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil,
&apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
wantErr: true,
},
{
name: "get failed - not found - type error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return nil
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil,
&apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&runtime.Unknown{}, nil)
},
wantErr: true,
},
{
name: "get failed - not found - store create error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return nil
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil,
&apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
)
store.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
wantErr: true,
},
{
name: "get worked - updated object error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return nil
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
wantErr: true,
},
{
name: "get worked - type error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return nil
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&runtime.Unknown{}, nil)
},
wantErr: true,
},
{
name: "get worked - update validation error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return nil
},
updateValidation: func(ctx context.Context, obj, old runtime.Object) error {
return assert.AnError
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
wantErr: true,
},
{
name: "get worked - store update error",
input: input{
parentCtx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-user",
createValidation: func(ctx context.Context, obj runtime.Object) error {
return nil
},
},
expect: output{
obj: nil,
created: false,
err: assert.AnError,
},
setup: func(objInfo *MockUpdatedObjectInfo, store *MockStore[*TestType, *TestTypeList]) {
store.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
objInfo.EXPECT().UpdatedObject(gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
store.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scheme := runtime.NewScheme()
addToSchemeTest(scheme)
gvk := schema.GroupVersionKind{Group: "ext.cattle.io", Version: "v1", Kind: "TestType"}
gvr := schema.GroupVersionResource{Group: "ext.cattle.io", Version: "v1", Resource: "testtypes"}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := NewMockStore[*TestType, *TestTypeList](ctrl)
mockObjInfo := NewMockUpdatedObjectInfo(ctrl)
tt.input.objInfo = mockObjInfo
tt.setup(mockObjInfo, mockStore)
testDelegate := &delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{
scheme: scheme,
t: &TestType{},
tList: &TestTypeList{},
gvk: gvk,
gvr: gvr,
store: mockStore,
},
}
obj, created, err := testDelegate.Update(tt.input.parentCtx, tt.input.name, tt.input.objInfo, tt.input.createValidation, tt.input.updateValidation, tt.input.forceAllowCreate, tt.input.options)
if tt.wantErr {
// check if we have an error
assert.Error(t, err)
// check if error is an apierror
_, ok := err.(apierrors.APIStatus)
assert.True(t, ok)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expect.obj, obj)
assert.Equal(t, tt.expect.created, created)
}
})
}
}
func TestDelegateError_Create(t *testing.T) {
type input struct {
ctx context.Context
obj runtime.Object
createValidation rest.ValidateObjectFunc
options *metav1.CreateOptions
}
type output struct {
createResult runtime.Object
err error
}
type testCase struct {
name string
storeSetup func(*MockStore[*TestType, *TestTypeList])
wantErr bool
input input
expect output
}
tests := []testCase{
{
name: "working case",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
obj: &TestType{},
options: &metav1.CreateOptions{},
},
expect: output{
createResult: &TestType{},
err: nil,
},
wantErr: false,
},
{
name: "missing user in the context",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
input: input{
ctx: context.Background(),
obj: &TestType{},
options: &metav1.CreateOptions{},
},
expect: output{
createResult: nil,
err: errMissingUserInfo,
},
wantErr: true,
},
{
name: "store create error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
obj: &TestType{},
options: &metav1.CreateOptions{},
},
expect: output{
createResult: nil,
err: assert.AnError,
},
wantErr: true,
},
{
name: "wrong type error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
obj: &runtime.Unknown{},
options: &metav1.CreateOptions{},
},
expect: output{
createResult: nil,
err: assert.AnError,
},
wantErr: true,
},
{
name: "create validation error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
obj: &TestType{},
createValidation: func(ctx context.Context, obj runtime.Object) error {
return assert.AnError
},
options: &metav1.CreateOptions{},
},
expect: output{
createResult: &TestType{},
err: assert.AnError,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scheme := runtime.NewScheme()
addToSchemeTest(scheme)
gvk := schema.GroupVersionKind{Group: "ext.cattle.io", Version: "v1", Kind: "TestType"}
gvr := schema.GroupVersionResource{Group: "ext.cattle.io", Version: "v1", Resource: "testtypes"}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := NewMockStore[*TestType, *TestTypeList](ctrl)
tt.storeSetup(mockStore)
testDelegate := &delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{
scheme: scheme,
t: &TestType{},
tList: &TestTypeList{},
gvk: gvk,
gvr: gvr,
store: mockStore,
},
}
result, err := testDelegate.Create(tt.input.ctx, tt.input.obj, tt.input.createValidation, tt.input.options)
if tt.wantErr {
// check if we have an error
assert.Error(t, err)
// check if error is an apierror
_, ok := err.(apierrors.APIStatus)
assert.True(t, ok)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expect.createResult, result)
}
})
}
}
func TestDelegateError_Delete(t *testing.T) {
type input struct {
ctx context.Context
name string
deleteValidation rest.ValidateObjectFunc
options *metav1.DeleteOptions
}
type output struct {
deleteResult runtime.Object
completed bool
err error
}
type testCase struct {
name string
storeSetup func(*MockStore[*TestType, *TestTypeList])
wantErr bool
input input
expect output
}
tests := []testCase{
{
name: "working case",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
ms.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-object",
options: &metav1.DeleteOptions{},
},
expect: output{
deleteResult: &TestType{},
completed: true,
err: nil,
},
wantErr: false,
},
{
name: "missing user in the context",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
input: input{
ctx: context.Background(),
name: "test-object",
options: &metav1.DeleteOptions{},
},
expect: output{
deleteResult: nil,
completed: false,
err: errMissingUserInfo,
},
wantErr: true,
},
{
name: "store get error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-object",
options: &metav1.DeleteOptions{},
},
expect: output{
deleteResult: nil,
completed: false,
err: assert.AnError,
},
wantErr: true,
},
{
name: "store delete error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
ms.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).Return(assert.AnError)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-object",
options: &metav1.DeleteOptions{},
},
expect: output{
deleteResult: &TestType{},
completed: true,
err: assert.AnError,
},
wantErr: true,
},
{
name: "delete validation error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-object",
deleteValidation: func(ctx context.Context, obj runtime.Object) error { return assert.AnError },
options: &metav1.DeleteOptions{},
},
expect: output{
deleteResult: nil,
completed: false,
err: assert.AnError,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scheme := runtime.NewScheme()
addToSchemeTest(scheme)
gvk := schema.GroupVersionKind{Group: "ext.cattle.io", Version: "v1", Kind: "TestType"}
gvr := schema.GroupVersionResource{Group: "ext.cattle.io", Version: "v1", Resource: "testtypes"}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := NewMockStore[*TestType, *TestTypeList](ctrl)
tt.storeSetup(mockStore)
testDelegate := &delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{
scheme: scheme,
t: &TestType{},
tList: &TestTypeList{},
gvk: gvk,
gvr: gvr,
store: mockStore,
},
}
result, completed, err := testDelegate.Delete(tt.input.ctx, tt.input.name, tt.input.deleteValidation, tt.input.options)
if tt.wantErr {
// check if we have an error
assert.Error(t, err)
// check if error is an apierror
_, ok := err.(apierrors.APIStatus)
assert.True(t, ok)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expect.deleteResult, result)
assert.Equal(t, tt.expect.completed, completed)
}
})
}
}
func TestDelegateError_Get(t *testing.T) {
type input struct {
ctx context.Context
name string
options *metav1.GetOptions
}
type output struct {
getResult runtime.Object
err error
}
type testCase struct {
name string
storeSetup func(*MockStore[*TestType, *TestTypeList])
wantErr bool
input input
expect output
}
tests := []testCase{
{
name: "working case",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&TestType{}, nil)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-object",
options: &metav1.GetOptions{},
},
expect: output{
getResult: &TestType{},
err: nil,
},
wantErr: false,
},
{
name: "missing user in the context",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {},
input: input{
ctx: context.Background(),
name: "testing-obj",
options: &metav1.GetOptions{},
},
expect: output{
getResult: nil,
err: errMissingUserInfo,
},
wantErr: true,
},
{
name: "store get error",
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
name: "test-object",
options: &metav1.GetOptions{},
},
expect: output{
getResult: &TestType{},
err: assert.AnError,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scheme := runtime.NewScheme()
addToSchemeTest(scheme)
gvk := schema.GroupVersionKind{Group: "ext.cattle.io", Version: "v1", Kind: "TestType"}
gvr := schema.GroupVersionResource{Group: "ext.cattle.io", Version: "v1", Resource: "testtypes"}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := NewMockStore[*TestType, *TestTypeList](ctrl)
tt.storeSetup(mockStore)
testDelegate := &delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{
scheme: scheme,
t: &TestType{},
tList: &TestTypeList{},
gvk: gvk,
gvr: gvr,
store: mockStore,
},
}
result, err := testDelegate.Get(tt.input.ctx, tt.input.name, tt.input.options)
if tt.wantErr {
// check if we have an error
assert.Error(t, err)
// check if error is an apierror
_, ok := err.(apierrors.APIStatus)
assert.True(t, ok)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expect.getResult, result)
}
})
}
}
func TestDelegateError_List(t *testing.T) {
type input struct {
ctx context.Context
listOptions *metainternalversion.ListOptions
}
type output struct {
listResult runtime.Object
err error
}
type testCase struct {
name string
storeSetup func(*MockStore[*TestType, *TestTypeList])
simulateConvertError bool
wantErr bool
input input
expect output
}
tests := []testCase{
{
name: "working case, for completion reasons",
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
listOptions: &metainternalversion.ListOptions{},
},
storeSetup: func(ms *MockStore[*TestType, *TestTypeList]) {
ms.EXPECT().List(gomock.Any(), gomock.Any()).Return(&TestTypeList{}, nil)
},
wantErr: false,
expect: output{
listResult: &TestTypeList{},
err: nil,
},
},
{
name: "missing user in the context",
input: input{
ctx: context.Background(),
listOptions: &metainternalversion.ListOptions{},
},
storeSetup: func(mockStore *MockStore[*TestType, *TestTypeList]) {},
wantErr: true,
expect: output{
listResult: nil,
err: errMissingUserInfo,
},
},
{
name: "convertListOptions error",
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
listOptions: &metainternalversion.ListOptions{},
},
storeSetup: func(mockStore *MockStore[*TestType, *TestTypeList]) {},
simulateConvertError: true,
wantErr: true,
expect: output{
listResult: nil,
err: assert.AnError,
},
},
{
name: "error returned by store",
input: input{
ctx: request.WithUser(context.Background(), &user.DefaultInfo{
Name: "test-user",
}),
listOptions: &metainternalversion.ListOptions{},
},
storeSetup: func(mockStore *MockStore[*TestType, *TestTypeList]) {
mockStore.EXPECT().List(gomock.Any(), gomock.Any()).Return(nil, assert.AnError)
},
wantErr: true,
expect: output{
listResult: nil,
err: assert.AnError,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scheme := runtime.NewScheme()
addToSchemeTest(scheme)
if !tt.simulateConvertError {
scheme.AddConversionFunc(&metainternalversion.ListOptions{}, &metav1.ListOptions{}, convert_internalversion_ListOptions_to_v1_ListOptions)
}
gvk := schema.GroupVersionKind{Group: "ext.cattle.io", Version: "v1", Kind: "TestType"}
gvr := schema.GroupVersionResource{Group: "ext.cattle.io", Version: "v1", Resource: "testtypes"}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := NewMockStore[*TestType, *TestTypeList](ctrl)
tt.storeSetup(mockStore)
testDelegate := &delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{
scheme: scheme,
t: &TestType{},
tList: &TestTypeList{},
gvk: gvk,
gvr: gvr,
store: mockStore,
},
}
result, err := testDelegate.List(tt.input.ctx, tt.input.listOptions)
if tt.wantErr {
// check if we have an error
assert.Error(t, err)
// check if error is an apierror
_, ok := err.(apierrors.APIStatus)
assert.True(t, ok)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expect.listResult, result)
}
})
}
}
func convert_internalversion_ListOptions_to_v1_ListOptions(in, out interface{}, s conversion.Scope) error {
i, ok := in.(*metainternalversion.ListOptions)
if !ok {
return errors.New("cannot convert in param into internalversion.ListOptions")
}
o, ok := out.(*metav1.ListOptions)
if !ok {
return errors.New("cannot convert out param into metav1.ListOptions")
}
if i.LabelSelector != nil {
o.LabelSelector = i.LabelSelector.String()
}
if i.FieldSelector != nil {
o.FieldSelector = i.FieldSelector.String()
}
o.Watch = i.Watch
o.ResourceVersion = i.ResourceVersion
o.TimeoutSeconds = i.TimeoutSeconds
return nil
}