MM 43666 - request bussiness email (#20315)

* MM-43666 - request bussiness email

* add the newValidEmail information

* revert ff model changes

* remove unnecessary pointer and fix test spelling

* fix unit tests and add a new one for when passing a valid email

* fix go lint

Co-authored-by: Pablo Velez Vidal <pablo.velez@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Pablo Andrés Vélez Vidal
2022-06-01 23:36:03 +02:00
коммит произвёл GitHub
родитель 24d9210035
Коммит d5333603ab
6 изменённых файлов: 168 добавлений и 18 удалений

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

@@ -44,6 +44,9 @@ func (api *API) InitCloud() {
// GET /api/v4/cloud/request-trial
api.BaseRoutes.Cloud.Handle("/request-trial", api.APISessionRequired(requestCloudTrial)).Methods("PUT")
// GET /api/v4/cloud/validate-business-email
api.BaseRoutes.Cloud.Handle("/validate-business-email", api.APISessionRequired(validateBusinessEmail)).Methods("POST")
// POST /api/v4/cloud/webhook
api.BaseRoutes.Cloud.Handle("/webhook", api.CloudAPIKeyRequired(handleCWSWebhook)).Methods("POST")
}
@@ -146,7 +149,21 @@ func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
changedSub, err := c.App.Cloud().RequestCloudTrial(c.AppContext.Session().UserId, currentSubscription.ID)
// check if the email needs to be set
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
// this value will not be empty when both emails (user admin and CWS customer) are not business email and
// we need to request a new email from the user via the request business email modal
var newValidBusinessEmail *model.ValidateBusinessEmailRequest
if err = json.Unmarshal(bodyBytes, &newValidBusinessEmail); err != nil {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
changedSub, err := c.App.Cloud().RequestCloudTrial(c.AppContext.Session().UserId, currentSubscription.ID, newValidBusinessEmail.Email)
if err != nil {
c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
return
@@ -161,6 +178,48 @@ func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write(json)
}
func validateBusinessEmail(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.validateBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
return
}
user, userErr := c.App.GetUser(c.AppContext.Session().UserId)
if userErr != nil {
c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.request_error", nil, "", http.StatusInternalServerError)
return
}
// validate current userAdmin email
errValidatingAdminEmail := c.App.Cloud().ValidateBusinessEmail(user.Id, user.Email)
// if the current admin email is not a valid email
if errValidatingAdminEmail != nil {
// get the cloud customer email
cloudCustomer, err := c.App.Cloud().GetCloudCustomer(user.Id)
if err != nil {
c.Err = model.NewAppError("Api4.valiateBusinessEmail", "api.cloud.request_error", nil, err.Error(), http.StatusInternalServerError)
return
}
// and validate that one
errValidatingSystemEmail := c.App.Cloud().ValidateBusinessEmail(user.Id, cloudCustomer.Email)
if errValidatingSystemEmail != nil {
c.Err = model.NewAppError("Api4.valiateBusinessEmail", "api.cloud.request_error", nil, errValidatingSystemEmail.Error(), http.StatusInternalServerError)
return
}
}
// if the email is valid, return ok
ReturnStatusOK(w)
}
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)

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

