MM-43529 - start freemium trial (#20163)

* MM-43529 - start freemium trial

* change the method to put

* Add unit testing for the request-trial-endpoint

* use correct require

* add the translation texts

* fix texts

* remove unnecessary log

* change response status code to forbidden when non cloud

Co-authored-by: Pablo Velez Vidal <pablo.velez@mattermost.com>
Этот коммит содержится в:
Pablo Andrés Vélez Vidal
2022-05-18 10:19:38 +02:00
коммит произвёл GitHub
родитель 617007a56d
Коммит f08d65909f
6 изменённых файлов: 187 добавлений и 0 удалений

Просмотреть файл

@@ -41,6 +41,9 @@ func (api *API) InitCloud() {
api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf", api.APISessionRequired(getSubscriptionInvoicePDF)).Methods("GET")
api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(changeSubscription)).Methods("PUT")
// GET /api/v4/cloud/request-trial
api.BaseRoutes.Cloud.Handle("/request-trial", api.APISessionRequired(requestCloudTrial)).Methods("PUT")
// POST /api/v4/cloud/webhook
api.BaseRoutes.Cloud.Handle("/webhook", api.CloudAPIKeyRequired(handleCWSWebhook)).Methods("POST")
}
@@ -121,6 +124,43 @@ func changeSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write(json)
}
func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
return
}
if !c.App.Config().FeatureFlags.CloudFree {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.cloud_free_feature_flag_off_error", nil, "", http.StatusInternalServerError)
return
}
currentSubscription, appErr := c.App.Cloud().GetSubscription(c.AppContext.Session().UserId)
if appErr != nil {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, appErr.Error(), http.StatusInternalServerError)
return
}
changedSub, err := c.App.Cloud().RequestCloudTrial(c.AppContext.Session().UserId, currentSubscription.ID)
if err != nil {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
json, err := json.Marshal(changedSub)
if err != nil {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
w.Write(json)
}
func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
c.Err = model.NewAppError("Api4.getCloudProducts", "api.cloud.license_error", nil, "", http.StatusNotImplemented)

Просмотреть файл

@@ -141,3 +141,108 @@ func Test_getCloudLimits(t *testing.T) {
require.Equal(t, *mockLimits.Messages.History, *limits.Messages.History)
})
}
func Test_requestTrial(t *testing.T) {
subscription := &model.Subscription{
ID: "MySubscriptionID",
CustomerID: "MyCustomer",
ProductID: "SomeProductId",
AddOns: []string{},
StartAt: 1000000000,
EndAt: 2000000000,
CreateAt: 1000000000,
Seats: 10,
DNS: "some.dns.server",
IsPaidTier: "false",
}
t.Run("NON Admin users are UNABLE to request the trial", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
th.App.ReloadConfig()
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
cloud := mocks.CloudInterface{}
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything).Return(subscription, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = &cloud
subscriptionChanged, r, err := th.Client.RequestCloudTrial()
t.Logf("\n\nresp %#v, \n\n r: %v\n\n, err: %v\n\n", subscriptionChanged, r, err)
require.Error(t, err)
require.Nil(t, subscriptionChanged)
require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden")
})
t.Run("cloudFree feature flag FALSE and Admin user are UNABLE to request the trial", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "false")
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
th.App.ReloadConfig()
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
cloud := mocks.CloudInterface{}
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything).Return(subscription, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = &cloud
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial()
require.Error(t, err)
require.Nil(t, subscriptionChanged)
require.Equal(t, http.StatusInternalServerError, r.StatusCode, "Expected 500 Internal Server Error")
})
t.Run("cloudFree feature flag TRUE and ADMIN user are ABLE to request the trial", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
th.App.ReloadConfig()
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
cloud := mocks.CloudInterface{}
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything).Return(subscription, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = &cloud
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial()
require.NoError(t, err)
require.Equal(t, subscriptionChanged, subscription)
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
})
}

Просмотреть файл

@@ -25,6 +25,8 @@ type CloudInterface interface {
ChangeSubscription(userID, subscriptionID string, subscriptionChange *model.SubscriptionChange) (*model.Subscription, error)
RequestCloudTrial(userID, subscriptionID string) (*model.Subscription, error)
// GetLicenseRenewalStatus checks on the portal whether it is possible to use token to renew a license
GetLicenseRenewalStatus(userID, token string) error
InvalidateCaches() error

Просмотреть файл

@@ -247,6 +247,29 @@ func (_m *CloudInterface) InvalidateCaches() error {
return r0
}
// RequestCloudTrial provides a mock function with given fields: userID, subscriptionID
func (_m *CloudInterface) RequestCloudTrial(userID string, subscriptionID string) (*model.Subscription, error) {
ret := _m.Called(userID, subscriptionID)
var r0 *model.Subscription
if rf, ok := ret.Get(0).(func(string, string) *model.Subscription); ok {
r0 = rf(userID, subscriptionID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Subscription)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(userID, subscriptionID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateCloudCustomer provides a mock function with given fields: userID, customerInfo
func (_m *CloudInterface) UpdateCloudCustomer(userID string, customerInfo *model.CloudCustomerInfo) (*model.CloudCustomer, error) {
ret := _m.Called(userID, customerInfo)

Просмотреть файл

@@ -463,6 +463,10 @@
"id": "api.cloud.app_error",
"translation": "Internal error during cloud api request."
},
{
"id": "api.cloud.cloud_free_feature_flag_off_error",
"translation": "CloudFree feature flag is off."
},
{
"id": "api.cloud.cws_webhook_event_missing_error",
"translation": "Webhook event was not handled. Either it is missing or it is not valid."

Просмотреть файл

@@ -7784,6 +7784,19 @@ func (c *Client4) ConfirmCustomerPayment(confirmRequest *ConfirmPaymentMethodReq
return BuildResponse(r), nil
}
func (c *Client4) RequestCloudTrial() (*Subscription, *Response, error) {
r, err := c.DoAPIPut(c.cloudRoute()+"/request-trial", "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var subscription *Subscription
json.NewDecoder(r.Body).Decode(&subscription)
return subscription, BuildResponse(r), nil
}
func (c *Client4) GetCloudCustomer() (*CloudCustomer, *Response, error) {
r, err := c.DoAPIGet(c.cloudRoute()+"/customer", "")
if err != nil {