package billing_svc

import (
	"context"
	"testing"

	"github.com/shopspring/decimal"
	. "github.com/smartystreets/goconvey/convey"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/serverless"
	"github.com/tidbcloud/serverless-service/internal/config"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/domain/facade/mock_facade"
	"github.com/tidbcloud/serverless-service/internal/domain/repo/mock_repo"
	"github.com/tidbcloud/serverless-service/internal/domain/service/mock_service"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/mock/gomock"
)

func Test_ServerlessBillingService_GetAllSpendLimits(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.GetAllSpendLimitsReq{OrgId: 1}
	clusterMap := map[uint64]*tidbmgmtservicev1.Cluster{
		1000: {ClusterId: 1000, ClusterName: "cluster0", OrganizationId: 1},
		1001: {ClusterId: 1001, ClusterName: "cluster1", OrganizationId: 1},
	}
	clusters := []*tidbmgmtservicev1.Cluster{
		{ClusterId: 1000, ClusterName: "cluster0", OrganizationId: 1},
		{ClusterId: 1001, ClusterName: "cluster1", OrganizationId: 1},
	}
	resultMap := map[uint64]*facade.ServerlessSpendLimitBizData{
		1000: {
			FreeCredits:        decimal.NewFromInt(6),
			InvoiceLimit:       decimal.NewFromInt(0),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
		1001: {
			FreeCredits:        decimal.NewFromInt(7),
			InvoiceLimit:       decimal.NewFromInt(5),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
	}
	clusters1 := []*tidbmgmtservicev1.Cluster{
		{ClusterId: 1000, ClusterName: "cluster0", OrganizationId: 1},
	}
	clusterMap1 := map[uint64]*tidbmgmtservicev1.Cluster{
		1000: {ClusterId: 1000, ClusterName: "cluster0", OrganizationId: 1},
	}
	resultMap1 := map[uint64]*facade.ServerlessSpendLimitBizData{
		1000: {
			FreeCredits:        decimal.NewFromInt(6),
			InvoiceLimit:       decimal.NewFromInt(20),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
	}
	config.Global().BizConfig.ServerlessFreeQuotaMaxCount = 5
	Convey("Test_GetAllSpendLimits", t, func() {
		Convey("should return err if list clusters failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(nil, e)
			_, err := svc.GetAllSpendLimits(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get Spending Limit settings failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(nil, e)
			_, err := svc.GetAllSpendLimits(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should succeed", func() {
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(true)

			resp, err := svc.GetAllSpendLimits(ctx, req)
			So(err, ShouldBeNil)
			So(resp.Total, ShouldEqual, 2)
			So(resp.Items, ShouldHaveLength, 2)
			So(resp.Items[0].FreeCredit, ShouldEqual, 600)
			So(resp.Items[1].FreeCredit, ShouldEqual, 600)
			So(resp.HasFreeCreditQuota, ShouldEqual, true)
		})

		Convey("should succeed 2", func() {
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters1, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap1, true).
				Return(resultMap1, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap1).Return(false)

			resp, err := svc.GetAllSpendLimits(ctx, req)
			So(err, ShouldBeNil)
			So(resp.Total, ShouldEqual, 1)
			So(resp.Items, ShouldHaveLength, 1)
			So(resp.Items[0].ClusterId, ShouldEqual, 1000)
			So(resp.Items[0].SpendLimit, ShouldEqual, 2000)
			So(resp.Items[0].FreeCredit, ShouldEqual, 600)
			So(resp.HasFreeCreditQuota, ShouldEqual, false)
		})
	})
}

func Test_ServerlessBillingService_GetSpendLimit(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.GetSpendLimitReq{OrgId: 1, ClusterId: 1001}
	cluster := &tidbmgmtservicev1.Cluster{
		ClusterId:      1001,
		ClusterName:    "cluster1",
		OrganizationId: 1,
		ClusterInfo: &tidbmgmtservicev1.Cluster_ServerlessCluster{
			ServerlessCluster: &globalserverlessv1.ServerlessServiceCluster{
				Cluster: &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
					AutoScaling: &regionalserverlessv1.ServerlessRegionalServiceClusterInfo_AutoScaling{
						MinRcu: 0,
						MaxRcu: 0,
					},
				},
			},
		},
	}

	Convey("Test_GetSpendLimit", t, func() {
		Convey("should return err if list clusters failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(nil, e)
			_, err := svc.GetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if it's essential plan", func() {
			cluster.GetServerlessCluster().GetCluster().AutoScaling.MinRcu = 2000
			cluster.GetServerlessCluster().GetCluster().AutoScaling.MaxRcu = 4000
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			_, err := svc.GetSpendLimit(ctx, req)
			So(err.Error(), ShouldContainSubstring, errorcode.ErrMsgInvalidClusterPlan)
			cluster.GetServerlessCluster().GetCluster().AutoScaling.MinRcu = 0
			cluster.GetServerlessCluster().GetCluster().AutoScaling.MaxRcu = 0
		})

		Convey("should return err if get Spending Limit failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(nil, e)
			_, err := svc.GetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get unmarshalable bizdata", func() {
			setting := &facade.SpendLimitSetting{BizData: `{unmarshalable}`}
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			_, err := svc.GetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should succeed", func() {
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			resp, err := svc.GetSpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1001)
			So(resp.SpendLimit, ShouldEqual, 500)
			So(resp.FreeCredit, ShouldEqual, 600)
		})
	})
}

