/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package controllers

import (
	"context"
	"time"

	"github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/api/v1alpha1"
	"github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/pkg/kubernetes/service"
	"github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/pkg/reconcile/storagenode/aws"
	mock_aws "github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/pkg/reconcile/storagenode/aws/mocks"
	"github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/pkg/shardingsphere"
	mock_shardingsphere "github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/pkg/shardingsphere/mocks"

	"bou.ke/monkey"
	dbmeshawsrds "github.com/database-mesh/golang-sdk/aws/client/rds"
	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

const (
	defaultTestNamespace          = "test-namespace"
	defaultTestStorageProvider    = "test-storage-provider"
	defaultTestStorageNode        = "test-storage-node"
	defaultTestInstanceIdentifier = "test-database-instance"
)

var (
	ctx        = context.Background()
	fakeClient client.Client
	reconciler *StorageNodeReconciler
	mockCtrl   *gomock.Controller
	mockAws    *mock_aws.MockIRdsClient
	mockSS     *mock_shardingsphere.MockIServer
)

func fakeStorageNodeReconciler() {
	logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

	scheme := runtime.NewScheme()
	Expect(v1alpha1.AddToScheme(scheme)).To(Succeed())
	Expect(corev1.AddToScheme(scheme)).To(Succeed())
	fakeClient = fake.NewClientBuilder().WithScheme(scheme).Build()

	reconciler = &StorageNodeReconciler{
		Client:             fakeClient,
		Log:                logf.Log,
		Recorder:           record.NewFakeRecorder(100),
		AwsRegion:          "AwsRegion",
		AwsAccessKeyID:     "AwsAccessKeyID",
		AwsSecretAccessKey: "AwsSecretAccessKey",
		Service:            service.NewServiceClient(fakeClient),
	}
}

var _ = BeforeEach(func() {
	fakeStorageNodeReconciler()
})

var _ = Describe("StorageNode Controller Mock Test For AWS Rds Instance", func() {
	BeforeEach(func() {
		// mock aws rds client
		mockCtrl = gomock.NewController(GinkgoT())
		mockAws = mock_aws.NewMockIRdsClient(mockCtrl)

		monkey.Patch(aws.NewRdsClient, func(rds dbmeshawsrds.RDS) aws.IRdsClient {
			return mockAws
		})

		// create default resource
		dbClass := &v1alpha1.StorageProvider{
			ObjectMeta: metav1.ObjectMeta{
				Name: defaultTestStorageProvider,
			},
			Spec: v1alpha1.StorageProviderSpec{
				Provisioner: v1alpha1.ProvisionerAWSRDSInstance,
			},
		}

		storageNode := &v1alpha1.StorageNode{
			ObjectMeta: metav1.ObjectMeta{
				Name:      defaultTestStorageNode,
				Namespace: defaultTestNamespace,
				Annotations: map[string]string{
					v1alpha1.AnnotationsInstanceIdentifier: defaultTestInstanceIdentifier,
				},
			},
			Spec: v1alpha1.StorageNodeSpec{
				StorageProviderName: defaultTestStorageProvider,
			},
		}
		Expect(fakeClient.Create(ctx, dbClass)).Should(Succeed())
		Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
	})

	AfterEach(func() {
		// delete default resource
		Expect(fakeClient.Delete(ctx, &v1alpha1.StorageNode{
			ObjectMeta: metav1.ObjectMeta{
				Name:      defaultTestStorageNode,
				Namespace: defaultTestNamespace,
			},
		})).Should(Succeed())
		Expect(fakeClient.Delete(ctx, &v1alpha1.StorageProvider{
			ObjectMeta: metav1.ObjectMeta{
				Name: defaultTestStorageProvider,
			},
		})).Should(Succeed())

		mockCtrl.Finish()
		monkey.UnpatchAll()
	})

	Context("create storage node", func() {
		It("should create storage node successfully", func() {
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-storage-node-1",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: defaultTestStorageProvider,
				},
				Status: v1alpha1.StorageNodeStatus{},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			sn := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, client.ObjectKey{Name: "test-storage-node", Namespace: "test-namespace"}, sn)).Should(Succeed())
			Expect(fakeClient.Delete(ctx, storageNode)).Should(Succeed())
		})
	})

	Context("create storage node with unknown StorageClassName", func() {
		It("should create storage node successfully", func() {
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-storage-node-2",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "no-database",
				},
				Status: v1alpha1.StorageNodeStatus{},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      "test-storage-node-2",
					Namespace: defaultTestNamespace,
				},
			}
			_, err := reconciler.Reconcile(ctx, req)
			Expect(client.IgnoreNotFound(err)).Should(Succeed())
			Expect(fakeClient.Delete(ctx, storageNode)).Should(Succeed())
		})
	})

	Context("reconcile storageNode", func() {
		It("should reconcile successfully with Creating Instance", func() {
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      defaultTestStorageNode,
					Namespace: defaultTestNamespace,
				},
			}

			rdsInstance := &dbmeshawsrds.DescInstance{
				DBInstanceStatus: dbmeshawsrds.DBInstanceStatusCreating,
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "127.0.0.1",
					Port:    3306,
				},
			}

			// mock aws rds client
			mockAws.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(rdsInstance, nil).AnyTimes()

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			newSN := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, client.ObjectKey{Name: "test-storage-node", Namespace: "test-namespace"}, newSN)).Should(Succeed())
			Expect(newSN.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseNotReady))
			Expect(newSN.Status.Instances).To(HaveLen(1))
			Expect(newSN.Status.Instances[0].Status).To(Equal(string(dbmeshawsrds.DBInstanceStatusCreating)))
		})

		It("should reconcile successfully with Available Instance", func() {
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      defaultTestStorageNode,
					Namespace: defaultTestNamespace,
				},
			}

			rdsInstance := &dbmeshawsrds.DescInstance{
				DBInstanceStatus: dbmeshawsrds.DBInstanceStatusAvailable,
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "127.0.0.1",
					Port:    3306,
				},
			}

			// mock aws rds client
			mockAws.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(rdsInstance, nil)
			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			newSN := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, client.ObjectKey{Name: defaultTestStorageNode, Namespace: defaultTestNamespace}, newSN)).Should(Succeed())

			Expect(newSN.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseReady))
			Expect(newSN.Status.Instances).To(HaveLen(1))
			Expect(newSN.Status.Instances[0].Status).To(Equal(string(dbmeshawsrds.DBInstanceStatusAvailable)))
		})
	})

	Context("reconcile storage node in Ready status when it's been deleted", func() {
		rdsInstanceAvailable := dbmeshawsrds.DescInstance{
			DBInstanceIdentifier: defaultTestInstanceIdentifier,
			DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
			Endpoint: dbmeshawsrds.Endpoint{
				Address: "127.0.0.1",
				Port:    3306,
			},
		}
		It("should be successful when instance is in available status", func() {
			deletingStorageNode := "test-deleting-storage-node"
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      deletingStorageNode,
					Namespace: defaultTestNamespace,
				},
			}
			readyStorageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      deletingStorageNode,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsInstanceIdentifier: defaultTestInstanceIdentifier,
					},
				},
				Spec: v1alpha1.StorageNodeSpec{StorageProviderName: defaultTestStorageProvider},
			}
			Expect(fakeClient.Create(ctx, readyStorageNode)).Should(Succeed())
			// mock aws rds client, get instance and return available status
			mockAws.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(&rdsInstanceAvailable, nil)
			// reconcile storage node, add instance and set status to ready
			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			// delete storage node
			Expect(fakeClient.Delete(ctx, &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      deletingStorageNode,
					Namespace: defaultTestNamespace,
				},
			})).Should(Succeed())

			// mock aws rds client, delete instance
			mockAws.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(&rdsInstanceAvailable, nil)
			mockAws.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)

			_, err = reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			// storage node status should be deleting
			deletingSN := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, client.ObjectKey{Name: deletingStorageNode, Namespace: defaultTestNamespace}, deletingSN)).Should(Succeed())
			Expect(deletingSN.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseDeleting))
		})

		It("should be successful when instance is in deleting status", func() {
			deletedStorageNodeName := "test-deleted-storage-node"
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      deletedStorageNodeName,
					Namespace: defaultTestNamespace,
				},
			}
			deleteTime := metav1.NewTime(time.Now())
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      deletedStorageNodeName,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsInstanceIdentifier: defaultTestInstanceIdentifier,
					},
					Finalizers: []string{
						FinalizerName,
					},
					DeletionTimestamp: &deleteTime,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: defaultTestStorageProvider,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseDeleting,
					Instances: []v1alpha1.InstanceStatus{
						{
							Status: string(dbmeshawsrds.DBInstanceStatusDeleting),
							Endpoint: v1alpha1.Endpoint{
								Address: "127.0.0.1",
								Port:    3306,
							},
						},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			// mock aws rds client, get nil instance
			mockAws.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(nil, nil)
			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			deletedCompleteSN := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, client.ObjectKey{Name: deletedStorageNodeName, Namespace: defaultTestNamespace}, deletedCompleteSN)).Should(Succeed())
			Expect(deletedCompleteSN.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseDeleteComplete))
		})

		It("should be successful when storage node is delete complete status", func() {
			deletedCompletedStorageNodeName := "test-delete-completed-storage-node"
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      deletedCompletedStorageNodeName,
					Namespace: defaultTestNamespace,
				},
			}
			deleteTime := metav1.NewTime(time.Now())
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      deletedCompletedStorageNodeName,
					Namespace: defaultTestNamespace,
					Finalizers: []string{
						FinalizerName,
					},
					DeletionTimestamp: &deleteTime,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: defaultTestStorageProvider,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseDeleteComplete,
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			finalSN := &v1alpha1.StorageNode{}
			err = fakeClient.Get(ctx, client.ObjectKey{Name: deletedCompletedStorageNodeName, Namespace: defaultTestNamespace}, finalSN)
			Expect(apierrors.IsNotFound(err)).To(BeTrue())
		})
	})

	Context("Test register storage node", func() {
		BeforeEach(func() {
			mockCtrl = gomock.NewController(GinkgoT())
			mockSS = mock_shardingsphere.NewMockIServer(mockCtrl)
			monkey.Patch(shardingsphere.NewServer, func(_, _ string, _ uint, _, _ string) (shardingsphere.IServer, error) {
				return mockSS, nil
			})
		})
		AfterEach(func() {
			mockCtrl.Finish()
		})
		It("should be successful when storage node is not registered", func() {
			nodeName := "test register storage node"
			cnName := "test-compute-node"
			req := ctrl.Request{
				NamespacedName: client.ObjectKey{
					Name:      nodeName,
					Namespace: defaultTestNamespace,
				},
			}
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      nodeName,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						AnnotationKeyRegisterStorageUnitEnabled: "true",
						v1alpha1.AnnotationsInstanceDBName:      "test_db",
						AnnotationKeyComputeNodeName:            cnName,
						AnnotationKeyLogicDatabaseName:          "sharding_db",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: defaultTestStorageProvider,
				},
			}
			ins := &dbmeshawsrds.DescInstance{
				DBInstanceIdentifier: "ins-test-register-storage-node",
				DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "127.0.0.1",
					Port:    3306,
				},
			}
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      cnName,
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{
									{
										User:     "root@%",
										Password: "root",
									},
								},
								Privilege: v1alpha1.ComputeNodePrivilege{
									Type: v1alpha1.AllPermitted,
								},
							},
							Props: map[string]string{
								ShardingSphereProtocolType: "MySQL",
							},
						},
					},
				},
			}

			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      cnName,
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{
							Name: "shardingsphere-proxy",
							Port: 3306,
						},
					},
					ClusterIP: "127.0.0.1",
					Type:      corev1.ServiceTypeClusterIP,
				},
			}

			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			// mock aws rds client, get available instance
			mockAws.EXPECT().GetInstance(gomock.Any(), gomock.Any()).Return(ins, nil).AnyTimes()
			// mock shardingsphere create database
			mockSS.EXPECT().CreateDatabase(gomock.Any()).Return(nil)
			// mock shardingsphere register storage unit
			mockSS.EXPECT().RegisterStorageUnit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
			// mock shardingsphere close connection
			mockSS.EXPECT().Close()

			// reconcile storage node status to Ready
			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			// reconcile to register storage node
			_, err = reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			registeredSN := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, client.ObjectKey{Name: nodeName, Namespace: defaultTestNamespace}, registeredSN)).Should(Succeed())
			Expect(registeredSN.Status.Registered).To(BeTrue())
		})
	})

	Context("Test getShardingsphereServer", func() {
		BeforeEach(func() {
			mockCtrl = gomock.NewController(GinkgoT())
			mockSS = mock_shardingsphere.NewMockIServer(mockCtrl)
			monkey.Patch(shardingsphere.NewServer, func(_, _ string, _ uint, _, _ string) (shardingsphere.IServer, error) {
				return mockSS, nil
			})
		})
		AfterEach(func() {
			mockCtrl.Finish()
			monkey.UnpatchAll()
		})
		It("should be successful when get shardingsphere server", func() {
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-get-shardingsphere-server",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{
									{
										User:     "root",
										Password: "root",
									},
								},
							},
						},
					},
				},
			}
			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-get-shardingsphere-server",
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{
							Name:     "http",
							Protocol: "TCP",
							Port:     3307,
						},
					},
				},
			}
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			sn := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-get-shardingsphere-server",
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						AnnotationKeyComputeNodeName: "test-get-shardingsphere-server",
					},
				},
			}

			ss, err := reconciler.getShardingsphereServer(ctx, sn)
			Expect(err).To(BeNil())
			Expect(ss).ToNot(BeNil())
		})
	})

	Context("Test registerStorageUnit", func() {
		BeforeEach(func() {
			mockCtrl = gomock.NewController(GinkgoT())
			mockSS = mock_shardingsphere.NewMockIServer(mockCtrl)
			monkey.Patch(shardingsphere.NewServer, func(_, _ string, _ uint, _, _ string) (shardingsphere.IServer, error) {
				return mockSS, nil
			})
		})
		AfterEach(func() {
			mockCtrl.Finish()
			monkey.UnpatchAll()
		})
		It("should be successful when register storage unit", func() {
			testName := "test-register-storage-unit"
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      testName,
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{
									{
										User:     "root",
										Password: "root",
									},
								},
							},
						},
					},
				},
			}
			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      testName,
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{
							Name:     "http",
							Protocol: "TCP",
							Port:     3307,
						},
					},
				},
			}
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			sn := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      testName,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						AnnotationKeyComputeNodeName:            testName,
						AnnotationKeyRegisterStorageUnitEnabled: "true",
						AnnotationKeyLogicDatabaseName:          testName,
						v1alpha1.AnnotationsInstanceDBName:      testName,
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: defaultTestStorageProvider,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseReady,
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusAvailable),
							Endpoint: v1alpha1.Endpoint{},
						},
					},
				},
			}

			storageProvider := &v1alpha1.StorageProvider{
				ObjectMeta: metav1.ObjectMeta{
					Name: defaultTestStorageProvider,
				},
				Spec: v1alpha1.StorageProviderSpec{
					Provisioner: v1alpha1.ProvisionerAWSRDSInstance,
					Parameters: map[string]string{
						"masterUsername":     testName,
						"masterUserPassword": testName,
					},
				},
			}

			mockSS.EXPECT().CreateDatabase(gomock.Any()).Return(nil)
			mockSS.EXPECT().Close().Return(nil)
			mockSS.EXPECT().RegisterStorageUnit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)

			Expect(reconciler.registerStorageUnit(ctx, sn, storageProvider)).To(BeNil())
			Expect(sn.Status.Registered).To(BeTrue())
		})

		Context("Test unregisterStorageUnit", func() {
			BeforeEach(func() {
				mockCtrl = gomock.NewController(GinkgoT())
				mockSS = mock_shardingsphere.NewMockIServer(mockCtrl)
				monkey.Patch(shardingsphere.NewServer, func(_, _ string, _ uint, _, _ string) (shardingsphere.IServer, error) {
					return mockSS, nil
				})
			})
			AfterEach(func() {
				mockCtrl.Finish()
				monkey.UnpatchAll()
			})
			It("should be successful when unregister storage unit", func() {
				testName := "test-unregister-storage-unit"

				cn := &v1alpha1.ComputeNode{
					ObjectMeta: metav1.ObjectMeta{
						Name:      testName,
						Namespace: defaultTestNamespace,
					},
					Spec: v1alpha1.ComputeNodeSpec{
						Bootstrap: v1alpha1.BootstrapConfig{
							ServerConfig: v1alpha1.ServerConfig{
								Authority: v1alpha1.ComputeNodeAuthority{
									Users: []v1alpha1.ComputeNodeUser{
										{
											User:     "root",
											Password: "root",
										},
									},
								},
							},
						},
					},
				}
				svc := &corev1.Service{
					ObjectMeta: metav1.ObjectMeta{
						Name:      testName,
						Namespace: defaultTestNamespace,
					},
					Spec: corev1.ServiceSpec{
						Ports: []corev1.ServicePort{
							{
								Name:     "http",
								Protocol: "TCP",
								Port:     3307,
							},
						},
					},
				}
				Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
				Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

				sn := &v1alpha1.StorageNode{
					ObjectMeta: metav1.ObjectMeta{
						Name:      testName,
						Namespace: defaultTestNamespace,
						Annotations: map[string]string{
							AnnotationKeyLogicDatabaseName:     testName,
							v1alpha1.AnnotationsInstanceDBName: testName,
							AnnotationKeyComputeNodeName:       testName,
						},
					},
					Status: v1alpha1.StorageNodeStatus{
						Registered: true,
					},
				}
				Expect(fakeClient.Create(ctx, sn)).Should(Succeed())

				mockSS.EXPECT().UnRegisterStorageUnit(gomock.Any(), gomock.Any()).Return(nil)
				mockSS.EXPECT().Close().Return(nil)
				Expect(reconciler.unregisterStorageUnit(ctx, sn)).To(BeNil())
			})
		})
	})
})

