Adds the main Property System Architecture components (#29644)
* 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. * 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 * 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 badly used translation string * Remove unused get in register group method --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d2b334e605
Коммит
ecdce71fc4
197
server/channels/store/storetest/mocks/PropertyFieldStore.go
Обычный файл
197
server/channels/store/storetest/mocks/PropertyFieldStore.go
Обычный файл
@@ -0,0 +1,197 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PropertyFieldStore is an autogenerated mock type for the PropertyFieldStore type
|
||||
type PropertyFieldStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: field
|
||||
func (_m *PropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
ret := _m.Called(field)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 *model.PropertyField
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.PropertyField) (*model.PropertyField, error)); ok {
|
||||
return rf(field)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.PropertyField) *model.PropertyField); ok {
|
||||
r0 = rf(field)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PropertyField)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.PropertyField) error); ok {
|
||||
r1 = rf(field)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: id
|
||||
func (_m *PropertyFieldStore) Delete(id string) error {
|
||||
ret := _m.Called(id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: id
|
||||
func (_m *PropertyFieldStore) Get(id string) (*model.PropertyField, error) {
|
||||
ret := _m.Called(id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 *model.PropertyField
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.PropertyField, error)); ok {
|
||||
return rf(id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.PropertyField); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PropertyField)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMany provides a mock function with given fields: ids
|
||||
func (_m *PropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
|
||||
ret := _m.Called(ids)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetMany")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyField
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]string) ([]*model.PropertyField, error)); ok {
|
||||
return rf(ids)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]string) []*model.PropertyField); ok {
|
||||
r0 = rf(ids)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyField)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]string) error); ok {
|
||||
r1 = rf(ids)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchPropertyFields provides a mock function with given fields: opts
|
||||
func (_m *PropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
ret := _m.Called(opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SearchPropertyFields")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyField
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(model.PropertyFieldSearchOpts) ([]*model.PropertyField, error)); ok {
|
||||
return rf(opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(model.PropertyFieldSearchOpts) []*model.PropertyField); ok {
|
||||
r0 = rf(opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyField)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(model.PropertyFieldSearchOpts) error); ok {
|
||||
r1 = rf(opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: field
|
||||
func (_m *PropertyFieldStore) Update(field []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
ret := _m.Called(field)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyField
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyField) ([]*model.PropertyField, error)); ok {
|
||||
return rf(field)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyField) []*model.PropertyField); ok {
|
||||
r0 = rf(field)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyField)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.PropertyField) error); ok {
|
||||
r1 = rf(field)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewPropertyFieldStore creates a new instance of PropertyFieldStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewPropertyFieldStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *PropertyFieldStore {
|
||||
mock := &PropertyFieldStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
89
server/channels/store/storetest/mocks/PropertyGroupStore.go
Обычный файл
89
server/channels/store/storetest/mocks/PropertyGroupStore.go
Обычный файл
@@ -0,0 +1,89 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PropertyGroupStore is an autogenerated mock type for the PropertyGroupStore type
|
||||
type PropertyGroupStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: name
|
||||
func (_m *PropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 *model.PropertyGroup
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.PropertyGroup, error)); ok {
|
||||
return rf(name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.PropertyGroup); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PropertyGroup)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Register provides a mock function with given fields: name
|
||||
func (_m *PropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Register")
|
||||
}
|
||||
|
||||
var r0 *model.PropertyGroup
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.PropertyGroup, error)); ok {
|
||||
return rf(name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.PropertyGroup); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PropertyGroup)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewPropertyGroupStore creates a new instance of PropertyGroupStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewPropertyGroupStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *PropertyGroupStore {
|
||||
mock := &PropertyGroupStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
215
server/channels/store/storetest/mocks/PropertyValueStore.go
Обычный файл
215
server/channels/store/storetest/mocks/PropertyValueStore.go
Обычный файл
@@ -0,0 +1,215 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PropertyValueStore is an autogenerated mock type for the PropertyValueStore type
|
||||
type PropertyValueStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: value
|
||||
func (_m *PropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
ret := _m.Called(value)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 *model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.PropertyValue) (*model.PropertyValue, error)); ok {
|
||||
return rf(value)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.PropertyValue) *model.PropertyValue); ok {
|
||||
r0 = rf(value)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PropertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.PropertyValue) error); ok {
|
||||
r1 = rf(value)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: id
|
||||
func (_m *PropertyValueStore) Delete(id string) error {
|
||||
ret := _m.Called(id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteForField provides a mock function with given fields: id
|
||||
func (_m *PropertyValueStore) DeleteForField(id string) error {
|
||||
ret := _m.Called(id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteForField")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: id
|
||||
func (_m *PropertyValueStore) Get(id string) (*model.PropertyValue, error) {
|
||||
ret := _m.Called(id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 *model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.PropertyValue, error)); ok {
|
||||
return rf(id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.PropertyValue); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.PropertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMany provides a mock function with given fields: ids
|
||||
func (_m *PropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
|
||||
ret := _m.Called(ids)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetMany")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]string) ([]*model.PropertyValue, error)); ok {
|
||||
return rf(ids)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]string) []*model.PropertyValue); ok {
|
||||
r0 = rf(ids)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]string) error); ok {
|
||||
r1 = rf(ids)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchPropertyValues provides a mock function with given fields: opts
|
||||
func (_m *PropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
ret := _m.Called(opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SearchPropertyValues")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)); ok {
|
||||
return rf(opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(model.PropertyValueSearchOpts) []*model.PropertyValue); ok {
|
||||
r0 = rf(opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(model.PropertyValueSearchOpts) error); ok {
|
||||
r1 = rf(opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: field
|
||||
func (_m *PropertyValueStore) Update(field []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
ret := _m.Called(field)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) ([]*model.PropertyValue, error)); ok {
|
||||
return rf(field)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) []*model.PropertyValue); ok {
|
||||
r0 = rf(field)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.PropertyValue) error); ok {
|
||||
r1 = rf(field)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewPropertyValueStore creates a new instance of PropertyValueStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewPropertyValueStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *PropertyValueStore {
|
||||
mock := &PropertyValueStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -798,6 +798,66 @@ func (_m *Store) ProductNotices() store.ProductNoticesStore {
|
||||
return r0
|
||||
}
|
||||
|
||||
// PropertyField provides a mock function with given fields:
|
||||
func (_m *Store) PropertyField() store.PropertyFieldStore {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PropertyField")
|
||||
}
|
||||
|
||||
var r0 store.PropertyFieldStore
|
||||
if rf, ok := ret.Get(0).(func() store.PropertyFieldStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.PropertyFieldStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PropertyGroup provides a mock function with given fields:
|
||||
func (_m *Store) PropertyGroup() store.PropertyGroupStore {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PropertyGroup")
|
||||
}
|
||||
|
||||
var r0 store.PropertyGroupStore
|
||||
if rf, ok := ret.Get(0).(func() store.PropertyGroupStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.PropertyGroupStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PropertyValue provides a mock function with given fields:
|
||||
func (_m *Store) PropertyValue() store.PropertyValueStore {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PropertyValue")
|
||||
}
|
||||
|
||||
var r0 store.PropertyValueStore
|
||||
if rf, ok := ret.Get(0).(func() store.PropertyValueStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.PropertyValueStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Reaction provides a mock function with given fields:
|
||||
func (_m *Store) Reaction() store.ReactionStore {
|
||||
ret := _m.Called()
|
||||
|
||||
461
server/channels/store/storetest/property_field_store.go
Обычный файл
461
server/channels/store/storetest/property_field_store.go
Обычный файл
@@ -0,0 +1,461 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPropertyFieldStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("CreatePropertyField", func(t *testing.T) { testCreatePropertyField(t, rctx, ss) })
|
||||
t.Run("GetPropertyField", func(t *testing.T) { testGetPropertyField(t, rctx, ss) })
|
||||
t.Run("GetManyPropertyFields", func(t *testing.T) { testGetManyPropertyFields(t, rctx, ss) })
|
||||
t.Run("UpdatePropertyField", func(t *testing.T) { testUpdatePropertyField(t, rctx, ss) })
|
||||
t.Run("DeletePropertyField", func(t *testing.T) { testDeletePropertyField(t, rctx, ss) })
|
||||
t.Run("SearchPropertyFields", func(t *testing.T) { testSearchPropertyFields(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func testCreatePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail if the property field already has an ID set", func(t *testing.T) {
|
||||
newField := &model.PropertyField{ID: "sampleid"}
|
||||
field, err := ss.PropertyField().Create(newField)
|
||||
require.Zero(t, field)
|
||||
var eii *store.ErrInvalidInput
|
||||
require.ErrorAs(t, err, &eii)
|
||||
})
|
||||
|
||||
t.Run("should fail if the property field is not valid", func(t *testing.T) {
|
||||
newField := &model.PropertyField{GroupID: ""}
|
||||
field, err := ss.PropertyField().Create(newField)
|
||||
require.Zero(t, field)
|
||||
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
|
||||
|
||||
newField = &model.PropertyField{GroupID: model.NewId(), Name: ""}
|
||||
field, err = ss.PropertyField().Create(newField)
|
||||
require.Zero(t, field)
|
||||
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
|
||||
})
|
||||
|
||||
newField := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "My new property field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"locked": true,
|
||||
"special": "value",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should be able to create a property field", func(t *testing.T) {
|
||||
field, err := ss.PropertyField().Create(newField)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, field.ID)
|
||||
require.NotZero(t, field.CreateAt)
|
||||
require.NotZero(t, field.UpdateAt)
|
||||
require.Zero(t, field.DeleteAt)
|
||||
})
|
||||
|
||||
t.Run("should enforce the field's uniqueness", func(t *testing.T) {
|
||||
newField.ID = ""
|
||||
field, err := ss.PropertyField().Create(newField)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, field)
|
||||
})
|
||||
}
|
||||
|
||||
func testGetPropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting field", func(t *testing.T) {
|
||||
field, err := ss.PropertyField().Get(model.NewId())
|
||||
require.Zero(t, field)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
})
|
||||
|
||||
t.Run("should be able to retrieve an existing property field", func(t *testing.T) {
|
||||
newField := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "My new property field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"locked": true,
|
||||
"special": "value",
|
||||
},
|
||||
}
|
||||
_, err := ss.PropertyField().Create(newField)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, newField.ID)
|
||||
|
||||
field, err := ss.PropertyField().Get(newField.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newField.ID, field.ID)
|
||||
require.True(t, field.Attrs["locked"].(bool))
|
||||
require.Equal(t, "value", field.Attrs["special"])
|
||||
})
|
||||
}
|
||||
|
||||
func testGetManyPropertyFields(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting fields", func(t *testing.T) {
|
||||
fields, err := ss.PropertyField().GetMany([]string{model.NewId(), model.NewId()})
|
||||
require.Empty(t, fields)
|
||||
require.ErrorContains(t, err, "missmatch results")
|
||||
})
|
||||
|
||||
newFields := []*model.PropertyField{}
|
||||
for _, fieldName := range []string{"field1", "field2", "field3"} {
|
||||
newField := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: fieldName,
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
_, err := ss.PropertyField().Create(newField)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, newField.ID)
|
||||
|
||||
newFields = append(newFields, newField)
|
||||
}
|
||||
|
||||
t.Run("should fail if at least one of the ids is nonexistent", func(t *testing.T) {
|
||||
fields, err := ss.PropertyField().GetMany([]string{newFields[0].ID, newFields[1].ID, model.NewId()})
|
||||
require.Empty(t, fields)
|
||||
require.ErrorContains(t, err, "missmatch results")
|
||||
})
|
||||
|
||||
t.Run("should be able to retrieve existing property fields", func(t *testing.T) {
|
||||
fields, err := ss.PropertyField().GetMany([]string{newFields[0].ID, newFields[1].ID, newFields[2].ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fields, 3)
|
||||
require.ElementsMatch(t, newFields, fields)
|
||||
})
|
||||
}
|
||||
|
||||
func testUpdatePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting field", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
ID: model.NewId(),
|
||||
GroupID: model.NewId(),
|
||||
Name: "My property field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
CreateAt: model.GetMillis(),
|
||||
}
|
||||
updatedField, err := ss.PropertyField().Update([]*model.PropertyField{field})
|
||||
require.Zero(t, updatedField)
|
||||
var enf *store.ErrNotFound
|
||||
require.ErrorAs(t, err, &enf)
|
||||
})
|
||||
|
||||
t.Run("should fail if the property field is not valid", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "My property field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
_, err := ss.PropertyField().Create(field)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, field.ID)
|
||||
|
||||
field.GroupID = ""
|
||||
updatedField, err := ss.PropertyField().Update([]*model.PropertyField{field})
|
||||
require.Zero(t, updatedField)
|
||||
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
|
||||
|
||||
field.GroupID = model.NewId()
|
||||
field.Name = ""
|
||||
updatedField, err = ss.PropertyField().Update([]*model.PropertyField{field})
|
||||
require.Zero(t, updatedField)
|
||||
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
|
||||
})
|
||||
|
||||
t.Run("should be able to update multiple property fields", func(t *testing.T) {
|
||||
field1 := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "First field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"locked": true,
|
||||
"special": "value",
|
||||
},
|
||||
}
|
||||
|
||||
field2 := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "Second field",
|
||||
Type: model.PropertyFieldTypeSelect,
|
||||
Attrs: map[string]any{
|
||||
"options": []string{"a", "b"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, field := range []*model.PropertyField{field1, field2} {
|
||||
_, err := ss.PropertyField().Create(field)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, field.ID)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
field1.Name = "Updated first"
|
||||
field1.Type = model.PropertyFieldTypeSelect
|
||||
field1.Attrs = map[string]any{
|
||||
"locked": false,
|
||||
"new_field": "new_value",
|
||||
}
|
||||
|
||||
field2.Name = "Updated second"
|
||||
field2.Attrs = map[string]any{
|
||||
"options": []string{"x", "y", "z"},
|
||||
}
|
||||
|
||||
_, err := ss.PropertyField().Update([]*model.PropertyField{field1, field2})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify first field
|
||||
updated1, err := ss.PropertyField().Get(field1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Updated first", updated1.Name)
|
||||
require.Equal(t, model.PropertyFieldTypeSelect, updated1.Type)
|
||||
require.False(t, updated1.Attrs["locked"].(bool))
|
||||
require.NotContains(t, updated1.Attrs, "special")
|
||||
require.Equal(t, "new_value", updated1.Attrs["new_field"])
|
||||
require.Greater(t, updated1.UpdateAt, updated1.CreateAt)
|
||||
|
||||
// Verify second field
|
||||
updated2, err := ss.PropertyField().Get(field2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Updated second", updated2.Name)
|
||||
require.Equal(t, model.PropertyFieldTypeSelect, updated2.Type)
|
||||
require.ElementsMatch(t, []string{"x", "y", "z"}, updated2.Attrs["options"])
|
||||
require.Greater(t, updated2.UpdateAt, updated2.CreateAt)
|
||||
})
|
||||
|
||||
t.Run("should not update any fields if one update is invalid", func(t *testing.T) {
|
||||
// Create two valid fields
|
||||
groupID := model.NewId()
|
||||
field1 := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Field 1",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
|
||||
field2 := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Field 2",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
|
||||
for _, field := range []*model.PropertyField{field1, field2} {
|
||||
_, err := ss.PropertyField().Create(field)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
originalUpdateAt1 := field1.UpdateAt
|
||||
originalUpdateAt2 := field2.UpdateAt
|
||||
|
||||
// Try to update both fields, but make one invalid
|
||||
field1.Name = "Valid update"
|
||||
field2.GroupID = "Invalid ID"
|
||||
|
||||
_, err := ss.PropertyField().Update([]*model.PropertyField{field1, field2})
|
||||
require.ErrorContains(t, err, "model.property_field.is_valid.app_error")
|
||||
|
||||
// Check that fields were not updated
|
||||
updated1, err := ss.PropertyField().Get(field1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Field 1", updated1.Name)
|
||||
require.Equal(t, originalUpdateAt1, updated1.UpdateAt)
|
||||
|
||||
updated2, err := ss.PropertyField().Get(field2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, groupID, updated2.GroupID)
|
||||
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
|
||||
})
|
||||
}
|
||||
|
||||
func testDeletePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting field", func(t *testing.T) {
|
||||
err := ss.PropertyField().Delete(model.NewId())
|
||||
var enf *store.ErrNotFound
|
||||
require.ErrorAs(t, err, &enf)
|
||||
})
|
||||
|
||||
newField := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "My property field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
|
||||
t.Run("should be able to delete an existing property field", func(t *testing.T) {
|
||||
field, err := ss.PropertyField().Create(newField)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, field.ID)
|
||||
|
||||
err = ss.PropertyField().Delete(field.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the field was soft-deleted
|
||||
deletedField, err := ss.PropertyField().Get(field.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, deletedField.DeleteAt)
|
||||
})
|
||||
|
||||
t.Run("should be able to create a new field with the same details as the deleted one", func(t *testing.T) {
|
||||
newField.ID = ""
|
||||
field, err := ss.PropertyField().Create(newField)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, field.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func testSearchPropertyFields(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
groupID := model.NewId()
|
||||
targetID := model.NewId()
|
||||
|
||||
// Define test property fields
|
||||
field1 := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Field 1",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetID: targetID,
|
||||
TargetType: "test_type",
|
||||
}
|
||||
|
||||
field2 := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Field 2",
|
||||
Type: model.PropertyFieldTypeSelect,
|
||||
TargetID: targetID,
|
||||
TargetType: "other_type",
|
||||
}
|
||||
|
||||
field3 := &model.PropertyField{
|
||||
GroupID: model.NewId(),
|
||||
Name: "Field 3",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "test_type",
|
||||
}
|
||||
|
||||
field4 := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Field 4",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "test_type",
|
||||
}
|
||||
|
||||
for _, field := range []*model.PropertyField{field1, field2, field3, field4} {
|
||||
_, err := ss.PropertyField().Create(field)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Delete one field for deletion tests
|
||||
require.NoError(t, ss.PropertyField().Delete(field4.ID))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts model.PropertyFieldSearchOpts
|
||||
expectedError bool
|
||||
expectedIDs []string
|
||||
}{
|
||||
{
|
||||
name: "negative page",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
Page: -1,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "negative per_page",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
Page: 0,
|
||||
PerPage: -1,
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "filter by group_id",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{field1.ID, field2.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by group_id including deleted",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
IncludeDeleted: true,
|
||||
},
|
||||
expectedIDs: []string{field1.ID, field2.ID, field4.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by target_type",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
TargetType: "test_type",
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{field1.ID, field3.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by target_id",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
TargetID: targetID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{field1.ID, field2.ID},
|
||||
},
|
||||
{
|
||||
name: "pagination page 0",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 0,
|
||||
PerPage: 2,
|
||||
IncludeDeleted: true,
|
||||
},
|
||||
expectedIDs: []string{field1.ID, field2.ID},
|
||||
},
|
||||
{
|
||||
name: "pagination page 1",
|
||||
opts: model.PropertyFieldSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 1,
|
||||
PerPage: 2,
|
||||
IncludeDeleted: true,
|
||||
},
|
||||
expectedIDs: []string{field4.ID},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
results, err := ss.PropertyField().SearchPropertyFields(tc.opts)
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
var ids = make([]string, len(results))
|
||||
for i, field := range results {
|
||||
ids[i] = field.ID
|
||||
}
|
||||
require.ElementsMatch(t, tc.expectedIDs, ids)
|
||||
})
|
||||
}
|
||||
}
|
||||
36
server/channels/store/storetest/property_group_store.go
Обычный файл
36
server/channels/store/storetest/property_group_store.go
Обычный файл
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPropertyGroupStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("RegisterAndGetPropertyGroup", func(t *testing.T) { testRegisterAndGetPropertyGroup(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func testRegisterAndGetPropertyGroup(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
groupName := "samplename"
|
||||
var id string
|
||||
|
||||
t.Run("should be able to register a new group", func(t *testing.T) {
|
||||
group, err := ss.PropertyGroup().Register(groupName)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, group.ID)
|
||||
require.Equal(t, groupName, group.Name)
|
||||
id = group.ID
|
||||
})
|
||||
|
||||
t.Run("should be able to retrieve an existing group", func(t *testing.T) {
|
||||
group, err := ss.PropertyGroup().Register(groupName)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, groupName, group.Name)
|
||||
require.Equal(t, id, group.ID)
|
||||
})
|
||||
}
|
||||
535
server/channels/store/storetest/property_value_store.go
Обычный файл
535
server/channels/store/storetest/property_value_store.go
Обычный файл
@@ -0,0 +1,535 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPropertyValueStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("CreatePropertyValue", func(t *testing.T) { testCreatePropertyValue(t, rctx, ss) })
|
||||
t.Run("GetPropertyValue", func(t *testing.T) { testGetPropertyValue(t, rctx, ss) })
|
||||
t.Run("GetManyPropertyValues", func(t *testing.T) { testGetManyPropertyValues(t, rctx, ss) })
|
||||
t.Run("UpdatePropertyValue", func(t *testing.T) { testUpdatePropertyValue(t, rctx, ss) })
|
||||
t.Run("DeletePropertyValue", func(t *testing.T) { testDeletePropertyValue(t, rctx, ss) })
|
||||
t.Run("SearchPropertyValues", func(t *testing.T) { testSearchPropertyValues(t, rctx, ss) })
|
||||
t.Run("DeleteForField", func(t *testing.T) { testDeleteForField(t, rctx, ss) })
|
||||
}
|
||||
|
||||
func testCreatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail if the property value already has an ID set", func(t *testing.T) {
|
||||
newValue := &model.PropertyValue{ID: "sampleid"}
|
||||
value, err := ss.PropertyValue().Create(newValue)
|
||||
require.Zero(t, value)
|
||||
var eii *store.ErrInvalidInput
|
||||
require.ErrorAs(t, err, &eii)
|
||||
})
|
||||
|
||||
t.Run("should fail if the property value is not valid", func(t *testing.T) {
|
||||
newValue := &model.PropertyValue{TargetID: ""}
|
||||
value, err := ss.PropertyValue().Create(newValue)
|
||||
require.Zero(t, value)
|
||||
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
|
||||
|
||||
newValue = &model.PropertyValue{TargetID: model.NewId(), TargetType: ""}
|
||||
value, err = ss.PropertyValue().Create(newValue)
|
||||
require.Zero(t, value)
|
||||
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
|
||||
})
|
||||
|
||||
newValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "test value",
|
||||
}
|
||||
|
||||
t.Run("should be able to create a property value", func(t *testing.T) {
|
||||
value, err := ss.PropertyValue().Create(newValue)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, value.ID)
|
||||
require.NotZero(t, value.CreateAt)
|
||||
require.NotZero(t, value.UpdateAt)
|
||||
require.Zero(t, value.DeleteAt)
|
||||
})
|
||||
|
||||
t.Run("should enforce the value's uniqueness", func(t *testing.T) {
|
||||
newValue.ID = ""
|
||||
value, err := ss.PropertyValue().Create(newValue)
|
||||
require.Error(t, err)
|
||||
require.Zero(t, value)
|
||||
})
|
||||
}
|
||||
|
||||
func testGetPropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting value", func(t *testing.T) {
|
||||
value, err := ss.PropertyValue().Get(model.NewId())
|
||||
require.Zero(t, value)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
})
|
||||
|
||||
t.Run("should be able to retrieve an existing property value", func(t *testing.T) {
|
||||
newValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "test value",
|
||||
}
|
||||
_, err := ss.PropertyValue().Create(newValue)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, newValue.ID)
|
||||
|
||||
value, err := ss.PropertyValue().Get(newValue.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newValue.ID, value.ID)
|
||||
require.Equal(t, newValue.Value, value.Value)
|
||||
})
|
||||
}
|
||||
|
||||
func testGetManyPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting values", func(t *testing.T) {
|
||||
values, err := ss.PropertyValue().GetMany([]string{model.NewId(), model.NewId()})
|
||||
require.Empty(t, values)
|
||||
require.ErrorContains(t, err, "missmatch results")
|
||||
})
|
||||
|
||||
newValues := []*model.PropertyValue{}
|
||||
for i := 0; i < 3; i++ {
|
||||
newValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: fmt.Sprintf("test value %d", i),
|
||||
}
|
||||
_, err := ss.PropertyValue().Create(newValue)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, newValue.ID)
|
||||
|
||||
newValues = append(newValues, newValue)
|
||||
}
|
||||
|
||||
t.Run("should fail if at least one of the ids is nonexistent", func(t *testing.T) {
|
||||
values, err := ss.PropertyValue().GetMany([]string{newValues[0].ID, newValues[1].ID, model.NewId()})
|
||||
require.Empty(t, values)
|
||||
require.ErrorContains(t, err, "missmatch results")
|
||||
})
|
||||
|
||||
t.Run("should be able to retrieve existing property values", func(t *testing.T) {
|
||||
values, err := ss.PropertyValue().GetMany([]string{newValues[0].ID, newValues[1].ID, newValues[2].ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 3)
|
||||
require.ElementsMatch(t, newValues, values)
|
||||
})
|
||||
}
|
||||
|
||||
func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting value", func(t *testing.T) {
|
||||
value := &model.PropertyValue{
|
||||
ID: model.NewId(),
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "test value",
|
||||
CreateAt: model.GetMillis(),
|
||||
}
|
||||
updatedValue, err := ss.PropertyValue().Update([]*model.PropertyValue{value})
|
||||
require.Zero(t, updatedValue)
|
||||
var enf *store.ErrNotFound
|
||||
require.ErrorAs(t, err, &enf)
|
||||
})
|
||||
|
||||
t.Run("should fail if the property value is not valid", func(t *testing.T) {
|
||||
value := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "test value",
|
||||
}
|
||||
_, err := ss.PropertyValue().Create(value)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, value.ID)
|
||||
|
||||
value.TargetID = ""
|
||||
updatedValue, err := ss.PropertyValue().Update([]*model.PropertyValue{value})
|
||||
require.Zero(t, updatedValue)
|
||||
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
|
||||
|
||||
value.TargetID = model.NewId()
|
||||
value.GroupID = ""
|
||||
updatedValue, err = ss.PropertyValue().Update([]*model.PropertyValue{value})
|
||||
require.Zero(t, updatedValue)
|
||||
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
|
||||
})
|
||||
|
||||
t.Run("should be able to update multiple property values", func(t *testing.T) {
|
||||
value1 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "value 1",
|
||||
}
|
||||
|
||||
value2 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "value 2",
|
||||
}
|
||||
|
||||
for _, value := range []*model.PropertyValue{value1, value2} {
|
||||
_, err := ss.PropertyValue().Create(value)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, value.ID)
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
value1.Value = "updated value 1"
|
||||
value2.Value = "updated value 2"
|
||||
|
||||
_, err := ss.PropertyValue().Update([]*model.PropertyValue{value1, value2})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify first value
|
||||
updated1, err := ss.PropertyValue().Get(value1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "updated value 1", updated1.Value)
|
||||
require.Greater(t, updated1.UpdateAt, updated1.CreateAt)
|
||||
|
||||
// Verify second value
|
||||
updated2, err := ss.PropertyValue().Get(value2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "updated value 2", updated2.Value)
|
||||
require.Greater(t, updated2.UpdateAt, updated2.CreateAt)
|
||||
})
|
||||
|
||||
t.Run("should not update any fields if one update is invalid", func(t *testing.T) {
|
||||
// Create two valid values
|
||||
groupID := model.NewId()
|
||||
value1 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: groupID,
|
||||
FieldID: model.NewId(),
|
||||
Value: "Value 1",
|
||||
}
|
||||
|
||||
value2 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: groupID,
|
||||
FieldID: model.NewId(),
|
||||
Value: "Value 2",
|
||||
}
|
||||
|
||||
for _, value := range []*model.PropertyValue{value1, value2} {
|
||||
_, err := ss.PropertyValue().Create(value)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
originalUpdateAt1 := value1.UpdateAt
|
||||
originalUpdateAt2 := value2.UpdateAt
|
||||
|
||||
// Try to update both value, but make one invalid
|
||||
value1.Value = "Valid update"
|
||||
value2.GroupID = "Invalid ID"
|
||||
|
||||
_, err := ss.PropertyValue().Update([]*model.PropertyValue{value1, value2})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "model.property_value.is_valid.app_error")
|
||||
|
||||
// Check that values were not updated
|
||||
updated1, err := ss.PropertyValue().Get(value1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Value 1", updated1.Value)
|
||||
require.Equal(t, originalUpdateAt1, updated1.UpdateAt)
|
||||
|
||||
updated2, err := ss.PropertyValue().Get(value2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, groupID, updated2.GroupID)
|
||||
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
|
||||
})
|
||||
}
|
||||
|
||||
func testDeletePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting value", func(t *testing.T) {
|
||||
err := ss.PropertyValue().Delete(model.NewId())
|
||||
var enf *store.ErrNotFound
|
||||
require.ErrorAs(t, err, &enf)
|
||||
})
|
||||
|
||||
t.Run("should be able to delete an existing property value", func(t *testing.T) {
|
||||
newValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "test value",
|
||||
}
|
||||
value, err := ss.PropertyValue().Create(newValue)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, value.ID)
|
||||
|
||||
err = ss.PropertyValue().Delete(value.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the value was soft-deleted
|
||||
deletedValue, err := ss.PropertyValue().Get(value.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, deletedValue.DeleteAt)
|
||||
})
|
||||
|
||||
t.Run("should be able to create a new value with the same details as the deleted one", func(t *testing.T) {
|
||||
sameDetailsValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: "test value",
|
||||
}
|
||||
value, err := ss.PropertyValue().Create(sameDetailsValue)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, value.ID)
|
||||
require.Equal(t, sameDetailsValue.Value, value.Value)
|
||||
})
|
||||
}
|
||||
|
||||
func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
groupID := model.NewId()
|
||||
targetID := model.NewId()
|
||||
fieldID := model.NewId()
|
||||
|
||||
// Define test property values
|
||||
value1 := &model.PropertyValue{
|
||||
GroupID: groupID,
|
||||
TargetID: targetID,
|
||||
TargetType: "test_type",
|
||||
FieldID: fieldID,
|
||||
Value: "value 1",
|
||||
}
|
||||
|
||||
value2 := &model.PropertyValue{
|
||||
GroupID: groupID,
|
||||
TargetID: targetID,
|
||||
TargetType: "other_type",
|
||||
FieldID: model.NewId(),
|
||||
Value: "value 2",
|
||||
}
|
||||
|
||||
value3 := &model.PropertyValue{
|
||||
GroupID: model.NewId(),
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
FieldID: model.NewId(),
|
||||
Value: "value 3",
|
||||
}
|
||||
|
||||
value4 := &model.PropertyValue{
|
||||
GroupID: groupID,
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
FieldID: fieldID,
|
||||
Value: "value 4",
|
||||
}
|
||||
|
||||
for _, value := range []*model.PropertyValue{value1, value2, value3, value4} {
|
||||
_, err := ss.PropertyValue().Create(value)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Delete one value for deletion tests
|
||||
require.NoError(t, ss.PropertyValue().Delete(value4.ID))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts model.PropertyValueSearchOpts
|
||||
expectedError bool
|
||||
expectedIDs []string
|
||||
}{
|
||||
{
|
||||
name: "negative page",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
Page: -1,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "negative per_page",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
Page: 0,
|
||||
PerPage: -1,
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "filter by group_id",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID, value2.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by group_id and target_type",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
GroupID: groupID,
|
||||
TargetType: "test_type",
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by group_id and target_type including deleted",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
GroupID: groupID,
|
||||
TargetType: "test_type",
|
||||
IncludeDeleted: true,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID, value4.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by target_id",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
TargetID: targetID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID, value2.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by group_id and target_id",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
GroupID: groupID,
|
||||
TargetID: targetID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID, value2.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by field_id",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
FieldID: fieldID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID},
|
||||
},
|
||||
{
|
||||
name: "filter by field_id including deleted",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
FieldID: fieldID,
|
||||
IncludeDeleted: true,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
},
|
||||
expectedIDs: []string{value1.ID, value4.ID},
|
||||
},
|
||||
{
|
||||
name: "pagination page 0",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 0,
|
||||
PerPage: 1,
|
||||
},
|
||||
expectedIDs: []string{value1.ID},
|
||||
},
|
||||
{
|
||||
name: "pagination page 1",
|
||||
opts: model.PropertyValueSearchOpts{
|
||||
GroupID: groupID,
|
||||
Page: 1,
|
||||
PerPage: 1,
|
||||
},
|
||||
expectedIDs: []string{value2.ID},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
results, err := ss.PropertyValue().SearchPropertyValues(tc.opts)
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
var ids = make([]string, len(results))
|
||||
for i, value := range results {
|
||||
ids[i] = value.ID
|
||||
}
|
||||
require.ElementsMatch(t, tc.expectedIDs, ids)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteForField(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
fieldID := model.NewId()
|
||||
|
||||
// Create test values
|
||||
value1 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: fieldID,
|
||||
Value: "value 1",
|
||||
}
|
||||
|
||||
value2 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: fieldID,
|
||||
Value: "value 2",
|
||||
}
|
||||
|
||||
value3 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(), // Different field ID
|
||||
Value: "value 3",
|
||||
}
|
||||
|
||||
for _, value := range []*model.PropertyValue{value1, value2, value3} {
|
||||
_, err := ss.PropertyValue().Create(value)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Delete values for the field
|
||||
err := ss.PropertyValue().DeleteForField(fieldID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify values were soft-deleted
|
||||
deletedValues, err := ss.PropertyValue().GetMany([]string{value1.ID, value2.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, deletedValues, 2)
|
||||
require.NotZero(t, deletedValues[0].DeleteAt)
|
||||
require.NotZero(t, deletedValues[1].DeleteAt)
|
||||
|
||||
// Verify value with different field ID was not deleted
|
||||
nonDeletedValue, err := ss.PropertyValue().Get(value3.ID)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, nonDeletedValue.DeleteAt)
|
||||
}
|
||||
@@ -66,6 +66,9 @@ type Store struct {
|
||||
DesktopTokensStore mocks.DesktopTokensStore
|
||||
ChannelBookmarkStore mocks.ChannelBookmarkStore
|
||||
ScheduledPostStore mocks.ScheduledPostStore
|
||||
PropertyGroupStore mocks.PropertyGroupStore
|
||||
PropertyFieldStore mocks.PropertyFieldStore
|
||||
PropertyValueStore mocks.PropertyValueStore
|
||||
}
|
||||
|
||||
func (s *Store) SetContext(context context.Context) { s.context = context }
|
||||
@@ -119,6 +122,9 @@ func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMet
|
||||
func (s *Store) SharedChannel() store.SharedChannelStore { return &s.SharedChannelStore }
|
||||
func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorityStore }
|
||||
func (s *Store) ScheduledPost() store.ScheduledPostStore { return &s.ScheduledPostStore }
|
||||
func (s *Store) PropertyGroup() store.PropertyGroupStore { return &s.PropertyGroupStore }
|
||||
func (s *Store) PropertyField() store.PropertyFieldStore { return &s.PropertyFieldStore }
|
||||
func (s *Store) PropertyValue() store.PropertyValueStore { return &s.PropertyValueStore }
|
||||
func (s *Store) PostAcknowledgement() store.PostAcknowledgementStore {
|
||||
return &s.PostAcknowledgementStore
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user