package customstatus

import (
	"strconv"
	"testing"
	"time"

	. "github.com/onsi/gomega"
	grpcmetadata "google.golang.org/grpc/metadata"
	"google.golang.org/protobuf/proto"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/db"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/schema"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func TestSetClusterCustomStatus(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())
	dbProvider := db.NewProvider(nil)

	type testCase struct {
		name      string
		inputsFn  func() *tidbmgmtservicev1.SetClusterCustomStatusRequest
		outputsFn func(*tidbmgmtservicev1.ClusterCustomStatus, error)
	}

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-1",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	cases := []testCase{
		{
			name: "Create should success",
			inputsFn: func() (c *tidbmgmtservicev1.SetClusterCustomStatusRequest) {
				mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
					StatusType: []tidbmgmtservicev1.CustomStatusType{
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
					},
					ClusterAction: []tidbmgmtservicev1.ClusterAction{
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
					},
				}
				overrideState := openapicommonv1beta1.ClusterState_RESTORING
				req := &tidbmgmtservicev1.SetClusterCustomStatusRequest{
					CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
						ClusterId:      clusterId,
						StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
						StatusName:     "status-test-1-restore",
						OverrideStatus: &overrideState,
						Mutex:          mutex,
						TimeoutSecs:    proto.Int64(3),
					},
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(status).ShouldNot(BeNil())
				g.Expect(strconv.FormatUint(status.ClusterId, 10)).Should(Equal(cluster.ClusterId))
				g.Expect(status.StatusType).Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE))
				g.Expect(status.StatusName).Should(Equal("status-test-1-restore"))
				g.Expect(status.OverrideStatus).ShouldNot(BeNil())
				g.Expect(status.OverrideStatus.String()).Should(Equal("RESTORING"))
				g.Expect(status.Mutex).ShouldNot(BeNil())
				g.Expect(status.Mutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT))
				g.Expect(status.Mutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER))
				g.Expect(status.TimeoutSecs).Should(Equal(proto.Int64(3)))
				g.Expect(status.IsExpired).Should(BeFalse())
				// Check the status in database
				var record schema.ClusterCustomStatus
				err = dbProvider.ReadDB(t.Context()).
					Model(&schema.ClusterCustomStatus{}).
					Where("cluster_id = ?", status.ClusterId).
					Take(&record).
					Error
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(record).ShouldNot(BeNil())
				g.Expect(record.StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE.String()))
				g.Expect(record.StatusName).Should(Equal("status-test-1-restore"))
				g.Expect(*record.OverrideState).Should(Equal("RESTORING"))
				g.Expect(record.CustomMutex).ShouldNot(BeNil())
				g.Expect(record.CustomMutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC.String(), tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE.String(), tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT.String()))
				g.Expect(record.CustomMutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER.String()))
				g.Expect(record.TimeoutSecs).Should(Equal(proto.Int64(3)))
			},
		},
		{
			name: "Create failed with existed status mutex",
			inputsFn: func() (c *tidbmgmtservicev1.SetClusterCustomStatusRequest) {
				mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
					StatusType: []tidbmgmtservicev1.CustomStatusType{
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
					},
					ClusterAction: []tidbmgmtservicev1.ClusterAction{
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
					},
				}
				req := &tidbmgmtservicev1.SetClusterCustomStatusRequest{
					CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
						ClusterId:  clusterId,
						StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
						StatusName: "status-test-2-cdc",
						Mutex:      mutex,
					},
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).Should(HaveOccurred())
				g.Expect(err.Error()).Should(ContainSubstring("Forbidden"))
			},
		},
		{
			name: "Create failed with purpose status expired",
			inputsFn: func() (c *tidbmgmtservicev1.SetClusterCustomStatusRequest) {
				mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
					StatusType: []tidbmgmtservicev1.CustomStatusType{
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
					},
					ClusterAction: []tidbmgmtservicev1.ClusterAction{
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_MODIFY_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_RESUME_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_PAUSE_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SCALE_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_TRAFFIC_FILTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_CREATE_PRIVATE_LINK,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_CREATE_VPC_PEERING,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_VPC_PEERING,
					},
				}
				req := &tidbmgmtservicev1.SetClusterCustomStatusRequest{
					CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
						ClusterId:  clusterId,
						StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_MAINTENANCE,
						StatusName: "status-test-3-maintenance",
						Mutex:      mutex,
					},
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).Should(HaveOccurred())
				g.Expect(err.Error()).Should(ContainSubstring("Forbidden"))
			},
		},
		{
			name: "Create success with existed status expired",
			inputsFn: func() (c *tidbmgmtservicev1.SetClusterCustomStatusRequest) {
				// wait for TimeoutSecs seconds
				time.Sleep(3 * time.Second)

				mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
					StatusType: []tidbmgmtservicev1.CustomStatusType{
						// tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
					},
					ClusterAction: []tidbmgmtservicev1.ClusterAction{
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_MODIFY_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_RESUME_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_PAUSE_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SCALE_CLUSTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_ROOT_PASSWORD,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SET_TRAFFIC_FILTER,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_CREATE_PRIVATE_LINK,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_CREATE_VPC_PEERING,
						tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_VPC_PEERING,
					},
				}
				overrideState := openapicommonv1beta1.ClusterState_MAINTENANCE
				req := &tidbmgmtservicev1.SetClusterCustomStatusRequest{
					CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
						ClusterId:      clusterId,
						StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_MAINTENANCE,
						StatusName:     "status-test-4-maintenance",
						Mutex:          mutex,
						OverrideStatus: &overrideState,
					},
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(status).ShouldNot(BeNil())
				g.Expect(strconv.FormatUint(status.ClusterId, 10)).Should(Equal(cluster.ClusterId))
				g.Expect(status.StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_MAINTENANCE))
				g.Expect(status.StatusName).Should(Equal("status-test-4-maintenance"))
				g.Expect(status.OverrideStatus.String()).Should(Equal("MAINTENANCE"))
				g.Expect(status.Mutex).ShouldNot(BeNil())
				g.Expect(status.IsExpired).Should(BeFalse())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := tc.inputsFn()
			cluster, err := service.SetClusterCustomStatus(ctx, req)
			tc.outputsFn(cluster, err)
		})
	}
}