var _ = Describe("StorageNode Controller Mock Test For AWS Aurora", func() {
	var provider *v1alpha1.StorageProvider
	BeforeEach(func() {
		provider = &v1alpha1.StorageProvider{
			ObjectMeta: metav1.ObjectMeta{
				Name: "aws-aurora",
			},
			Spec: v1alpha1.StorageProviderSpec{
				Provisioner: v1alpha1.ProvisionerAWSAurora,
				Parameters: map[string]string{
					"engine":             "aurora-mysql",
					"engineVersion":      "5.7",
					"masterUsername":     "root",
					"masterUserPassword": "root",
				},
			},
		}
		Expect(fakeClient.Create(ctx, provider)).Should(Succeed())

		// mock aws client
		// mock aws rds client
		mockCtrl = gomock.NewController(GinkgoT())
		mockAws = mock_aws.NewMockIRdsClient(mockCtrl)
		monkey.Patch(aws.NewRdsClient, func(rds dbmeshawsrds.RDS) aws.IRdsClient {
			return mockAws
		})
		mockSS = mock_shardingsphere.NewMockIServer(mockCtrl)
		monkey.Patch(shardingsphere.NewServer, func(_, _ string, _ uint, _, _ string) (shardingsphere.IServer, error) {
			return mockSS, nil
		})
	})

	AfterEach(func() {
		mockCtrl.Finish()
		monkey.UnpatchAll()
	})

	Context("reconcile storage node", func() {
		It("should success when aws aurora cluster is not exits", func() {
			name := "test-aws-aurora-not-exists"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			storageNode := v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-aurora",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "aws-aurora",
				},
			}
			Expect(fakeClient.Create(ctx, &storageNode)).Should(Succeed())

			descCluster := &dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-aurora",
				PrimaryEndpoint:     "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				ReaderEndpoint:      "test-aws-aurora.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                3306,
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
			}
			descInstance := &dbmeshawsrds.DescInstance{
				DBInstanceIdentifier: "test-aws-aurora-1",
				DBClusterIdentifier:  "test-aws-aurora",
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
					Port:    3306,
				},
				DBInstanceStatus: dbmeshawsrds.DBInstanceStatusAvailable,
			}

			// mock aws aurora cluster is not exist
			mockAws.EXPECT().GetAuroraCluster(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
			// mock create aws aurora cluster
			mockAws.EXPECT().CreateAuroraCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
			// mock aws aurora cluster is created
			mockAws.EXPECT().GetAuroraCluster(gomock.Any(), gomock.Any()).Return(descCluster, nil).Times(1)
			// mock aws instance is created
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{descInstance}, nil).Times(1)

			req := ctrl.Request{NamespacedName: namespacedName}
			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			sn := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, namespacedName, sn)).Should(Succeed())
			Expect(sn.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseReady))
		})

		It("should success when storage node been delete", func() {
			name := "test-aws-aurora-deleted"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-aurora",
					},
					Finalizers: []string{FinalizerName},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "aws-aurora",
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseReady,
					Cluster: v1alpha1.ClusterStatus{
						Status:          string(dbmeshawsrds.DBClusterStatusAvailable),
						PrimaryEndpoint: v1alpha1.Endpoint{Address: "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						ReaderEndpoints: []v1alpha1.Endpoint{{Address: "test-aws-aurora.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306}},
					},
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusAvailable),
							Endpoint: v1alpha1.Endpoint{Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						},
					},
				},
			}

			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			descCluster := &dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-aurora",
				PrimaryEndpoint:     "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				ReaderEndpoint:      "test-aws-aurora.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                3306,
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
			}

			descInstance := &dbmeshawsrds.DescInstance{
				DBInstanceIdentifier: "test-aws-aurora-1",
				DBClusterIdentifier:  "test-aws-aurora",
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
					Port:    3306,
				},
				DBInstanceStatus: dbmeshawsrds.DBInstanceStatusAvailable,
			}

			Expect(fakeClient.Delete(ctx, storageNode)).Should(Succeed())

			// mock aws aurora is exists
			mockAws.EXPECT().GetAuroraCluster(gomock.Any(), gomock.Any()).Return(descCluster, nil).Times(1)
			// mock get instances of aws aurora
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{descInstance}, nil).Times(1)
			// mock delete aws aurora cluster
			mockAws.EXPECT().DeleteAuroraCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			Expect(fakeClient.Get(ctx, namespacedName, storageNode)).Should(Succeed())
			Expect(storageNode.DeletionTimestamp).NotTo(BeNil())
			Expect(storageNode.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseDeleting))
		})

		It("should be success when storage node is deleting", func() {
			name := "test-aws-aurora-deleting"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			deletionTimestamp := metav1.Now()
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-aurora",
					},
					Finalizers:        []string{FinalizerName},
					DeletionTimestamp: &deletionTimestamp,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "aws-aurora",
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseDeleting,
					Cluster: v1alpha1.ClusterStatus{
						Status:          string(dbmeshawsrds.DBClusterStatusDeleting),
						PrimaryEndpoint: v1alpha1.Endpoint{Address: "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						ReaderEndpoints: []v1alpha1.Endpoint{{Address: "test-aws-aurora.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306}},
					},
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusDeleting),
							Endpoint: v1alpha1.Endpoint{Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			// mock aws aurora is not exists
			mockAws.EXPECT().GetAuroraCluster(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
			// mock get instances of aws aurora is not exists
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			Expect(fakeClient.Get(ctx, namespacedName, storageNode)).Should(Succeed())
			Expect(storageNode.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseDeleteComplete))
		})

		It("should be success when storage node is delete completed", func() {
			name := "test-aws-aurora-delete-completed"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			deletionTimestamp := metav1.Now()
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-aurora",
					},
					Finalizers:        []string{FinalizerName},
					DeletionTimestamp: &deletionTimestamp,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "aws-aurora",
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseDeleteComplete,
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			err = fakeClient.Get(ctx, namespacedName, storageNode)
			Expect(apierrors.IsNotFound(err)).To(BeTrue())
		})

		It("should be success when storage node is ready for register", func() {
			name := "test-aws-aurora-ready-for-register"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier:   "test-aws-aurora",
						AnnotationKeyRegisterStorageUnitEnabled: "true",
						AnnotationKeyLogicDatabaseName:          "test-logic-db",
						v1alpha1.AnnotationsInstanceDBName:      "test-instance-db",
						AnnotationKeyComputeNodeName:            "test-compute-node",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "aws-aurora",
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseReady,
					Cluster: v1alpha1.ClusterStatus{
						Status:          string(dbmeshawsrds.DBClusterStatusAvailable),
						PrimaryEndpoint: v1alpha1.Endpoint{Address: "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						ReaderEndpoints: []v1alpha1.Endpoint{{Address: "test-aws-aurora.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306}},
					},
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusAvailable),
							Endpoint: v1alpha1.Endpoint{Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						},
					},
				},
			}
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{{User: "test-user", Password: "test-password"}},
							},
							Mode:  v1alpha1.ComputeNodeServerMode{},
							Props: nil,
						},
					},
				},
			}
			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{Name: "http", Protocol: "TCP", Port: 3307},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			// mock aws aurora is available
			mockAws.EXPECT().GetAuroraCluster(gomock.Any(), gomock.Any()).Return(&dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-aurora",
				PrimaryEndpoint:     "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                int32(3306),
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
			}, nil).Times(1)
			// mock get instances of aws aurora are available
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{
				{
					DBInstanceIdentifier: "test-aws-aurora-instance-0",
					DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
					Endpoint:             dbmeshawsrds.Endpoint{Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
				},
			}, nil).Times(1)

			host, port, username, password := getDatasourceInfoFromCluster(storageNode, provider)

			// mock shardingsphere
			mockSS.EXPECT().CreateDatabase(gomock.Any()).Return(nil).Times(1)
			mockSS.EXPECT().RegisterStorageUnit("test-logic-db", getDSName(storageNode), host, uint(port), "test-instance-db", username, password).Return(nil).Times(1)
			mockSS.EXPECT().Close().Return(nil).Times(1)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			err = fakeClient.Get(ctx, namespacedName, storageNode)
			Expect(storageNode.Status.Registered).To(BeTrue())
		})

		It("should be success unregistered when storage node is deleting", func() {
			snName := "test-aws-aurora-unregistered"
			namespacedName := types.NamespacedName{
				Name:      snName,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}

			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      snName,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier:   "test-aws-aurora",
						AnnotationKeyRegisterStorageUnitEnabled: "true",
						AnnotationKeyLogicDatabaseName:          "test-logic-db",
						v1alpha1.AnnotationsInstanceDBName:      "test-instance-db",
						AnnotationKeyComputeNodeName:            "test-compute-node",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: "aws-aurora",
					Replicas:            2,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase:      v1alpha1.StorageNodePhaseReady,
					Registered: true,
				},
			}
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{{User: "test-user", Password: "test-password"}},
							},
						},
					},
				},
			}
			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{Name: "http", Protocol: "TCP", Port: 3307},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			// mock aws aurora is available
			mockAws.EXPECT().GetAuroraCluster(gomock.Any(), gomock.Any()).Return(&dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-aurora",
				PrimaryEndpoint:     "test-aws-aurora.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                int32(3306),
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
				DBClusterMembers: []dbmeshawsrds.ClusterMember{
					{DBInstanceIdentifier: "test-aws-aurora-instance-0"},
					{DBInstanceIdentifier: "test-aws-aurora-instance-1"},
				},
			}, nil).Times(2)
			// mock get instances of aws aurora are available
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{
				{
					DBInstanceIdentifier: "test-aws-aurora-instance-0",
					DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
					Endpoint:             dbmeshawsrds.Endpoint{Address: "test-aws-aurora-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
				},
				{
					DBInstanceIdentifier: "test-aws-aurora-instance-1",
					DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
					Endpoint:             dbmeshawsrds.Endpoint{Address: "test-aws-aurora-2.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
				},
			}, nil).Times(2)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			Expect(fakeClient.Delete(ctx, storageNode)).Should(Succeed())
			// mock shardingsphere
			mockSS.EXPECT().UnRegisterStorageUnit("test-logic-db", getDSName(storageNode)).Return(nil).Times(1)
			mockSS.EXPECT().Close().Return(nil).Times(1)

			// mock delete aws aurora
			mockAws.EXPECT().DeleteAuroraCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

			_, err = reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			err = fakeClient.Get(ctx, namespacedName, storageNode)
			Expect(storageNode.Status.Registered).To(BeFalse())
		})
	})
})

