package k8s

import (
	"context"
	"testing"

	v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	. "github.com/onsi/gomega"
	"github.com/upbound/provider-aws/apis/ec2/v1beta1"
	eksv1beta1 "github.com/upbound/provider-aws/apis/eks/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/ticdc-service-ng/pkg/consts"
	"github.com/tidbcloud/ticdc-service-ng/pkg/scheme"
	"github.com/tidbcloud/ticdc-service-ng/pkg/util"
)

func withLastAppliedAnnotation(obj client.Object) {
	bs, err := GetModifiedConfiguration(obj, v1beta1.SchemeBuilder.GroupVersion, false)
	if err != nil {
		panic(err)
	}
	util.SetAnnotation(obj, consts.LastAppliedConfigAnnotation, string(bs))
}

// used for test map modification
func withLabel(key, val string) func(obj client.Object) {
	return func(obj client.Object) {
		labels := obj.GetLabels()
		if labels == nil {
			labels = map[string]string{}
		}
		labels[key] = val

		obj.SetLabels(labels)
	}
}

// used for test array modification
func withManagementPolicy(policy v1.ManagementAction) func(obj client.Object) {
	return func(obj client.Object) {
		vpc := obj.(*v1beta1.VPC)
		vpc.Spec.ManagementPolicies = append(vpc.Spec.ManagementPolicies, policy)
	}
}

func newTestVPC(fns ...func(obj client.Object)) *v1beta1.VPC {
	vpc := &v1beta1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-vpc",
			Annotations: map[string]string{
				"crossplane": "vpc-id-xxxxx",
			},
		},
		Spec: v1beta1.VPCSpec{
			ForProvider: v1beta1.VPCParameters_2{
				Tags: map[string]*string{
					"Name":            ptr.To("test-vpc"),
					"crossplane-kind": ptr.To("vpc.ec2.aws.crossplane.io"),
					"crossplane-name": ptr.To("test"),
				},
			},
			ResourceSpec: v1.ResourceSpec{
				ManagementPolicies: []v1.ManagementAction{
					v1.ManagementActionCreate,
					v1.ManagementActionUpdate,
					v1.ManagementActionDelete,
				},
			},
		},
	}

	for _, fn := range fns {
		fn(vpc)
	}
	return vpc
}