func TestGetClusterCustomStatus(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())
	dbProvider := db.NewProvider(nil)

	type testCase struct {
		name      string
		inputsFn  func() *tidbmgmtservicev1.GetClusterCustomStatusByIDRequest
		outputsFn func(*tidbmgmtservicev1.ClusterCustomStatus, error)
	}

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-2",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
		StatusType: []tidbmgmtservicev1.CustomStatusType{
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
		},
		ClusterAction: []tidbmgmtservicev1.ClusterAction{
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
		},
	}

	overrideState := openapicommonv1beta1.ClusterState_RESTORING
	s, err := service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:      clusterId,
			StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			StatusName:     "status-test-5-restore",
			OverrideStatus: &overrideState,
			Mutex:          mutex,
			TimeoutSecs:    proto.Int64(3),
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	cases := []testCase{
		{
			name: "Get should success with status id",
			inputsFn: func() (c *tidbmgmtservicev1.GetClusterCustomStatusByIDRequest) {
				req := &tidbmgmtservicev1.GetClusterCustomStatusByIDRequest{
					ClusterId: clusterId,
					StatusId:  s.StatusId,
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(status).ShouldNot(BeNil())
				g.Expect(strconv.FormatUint(status.ClusterId, 10)).Should(Equal(cluster.ClusterId))
				g.Expect(status.StatusType).Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE))
				g.Expect(status.StatusName).Should(Equal("status-test-5-restore"))
				g.Expect(status.OverrideStatus.String()).Should(Equal("RESTORING"))
				g.Expect(status.Mutex).ShouldNot(BeNil())
				g.Expect(status.Mutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT))
				g.Expect(status.Mutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER))
				g.Expect(status.TimeoutSecs).Should(Equal(proto.Int64(3)))
				g.Expect(status.IsExpired).Should(BeFalse())
				// Check the status in database
				var record schema.ClusterCustomStatus
				err = dbProvider.ReadDB(t.Context()).
					Model(&schema.ClusterCustomStatus{}).
					Where("cluster_id = ?", status.ClusterId).
					Take(&record).
					Error
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(record).ShouldNot(BeNil())
				g.Expect(record.StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE.String()))
				g.Expect(record.StatusName).Should(Equal("status-test-5-restore"))
				g.Expect(*record.OverrideState).Should(Equal("RESTORING"))
				g.Expect(record.CustomMutex).ShouldNot(BeNil())
				g.Expect(record.CustomMutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC.String(), tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE.String(), tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT.String()))
				g.Expect(record.CustomMutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER.String()))
				g.Expect(record.TimeoutSecs).Should(Equal(proto.Int64(3)))
			},
		},
		{
			name: "Get should failed with no cluster id",
			inputsFn: func() (c *tidbmgmtservicev1.GetClusterCustomStatusByIDRequest) {
				req := &tidbmgmtservicev1.GetClusterCustomStatusByIDRequest{
					StatusId: s.StatusId,
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).Should(HaveOccurred())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := tc.inputsFn()
			cluster, err := service.GetClusterCustomStatusByID(ctx, req)
			tc.outputsFn(cluster, err)
		})
	}
}