func Test_ServerlessBillingService_ModifySpendLimit(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1001, SpendLimit: 100, Region: "us-east-1"}

	cluster := &tidbmgmtservicev1.Cluster{
		ClusterId:      1001,
		ClusterName:    "cluster1",
		OrganizationId: 1,
		CloudProvider:  "aws",
		Region:         "us-east-1",
	}
	spInfo := &facade.SpendLimitInfo{Costs: decimal.NewFromFloat(0.6)}
	tenantBillingCheck := &facade.ActionCheckResp{
		Pass: true,
	}
	Convey("Test_ModifySpendLimit", t, func() {
		Convey("should return err if exceeds max value", func() {
			req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1002, SpendLimit: 100000000}
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should allow value above default max when org has override", func() {
			prev := config.Global().BizConfig.SpendLimitMaxOverrideTenants
			config.Global().BizConfig.SpendLimitMaxOverrideTenants = map[uint64]uint32{1: 50000000}
			defer func() { config.Global().BizConfig.SpendLimitMaxOverrideTenants = prev }()

			overrideReq := &serverless.SetSpendLimitReq{
				OrgId: 1, ClusterId: 1001, SpendLimit: 2000000, Region: "us-east-1",
			}
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingFacade.EXPECT().ModifySpendLimit(gomock.Any(), gomock.Any()).Return(nil)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(&facade.Tenant{ID: 1}, nil)
			clusterTidbPool.EXPECT().Save(gomock.Any(), gomock.Any()).Return(nil)
			resp, err := svc.ModifySpendLimit(ctx, overrideReq)
			So(err, ShouldBeNil)
			So(resp.SpendLimit, ShouldEqual, 2000000)
		})

		Convey("should return err when exceeds override max", func() {
			prev := config.Global().BizConfig.SpendLimitMaxOverrideTenants
			config.Global().BizConfig.SpendLimitMaxOverrideTenants = map[uint64]uint32{1: 50000000}
			defer func() { config.Global().BizConfig.SpendLimitMaxOverrideTenants = prev }()

			overrideReq := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1001, SpendLimit: 100000000}
			_, err := svc.ModifySpendLimit(ctx, overrideReq)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if billing action check is false", func() {
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(
				&facade.ActionCheckResp{
					Pass: false,
				}, nil)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get cluster failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(nil, e)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if billing action check failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(nil, e)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get Spending Limit failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(nil, e)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get unmarshalable bizdata", func() {
			setting := &facade.SpendLimitSetting{BizData: `{unmarshalable}`}
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return directly if no changes", func() {
			req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1001, SpendLimit: 500, Region: "us-east-1"}
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}

			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			clusterTidbPool.EXPECT().Save(gomock.Any(), gomock.Any()).Return(nil)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldBeNil)
		})

		Convey("should return err if get Spending Limit info failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}

			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(nil, e)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if less than min value", func() {
			req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1001, SpendLimit: 1, Region: "us-east-1"}
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}

			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if modify Spending Limit failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}

			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingFacade.EXPECT().ModifySpendLimit(gomock.Any(), gomock.Any()).Return(e)
			_, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("return nil even if save cluster tidb pool failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}

			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingFacade.EXPECT().ModifySpendLimit(gomock.Any(), gomock.Any()).DoAndReturn(
				func(ctx context.Context, s *facade.SpendLimitSetting) error {
					So(s.Limit, ShouldEqual, decimal.NewFromInt(8))
					return nil
				},
			)
			clusterTidbPool.EXPECT().Save(gomock.Any(), gomock.Any()).Return(e)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(nil, nil)
			resp, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1001)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 700)
		})

		Convey("should succeed", func() {
			setting := &facade.SpendLimitSetting{
				BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`,
			}

			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingFacade.EXPECT().ModifySpendLimit(gomock.Any(), gomock.Any()).DoAndReturn(
				func(ctx context.Context, s *facade.SpendLimitSetting) error {
					So(s.Limit, ShouldEqual, decimal.NewFromInt(8))
					return nil
				},
			)
			clusterTidbPool.EXPECT().Save(gomock.Any(), gomock.Any()).Return(nil)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(nil, nil)
			resp, err := svc.ModifySpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1001)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 700)
		})
	})
}

func Test_ServerlessBillingService_SetSpendLimit(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1002, SpendLimit: 100, Region: "us-east-1"}
	cluster := &tidbmgmtservicev1.Cluster{
		ClusterId:      1002,
		ClusterName:    "cluster1",
		OrganizationId: 1,
		CloudProvider:  "aws",
		Region:         "us-east-1",
	}
	clusterMap := map[uint64]*tidbmgmtservicev1.Cluster{
		1000: {ClusterId: 1000, ClusterName: "cluster0", OrganizationId: 1},
		1001: {ClusterId: 1001, ClusterName: "cluster1", OrganizationId: 1},
	}
	clusters := []*tidbmgmtservicev1.Cluster{
		{ClusterId: 1000, ClusterName: "cluster0", OrganizationId: 1},
		{ClusterId: 1001, ClusterName: "cluster1", OrganizationId: 1},
	}
	skuPriceMap := map[constant.SkuType]*facade.BillingCharge{
		constant.SkuTypeRowStorage:    {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeColumnStorage: {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeRequestUnit:   {RawPrice: decimal.NewFromFloat(0.0000001)},
	}
	resultMap := map[uint64]*facade.ServerlessSpendLimitBizData{
		1000: {
			FreeCredits:        decimal.NewFromInt(6),
			InvoiceLimit:       decimal.NewFromInt(0),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
		1001: {
			FreeCredits:        decimal.NewFromInt(7),
			InvoiceLimit:       decimal.NewFromInt(5),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
	}
	tenantBillingCheck := &facade.ActionCheckResp{
		Pass: true,
	}

	Convey("Test_SetSpendLimit", t, func() {
		Convey("should return err if exceeds max value", func() {
			req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1002, SpendLimit: 100000000}
			_, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should allow value above default max when org has override", func() {
			prev := config.Global().BizConfig.SpendLimitMaxOverrideTenants
			config.Global().BizConfig.SpendLimitMaxOverrideTenants = map[uint64]uint32{1: 50000000}
			defer func() { config.Global().BizConfig.SpendLimitMaxOverrideTenants = prev }()

			overrideReq := &serverless.SetSpendLimitReq{
				OrgId: 1, ClusterId: 1002, SpendLimit: 2000000, Region: "us-east-1",
			}
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1002)).Return(nil, nil)
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(false)
			billingFacade.EXPECT().SetSpendLimit(gomock.Any(), gomock.Any()).Return(nil)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(&facade.Tenant{ID: 1}, nil)
			resp, err := svc.SetSpendLimit(ctx, overrideReq)
			So(err, ShouldBeNil)
			So(resp.SpendLimit, ShouldEqual, 2000000)
		})

		Convey("should return err if billing action check is false", func() {
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(
				&facade.ActionCheckResp{
					Pass: false,
				}, nil)
			_, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return nil if get Spending Limit settings failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1002)).Return(nil, nil)
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(nil, e)
			billingFacade.EXPECT().SetSpendLimit(gomock.Any(), gomock.Any()).Return(nil)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(&facade.Tenant{ID: 1}, nil)
			// accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(&facade.Tenant{ID: 1}, nil)
			_, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldBeNil)
		})

		Convey("should return err if has no free quotas", func() {
			req := &serverless.SetSpendLimitReq{OrgId: 1, ClusterId: 1002, SpendLimit: 0, Region: "us-east-1"}
			// billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1002)).Return(nil, nil)
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(false)
			_, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if set Spending Limit failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1002)).Return(nil, nil)
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(true)
			billingFacade.EXPECT().SetSpendLimit(gomock.Any(), gomock.Any()).Return(e)
			// accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(&facade.Tenant{ID: 1}, nil)
			_, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should succeed w/ free credit", func() {
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1002)).Return(nil, nil)
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(true)
			billingFacade.EXPECT().SetSpendLimit(gomock.Any(), gomock.Any()).Return(nil)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(&facade.Tenant{ID: 1}, nil)
			resp, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1002)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 700)
		})

		Convey("should succeed w/o free credit", func() {
			billingFacade.EXPECT().BillingActionCheck(gomock.Any(), uint64(1)).Return(tenantBillingCheck, nil)
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1002)).Return(nil, nil)
			mgmtFacade.EXPECT().
				ListAllClustersInOrg(gomock.Any(), uint64(1), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(clusters, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), clusterMap, true).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(false)
			billingFacade.EXPECT().SetSpendLimit(gomock.Any(), gomock.Any()).Return(nil)
			accountFacade.EXPECT().GetTenant(gomock.Any(), uint64(1)).Return(nil, nil)
			resp, err := svc.SetSpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1002)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 0)
		})
	})
}

func Test_ServerlessBillingService_PredictSpendLimit(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.PredictSpendLimitReq{OrgId: 1, SpendLimit: 100, Region: "regions/aws-us-east-1"}

	skuPriceMap := map[constant.SkuType]*facade.BillingCharge{
		constant.SkuTypeRowStorage:    {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeColumnStorage: {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeRequestUnit:   {RawPrice: decimal.NewFromFloat(0.0000001)},
	}
	resultMap := map[uint64]*facade.ServerlessSpendLimitBizData{
		1000: {
			FreeCredits:        decimal.NewFromInt(6),
			InvoiceLimit:       decimal.NewFromInt(0),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
		1001: {
			FreeCredits:        decimal.NewFromInt(7),
			InvoiceLimit:       decimal.NewFromInt(5),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
	}

	Convey("Test_PredictSpendLimit", t, func() {
		Convey("should return err if exceeds max value", func() {
			req := &serverless.PredictSpendLimitReq{OrgId: 1, Region: "regions/aws-us-east-1", SpendLimit: 100000000}
			_, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if region is invalid", func() {
			req := &serverless.PredictSpendLimitReq{OrgId: 1, Region: "us-east-1", SpendLimit: 100}
			_, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get Spending Limit settings failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), gomock.Any(), false).
				Return(nil, e)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), gomock.Any()).Return(true)
			_, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldBeNil)
		})

		Convey("should return err if get sku price failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			billingService.EXPECT().GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).Return(nil, e)
			_, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should succeed w/ free credit", func() {
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), gomock.Any(), false).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(true)
			resp, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 700)
			So(resp.MaxRequestUnits, ShouldEqual, 60000000)
			So(resp.MaxColumnStorage, ShouldEqual, 10240)
			So(resp.MaxRowStorage, ShouldEqual, 10240)
			So(resp.FreeRequestUnits, ShouldEqual, 50000000)
			So(resp.FreeRowStorage, ShouldEqual, 5120)
			So(resp.FreeColumnStorage, ShouldEqual, 5120)
		})

		Convey("should succeed w/o free credit", func() {
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), gomock.Any(), false).
				Return(resultMap, nil)
			billingService.EXPECT().HasFreeCreditQuota(gomock.Any(), resultMap).Return(false)
			resp, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 0)
			So(resp.MaxRequestUnits, ShouldEqual, 10000000)
			So(resp.MaxColumnStorage, ShouldEqual, 5120)
			So(resp.MaxRowStorage, ShouldEqual, 5120)
			So(resp.FreeRequestUnits, ShouldEqual, 0)
			So(resp.FreeRowStorage, ShouldEqual, 0)
			So(resp.FreeColumnStorage, ShouldEqual, 0)
		})
	})
}

func Test_ServerlessBillingService_PredictClusterSpendLimit(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.PredictClusterSpendLimitReq{OrgId: 1, ClusterId: 1001, SpendLimit: 100, Region: "us-east-1"}
	cluster := &tidbmgmtservicev1.Cluster{
		ClusterId:      1001,
		ClusterName:    "cluster1",
		OrganizationId: 1,
		CloudProvider:  "aws",
		Region:         "us-east-1",
	}
	skuPriceMap := map[constant.SkuType]*facade.BillingCharge{
		constant.SkuTypeRowStorage:    {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeColumnStorage: {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeRequestUnit:   {RawPrice: decimal.NewFromFloat(0.0000001)},
	}
	resultMap := map[uint64]*facade.ServerlessSpendLimitBizData{
		1000: {
			FreeCredits:        decimal.NewFromInt(6),
			InvoiceLimit:       decimal.NewFromInt(0),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
		1001: {
			FreeCredits:        decimal.NewFromInt(7),
			InvoiceLimit:       decimal.NewFromInt(5),
			ActiveTotalCredits: decimal.NewFromInt(6),
		},
	}

	Convey("Test_PredictSpendLimit", t, func() {
		Convey("should return err if exceeds max value", func() {
			req := &serverless.PredictSpendLimitReq{OrgId: 1, SpendLimit: 100000000}
			_, err := svc.PredictSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if cluster not found", func() {
			req := &serverless.PredictClusterSpendLimitReq{OrgId: 1, ClusterId: 1002}
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1002), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(nil, bizerr.New(0, "not found"))
			_, err := svc.PredictClusterSpendLimit(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should succeed", func() {
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL).
				Return(cluster, nil)
			billingService.EXPECT().
				GetTenantSpendLimitsSettings(gomock.Any(), uint64(1), gomock.Any(), false).
				Return(resultMap, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			resp, err := svc.PredictClusterSpendLimit(ctx, req)
			So(err, ShouldBeNil)
			So(resp.SpendLimit, ShouldEqual, 100)
			So(resp.FreeCredit, ShouldEqual, 600)
			So(resp.MaxRequestUnits, ShouldEqual, 60000000)
			So(resp.MaxColumnStorage, ShouldEqual, 10240)
			So(resp.MaxRowStorage, ShouldEqual, 10240)
			So(resp.FreeRequestUnits, ShouldEqual, 50000000)
			So(resp.FreeRowStorage, ShouldEqual, 5120)
			So(resp.FreeColumnStorage, ShouldEqual, 5120)
		})
	})
}

func Test_ServerlessBillingService_GetSpendLimitDetails(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.GetSpendLimitDetailsReq{OrgId: 1, ClusterId: 1001}
	cluster := &tidbmgmtservicev1.Cluster{
		ClusterId:      1001,
		ClusterName:    "cluster0",
		OrganizationId: 1,
		CloudProvider:  "aws",
		Region:         "us-east-1",
		ClusterInfo: &tidbmgmtservicev1.Cluster_ServerlessCluster{
			ServerlessCluster: &globalserverlessv1.ServerlessServiceCluster{
				UserStatus: globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_NORMAL,
			},
		},
	}
	skuUsage := map[constant.SkuType]float64{constant.SkuTypeRowStorage: 1280}
	skuPriceMap := map[constant.SkuType]*facade.BillingCharge{
		constant.SkuTypeRowStorage:    {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeColumnStorage: {RawPrice: decimal.NewFromFloat(0.0001953125)},
		constant.SkuTypeRequestUnit:   {RawPrice: decimal.NewFromFloat(0.0000001)},
	}
	setting := &facade.SpendLimitSetting{BizData: `{"free_credits": 7, "invoice_limit": 5, "active_total_credits": 6}`}
	spInfo := &facade.SpendLimitInfo{Costs: decimal.NewFromFloat(0.6)}
	/*
		skuSpent := map[constant.SkuType]*facade.ServerlessSkuCost{
			constant.SkuTypeRowStorage: {
				SpendLimitRawPrice: decimal.NewFromInt(0),
				FreeCreditRawPrice: decimal.NewFromFloat(0.2),
			},
			constant.SkuTypeColumnStorage: {
				SpendLimitRawPrice: decimal.NewFromInt(0),
				FreeCreditRawPrice: decimal.NewFromInt(0),
			},
			constant.SkuTypeRequestUnit: {
				TotalCostRawPrice:  decimal.NewFromFloat(0.4),
				SpendLimitRawPrice: decimal.NewFromInt(0),
				FreeCreditRawPrice: decimal.NewFromFloat(0.4),
			},
		}
		skuTypes := []constant.SkuType{constant.SkuTypeRequestUnit, constant.SkuTypeColumnStorage, constant.SkuTypeRowStorage}
	*/
	skuSpent := &facade.CompleteCostExplorerResult{
		InternalCosts: facade.CostExplorerResult{
			CreditsCosts: facade.Costs{
				KindCategoryUnitsCost: map[string]facade.CategoryItemCosts{
					"TiDB Cloud Starter": {
						CategoryItemUnitsCosts: map[string]facade.CategoryCosts{
							"Request Units": {
								Order: 1,
								Price: facade.Price{
									RawPrice: decimal.NewFromFloat(0.4),
								},
							},
							"Row-based Storage": {
								Order: 1,
								Price: facade.Price{
									RawPrice: decimal.NewFromFloat(0.2),
								},
							},
						},
					},
				},
			},
		},
	}

	Convey("Test_GetSpendLimitDetails", t, func() {
		Convey("should return err if get cluster failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(nil, e)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get Spending Limit failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(nil, e)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get unmarshalable bizdata", func() {
			setting := &facade.SpendLimitSetting{BizData: `{unmarshalable}`}
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get Spending Limit info failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingService.EXPECT().GetSkuStorageUsage(gomock.Any(), cluster).Return(skuUsage, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(nil, e)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get sku unit prices failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingService.EXPECT().GetSkuStorageUsage(gomock.Any(), cluster).Return(skuUsage, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingService.EXPECT().GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).Return(nil, e)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if calculate cluster costs failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingService.EXPECT().GetSkuStorageUsage(gomock.Any(), cluster).Return(skuUsage, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				CalculateClusterCostThisMonth(gomock.Any(), uint64(1), "1001", constant.ClusterTypeCluster, nil, "", "").
				Return(nil, e)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return err if get storage usage failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingService.EXPECT().GetSkuStorageUsage(gomock.Any(), cluster).Return(skuUsage, e)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				CalculateClusterCostThisMonth(gomock.Any(), uint64(1), "1001", constant.ClusterTypeCluster, nil, "", "").
				Return(skuSpent, nil)
			billingService.EXPECT().
				GetCostExplorerServerlessSkuCost(skuSpent).
				Return(&facade.BillingSubCostExplorerResult{
					RuFreeCost:        decimal.NewFromFloat(0.4),
					RowStoreFreeCost:  decimal.NewFromFloat(0.2),
					ColumnarFreeCost:  decimal.NewFromFloat(0),
					RuSpentCost:       decimal.NewFromFloat(0),
					RowStoreSpentCost: decimal.NewFromFloat(0),
					ColumnarSpentCost: decimal.NewFromFloat(0),
				})
			clusterTidbPool.EXPECT().Query(gomock.Any(), gomock.Any()).Return(nil, nil)
			_, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should succeed even if not normal", func() {
			// cluster := &core.Cluster{Id: 1000, Name: "cluster0", TenantId: 1, RegionName: "us-east-1", Status: core.ClusterStatus_CLUSTER_STATUS_LAUNCHING}
			cluster := &tidbmgmtservicev1.Cluster{
				ClusterId:      1000,
				ClusterName:    "cluster0",
				OrganizationId: 1,
				Region:         "us-east-1",
				ClusterInfo: &tidbmgmtservicev1.Cluster_ServerlessCluster{
					ServerlessCluster: &globalserverlessv1.ServerlessServiceCluster{
						UserStatus: globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_CREATING,
					},
				},
			}
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			// billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			resp, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1001)
			So(resp.SpendLimit, ShouldEqual, 0)
			So(resp.FreeCredit, ShouldEqual, 0)
			So(resp.SpentTotal, ShouldEqual, 0)
			So(resp.SpentFree.RequestUnit, ShouldEqual, 0)
			So(resp.SpentFree.RowStorage, ShouldEqual, 0)
			So(resp.SpentFree.ColumnStorage, ShouldEqual, 0)
			So(resp.SpentPaid.RequestUnit, ShouldEqual, 0)
			So(resp.SpentPaid.RowStorage, ShouldEqual, 0)
			So(resp.SpentPaid.ColumnStorage, ShouldEqual, 0)
			So(resp.Usages.RequestUnit, ShouldEqual, 0)
			So(resp.Usages.RowStorage, ShouldEqual, 0)
			So(resp.Usages.ColumnStorage, ShouldEqual, 0)
		})

		Convey("should succeed", func() {
			mgmtFacade.EXPECT().
				GetCluster(gomock.Any(), uint64(1001), tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_BASIC).
				Return(cluster, nil)
			billingService.EXPECT().GetSkuStorageUsage(gomock.Any(), cluster).Return(skuUsage, nil)
			billingFacade.EXPECT().GetSpendLimit(gomock.Any(), uint64(1), uint64(1001)).Return(setting, nil)
			billingFacade.EXPECT().GetSpendLimitInfo(gomock.Any(), setting).Return(spInfo, nil)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(skuPriceMap, nil)
			billingService.EXPECT().
				CalculateClusterCostThisMonth(gomock.Any(), uint64(1), "1001", constant.ClusterTypeCluster, nil, "", "").
				Return(skuSpent, nil)
			resp, err := svc.GetSpendLimitDetails(ctx, req)
			So(err, ShouldBeNil)
			So(resp.ClusterId, ShouldEqual, 1001)
			So(resp.SpendLimit, ShouldEqual, 500)
			So(resp.FreeCredit, ShouldEqual, 600)
			So(resp.SpentTotal, ShouldEqual, 60)
			So(resp.SpentFree.RequestUnit, ShouldEqual, 40)
			So(resp.SpentFree.RowStorage, ShouldEqual, 20)
			So(resp.SpentFree.ColumnStorage, ShouldEqual, 0)
			So(resp.SpentPaid.RequestUnit, ShouldEqual, 0)
			So(resp.SpentPaid.RowStorage, ShouldEqual, 0)
			So(resp.SpentPaid.ColumnStorage, ShouldEqual, 0)
			So(resp.Usages.RequestUnit, ShouldEqual, 4000000)
			So(resp.Usages.RowStorage, ShouldEqual, 1280)
			So(resp.Usages.ColumnStorage, ShouldEqual, 0)
		})
	})
}

func Test_ServerlessBillingService_GetResourceLimitMode(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	req := &serverless.GetResourceLimitModeReq{OrgId: 1, ClusterId: 1001}

	Convey("Test_GetSpendLimitDetails", t, func() {
		Convey("should return err if get cluster failed", func() {
			e := bizerr.New(bizerr.ErrCodeUnknown, "failed")
			clusterRepo.EXPECT().GetClusterByID(gomock.Any(), uint64(1001)).Return(nil, e)
			_, err := svc.GetResourceLimitMode(ctx, req)
			So(err, ShouldNotBeNil)
		})

		Convey("should return unspecified if cluster mode unknown", func() {
			clusterRepo.EXPECT().GetClusterByID(gomock.Any(), uint64(1001)).Return(&entity.Cluster{
				ID: 1001, RuMode: constant.ClusterRUMode(""),
			}, nil)
			resp, err := svc.GetResourceLimitMode(ctx, req)
			So(err, ShouldBeNil)
			So(resp.RuMode, ShouldEqual, serverless.GetResourceLimitModeResp_UNSPECIFIED)
		})

		Convey("should succeed", func() {
			clusterRepo.EXPECT().GetClusterByID(gomock.Any(), uint64(1001)).Return(&entity.Cluster{
				ID: 1001, RuMode: constant.ClusterRUModeStandard,
			}, nil)
			resp, err := svc.GetResourceLimitMode(ctx, req)
			So(err, ShouldBeNil)
			So(resp.RuMode, ShouldEqual, serverless.GetResourceLimitModeResp_STANDARD)
		})
	})
}

func Test_ServerlessBillingService_PredictCost(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	billingFacade := mock_facade.NewMockBillingFacade(ctrl)
	clusterRepo := mock_repo.NewMockClusterRepo(ctrl)
	clusterTidbPool := mock_repo.NewMockClusterTidbPoolRepo(ctrl)
	accountFacade := mock_facade.NewMockAccountFacade(ctrl)
	o11yFacade := mock_facade.NewMockO11yFacade(ctrl)
	billingService := mock_service.NewMockBillingService(ctrl)
	mgmtFacade := mock_facade.NewMockMgmtSvcFacade(ctrl)
	svc := NewBillingService(
		billingFacade,
		clusterRepo,
		clusterTidbPool,
		billingService,
		accountFacade,
		o11yFacade,
		nil,
		mgmtFacade,
	)

	Convey("Test_PredictCost", t, func() {
		Convey("should parse region in unified formats", func() {
			testCases := []struct {
				inputRegion    string
				expectedRegion string
			}{
				{inputRegion: "regions/aws-us-east-1", expectedRegion: "us-east-1"},
				{inputRegion: "aws-us-east-1", expectedRegion: "us-east-1"},
				{inputRegion: "regions/aws-us-west-2", expectedRegion: "us-west-2"},
				{inputRegion: "aws-us-west-2", expectedRegion: "us-west-2"},
				{inputRegion: "regions/azure-eastus2", expectedRegion: "eastus2"},
				{inputRegion: "azure-eastus2", expectedRegion: "eastus2"},
				{inputRegion: "regions/gcp-us-central1", expectedRegion: "us-central1"},
				{inputRegion: "gcp-us-central1", expectedRegion: "us-central1"},
				{inputRegion: "regions/gcp-us-west1", expectedRegion: "us-west1"},
				{inputRegion: "gcp-us-west1", expectedRegion: "us-west1"},
			}

			for _, tc := range testCases {
				billingService.EXPECT().
					GetSkuUnitPrices(gomock.Any(), "aws", tc.expectedRegion, uint64(1)).
					Return(map[constant.SkuType]*facade.BillingCharge{
						constant.SkuTypeRequestCapacityUnit: {RawPrice: decimal.NewFromFloat(0.01)},
					}, nil)

				resp, err := svc.PredictCost(ctx, &serverless.PredictCostReq{
					CloudProvider: "aws",
					Region:        tc.inputRegion,
					Rcu:           1,
				})
				So(err, ShouldBeNil)
				So(resp.Region, ShouldEqual, tc.expectedRegion)
			}
		})

		Convey("should use essential sku when service plan is empty", func() {
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(map[constant.SkuType]*facade.BillingCharge{
					constant.SkuTypeRequestCapacityUnit: {RawPrice: decimal.NewFromFloat(0.01)},
				}, nil)

			resp, err := svc.PredictCost(ctx, &serverless.PredictCostReq{
				CloudProvider: "aws",
				Region:        "regions/aws-us-east-1",
				Rcu:           1,
			})
			So(err, ShouldBeNil)
			So(resp.RcuCostHourly, ShouldEqual, "0.60")
			So(resp.RcuCostMonthly, ShouldEqual, "438.00")
		})

		Convey("should not call premium sku path when service plan is not premium", func() {
			billingFacade.EXPECT().CalcFee(gomock.Any(), gomock.Any()).Times(0)
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "aws", "us-east-1", uint64(1)).
				Return(map[constant.SkuType]*facade.BillingCharge{
					constant.SkuTypeRequestCapacityUnit: {RawPrice: decimal.NewFromFloat(0.01)},
				}, nil)

			resp, err := svc.PredictCost(ctx, &serverless.PredictCostReq{
				CloudProvider: "aws",
				Region:        "regions/aws-us-east-1",
				Rcu:           1,
				ServicePlan:   "starter",
			})
			So(err, ShouldBeNil)
			So(resp.RcuCostHourly, ShouldEqual, "0.60")
			So(resp.RcuCostMonthly, ShouldEqual, "438.00")
		})

		Convey("should use premium sku when service plan is premium", func() {
			billingFacade.EXPECT().
				CalcFee(gomock.Any(), gomock.Any()).
				Return(map[string]*facade.BillingCharge{
					"rcu:aws:us-east-1:premium:all:total": {RawPrice: decimal.NewFromFloat(0.02)},
				}, nil)

			resp, err := svc.PredictCost(ctx, &serverless.PredictCostReq{
				CloudProvider: "aws",
				Region:        "regions/aws-us-east-1",
				Rcu:           1,
				ServicePlan:   "premium",
			})
			So(err, ShouldBeNil)
			So(resp.RcuCostHourly, ShouldEqual, "1.20")
			So(resp.RcuCostMonthly, ShouldEqual, "876.00")
		})

		Convey("should resolve gcp provider and region", func() {
			billingService.EXPECT().
				GetSkuUnitPrices(gomock.Any(), "gcp", "us-central1", uint64(1)).
				Return(map[constant.SkuType]*facade.BillingCharge{
					constant.SkuTypeRequestCapacityUnit: {RawPrice: decimal.NewFromFloat(0.01)},
				}, nil)

			resp, err := svc.PredictCost(ctx, &serverless.PredictCostReq{
				CloudProvider: "gcp",
				Region:        "regions/gcp-us-central1",
				Rcu:           1,
			})
			So(err, ShouldBeNil)
			So(resp.Region, ShouldEqual, "us-central1")
			So(resp.RcuCostHourly, ShouldEqual, "0.60")
		})
	})
}

func Test_ServerlessBillingService_GetPricingDetails(t *testing.T) {
	ctx := context.Background()
	svc := NewBillingService(nil, nil, nil, nil, nil, nil, nil, nil)
	original := config.Global().WorkerConfig.RegionalConfig
	defer func() {
		config.Global().WorkerConfig.RegionalConfig = original
	}()
	config.Global().WorkerConfig.RegionalConfig = []config.RegionalConfig{
		{
			Provider:           config.ProviderGCP,
			Region:             "us-central1",
			RUPrice:            "0.10",
			RCUPrice:           "0.20",
			RowStoragePrice:    "0.30",
			ColumnStoragePrice: "0.40",
		},
		{
			Provider:           config.ProviderAWS,
			Region:             "us-east-1",
			RUPrice:            "0.11",
			RCUPrice:           "0.21",
			RowStoragePrice:    "0.31",
			ColumnStoragePrice: "0.41",
		},
		{
			Provider:           config.ProviderAliCloud,
			Region:             "cn-beijing",
			RUPrice:            "0.12",
			RCUPrice:           "0.22",
			RowStoragePrice:    "0.32",
			ColumnStoragePrice: "0.42",
		},
	}

	Convey("Test_GetPricingDetails", t, func() {
		testCases := []struct {
			provider string
			region   string
			wantRU   string
		}{
			{provider: "gcp", region: "regions/gcp-us-central1", wantRU: "0.10"},
			{provider: "gcp", region: "gcp-us-central1", wantRU: "0.10"},
			{provider: "gcp", region: "us-central1", wantRU: "0.10"},
			{provider: "aws", region: "regions/aws-us-east-1", wantRU: "0.11"},
			{provider: "aws", region: "us-east-1", wantRU: "0.11"},
			{provider: "alicloud", region: "regions/alicloud-cn-beijing", wantRU: "0.12"},
			{provider: "alicloud", region: "cn-beijing", wantRU: "0.12"},
		}
		for _, tc := range testCases {
			resp, err := svc.GetPricingDetails(ctx, &serverless.GetPricingDetailsReq{
				CloudProvider: tc.provider,
				Region:        tc.region,
			})
			So(err, ShouldBeNil)
			So(resp.RequestUnits, ShouldEqual, tc.wantRU)
			So(resp.CloudProvider, ShouldEqual, tc.provider)
		}

		_, err := svc.GetPricingDetails(ctx, &serverless.GetPricingDetailsReq{
			CloudProvider: "gcp",
			Region:        "regions/gcp-europe-west1",
		})
		So(err, ShouldNotBeNil)
	})
}
