diff --git a/pkg/controllers/workspace_controller_test.go b/pkg/controllers/workspace_controller_test.go index d010656cd..d20d16e7b 100644 --- a/pkg/controllers/workspace_controller_test.go +++ b/pkg/controllers/workspace_controller_test.go @@ -17,6 +17,7 @@ import ( "gotest.tools/assert" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "knative.dev/pkg/apis" ) @@ -220,66 +221,54 @@ func TestSelectWorkspaceNodes(t *testing.T) { } func TestCreateAndValidateNode(t *testing.T) { - mockClient := utils.NewClient() - - mockMachine := utils.MockMachine - testcases := map[string]struct { - callMocks func(c *utils.Client) - expectedError error + callMocks func(c *utils.MockClient) + machineConditions apis.Conditions + expectedError error }{ "Node is not created because machine creation fails": { - callMocks: func(c *utils.Client) { - //create machine call should not return any errors + callMocks: func(c *utils.MockClient) { c.On("Create", mock.IsType(context.Background()), mock.IsType(&v1alpha5.Machine{}), mock.Anything).Return(nil) - - machineConditions := apis.Conditions{ - { - Type: v1alpha5.MachineLaunched, - Status: corev1.ConditionFalse, - Message: machine.ErrorInstanceTypesUnavailable, - }, - } - - mockMachine.Status.Conditions = machineConditions - - //insert mock machine in map so mock Get call retrieves this object - c.InsertObjectInMap(&mockMachine) - c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&v1alpha5.Machine{}), mock.Anything).Return(nil) c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&v1alpha1.Workspace{}), mock.Anything).Return(nil) c.StatusMock.On("Update", mock.IsType(context.Background()), mock.IsType(&v1alpha1.Workspace{}), mock.Anything).Return(nil) }, + machineConditions: apis.Conditions{ + { + Type: v1alpha5.MachineLaunched, + Status: corev1.ConditionFalse, + Message: machine.ErrorInstanceTypesUnavailable, + }, + }, expectedError: errors.New(machine.ErrorInstanceTypesUnavailable), }, "A machine is successfully created": { - callMocks: func(c *utils.Client) { - //create machine call should not return any errors + callMocks: func(c *utils.MockClient) { c.On("Create", mock.IsType(context.Background()), mock.IsType(&v1alpha5.Machine{}), mock.Anything).Return(nil) - - machineConditions := apis.Conditions{ - { - Type: apis.ConditionReady, - Status: corev1.ConditionTrue, - }, - } - - mockMachine.Status.Conditions = machineConditions - - //insert mock machine in map so mock Get call retrieves this object - c.InsertObjectInMap(&mockMachine) - - //get machine call should not return any errors c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&v1alpha5.Machine{}), mock.Anything).Return(nil) - //get machine call should not return any errors c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil) }, + machineConditions: apis.Conditions{ + { + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }, + }, expectedError: nil, }, } for k, tc := range testcases { t.Run(k, func(t *testing.T) { + mockClient := utils.NewClient() + mockMachine := &v1alpha5.Machine{} + + mockClient.UpdateCb = func(key types.NamespacedName) { + mockClient.GetObjectFromMap(mockMachine, key) + mockMachine.Status.Conditions = tc.machineConditions + mockClient.CreateOrUpdateObjectInMap(mockMachine) + } + tc.callMocks(mockClient) reconciler := &WorkspaceReconciler{ diff --git a/pkg/utils/mockClient.go b/pkg/utils/mockClient.go index ab8833266..05d91a803 100644 --- a/pkg/utils/mockClient.go +++ b/pkg/utils/mockClient.go @@ -16,134 +16,140 @@ import ( ) // Client is a mock for the controller-runtime dynamic client interface. -type Client struct { +type MockClient struct { mock.Mock ObjectMap map[reflect.Type]map[k8sClient.ObjectKey]k8sClient.Object - StatusMock *StatusClient + StatusMock *MockStatusClient + UpdateCb func(key types.NamespacedName) } -var _ k8sClient.Client = &Client{} +var _ k8sClient.Client = &MockClient{} -func NewClient() *Client { - return &Client{ - StatusMock: &StatusClient{}, +func NewClient() *MockClient { + return &MockClient{ + StatusMock: &MockStatusClient{}, ObjectMap: map[reflect.Type]map[k8sClient.ObjectKey]k8sClient.Object{}, } } // Retrieves or creates a map associated with the type of obj -func (c *Client) ensureMapFor(obj k8sClient.Object) map[k8sClient.ObjectKey]k8sClient.Object { +func (m *MockClient) ensureMapFor(obj k8sClient.Object) map[k8sClient.ObjectKey]k8sClient.Object { t := reflect.TypeOf(obj) - if _, ok := c.ObjectMap[t]; !ok { + if _, ok := m.ObjectMap[t]; !ok { //create a new map with the object key if it doesn't exist - c.ObjectMap[t] = map[k8sClient.ObjectKey]k8sClient.Object{} + m.ObjectMap[t] = map[k8sClient.ObjectKey]k8sClient.Object{} } - return c.ObjectMap[t] + return m.ObjectMap[t] } -func (c *Client) InsertObjectInMap(obj k8sClient.Object) { - relevantMap := c.ensureMapFor(obj) +func (m *MockClient) CreateOrUpdateObjectInMap(obj k8sClient.Object) { + relevantMap := m.ensureMapFor(obj) objKey := k8sClient.ObjectKeyFromObject(obj) relevantMap[objKey] = obj } -// StatusClient interface - -func (c *Client) Status() k8sClient.StatusWriter { - return c.StatusMock -} +func (m *MockClient) GetObjectFromMap(obj k8sClient.Object, key types.NamespacedName) { + relevantMap := m.ensureMapFor(obj) -// Reader interface - -func (c *Client) Get(ctx context.Context, key types.NamespacedName, obj k8sClient.Object, opts ...k8sClient.GetOption) error { - relevantMap := c.ensureMapFor(obj) - - for _, val := range relevantMap { + if val, ok := relevantMap[key]; ok { v := reflect.ValueOf(obj).Elem() v.Set(reflect.ValueOf(val).Elem()) - break } +} - args := c.Called(ctx, key, obj, opts) +// k8s Client interface +func (m *MockClient) Get(ctx context.Context, key types.NamespacedName, obj k8sClient.Object, opts ...k8sClient.GetOption) error { + //make any necessary changes to the object + m.UpdateCb(key) + + m.GetObjectFromMap(obj, key) + + args := m.Called(ctx, key, obj, opts) return args.Error(0) } -func (c *Client) List(ctx context.Context, list k8sClient.ObjectList, opts ...k8sClient.ListOption) error { - args := c.Called(ctx, list, opts) +func (m *MockClient) List(ctx context.Context, list k8sClient.ObjectList, opts ...k8sClient.ListOption) error { + args := m.Called(ctx, list, opts) return args.Error(0) } -// Writer interface +func (m *MockClient) Create(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.CreateOption) error { + m.CreateOrUpdateObjectInMap(obj) -func (c *Client) Create(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.CreateOption) error { - args := c.Called(ctx, obj, opts) + args := m.Called(ctx, obj, opts) return args.Error(0) } -func (c *Client) Delete(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.DeleteOption) error { - args := c.Called(ctx, obj, opts) +func (m *MockClient) Delete(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.DeleteOption) error { + args := m.Called(ctx, obj, opts) return args.Error(0) } -func (c *Client) Update(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.UpdateOption) error { - args := c.Called(ctx, obj, opts) +func (m *MockClient) Update(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.UpdateOption) error { + args := m.Called(ctx, obj, opts) return args.Error(0) } -func (c *Client) Patch(ctx context.Context, obj k8sClient.Object, patch k8sClient.Patch, opts ...k8sClient.PatchOption) error { - args := c.Called(ctx, obj, patch, opts) +func (m *MockClient) Patch(ctx context.Context, obj k8sClient.Object, patch k8sClient.Patch, opts ...k8sClient.PatchOption) error { + args := m.Called(ctx, obj, patch, opts) return args.Error(0) } -func (c *Client) DeleteAllOf(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.DeleteAllOfOption) error { - args := c.Called(ctx, obj, opts) +func (m *MockClient) DeleteAllOf(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.DeleteAllOfOption) error { + args := m.Called(ctx, obj, opts) return args.Error(0) } // SubResource implements client.Client -func (*Client) SubResource(subResource string) k8sClient.SubResourceClient { +func (m *MockClient) SubResource(subResource string) k8sClient.SubResourceClient { panic("unimplemented") } // GroupVersionKindFor implements client.Client -func (*Client) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { +func (m *MockClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { panic("unimplemented") } // IsObjectNamespaced implements client.Client -func (*Client) IsObjectNamespaced(obj runtime.Object) (bool, error) { +func (m *MockClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { panic("unimplemented") } -func (c *Client) Scheme() *runtime.Scheme { - args := c.Called() +func (m *MockClient) Scheme() *runtime.Scheme { + args := m.Called() return args.Get(0).(*runtime.Scheme) } -func (c *Client) RESTMapper() meta.RESTMapper { - args := c.Called() +func (m *MockClient) RESTMapper() meta.RESTMapper { + args := m.Called() return args.Get(0).(meta.RESTMapper) } -type StatusClient struct { +// StatusClient interface + +func (m *MockClient) Status() k8sClient.StatusWriter { + return m.StatusMock +} + +type MockStatusClient struct { mock.Mock } -func (c *StatusClient) Create(ctx context.Context, obj k8sClient.Object, subResource k8sClient.Object, opts ...k8sClient.SubResourceCreateOption) error { - args := c.Called(ctx, obj, opts) +func (m *MockStatusClient) Create(ctx context.Context, obj k8sClient.Object, subResource k8sClient.Object, opts ...k8sClient.SubResourceCreateOption) error { + args := m.Called(ctx, obj, opts) return args.Error(0) } -func (c *StatusClient) Patch(ctx context.Context, obj k8sClient.Object, patch k8sClient.Patch, opts ...k8sClient.SubResourcePatchOption) error { - args := c.Called(ctx, obj, opts) +func (m *MockStatusClient) Patch(ctx context.Context, obj k8sClient.Object, patch k8sClient.Patch, opts ...k8sClient.SubResourcePatchOption) error { + args := m.Called(ctx, obj, opts) return args.Error(0) } -func (c *StatusClient) Update(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.SubResourceUpdateOption) error { - args := c.Called(ctx, obj, opts) +func (m *MockStatusClient) Update(ctx context.Context, obj k8sClient.Object, opts ...k8sClient.SubResourceUpdateOption) error { + args := m.Called(ctx, obj, opts) return args.Error(0) } -var _ k8sClient.StatusWriter = &StatusClient{} +var _ k8sClient.StatusWriter = &MockStatusClient{}