func TestListClusterCustomStatus(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())

	type testCase struct {
		name      string
		inputsFn  func() *tidbmgmtservicev1.ListClusterCustomStatusRequest
		outputsFn func(*tidbmgmtservicev1.ListClusterCustomStatusResponse, error)
	}

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-3",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
		StatusType: []tidbmgmtservicev1.CustomStatusType{
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
		},
		ClusterAction: []tidbmgmtservicev1.ClusterAction{
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
		},
	}
	overrideState := openapicommonv1beta1.ClusterState_RESTORING
	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:      clusterId,
			StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			StatusName:     "status-test-6-restore",
			OverrideStatus: &overrideState,
			Mutex:          mutex,
			TimeoutSecs:    proto.Int64(3),
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:  clusterId,
			StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
			StatusName: "status-test-7-backup",
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:  clusterId,
			StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
			StatusName: "status-test-8-backup",
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	cases := []testCase{
		{
			name: "list should success",
			inputsFn: func() (c *tidbmgmtservicev1.ListClusterCustomStatusRequest) {
				req := &tidbmgmtservicev1.ListClusterCustomStatusRequest{
					ClusterIds: []uint64{clusterId},
					StatusTypes: []tidbmgmtservicev1.CustomStatusType{
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
					},
				}
				return req
			},
			outputsFn: func(resp *tidbmgmtservicev1.ListClusterCustomStatusResponse, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(resp).ShouldNot(BeNil())
				g.Expect(resp.CustomStatuses).Should(HaveLen(2))
				g.Expect(resp.CustomStatuses[0].StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP))
				g.Expect(resp.CustomStatuses[0].StatusName).
					Should(Equal("status-test-8-backup"))
				// sort by create time desc
				g.Expect(resp.CustomStatuses[0].OverrideStatus).Should(BeNil())
				g.Expect(resp.CustomStatuses[0].Mutex).Should(BeNil())
				g.Expect(resp.CustomStatuses[0].TimeoutSecs).Should(BeNil())
				g.Expect(resp.CustomStatuses[0].IsExpired).Should(BeFalse())
				g.Expect(resp.CustomStatuses[1].StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP))
				g.Expect(resp.CustomStatuses[1].StatusName).Should(Equal("status-test-7-backup"))
				g.Expect(resp.CustomStatuses[1].OverrideStatus).Should(BeNil())
				g.Expect(resp.CustomStatuses[1].Mutex).Should(BeNil())
				g.Expect(resp.CustomStatuses[1].TimeoutSecs).Should(BeNil())
				g.Expect(resp.CustomStatuses[1].IsExpired).Should(BeFalse())
			},
		},
		{
			name: "list should success 2",
			inputsFn: func() (c *tidbmgmtservicev1.ListClusterCustomStatusRequest) {
				req := &tidbmgmtservicev1.ListClusterCustomStatusRequest{
					ClusterIds: []uint64{clusterId},
					StatusTypes: []tidbmgmtservicev1.CustomStatusType{
						tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
					},
				}
				return req
			},
			outputsFn: func(resp *tidbmgmtservicev1.ListClusterCustomStatusResponse, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(resp).ShouldNot(BeNil())
				g.Expect(resp.CustomStatuses).Should(HaveLen(1))
				status := resp.CustomStatuses[0]
				g.Expect(status).ShouldNot(BeNil())
				g.Expect(strconv.FormatUint(status.ClusterId, 10)).Should(Equal(cluster.ClusterId))
				g.Expect(status.StatusType).Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE))
				g.Expect(status.StatusName).Should(Equal("status-test-6-restore"))
				g.Expect(status.OverrideStatus.String()).Should(Equal("RESTORING"))
				g.Expect(status.Mutex).ShouldNot(BeNil())
				g.Expect(status.Mutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT))
				g.Expect(status.Mutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER))
				g.Expect(status.TimeoutSecs).Should(Equal(proto.Int64(3)))
				g.Expect(status.IsExpired).Should(BeFalse())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := tc.inputsFn()
			resp, err := service.ListClusterCustomStatus(ctx, req)
			tc.outputsFn(resp, err)
		})
	}
}