var _ = Describe("StorageNode Controller Mock Test For AWS RDS Cluster", func() {
	var provider *v1alpha1.StorageProvider
	var providerName = "aws-rds-cluster"
	BeforeEach(func() {
		provider = &v1alpha1.StorageProvider{
			ObjectMeta: metav1.ObjectMeta{
				Name: providerName,
			},
			Spec: v1alpha1.StorageProviderSpec{
				Provisioner: v1alpha1.ProvisionerAWSRDSCluster,
				Parameters: map[string]string{
					"engine":             "mysql",
					"engineVersion":      "5.7",
					"masterUsername":     "root",
					"masterUserPassword": "root",
					"allocatedStorage":   "20",
				},
			},
		}
		Expect(fakeClient.Create(ctx, provider)).Should(Succeed())

		// mock aws client
		// mock aws rds client
		mockCtrl = gomock.NewController(GinkgoT())
		mockAws = mock_aws.NewMockIRdsClient(mockCtrl)
		monkey.Patch(aws.NewRdsClient, func(rds dbmeshawsrds.RDS) aws.IRdsClient {
			return mockAws
		})
		mockSS = mock_shardingsphere.NewMockIServer(mockCtrl)
		monkey.Patch(shardingsphere.NewServer, func(_, _ string, _ uint, _, _ string) (shardingsphere.IServer, error) {
			return mockSS, nil
		})
	})

	AfterEach(func() {
		mockCtrl.Finish()
		monkey.UnpatchAll()
	})

	Context("reconcile storage node", func() {
		It("should success when aws rds cluster is not exits", func() {
			name := "test-aws-rds-cluster-not-exists"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			storageNode := v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-rds-cluster",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: providerName,
				},
			}
			Expect(fakeClient.Create(ctx, &storageNode)).Should(Succeed())

			descCluster := &dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-rds-cluster",
				PrimaryEndpoint:     "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				ReaderEndpoint:      "test-aws-rds-cluster.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                3306,
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
			}
			descInstance := &dbmeshawsrds.DescInstance{
				DBInstanceIdentifier: "test-aws-rds-cluster-instance-1",
				DBClusterIdentifier:  "test-aws-rds-cluster",
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "test-aws-rds-cluster-instance-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
					Port:    3306,
				},
				DBInstanceStatus: dbmeshawsrds.DBInstanceStatusAvailable,
			}

			// mock aws rds cluster is not exist
			mockAws.EXPECT().GetRDSCluster(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
			// mock create aws aurora cluster
			mockAws.EXPECT().CreateRDSCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
			// mock aws aurora cluster is created
			mockAws.EXPECT().GetRDSCluster(gomock.Any(), gomock.Any()).Return(descCluster, nil).Times(1)
			// mock aws instance is created
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{descInstance}, nil).Times(1)

			req := ctrl.Request{NamespacedName: namespacedName}
			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			sn := &v1alpha1.StorageNode{}
			Expect(fakeClient.Get(ctx, namespacedName, sn)).Should(Succeed())
			Expect(sn.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseReady))
		})

		It("should success when storage node been delete", func() {
			name := "test-aws-rds-cluster-deleted"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-rds-cluster",
					},
					Finalizers: []string{FinalizerName},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: providerName,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseReady,
					Cluster: v1alpha1.ClusterStatus{
						Status:          string(dbmeshawsrds.DBClusterStatusAvailable),
						PrimaryEndpoint: v1alpha1.Endpoint{Address: "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						ReaderEndpoints: []v1alpha1.Endpoint{{Address: "test-aws-rds-cluster.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306}},
					},
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusAvailable),
							Endpoint: v1alpha1.Endpoint{Address: "test-aws-rds-cluster-instance-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						},
					},
				},
			}

			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			descCluster := &dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-rds-cluster",
				PrimaryEndpoint:     "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				ReaderEndpoint:      "test-aws-rds-cluster.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                3306,
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
			}

			descInstance := &dbmeshawsrds.DescInstance{
				DBInstanceIdentifier: "test-aws-rds-cluster-1",
				DBClusterIdentifier:  "test-aws-rds-cluster",
				Endpoint: dbmeshawsrds.Endpoint{
					Address: "test-aws-rds-cluster-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
					Port:    3306,
				},
				DBInstanceStatus: dbmeshawsrds.DBInstanceStatusAvailable,
			}

			Expect(fakeClient.Delete(ctx, storageNode)).Should(Succeed())

			// mock aws rds cluster is exists
			mockAws.EXPECT().GetRDSCluster(gomock.Any(), gomock.Any()).Return(descCluster, nil).Times(1)
			// mock get instances of aws rds cluster
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{descInstance}, nil).Times(1)
			// mock delete aws rds cluster
			mockAws.EXPECT().DeleteRDSCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			Expect(fakeClient.Get(ctx, namespacedName, storageNode)).Should(Succeed())
			Expect(storageNode.DeletionTimestamp).NotTo(BeNil())
			Expect(storageNode.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseDeleting))
		})

		It("should be success when storage node is deleting", func() {
			name := "test-aws-rds-cluster-deleting"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			deletionTimestamp := metav1.Now()
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-rds-cluster",
					},
					Finalizers:        []string{FinalizerName},
					DeletionTimestamp: &deletionTimestamp,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: providerName,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseDeleting,
					Cluster: v1alpha1.ClusterStatus{
						Status:          string(dbmeshawsrds.DBClusterStatusDeleting),
						PrimaryEndpoint: v1alpha1.Endpoint{Address: "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						ReaderEndpoints: []v1alpha1.Endpoint{{Address: "test-aws-rds-cluster.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306}},
					},
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusDeleting),
							Endpoint: v1alpha1.Endpoint{Address: "test-aws-rds-cluster-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			// mock aws rds cluster is not exists
			mockAws.EXPECT().GetRDSCluster(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
			// mock get instances of aws rds cluster is not exists
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			Expect(fakeClient.Get(ctx, namespacedName, storageNode)).Should(Succeed())
			Expect(storageNode.Status.Phase).To(Equal(v1alpha1.StorageNodePhaseDeleteComplete))
		})

		It("should be success when storage node is delete completed", func() {
			name := "test-aws-rds-cluster-delete-completed"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			deletionTimestamp := metav1.Now()
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier: "test-aws-rds-cluster",
					},
					Finalizers:        []string{FinalizerName},
					DeletionTimestamp: &deletionTimestamp,
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: providerName,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseDeleteComplete,
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())
			err = fakeClient.Get(ctx, namespacedName, storageNode)
			Expect(apierrors.IsNotFound(err)).To(BeTrue())
		})

		It("should be success when storage node is ready for register", func() {
			name := "test-aws-rds-cluster-ready-for-register"
			namespacedName := types.NamespacedName{
				Name:      name,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}
			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      name,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier:   "test-aws-rds-cluster",
						AnnotationKeyRegisterStorageUnitEnabled: "true",
						AnnotationKeyLogicDatabaseName:          "test-logic-db",
						v1alpha1.AnnotationsInstanceDBName:      "test-instance-db",
						AnnotationKeyComputeNodeName:            "test-compute-node",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: providerName,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase: v1alpha1.StorageNodePhaseReady,
					Cluster: v1alpha1.ClusterStatus{
						Status:          string(dbmeshawsrds.DBClusterStatusAvailable),
						PrimaryEndpoint: v1alpha1.Endpoint{Address: "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						ReaderEndpoints: []v1alpha1.Endpoint{{Address: "test-aws-rds-cluster.cluster-ro-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306}},
					},
					Instances: []v1alpha1.InstanceStatus{
						{
							Status:   string(dbmeshawsrds.DBInstanceStatusAvailable),
							Endpoint: v1alpha1.Endpoint{Address: "test-aws-rds-cluster-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
						},
					},
				},
			}
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{{User: "test-user", Password: "test-password"}},
							},
							Mode:  v1alpha1.ComputeNodeServerMode{},
							Props: nil,
						},
					},
				},
			}
			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{Name: "http", Protocol: "TCP", Port: 3307},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			// mock aws rds cluster is available
			mockAws.EXPECT().GetRDSCluster(gomock.Any(), gomock.Any()).Return(&dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-rds-cluster",
				PrimaryEndpoint:     "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                int32(3306),
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
			}, nil).Times(1)
			// mock get instances of aws aurora are available
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{
				{
					DBInstanceIdentifier: "test-aws-rds-cluster-instance-0",
					DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
					Endpoint:             dbmeshawsrds.Endpoint{Address: "test-aws-rds-cluster-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
				},
			}, nil).Times(1)

			host, port, username, password := getDatasourceInfoFromCluster(storageNode, provider)

			// mock shardingsphere
			mockSS.EXPECT().CreateDatabase(gomock.Any()).Return(nil).Times(1)
			mockSS.EXPECT().RegisterStorageUnit("test-logic-db", getDSName(storageNode), host, uint(port), "test-instance-db", username, password).Return(nil).Times(1)
			mockSS.EXPECT().Close().Return(nil).Times(1)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			err = fakeClient.Get(ctx, namespacedName, storageNode)
			Expect(storageNode.Status.Registered).To(BeTrue())
		})

		It("should be success unregistered when storage node is deleting", func() {
			snName := "test-aws-rds-cluster-unregistered"
			namespacedName := types.NamespacedName{
				Name:      snName,
				Namespace: defaultTestNamespace,
			}
			req := ctrl.Request{NamespacedName: namespacedName}

			storageNode := &v1alpha1.StorageNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      snName,
					Namespace: defaultTestNamespace,
					Annotations: map[string]string{
						v1alpha1.AnnotationsClusterIdentifier:   "test-aws-rds-cluster",
						AnnotationKeyRegisterStorageUnitEnabled: "true",
						AnnotationKeyLogicDatabaseName:          "test-logic-db",
						v1alpha1.AnnotationsInstanceDBName:      "test-instance-db",
						AnnotationKeyComputeNodeName:            "test-compute-node",
					},
				},
				Spec: v1alpha1.StorageNodeSpec{
					StorageProviderName: providerName,
					Replicas:            2,
				},
				Status: v1alpha1.StorageNodeStatus{
					Phase:      v1alpha1.StorageNodePhaseReady,
					Registered: true,
				},
			}
			cn := &v1alpha1.ComputeNode{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: v1alpha1.ComputeNodeSpec{
					Bootstrap: v1alpha1.BootstrapConfig{
						ServerConfig: v1alpha1.ServerConfig{
							Authority: v1alpha1.ComputeNodeAuthority{
								Users: []v1alpha1.ComputeNodeUser{{User: "test-user", Password: "test-password"}},
							},
						},
					},
				},
			}
			svc := &corev1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-compute-node",
					Namespace: defaultTestNamespace,
				},
				Spec: corev1.ServiceSpec{
					Ports: []corev1.ServicePort{
						{Name: "http", Protocol: "TCP", Port: 3307},
					},
				},
			}
			Expect(fakeClient.Create(ctx, storageNode)).Should(Succeed())
			Expect(fakeClient.Create(ctx, cn)).Should(Succeed())
			Expect(fakeClient.Create(ctx, svc)).Should(Succeed())

			// mock aws rds cluster is available
			mockAws.EXPECT().GetRDSCluster(gomock.Any(), gomock.Any()).Return(&dbmeshawsrds.DescCluster{
				DBClusterIdentifier: "test-aws-rds-cluster",
				PrimaryEndpoint:     "test-aws-rds-cluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com",
				Port:                int32(3306),
				Status:              string(dbmeshawsrds.DBClusterStatusAvailable),
				DBClusterMembers: []dbmeshawsrds.ClusterMember{
					{DBInstanceIdentifier: "test-aws-rds-cluster-instance-0"},
					{DBInstanceIdentifier: "test-aws-rds-cluster-instance-1"},
				},
			}, nil).Times(2)
			// mock get instances of aws aurora are available
			mockAws.EXPECT().GetInstancesByFilters(gomock.Any(), gomock.Any()).Return([]*dbmeshawsrds.DescInstance{
				{
					DBInstanceIdentifier: "test-aws-rds-cluster-instance-0",
					DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
					Endpoint:             dbmeshawsrds.Endpoint{Address: "test-aws-rds-cluster-1.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
				},
				{
					DBInstanceIdentifier: "test-aws-rds-cluster-instance-1",
					DBInstanceStatus:     dbmeshawsrds.DBInstanceStatusAvailable,
					Endpoint:             dbmeshawsrds.Endpoint{Address: "test-aws-rds-cluster-2.cluster-xxxxxx.us-east-1.rds.amazonaws.com", Port: 3306},
				},
			}, nil).Times(2)

			_, err := reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			Expect(fakeClient.Delete(ctx, storageNode)).Should(Succeed())
			// mock shardingsphere
			mockSS.EXPECT().UnRegisterStorageUnit("test-logic-db", getDSName(storageNode)).Return(nil).Times(1)
			mockSS.EXPECT().Close().Return(nil).Times(1)

			// mock delete aws rds cluster
			mockAws.EXPECT().DeleteRDSCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

			_, err = reconciler.Reconcile(ctx, req)
			Expect(err).To(BeNil())

			err = fakeClient.Get(ctx, namespacedName, storageNode)
			Expect(storageNode.Status.Registered).To(BeFalse())
		})
	})
})