@@ -6,6 +6,7 @@ package api4
import (
"errors"
"net/http"
"net/http/httptest"
"os"
"testing"
@@ -156,6 +157,8 @@ func Test_requestTrial(t *testing.T) {
IsPaidTier: "false",
}
newValidBusinessEmail := model.ValidateBusinessEmailRequest{Email: ""}
t.Run("NON Admin users are UNABLE to request the trial", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
@@ -171,7 +174,7 @@ func Test_requestTrial(t *testing.T) {
cloud := mocks.CloudInterface{}
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
@@ -179,8 +182,7 @@ func Test_requestTrial(t *testing.T) {
}()
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)
subscriptionChanged, r, err := th.Client.RequestCloudTrial(&newValidBusinessEmail)
require.Error(t, err)
require.Nil(t, subscriptionChanged)
require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden")
@@ -201,7 +203,7 @@ func Test_requestTrial(t *testing.T) {
cloud := mocks.CloudInterface{}
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
@@ -209,7 +211,7 @@ func Test_requestTrial(t *testing.T) {
}()
th.App.Srv().Cloud = &cloud
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial()
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial(&newValidBusinessEmail)
require.Error(t, err)
require.Nil(t, subscriptionChanged)
@@ -231,7 +233,7 @@ func Test_requestTrial(t *testing.T) {
cloud := mocks.CloudInterface{}
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything).Return(subscription, nil)
cloud.Mock.On("RequestCloudTrial", mock.Anything, mock.Anything, "").Return(subscription, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
@@ -239,10 +241,69 @@ func Test_requestTrial(t *testing.T) {
}()
th.App.Srv().Cloud = &cloud
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial()
subscriptionChanged, r, err := th.SystemAdminClient.RequestCloudTrial(&newValidBusinessEmail)
require.NoError(t, err)
require.Equal(t, subscriptionChanged, subscription)
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
})
t.Run("cloudFree feature flag TRUE and ADMIN user are ABLE to request the trial with valid business email", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
// patch the customer with the additional contact updated with the valid business email
newValidBusinessEmail.Email = *model.NewString("valid.email@mattermost.com")
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, "valid.email@mattermost.com").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(&newValidBusinessEmail)
require.NoError(t, err)
require.Equal(t, subscriptionChanged, subscription)
require.Equal(t, http.StatusOK, r.StatusCode, "Status OK")
})
}
func Test_validateBusinessEmail(t *testing.T) {
t.Run("Initial request has invalid email", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
cloud := mocks.CloudInterface{}
resp := httptest.NewRecorder()
cloud.Mock.On("ValidateBusinessEmail", mock.Anything).Return(resp, nil)
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = &cloud
_, err := th.Client.ValidateBusinessEmail()
require.Error(t, err)
})
}

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

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

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

@@ -247,13 +247,13 @@ 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)
// RequestCloudTrial provides a mock function with given fields: userID, subscriptionID, newValidBusinessEmail
func (_m *CloudInterface) RequestCloudTrial(userID string, subscriptionID string, newValidBusinessEmail string) (*model.Subscription, error) {
ret := _m.Called(userID, subscriptionID, newValidBusinessEmail)
var r0 *model.Subscription
if rf, ok := ret.Get(0).(func(string, string) *model.Subscription); ok {
r0 = rf(userID, subscriptionID)
if rf, ok := ret.Get(0).(func(string, string, string) *model.Subscription); ok {
r0 = rf(userID, subscriptionID, newValidBusinessEmail)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Subscription)
@@ -261,8 +261,8 @@ func (_m *CloudInterface) RequestCloudTrial(userID string, subscriptionID string
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(userID, subscriptionID)
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
r1 = rf(userID, subscriptionID, newValidBusinessEmail)
} else {
r1 = ret.Error(1)
}
@@ -329,3 +329,17 @@ func (_m *CloudInterface) UpdateSubscriptionFromHook(_a0 *model.ProductLimits, _
return r0
}
// ValidateBusinessEmail provides a mock function with given fields: userID, email
func (_m *CloudInterface) ValidateBusinessEmail(userID string, email string) error {
ret := _m.Called(userID, email)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(userID, email)
} else {
r0 = ret.Error(0)
}
return r0
}

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

@@ -7784,8 +7784,9 @@ 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", "")
func (c *Client4) RequestCloudTrial(email *ValidateBusinessEmailRequest) (*Subscription, *Response, error) {
payload, _ := json.Marshal(email)
r, err := c.DoAPIPutBytes(c.cloudRoute()+"/request-trial", payload)
if err != nil {
return nil, BuildResponse(r), err
}
@@ -7797,6 +7798,16 @@ func (c *Client4) RequestCloudTrial() (*Subscription, *Response, error) {
return subscription, BuildResponse(r), nil
}
func (c *Client4) ValidateBusinessEmail() (*Response, error) {
r, err := c.DoAPIPost(c.cloudRoute()+"/validate-business-email", "")
if err != nil {
return BuildResponse(r), err
}
defer closeBody(r)
return BuildResponse(r), nil
}
func (c *Client4) GetCloudCustomer() (*CloudCustomer, *Response, error) {
r, err := c.DoAPIGet(c.cloudRoute()+"/customer", "")
if err != nil {

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

@@ -84,6 +84,10 @@ type CloudCustomer struct {
PaymentMethod *PaymentMethod `json:"payment_method"`
}
type ValidateBusinessEmailRequest struct {
Email string `json:"email"`
}
// CloudCustomerInfo represents editable info of a customer.
type CloudCustomerInfo struct {
Name string `json:"name"`