func TestIsClusterCustomStatusExisted(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())

	type testCase struct {
		name      string
		inputsFn  func() *tidbmgmtservicev1.IsClusterCustomStatusExistedRequest
		outputsFn func(*tidbmgmtservicev1.IsClusterCustomStatusExistedResponse, error)
	}

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-4",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:  clusterId,
			StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
			StatusName: "status-test-9-backup",
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:  clusterId,
			StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
			StatusName: "status-test-10-backup",
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	cases := []testCase{
		{
			name: "is status existed should success",
			inputsFn: func() (c *tidbmgmtservicev1.IsClusterCustomStatusExistedRequest) {
				req := &tidbmgmtservicev1.IsClusterCustomStatusExistedRequest{
					ClusterId:  clusterId,
					StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
				}
				return req
			},
			outputsFn: func(resp *tidbmgmtservicev1.IsClusterCustomStatusExistedResponse, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(resp).ShouldNot(BeNil())
				g.Expect(resp.IsExisted).Should(BeTrue())
				g.Expect(resp.ExistedCustomStatuses).Should(HaveLen(2))
				g.Expect(resp.ExistedCustomStatuses[0].StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP))
				g.Expect(resp.ExistedCustomStatuses[0].StatusName).Should(Equal("status-test-10-backup"))
				g.Expect(resp.ExistedCustomStatuses[0].OverrideStatus).Should(BeNil())
				g.Expect(resp.ExistedCustomStatuses[0].Mutex).Should(BeNil())
				g.Expect(resp.ExistedCustomStatuses[0].TimeoutSecs).Should(BeNil())
				g.Expect(resp.ExistedCustomStatuses[0].IsExpired).Should(BeFalse())
				g.Expect(resp.ExistedCustomStatuses[1].StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP))
				g.Expect(resp.ExistedCustomStatuses[1].StatusName).Should(Equal("status-test-9-backup"))
				g.Expect(resp.ExistedCustomStatuses[1].OverrideStatus).Should(BeNil())
				g.Expect(resp.ExistedCustomStatuses[1].Mutex).Should(BeNil())
				g.Expect(resp.ExistedCustomStatuses[1].TimeoutSecs).Should(BeNil())
				g.Expect(resp.ExistedCustomStatuses[1].IsExpired).Should(BeFalse())
			},
		},
		{
			name: "is status existed failed with no cluster id",
			inputsFn: func() (c *tidbmgmtservicev1.IsClusterCustomStatusExistedRequest) {
				req := &tidbmgmtservicev1.IsClusterCustomStatusExistedRequest{
					StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
				}
				return req
			},
			outputsFn: func(resp *tidbmgmtservicev1.IsClusterCustomStatusExistedResponse, err error) {
				g.Expect(err).Should(HaveOccurred())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := tc.inputsFn()
			resp, err := service.IsClusterCustomStatusExisted(ctx, req)
			tc.outputsFn(resp, err)
		})
	}
}