func TestApplyX(t *testing.T) {
	sche := scheme.Scheme
	utilruntime.Must(v1beta1.SchemeBuilder.AddToScheme(sche))
	cases := []struct {
		desc    string
		current []client.Object
		changes []struct {
			applyObj client.Object
			updateFn func(obj client.Object)
		}
		expected client.Object
	}{
		{
			desc: "create obj by apply",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(),
				},
			},
			expected: newTestVPC(withLastAppliedAnnotation),
		},
		{
			desc: "apply obj multiple times: same obj",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(),
				},
				{
					applyObj: newTestVPC(),
				},
			},
			expected: newTestVPC(withLastAppliedAnnotation),
		},
		{
			desc: "apply obj multiple times: change map",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withLabel("aaa", "bbb")),
				},
				{
					applyObj: newTestVPC(withLabel("ccc", "ddd")),
				},
			},
			expected: newTestVPC(withLabel("ccc", "ddd"), withLastAppliedAnnotation),
		},
		{
			desc: "apply obj multiple times: remove item from map",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withLabel("aaa", "bbb"), withLabel("ccc", "ddd")),
				},
				{
					applyObj: newTestVPC(withLabel("ccc", "ddd")),
				},
				{
					applyObj: newTestVPC(),
				},
			},
			expected: newTestVPC(withLastAppliedAnnotation),
		},
		{
			desc: "apply obj multiple times: change list",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize)),
				},
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionObserve)),
				},
			},
			expected: newTestVPC(withManagementPolicy(v1.ManagementActionObserve), withLastAppliedAnnotation),
		},
		{
			desc: "apply obj multiple times: remove item from list",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withManagementPolicy(v1.ManagementActionObserve)),
				},
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize)),
				},
			},
			expected: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withLastAppliedAnnotation),
		},
		{
			desc: "apply obj multiple times: change item of list",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withManagementPolicy(v1.ManagementActionObserve)),
				},
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withManagementPolicy(v1.ManagementActionAll)),
				},
			},
			expected: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withManagementPolicy(v1.ManagementActionAll), withLastAppliedAnnotation),
		},
		{
			desc: "apply obj which is changed by third party: change map",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(),
				},
				{
					updateFn: withLabel("third-party.com/change", "test"),
				},
				{
					applyObj: newTestVPC(),
				},
			},
			expected: newTestVPC(withLastAppliedAnnotation, withLabel("third-party.com/change", "test")),
		},
		{
			desc: "apply obj which is changed by third party: append item into list",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionObserve)),
				},
				{
					updateFn: withManagementPolicy(v1.ManagementActionLateInitialize),
				},
				{ // apply with the same value as the last time, third-party change will not be overwritten
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionObserve)),
				},
			},
			expected: newTestVPC(withManagementPolicy(v1.ManagementActionObserve), withLastAppliedAnnotation, withManagementPolicy(v1.ManagementActionLateInitialize)),
		},
		{
			desc: "apply obj which is changed by third party: change item of list",
			changes: []struct {
				applyObj client.Object
				updateFn func(obj client.Object)
			}{
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize)),
				},
				{
					updateFn: withManagementPolicy(v1.ManagementActionAll),
				},
				{
					applyObj: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withManagementPolicy(v1.ManagementActionObserve)),
				},
			},
			expected: newTestVPC(withManagementPolicy(v1.ManagementActionLateInitialize), withManagementPolicy(v1.ManagementActionObserve), withLastAppliedAnnotation),
		},
	}
	for _, c := range cases {
		t.Run(c.desc, func(t *testing.T) {
			g := NewWithT(t)
			cli := fake.NewClientBuilder().WithScheme(sche).WithObjects(c.current...).Build()
			var obj client.Object
			for _, change := range c.changes {
				if change.applyObj != nil {
					obj = change.applyObj.DeepCopyObject().(client.Object)
					err := Apply(context.Background(), cli, obj)
					if err != nil {
						panic(err)
					}
				} else {
					change.updateFn(obj)
					err := cli.Update(context.Background(), obj)
					if err != nil {
						panic(err)
					}
				}
			}
			// reset resource version and gvk to avoid diff
			obj.SetResourceVersion("")
			obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
			g.Expect(obj).To(Equal(c.expected))
		})
	}
}

