Custom profile attributes field endpoints (#29662)
* Adds the main Property System Architecture components This change adds the necessary migrations for the Property Groups, Fields and Values tables to be created, the store layer and a Property Service that can be used from the app layer. * Adds Custom Profile Attributes endpoints and app layer * implement get and patch cpa values * run i18n-extract * Update property field type to use user instead of person * Update PropertyFields to allow for unique nondeleted fields and remove redundant indexes * Update PropertyValues to allow for unique nondeleted fields and remove redundant indexes * Use StringMap instead of the map[string]any on property fields * Add i18n strings * Revert "Use StringMap instead of the map[string]any on property fields" This reverts commit e2735ab0f8589d2524d636419ca0cb144575c4d6. * Cast JSON binary data to string and add todo note for StringMap use * Add mocks to the retrylayer tests * Cast JSON binary data to string in property value store * Check for binary parameter instead of casting to string for JSON data * Fix bad merge * Check property field type is one of the allowed ones * Avoid reusing err variable to be explicit about the returned value * Merge Property System Migrations into one file * Adds NOT NULL to timestamps at the DB level * Update stores to use tableSelectQuery instead of a slice var * Update PropertyField model translations to be more explicit and avoid repetition * Update PropertyValue model translations to be more explicit and avoid repetition * Use ExecBuilder instead of ToSql&Exec * Update property field errors to add context * Ensure PerPage is greater than zero * Update store errors to give more context * Use ExecBuilder in the property stores where possible * Add an on conflict suffix to the group register to avoid race conditions * Remove user profile API documentation changes * Update patchCPAValues endpoint and docs to return the updated information * Merge two similar error conditions * Use a route function for ListCPAValues * Remove badly used translation string * Remove unused get in register group method * Adds input sanitization and validation to the CPA API endpoints * Takes login outside of one test case to make it clear it affects multiple t.Runs * Fix wrap error and return code when property field has been deleted * Fix receiver name * Adds comment to move the CPA group ID to the db cache * Set the PerPage of CPA fields to the fields limit * Update server/channels/app/custom_profile_attributes_test.go Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com> * Standardize group ID access * Avoid polluting the state between tests * Use specific errors for the retrieval of CPA group --------- Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c6984941f1
Коммит
ca34c6a03f
269
server/channels/api4/custom_profile_attributes_test.go
Обычный файл
269
server/channels/api4/custom_profile_attributes_test.go
Обычный файл
@@ -0,0 +1,269 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateCPAField(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES")
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("a user without admin permissions should not be able to create a field", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
|
||||
_, resp, err := th.Client.CreateCPAField(context.Background(), field)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
field := &model.PropertyField{Name: model.NewId()}
|
||||
|
||||
createdField, resp, err := client.CreateCPAField(context.Background(), field)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, createdField)
|
||||
}, "an invalid field should be rejected")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
name := model.NewId()
|
||||
field := &model.PropertyField{
|
||||
Name: fmt.Sprintf(" %s\t", name), // name should be sanitized
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{"visibility": "default"},
|
||||
}
|
||||
|
||||
createdField, resp, err := client.CreateCPAField(context.Background(), field)
|
||||
CheckCreatedStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, createdField.ID)
|
||||
require.Equal(t, name, createdField.Name)
|
||||
require.Equal(t, "default", createdField.Attrs["visibility"])
|
||||
}, "a user with admin permissions should be able to create the field")
|
||||
}
|
||||
|
||||
func TestListCPAFields(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES")
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{"visibility": "default"},
|
||||
}
|
||||
createdField, _, err := th.SystemAdminClient.CreateCPAField(context.Background(), field)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
t.Run("any user should be able to list fields", func(t *testing.T) {
|
||||
fields, resp, err := th.Client.ListCPAFields(context.Background())
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, fields)
|
||||
require.Len(t, fields, 1)
|
||||
require.Equal(t, createdField.ID, fields[0].ID)
|
||||
})
|
||||
|
||||
t.Run("the endpoint should only list non deleted fields", func(t *testing.T) {
|
||||
require.Nil(t, th.App.DeleteCPAField(createdField.ID))
|
||||
fields, resp, err := th.Client.ListCPAFields(context.Background())
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, fields)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchCPAField(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES")
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("a user without admin permissions should not be able to patch a field", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
patch := &model.PropertyFieldPatch{Name: model.NewPointer(model.NewId())}
|
||||
_, resp, err := th.Client.PatchCPAField(context.Background(), createdField.ID, patch)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
newName := model.NewId()
|
||||
patch := &model.PropertyFieldPatch{Name: model.NewPointer(fmt.Sprintf(" %s \t ", newName))} // name should be sanitized
|
||||
patchedField, resp, err := client.PatchCPAField(context.Background(), createdField.ID, patch)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newName, patchedField.Name)
|
||||
}, "a user with admin permissions should be able to patch the field")
|
||||
}
|
||||
|
||||
func TestDeleteCPAField(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES")
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("a user without admin permissions should not be able to delete a field", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, _, err := th.SystemAdminClient.CreateCPAField(context.Background(), field)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
resp, err := th.Client.DeleteCPAField(context.Background(), createdField.ID)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, _, err := th.SystemAdminClient.CreateCPAField(context.Background(), field)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createdField)
|
||||
require.Zero(t, createdField.DeleteAt)
|
||||
|
||||
resp, err := client.DeleteCPAField(context.Background(), createdField.ID)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedField, appErr := th.App.GetCPAField(createdField.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, deletedField.DeleteAt)
|
||||
}, "a user with admin permissions should be able to delete the field")
|
||||
}
|
||||
|
||||
func TestListCPAValues(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES")
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.RemovePermissionFromRole(model.PermissionViewMembers.Id, model.SystemUserRoleId)
|
||||
defer func() {
|
||||
th.AddPermissionToRole(model.PermissionViewMembers.Id, model.SystemUserRoleId)
|
||||
}()
|
||||
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
values := map[string]string{}
|
||||
values[createdField.ID] = "Field Value"
|
||||
_, _, err := th.Client.PatchCPAValues(context.Background(), values)
|
||||
require.NoError(t, err)
|
||||
|
||||
// login with Client2 from this point on
|
||||
th.LoginBasic2()
|
||||
|
||||
t.Run("any team member should be able to list values", func(t *testing.T) {
|
||||
values, resp, err := th.Client.ListCPAValues(context.Background(), th.BasicUser.Id)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, values)
|
||||
require.Len(t, values, 1)
|
||||
})
|
||||
|
||||
t.Run("non team member should NOT be able to list values", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.RemoveTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, resp, err = th.Client.ListCPAValues(context.Background(), th.BasicUser.Id)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchCPAValues(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_CUSTOMPROFILEATTRIBUTES")
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
field := &model.PropertyField{
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
t.Run("any team member should be able to create their own values", func(t *testing.T) {
|
||||
values := map[string]string{}
|
||||
value := "Field Value"
|
||||
values[createdField.ID] = fmt.Sprintf(" %s ", value) // value should be sanitized
|
||||
patchedValues, resp, err := th.Client.PatchCPAValues(context.Background(), values)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, patchedValues)
|
||||
require.Len(t, patchedValues, 1)
|
||||
require.Equal(t, value, patchedValues[createdField.ID])
|
||||
|
||||
values, resp, err = th.Client.ListCPAValues(context.Background(), th.BasicUser.Id)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, values)
|
||||
require.Len(t, values, 1)
|
||||
require.Equal(t, "Field Value", values[createdField.ID])
|
||||
})
|
||||
|
||||
t.Run("any team member should be able to patch their own values", func(t *testing.T) {
|
||||
values, resp, err := th.Client.ListCPAValues(context.Background(), th.BasicUser.Id)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, values)
|
||||
require.Len(t, values, 1)
|
||||
|
||||
value := "Updated Field Value"
|
||||
values[createdField.ID] = fmt.Sprintf(" %s \t", value) // value should be sanitized
|
||||
patchedValues, resp, err := th.Client.PatchCPAValues(context.Background(), values)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, value, patchedValues[createdField.ID])
|
||||
|
||||
values, resp, err = th.Client.ListCPAValues(context.Background(), th.BasicUser.Id)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, value, values[createdField.ID])
|
||||
})
|
||||
}
|
||||
Ссылка в новой задаче
Block a user