func TestDeleteClusterCustomStatus(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())

	type testCase struct {
		name      string
		inputsFn  func() *tidbmgmtservicev1.DeleteClusterCustomStatusRequest
		outputsFn func(error)
	}

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-5",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	s, err := service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:  clusterId,
			StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
			StatusName: "status-test-11-backup",
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:  clusterId,
			StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
			StatusName: "status-test-12-backup",
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	cases := []testCase{
		{
			name: "Delete failed with only cluster id",
			inputsFn: func() (c *tidbmgmtservicev1.DeleteClusterCustomStatusRequest) {
				req := &tidbmgmtservicev1.DeleteClusterCustomStatusRequest{
					ClusterId: clusterId,
				}
				return req
			},
			outputsFn: func(err error) {
				g.Expect(err).Should(HaveOccurred())
			},
		},
		{
			name: "Delete failed with no cluster id",
			inputsFn: func() (c *tidbmgmtservicev1.DeleteClusterCustomStatusRequest) {
				req := &tidbmgmtservicev1.DeleteClusterCustomStatusRequest{
					StatusId: s.StatusId,
				}
				return req
			},
			outputsFn: func(err error) {
				g.Expect(err).Should(HaveOccurred())
			},
		},
		{
			name: "Delete success with status id",
			inputsFn: func() (c *tidbmgmtservicev1.DeleteClusterCustomStatusRequest) {
				req := &tidbmgmtservicev1.DeleteClusterCustomStatusRequest{
					ClusterId: clusterId,
					StatusId:  s.StatusId,
				}
				return req
			},
			outputsFn: func(err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := tc.inputsFn()
			_, err := service.DeleteClusterCustomStatus(ctx, req)
			tc.outputsFn(err)
		})
	}
}

func TestOverrideStatus(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-6",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
		StatusType: []tidbmgmtservicev1.CustomStatusType{
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
		},
		ClusterAction: []tidbmgmtservicev1.ClusterAction{
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
		},
	}
	overrideState := openapicommonv1beta1.ClusterState_RESTORING
	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:      clusterId,
			StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			StatusName:     "status-test-13-restore",
			OverrideStatus: &overrideState,
			Mutex:          mutex,
			TimeoutSecs:    proto.Int64(3),
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	newCls, err := clsSvc.GetCluster(ctx, &openapiserverlessv1beta1.GetClusterRequest{ClusterId: cluster.ClusterId})
	g.Expect(err).ShouldNot(HaveOccurred())
	g.Expect(newCls).ShouldNot(BeNil())
	g.Expect(newCls.State).Should(Equal(openapicommonv1beta1.ClusterState_RESTORING))
}

func TestActionMutex(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-7",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	createdCluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(createdCluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
		StatusType: []tidbmgmtservicev1.CustomStatusType{
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
		},
		ClusterAction: []tidbmgmtservicev1.ClusterAction{
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
		},
	}

	overrideState := openapicommonv1beta1.ClusterState_RESTORING
	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:      clusterId,
			StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			StatusName:     "status-test-13-restore",
			OverrideStatus: &overrideState,
			Mutex:          mutex,
			TimeoutSecs:    proto.Int64(3),
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	gotCluster, err := clsSvc.GetCluster(
		ctx,
		&openapiserverlessv1beta1.GetClusterRequest{ClusterId: createdCluster.ClusterId},
	)
	g.Expect(err).ShouldNot(HaveOccurred())
	g.Expect(gotCluster).ShouldNot(BeNil())
	g.Expect(gotCluster.State).Should(Equal(openapicommonv1beta1.ClusterState_RESTORING))

	// delete cluster failed
	_, err = clsSvc.DeleteCluster(
		ctx,
		&openapiserverlessv1beta1.DeleteClusterRequest{ClusterId: createdCluster.ClusterId},
	)
	g.Expect(err.Error()).Should(ContainSubstring("Forbidden"))

	// wait for status timeout
	time.Sleep(3 * time.Second)
	deletedCluster, err := clsSvc.DeleteCluster(
		ctx,
		&openapiserverlessv1beta1.DeleteClusterRequest{ClusterId: createdCluster.ClusterId},
	)
	g.Expect(err).ShouldNot(HaveOccurred())
	g.Expect(deletedCluster).ShouldNot(BeNil())
	g.Expect(deletedCluster.State).Should(Equal(openapicommonv1beta1.ClusterState_DELETED))
}