func TestApply(t *testing.T) {
	sche := scheme.Scheme
	utilruntime.Must(eksv1beta1.SchemeBuilder.AddToScheme(sche))
	utilruntime.Must(v1beta1.SchemeBuilder.AddToScheme(sche))
	ctx := context.TODO()
	nodeGroup := eksv1beta1.NodeGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-ng",
			Labels: map[string]string{
				"foo": "bar",
			},
		},
		Spec: eksv1beta1.NodeGroupSpec{
			ForProvider: eksv1beta1.NodeGroupParameters{
				Region: ptr.To("us-west-2"),
				Tags: map[string]*string{
					"hello": ptr.To("world"),
				},
			},
		},
	}
	vpc := v1beta1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-vpc",
			Annotations: map[string]string{
				"crossplane": "vpc-id-xxxxx",
			},
		},
		Spec: v1beta1.VPCSpec{
			ForProvider: v1beta1.VPCParameters_2{
				Tags: map[string]*string{
					"Name":            ptr.To("test-vpc"),
					"crossplane-kind": ptr.To("vpc.ec2.aws.crossplane.io"),
					"crossplane-name": ptr.To("test"),
				},
			},
			ResourceSpec: v1.ResourceSpec{
				ManagementPolicies: []v1.ManagementAction{
					v1.ManagementActionCreate,
					v1.ManagementActionUpdate,
					v1.ManagementActionDelete,
				},
			},
		},
		Status: v1beta1.VPCStatus{},
	}

	cases := map[string]struct {
		existObjs []client.Object
		// createdByApply is true if the exists objects are created by Apply function.
		createdByApply bool
		// checkExpectedSpecAndLabels is true if the expectedObj should be checked.
		checkExpectedObj bool
		// modifiedObj is the object to be applied.
		modifiedObj client.Object
		// expectedObj is the expected object after the expect function.
		expectedObj client.Object
		expect      func(g *WithT, cli client.Client, modifiedObj client.Object)
	}{
		"try to create an invalid CR with Apply func": {
			existObjs:      []client.Object{},
			createdByApply: false,
			modifiedObj:    nil,
			expectedObj:    nil,
			expect: func(g *WithT, cli client.Client, _ client.Object) {
				g.Expect(Apply(ctx, cli, &eksv1beta1.NodeGroup{})).ShouldNot(Succeed())
			},
		},
		"CR is NOT created by Apply func, apply with the same modification": {
			existObjs: []client.Object{
				nodeGroup.DeepCopy(),
			},
			createdByApply:   false,
			checkExpectedObj: true,
			modifiedObj:      nodeGroup.DeepCopy(),
			expectedObj:      nodeGroup.DeepCopy(),
			expect: func(g *WithT, cli client.Client, modifiedObj client.Object) {
				var ng eksv1beta1.NodeGroup
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modifiedObj), &ng)).To(Succeed())
				beforeRV := ng.GetResourceVersion()

				// copyModified is a copy of the modified object,
				// since the modified object will be modified by Apply function.
				copyModified := modifiedObj.DeepCopyObject().(client.Object)
				g.Expect(Apply(ctx, cli, copyModified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modifiedObj), &ng)).To(Succeed())
				// The resource version should change, since the exists object does not have the last applied annotation.
				g.Expect(ng.GetResourceVersion()).NotTo(Equal(beforeRV))

				beforeRV = ng.GetResourceVersion()
				g.Expect(Apply(ctx, cli, modifiedObj)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modifiedObj), &ng)).To(Succeed())
				// The resource version should not change, since the object has the last applied annotation.
				g.Expect(ng.GetResourceVersion()).To(Equal(beforeRV))
			},
		},
		"CR is NOT created by Apply func, update the different field": {
			existObjs: []client.Object{
				nodeGroup.DeepCopy(),
			},
			createdByApply:   false,
			checkExpectedObj: false,
			modifiedObj:      nil,
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, _ client.Object) {
				ng := nodeGroup.DeepCopy()
				ng.Spec.ForProvider.ClusterName = ptr.To("test-cluster")
				ng.Spec.ForProvider.Labels = map[string]*string{
					"foo": ptr.To("bar"),
				}
				expectedObj := ng.DeepCopy()
				g.Expect(Apply(ctx, cli, ng)).Should(Succeed())
				checkExpectedSpecAndLabels(g, ctx, cli, client.ObjectKeyFromObject(ng), expectedObj)
			},
		},
		"CR is created by Apply func, updated by different clients": {
			existObjs: []client.Object{
				nodeGroup.DeepCopy(),
			},
			createdByApply:   true,
			checkExpectedObj: false,
			modifiedObj:      nil,
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, _ client.Object) {
				// Check if the nodegroup is created.
				currentNG := eksv1beta1.NodeGroup{}
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(&nodeGroup), &currentNG)).To(Succeed())
				checkExpectedSpecAndLabels(g, ctx, cli, client.ObjectKeyFromObject(&nodeGroup), &nodeGroup)

				// Mimic a "third-party" (like Crossplane) set default values and add annotations to the same object.
				version := "1.18"
				currentNG.Spec.ForProvider.Version = &version
				currentNG.Spec.DeletionPolicy = v1.DeletionDelete
				util.SetAnnotation(&currentNG, "crossplane.io/external-create-pending", "test")
				expected := currentNG.DeepCopy()
				g.Expect(cli.Update(ctx, &currentNG)).Should(Succeed())
				g.Expect(currentNG.GetResourceVersion()).Should(Equal("2"))

				// Should not update the nodegroup when apply the same modification.
				modified := nodeGroup.DeepCopy()
				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				// The resource version should not change.
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(&nodeGroup), &currentNG)).To(Succeed())
				g.Expect(currentNG.GetResourceVersion()).Should(Equal("2"))
				// The nodegroup should both have values from "third-party" and the original object.
				checkExpectedSpecAndLabels(g, ctx, cli, client.ObjectKeyFromObject(&nodeGroup), expected)
			},
		},
		"CR is NOT created by Apply func, updated by different clients": {
			existObjs: []client.Object{
				nodeGroup.DeepCopy(),
			},
			createdByApply:   false,
			checkExpectedObj: false,
			modifiedObj:      nil,
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, _ client.Object) {
				currentNG := eksv1beta1.NodeGroup{}
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(&nodeGroup), &currentNG)).To(Succeed())

				// setDefaultValues mimics a "third-party" (like Crossplane) to set default values.
				setDefaultValues := func(current *eksv1beta1.NodeGroup) *eksv1beta1.NodeGroup {
					version := "1.18"
					current.Spec.ForProvider.Version = &version
					current.Spec.DeletionPolicy = v1.DeletionDelete
					util.SetAnnotation(current, "crossplane.io/external-create-pending", "test")
					return current.DeepCopy()
				}

				_ = setDefaultValues(&currentNG)
				g.Expect(cli.Update(ctx, &currentNG)).Should(Succeed())
				// The resource version is 999 because the nodegroup is not created by Apply func.
				// After the update, the resource version is 1000.
				g.Expect(currentNG.GetResourceVersion()).Should(Equal("1000"))

				// Applied modification will overwrite the "third-party" modification,
				// because the nodegroup does not have the last applied annotation yet.
				modified := nodeGroup.DeepCopy()
				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(&nodeGroup), &currentNG)).To(Succeed())
				// The resource version should change, because the nodegroup does not have the last applied annotation.
				g.Expect(currentNG.GetResourceVersion()).Should(Equal("1001"))

				// The third-party try to set default values again.
				expected := setDefaultValues(&currentNG)
				g.Expect(cli.Update(ctx, &currentNG)).Should(Succeed())
				g.Expect(currentNG.GetResourceVersion()).Should(Equal("1002"))

				// The controller uses Apply func to update the nodegroup with the same modification as the last time.
				modified = nodeGroup.DeepCopy()
				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(&nodeGroup), &currentNG)).To(Succeed())
				// The resource version should not change, because the nodegroup has the last applied annotation.
				g.Expect(currentNG.GetResourceVersion()).Should(Equal("1002"))

				// The nodegroup should both have values from "third-party" and the original object.
				checkExpectedSpecAndLabels(g, ctx, cli, client.ObjectKeyFromObject(&nodeGroup), expected)
			},
		},
		"CR is created by Apply func, update list with different order": {
			existObjs: []client.Object{
				vpc.DeepCopy(),
			},
			createdByApply:   true,
			checkExpectedObj: false,
			modifiedObj:      vpc.DeepCopy(),
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, modified client.Object) {
				// Change the order of managementpolicies.
				managementPolicies := v1.ManagementPolicies{
					v1.ManagementActionUpdate,
					v1.ManagementActionCreate,
					v1.ManagementActionDelete,
				}
				modified.(*v1beta1.VPC).Spec.ManagementPolicies = managementPolicies

				current := v1beta1.VPC{}
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				currentRV := current.GetResourceVersion()

				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				g.Expect(current.GetResourceVersion()).ShouldNot(Equal(currentRV))
				g.Expect(current.Spec.ResourceSpec.ManagementPolicies).Should(Equal(managementPolicies))
				g.Expect(current.Status).Should(Equal(vpc.Status))
				g.Expect(current.Annotations).Should(HaveKeyWithValue("crossplane", "vpc-id-xxxxx"))
				g.Expect(current.Annotations).Should(HaveLen(2))
			},
		},
		"CR is created by Apply func, update list with same order": {
			existObjs: []client.Object{
				vpc.DeepCopy(),
			},
			createdByApply:   true,
			checkExpectedObj: false,
			modifiedObj:      vpc.DeepCopy(),
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, modified client.Object) {
				current := v1beta1.VPC{}
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				currentRV := current.GetResourceVersion()

				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				g.Expect(current.GetResourceVersion()).Should(Equal(currentRV))
				g.Expect(current.Status).Should(Equal(vpc.Status))
				g.Expect(current.Annotations).Should(HaveKeyWithValue("crossplane", "vpc-id-xxxxx"))
				g.Expect(current.Annotations).Should(HaveLen(2))
			},
		},
		"CR is NOT created by Apply func, update list with same order": {
			existObjs: []client.Object{
				vpc.DeepCopy(),
			},
			createdByApply:   false,
			checkExpectedObj: false,
			modifiedObj:      vpc.DeepCopy(),
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, modified client.Object) {
				current := v1beta1.VPC{}
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				currentRV := current.GetResourceVersion()

				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				g.Expect(current.GetResourceVersion()).ShouldNot(Equal(currentRV))
				g.Expect(current.Status).Should(Equal(vpc.Status))
				g.Expect(current.Annotations).Should(HaveKeyWithValue("crossplane", "vpc-id-xxxxx"))
				g.Expect(current.Annotations).Should(HaveLen(2))
			},
		},
		"CR is NOT created by Apply func, update list with different order": {
			existObjs: []client.Object{
				vpc.DeepCopy(),
			},
			createdByApply:   false,
			checkExpectedObj: false,
			modifiedObj:      vpc.DeepCopy(),
			expectedObj:      nil,
			expect: func(g *WithT, cli client.Client, modified client.Object) {
				// Change the order of tags.
				managementPolicies := v1.ManagementPolicies{
					v1.ManagementActionUpdate,
					v1.ManagementActionCreate,
					v1.ManagementActionDelete,
				}
				modified.(*v1beta1.VPC).Spec.ManagementPolicies = managementPolicies

				current := v1beta1.VPC{}
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				currentRV := current.GetResourceVersion()
				g.Expect(current.Annotations).Should(HaveKeyWithValue("crossplane", "vpc-id-xxxxx"))
				g.Expect(current.Annotations).Should(HaveLen(1))

				g.Expect(Apply(ctx, cli, modified)).Should(Succeed())
				g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(modified), &current)).To(Succeed())
				g.Expect(current.Status).Should(Equal(vpc.Status))
				g.Expect(current.GetResourceVersion()).ShouldNot(Equal(currentRV))
				g.Expect(current.Spec.ResourceSpec.ManagementPolicies).Should(Equal(managementPolicies))
				g.Expect(current.Annotations).Should(HaveKeyWithValue("crossplane", "vpc-id-xxxxx"))
				g.Expect(current.Annotations).Should(HaveLen(2))
			},
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewWithT(t)
			s := runtime.NewScheme()
			g.Expect(eksv1beta1.SchemeBuilder.AddToScheme(s)).Should(Succeed())
			g.Expect(v1beta1.SchemeBuilder.AddToScheme(s)).Should(Succeed())

			var cli client.Client
			if c.createdByApply {
				cli = fake.NewClientBuilder().WithScheme(s).Build()
				for _, obj := range c.existObjs {
					g.Expect(Apply(ctx, cli, obj)).Should(Succeed())
				}
			} else {
				cli = fake.NewClientBuilder().WithScheme(s).WithObjects(c.existObjs...).Build()
			}

			c.expect(g, cli, c.modifiedObj)

			if c.checkExpectedObj {
				checkExpectedSpecAndLabels(g, ctx, cli, client.ObjectKeyFromObject(&nodeGroup), c.expectedObj)
			}
		})
	}
}

func checkExpectedSpecAndLabels(g *WithT, ctx context.Context, cli client.Client, key client.ObjectKey, expectedObj client.Object) {
	var ng eksv1beta1.NodeGroup
	g.Expect(cli.Get(ctx, key, &ng)).To(Succeed())
	g.Expect(ng.Spec).To(Equal(expectedObj.(*eksv1beta1.NodeGroup).Spec))
	g.Expect(ng.Labels).To(Equal(expectedObj.GetLabels()))
	g.Expect(ng.Annotations).Should(HaveKey(consts.LastAppliedConfigAnnotation))
}

func Test_checkObject(t *testing.T) {
	tests := []struct {
		name    string
		obj     client.Object
		wantErr bool
	}{
		{
			name:    "nil object",
			obj:     nil,
			wantErr: true,
		},
		{
			name:    "empty object",
			obj:     &eksv1beta1.NodeGroup{},
			wantErr: true,
		},
		{
			name: "object with obj meta only",
			obj: &eksv1beta1.NodeGroup{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := checkObject(tt.obj); (err != nil) != tt.wantErr {
				t.Errorf("checkObject() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}
