grafana/pkg/apiserver/rest/dualwriter_mode1_test.go
Leonor Oliveira 0a2c5065a0
Choose mode. Turn DualWriter into a real interface. Better Logging (#87291)
* Choose mode. Add log field on top level dualwriter

* Add logs

* Turn DualWriter into a full interface. Fix tests

* Lint

* Use struct for dualWriter interface

* Use struct

* Default should be legacyStore for all entities

* Fix test. Get rid of extra concrete type

* Remove comment

* Add comment

* Temp set dualwriter mode 2 for playlists while configs are not in place

* Add modes type + add comment on what each mode does

* Don't require watcher interface for now

* Use storage implementation on mode 2

* Update pkg/apiserver/rest/dualwriter_mode2.go

Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>

* Pass log values to the context

* test

* Update pkg/apiserver/rest/dualwriter_mode3.go

Co-authored-by: Dan Cech <dcech@grafana.com>

---------

Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
Co-authored-by: Dan Cech <dcech@grafana.com>
2024-05-06 23:18:28 +02:00

375 lines
11 KiB
Go

package rest
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/apis/example"
)
var exampleObj = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "1"}, Spec: example.PodSpec{}, Status: example.PodStatus{}}
var exampleObjDifferentRV = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "3"}, Spec: example.PodSpec{}, Status: example.PodStatus{}}
var anotherObj = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "2"}, Spec: example.PodSpec{}, Status: example.PodStatus{}}
var failingObj = &example.Pod{TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{Name: "object-fail", ResourceVersion: "2"}, Spec: example.PodSpec{}, Status: example.PodStatus{}}
var exampleList = &example.PodList{TypeMeta: metav1.TypeMeta{}, ListMeta: metav1.ListMeta{}, Items: []example.Pod{*exampleObj}}
var anotherList = &example.PodList{Items: []example.Pod{*anotherObj}}
func TestMode1_Create(t *testing.T) {
type testCase struct {
name string
input runtime.Object
setupLegacyFn func(m *mock.Mock, input runtime.Object)
setupStorageFn func(m *mock.Mock, input runtime.Object)
wantErr bool
}
tests :=
[]testCase{
{
name: "creating an object only in the legacy store",
input: exampleObj,
setupLegacyFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, input, mock.Anything, mock.Anything).Return(exampleObj, nil)
},
setupStorageFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, anotherObj, mock.Anything, mock.Anything).Return(anotherObj, nil)
},
},
{
name: "error when creating object in the legacy store fails",
input: failingObj,
setupLegacyFn: func(m *mock.Mock, input runtime.Object) {
m.On("Create", mock.Anything, failingObj, mock.Anything, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
}
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
}
dw := selectDualWriter(Mode1, ls, us)
obj, err := dw.Create(context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{})
if tt.wantErr {
assert.Error(t, err)
continue
}
us.AssertNotCalled(t, "Create", context.Background(), tt.input, func(context.Context, runtime.Object) error { return nil }, &metav1.CreateOptions{})
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
}
}
func TestMode1_Get(t *testing.T) {
type testCase struct {
name string
input string
setupLegacyFn func(m *mock.Mock, name string)
setupStorageFn func(m *mock.Mock, name string)
wantErr bool
}
tests :=
[]testCase{
{
name: "get an object only in the legacy store",
input: "foo",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(exampleObj, nil)
},
setupStorageFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(anotherObj, nil)
},
},
{
name: "error when getting an object in the legacy store fails",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Get", mock.Anything, name, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
}
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
}
dw := selectDualWriter(Mode1, ls, us)
obj, err := dw.Get(context.Background(), tt.input, &metav1.GetOptions{})
if tt.wantErr {
assert.Error(t, err)
continue
}
us.AssertNotCalled(t, "Get", context.Background(), tt.name, &metav1.GetOptions{})
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
}
}
func TestMode1_List(t *testing.T) {
type testCase struct {
name string
setupLegacyFn func(m *mock.Mock)
setupStorageFn func(m *mock.Mock)
wantErr bool
}
tests :=
[]testCase{
{
name: "error when listing an object in the legacy store is not implemented",
setupLegacyFn: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(&example.PodList{}, errors.New("error"))
},
},
// TODO: legacy list is missing
}
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m)
}
dw := selectDualWriter(Mode1, ls, us)
_, err := dw.List(context.Background(), &metainternalversion.ListOptions{})
if tt.wantErr {
assert.Error(t, err)
continue
}
}
}
func TestMode1_Delete(t *testing.T) {
type testCase struct {
name string
input string
setupLegacyFn func(m *mock.Mock, name string)
setupStorageFn func(m *mock.Mock, name string)
wantErr bool
}
tests :=
[]testCase{
{
name: "deleting an object in the legacy store",
input: "foo",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
},
{
name: "error when deleting an object in the legacy store",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, name string) {
m.On("Delete", mock.Anything, name, mock.Anything, mock.Anything).Return(nil, false, errors.New("error"))
},
wantErr: true,
},
}
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
}
dw := selectDualWriter(Mode1, ls, us)
obj, _, err := dw.Delete(context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
if tt.wantErr {
assert.Error(t, err)
continue
}
us.AssertNotCalled(t, "Delete", context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
}
}
func TestMode1_DeleteCollection(t *testing.T) {
type testCase struct {
name string
input *metav1.DeleteOptions
setupLegacyFn func(m *mock.Mock, input *metav1.DeleteOptions)
setupStorageFn func(m *mock.Mock, input *metav1.DeleteOptions)
wantErr bool
}
tests :=
[]testCase{
{
name: "deleting a collection in the legacy store",
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "foo"}},
setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) {
m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(exampleObj, nil)
},
},
{
name: "error deleting a collection in the legacy store",
input: &metav1.DeleteOptions{TypeMeta: metav1.TypeMeta{Kind: "fail"}},
setupLegacyFn: func(m *mock.Mock, input *metav1.DeleteOptions) {
m.On("DeleteCollection", mock.Anything, mock.Anything, input, mock.Anything).Return(nil, errors.New("error"))
},
wantErr: true,
},
}
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
}
dw := selectDualWriter(Mode1, ls, us)
obj, err := dw.DeleteCollection(context.Background(), func(ctx context.Context, obj runtime.Object) error { return nil }, tt.input, &metainternalversion.ListOptions{})
if tt.wantErr {
assert.Error(t, err)
continue
}
us.AssertNotCalled(t, "DeleteCollection", context.Background(), tt.input, func(ctx context.Context, obj runtime.Object) error { return nil }, &metav1.DeleteOptions{})
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
}
}
func TestMode1_Update(t *testing.T) {
type testCase struct {
name string
input string
setupLegacyFn func(m *mock.Mock, input string)
setupStorageFn func(m *mock.Mock, input string)
setupGetFn func(m *mock.Mock, input string)
wantErr bool
}
tests :=
[]testCase{
{
name: "update an object in legacy",
input: "foo",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(exampleObj, false, nil)
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(anotherObj, false, nil)
},
setupGetFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(exampleObj, nil)
},
},
{
name: "error updating an object in legacy",
input: "object-fail",
setupLegacyFn: func(m *mock.Mock, input string) {
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, false, errors.New("error"))
},
setupGetFn: func(m *mock.Mock, input string) {
m.On("Get", mock.Anything, input, mock.Anything).Return(exampleObj, nil)
},
setupStorageFn: func(m *mock.Mock, input string) {
m.On("Update", mock.Anything, input, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(anotherObj, false, nil)
},
wantErr: true,
},
}
for _, tt := range tests {
l := (LegacyStorage)(nil)
s := (Storage)(nil)
m := &mock.Mock{}
ls := legacyStoreMock{m, l}
us := storageMock{m, s}
if tt.setupLegacyFn != nil {
tt.setupLegacyFn(m, tt.input)
}
if tt.setupStorageFn != nil {
tt.setupStorageFn(m, tt.input)
}
if tt.setupGetFn != nil {
tt.setupGetFn(m, tt.input)
}
dw := selectDualWriter(Mode1, ls, us)
obj, _, err := dw.Update(context.Background(), tt.input, UpdatedObjInfoObj{}, func(ctx context.Context, obj runtime.Object) error { return nil }, func(ctx context.Context, obj, old runtime.Object) error { return nil }, false, &metav1.UpdateOptions{})
if tt.wantErr {
assert.Error(t, err)
continue
}
assert.Equal(t, obj, exampleObj)
assert.NotEqual(t, obj, anotherObj)
}
}