func TestGetClusterCustomStatusByName(t *testing.T) {
	g := NewWithT(t)

	service, clsSvc, err := initService()
	g.Expect(err).ShouldNot(HaveOccurred())
	dbProvider := db.NewProvider(nil)

	type testCase struct {
		name      string
		inputsFn  func() *tidbmgmtservicev1.GetClusterCustomStatusByNameRequest
		outputsFn func(*tidbmgmtservicev1.ClusterCustomStatus, error)
	}

	// create test cluster
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.UserEmailHeaderKey:        "foo@bar.com",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapiserverlessv1beta1.CreateClusterRequest{
		Cluster: &openapiserverlessv1beta1.Cluster{
			DisplayName: "status-test-8",
			Region:      &openapicommonv1beta1.Region{Name: "regions/aws-us-west-2"},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}

	cluster, err := clsSvc.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())
	clusterId, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	g.Expect(err).ShouldNot(HaveOccurred())

	mutex := &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
		StatusType: []tidbmgmtservicev1.CustomStatusType{
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT,
		},
		ClusterAction: []tidbmgmtservicev1.ClusterAction{
			tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER,
		},
	}

	overrideState := openapicommonv1beta1.ClusterState_RESTORING
	_, err = service.SetClusterCustomStatus(ctx, &tidbmgmtservicev1.SetClusterCustomStatusRequest{
		CustomStatus: &tidbmgmtservicev1.ClusterCustomStatus{
			ClusterId:      clusterId,
			StatusType:     tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
			StatusName:     "status-test-14-restore",
			OverrideStatus: &overrideState,
			Mutex:          mutex,
			TimeoutSecs:    proto.Int64(3),
		},
	})
	g.Expect(err).ShouldNot(HaveOccurred())

	cases := []testCase{
		{
			name: "Get should success with name and type",
			inputsFn: func() (c *tidbmgmtservicev1.GetClusterCustomStatusByNameRequest) {
				req := &tidbmgmtservicev1.GetClusterCustomStatusByNameRequest{
					ClusterId:  clusterId,
					StatusName: "status-test-14-restore",
					StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(status).ShouldNot(BeNil())
				g.Expect(strconv.FormatUint(status.ClusterId, 10)).Should(Equal(cluster.ClusterId))
				g.Expect(status.StatusType).Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE))
				g.Expect(status.StatusName).Should(Equal("status-test-14-restore"))
				g.Expect(status.OverrideStatus.String()).Should(Equal("RESTORING"))
				g.Expect(status.Mutex).ShouldNot(BeNil())
				g.Expect(status.Mutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE, tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT))
				g.Expect(status.Mutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER))
				g.Expect(status.TimeoutSecs).Should(Equal(proto.Int64(3)))
				g.Expect(status.IsExpired).Should(BeFalse())
				// Check the status in database
				var record schema.ClusterCustomStatus
				err = dbProvider.ReadDB(t.Context()).
					Model(&schema.ClusterCustomStatus{}).
					Where("cluster_id = ?", status.ClusterId).
					Take(&record).
					Error
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(record).ShouldNot(BeNil())
				g.Expect(record.StatusType).
					Should(Equal(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE.String()))
				g.Expect(record.StatusName).Should(Equal("status-test-14-restore"))
				g.Expect(*record.OverrideState).Should(Equal("RESTORING"))
				g.Expect(record.CustomMutex).ShouldNot(BeNil())
				g.Expect(record.CustomMutex.StatusType).
					Should(ContainElements(tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC.String(), tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE.String(), tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_IMPORT.String()))
				g.Expect(record.CustomMutex.ClusterAction).
					Should(ContainElements(tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_DELETE_CLUSTER.String()))
				g.Expect(record.TimeoutSecs).Should(Equal(proto.Int64(3)))
			},
		},
		{
			name: "Get should failed with no cluster id",
			inputsFn: func() (c *tidbmgmtservicev1.GetClusterCustomStatusByNameRequest) {
				req := &tidbmgmtservicev1.GetClusterCustomStatusByNameRequest{
					StatusName: "status-test-14-restore",
					StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_RESTORE,
				}
				return req
			},
			outputsFn: func(status *tidbmgmtservicev1.ClusterCustomStatus, err error) {
				g.Expect(err).Should(HaveOccurred())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := tc.inputsFn()
			cluster, err := service.GetClusterCustomStatusByName(ctx, req)
			tc.outputsFn(cluster, err)
		})
	}
}
