Skip to content
Snippets Groups Projects
Commit 0c840961 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

Merge branch 'feature/PRXS-1674-ExtensionsUpdateCollectionStrategy' into 'master'

Реализована стратегия обновления коллекций для расширений с проверкой метадаты

See merge request perxis/perxis-go!98
parents 1ee79c63 1e620e6d
No related branches found
No related tags found
No related merge requests found
Subproject commit 81c967842f55811b459e455572703631712d7d86
Subproject commit 8c2633f87320a29c7abd9389cedda60a64f88bfa
......@@ -4,9 +4,12 @@ import (
"context"
"fmt"
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/content"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/items"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/setup"
pb "git.perx.ru/perxis/perxis-go/proto/extensions"
)
......@@ -22,7 +25,7 @@ const (
StateInProgress = pb.SpaceExtensions_IN_PROGRESS
StateFail = pb.SpaceExtensions_FAIL
ExtensionMetadataKey = "extension"
MetadataKey = "extension"
)
type (
......@@ -105,3 +108,24 @@ func ExtensionFromError(err error) string {
ext, _ := v.(string)
return ext
}
func isMetadataEqual(s1, s2 *schema.Schema) bool {
if s1.Metadata == nil && s2.Metadata == nil {
return true
}
if s1.Metadata == nil || s2.Metadata == nil {
return false
}
return s1.Metadata[MetadataKey] == s2.Metadata[MetadataKey]
}
// UpdateCollectionStrategy В дополнение к стратегии по умолчанию делает проверку, что обновляемая
// коллекция была установлена расширением. Если в метаданных схемы отсутствует специальный ключ `MetadataKey`,
// это означает, что коллекция была создана пользователем и отношения к расширению не имеет - вернется ошибка.
func UpdateCollectionStrategy(s *setup.Setup, exist, collection *collections.Collection) (*collections.Collection, bool, bool, error) {
if !s.IsForce() && !collection.IsView() && !exist.IsView() && !isMetadataEqual(collection.Schema, exist.Schema) {
return nil, false, false, errors.WithDetailf(collections.ErrAlreadyExists, "Коллекция с идентификатором '%s' "+
"уже существует. Удалите ее или вызовите установку расширения с флагом Force", collection.ID)
}
return setup.DefaultUpdateCollectionStrategyFn(s, exist, collection)
}
package extension
import (
"testing"
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
"git.perx.ru/perxis/perxis-go/pkg/setup"
"github.com/stretchr/testify/assert"
)
func Test_isMetadataEqual(t *testing.T) {
tests := []struct {
name string
s1 *schema.Schema
s2 *schema.Schema
want bool
}{
{
"Not equal #1 (no metadata)",
schema.New("name", field.String()).WithMetadata(MetadataKey, "test"),
schema.New("name", field.String()),
false,
},
{
"Not equal #2 (different metadata)",
schema.New("name", field.String()).WithMetadata(MetadataKey, "test"),
schema.New("name", field.String()).WithMetadata("test", "test"),
false,
},
{
"Equal #1 (no metadata)",
schema.New("name", field.String()),
schema.New("name", field.String()),
true,
},
{
"Equal #2 (equal metadata)",
schema.New("name", field.String()).WithMetadata(MetadataKey, "test"),
schema.New("name", field.String()).WithMetadata(MetadataKey, "test"),
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, isMetadataEqual(tt.s1, tt.s2), "isMetadataExtensionEqual(%v, %v)", tt.s1, tt.s2)
})
}
}
func TestDefaultUpdateCollectionStrategyFn(t *testing.T) {
tests := []struct {
name string
exist *collections.Collection
collection *collections.Collection
force bool
wantErr func(err error)
}{
{
name: "collection belongs to extension",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
wantErr: nil,
},
{
name: "collection belongs to another extension",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-2")},
wantErr: func(err error) {
assert.ErrorIs(t, err, collections.ErrAlreadyExists)
assert.Equal(t, "Коллекция с идентификатором 'coll' уже существует. Удалите ее или "+
"вызовите установку расширения с флагом Force", errors.GetDetail(err))
},
},
{
name: "collection was created by user",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String())},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
wantErr: func(err error) {
assert.ErrorIs(t, err, collections.ErrAlreadyExists)
assert.Equal(t, "Коллекция с идентификатором 'coll' уже существует. Удалите ее или "+
"вызовите установку расширения с флагом Force", errors.GetDetail(err))
},
},
{
name: "collection was created by user with force",
exist: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
collection: &collections.Collection{ID: "coll", SpaceID: "sp", EnvID: "env", Name: "new name",
Schema: schema.New("name", field.String()).WithMetadata("extension", "extension-1")},
force: true,
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
set := setup.NewSetup(nil, "sp", "env", nil).WithForce(tt.force)
_, _, _, err := UpdateCollectionStrategy(set, tt.exist, tt.collection)
if tt.wantErr != nil {
tt.wantErr(err)
return
}
})
}
}
......@@ -8,8 +8,6 @@ import (
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/data"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/extension"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"go.uber.org/zap"
)
......@@ -73,26 +71,7 @@ func DeleteCollectionIfRemove() CollectionsOption {
}
}
func isMetadataExtensionEqual(s1, s2 *schema.Schema) bool {
if s1.Metadata == nil && s2.Metadata == nil {
return true
}
if s1.Metadata == nil || s2.Metadata == nil {
return false
}
return s1.Metadata[extension.ExtensionMetadataKey] == s2.Metadata[extension.ExtensionMetadataKey]
}
func DefaultUpdateCollectionStrategy() CollectionsOption {
return func(c *CollectionConfig) {
c.UpdateFn = func(s *Setup, exist, collection *collections.Collection) (*collections.Collection, bool, bool, error) {
if !s.IsForce() && !collection.IsView() && !exist.IsView() && !isMetadataExtensionEqual(collection.Schema, exist.Schema) {
return nil, false, false, errors.WithDetailf(collections.ErrAlreadyExists, "Коллекция с идентификатором '%s' "+
"уже существует. Удалите ее или вызовите установку расширения с флагом Force", collection.ID)
}
func DefaultUpdateCollectionStrategyFn(_ *Setup, exist, collection *collections.Collection) (*collections.Collection, bool, bool, error) {
if len(exist.Tags) > 0 {
collection.Tags = data.SetFromSlice(append(exist.Tags, collection.Tags...))
}
......@@ -109,6 +88,16 @@ func DefaultUpdateCollectionStrategy() CollectionsOption {
return collection, update, setSchema, nil
}
func DefaultUpdateCollectionStrategy() CollectionsOption {
return func(c *CollectionConfig) {
c.UpdateFn = DefaultUpdateCollectionStrategyFn
}
}
func WithUpdateCollectionStrategy(fn UpdateCollectionFn) CollectionsOption {
return func(c *CollectionConfig) {
c.UpdateFn = fn
}
}
......
......@@ -10,7 +10,6 @@ import (
"git.perx.ru/perxis/perxis-go/pkg/environments"
envmocks "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/extension"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
"github.com/stretchr/testify/assert"
......@@ -107,64 +106,6 @@ func TestSetup_InstallCollections(t *testing.T) {
assert.Contains(t, errors.GetDetail(err), "Возникла ошибка при настройке коллекции space(1)")
},
},
{
name: "Update extension collection with metadata",
collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
collectionsCall: func(svc *mockscollections.Collections) {
svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}, nil).Once()
svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}).Return(nil).Once()
svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")).Return(nil).Once()
},
envsCall: func(svc *envmocks.Environments) {
svc.On("Migrate", mock.Anything, "sp", "env").Return(nil).Once()
},
wantErr: func(t *testing.T, err error) {
assert.NoError(t, err)
},
},
{
name: "Fail to update user collection with same id as in extensions collection",
collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
collectionsCall: func(svc *mockscollections.Collections) {
svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
},
wantErr: func(t *testing.T, err error) {
assert.Error(t, err)
assert.ErrorIs(t, err, collections.ErrAlreadyExists)
},
},
{
name: "Update user collection with same id as in extensions collection with force",
collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
collectionsCall: func(svc *mockscollections.Collections) {
svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}).Return(nil).Once()
svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")).Return(nil).Once()
},
envsCall: func(svc *envmocks.Environments) {
svc.On("Migrate", mock.Anything, "sp", "env").Return(nil).Once()
},
wantErr: func(t *testing.T, err error) {
assert.NoError(t, err)
},
force: true,
},
{
name: "Update exist view collection with the same id",
collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
collectionsCall: func(svc *mockscollections.Collections) {
svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp2", EnvID: environments.DefaultEnvironment, CollectionID: "2"}}, nil).Once()
svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}).Return(nil).Once()
svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")).Return(nil).Once()
},
envsCall: func(svc *envmocks.Environments) {
svc.On("Migrate", mock.Anything, "sp", "env").Return(nil).Once()
},
wantErr: func(t *testing.T, err error) {
assert.NoError(t, err)
},
},
//todo после удаления флага _alwaysSetSchema данный тест будет работать, сейчас он будет всегда падать, поэтому пока закомментирован
{
name: "Update view collection with the same id to new view collection",
collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp3", EnvID: environments.DefaultEnvironment, CollectionID: "3"}}},
......@@ -215,42 +156,3 @@ func TestSetup_InstallCollections(t *testing.T) {
})
}
}
func Test_isMetadataExtensionEqual(t *testing.T) {
tests := []struct {
name string
s1 *schema.Schema
s2 *schema.Schema
want bool
}{
{
"Not equal #1 (no metadata)",
schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test"),
schema.New("name", field.String()),
false,
},
{
"Not equal #2 (different metadata)",
schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test"),
schema.New("name", field.String()).WithMetadata("test", "test"),
false,
},
{
"Equal #1 (no metadata)",
schema.New("name", field.String()),
schema.New("name", field.String()),
true,
},
{
"Equal #2 (equal metadata)",
schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test"),
schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test"),
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, isMetadataExtensionEqual(tt.s1, tt.s2), "isMetadataExtensionEqual(%v, %v)", tt.s1, tt.s2)
})
}
}
......@@ -12,7 +12,6 @@ import (
"git.perx.ru/perxis/perxis-go/pkg/data"
environmentMock "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/extension"
"git.perx.ru/perxis/perxis-go/pkg/items"
itemsMock "git.perx.ru/perxis/perxis-go/pkg/items/mocks"
"git.perx.ru/perxis/perxis-go/pkg/roles"
......@@ -66,7 +65,7 @@ func getActions() []*items.Item {
ID: "act",
SpaceID: spaceID,
EnvID: envID,
CollectionID: extension.ActionsCollectionID,
CollectionID: "actions",
Data: map[string]interface{}{
"action": "act",
"name": "Action",
......@@ -705,7 +704,7 @@ func TestSetupInstall(t *testing.T) {
//
// itmMock := &itemsMock.Items{}
// for _, act := range getActions() {
// itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
// itmMock.On("Get", mock.Anything, spaceID, envID, "actions", act.ID).
// Return(nil, items.ErrNotFound).
// Once()
//
......@@ -775,7 +774,7 @@ func TestSetupUninstall(t *testing.T) {
del := args[1].(*items.Item)
require.Equal(t, spaceID, del.SpaceID)
require.Equal(t, envID, del.EnvID)
require.Equal(t, extension.ActionsCollectionID, del.CollectionID)
require.Equal(t, "actions", del.CollectionID)
require.Equal(t, act.ID, del.ID)
}).
Return(nil).
......@@ -827,7 +826,7 @@ func TestSetupUninstall(t *testing.T) {
del := args[1].(*items.Item)
require.Equal(t, spaceID, del.SpaceID)
require.Equal(t, envID, del.EnvID)
require.Equal(t, extension.ActionsCollectionID, del.CollectionID)
require.Equal(t, "actions", del.CollectionID)
require.Equal(t, act.ID, del.ID)
}).
Return(nil).
......@@ -863,7 +862,7 @@ func TestSetupUninstall(t *testing.T) {
itmMock := &itemsMock.Items{}
for _, act := range getActions() {
itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
itmMock.On("Delete", mock.Anything, spaceID, envID, "actions", act.ID).
Return(nil).
Once()
}
......@@ -897,7 +896,7 @@ func TestSetupUninstall(t *testing.T) {
itmMock := &itemsMock.Items{}
for _, act := range getActions() {
itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
itmMock.On("Delete", mock.Anything, spaceID, envID, "actions", act.ID).
Return(nil).
Once()
}
......@@ -937,7 +936,7 @@ func TestSetupUninstall(t *testing.T) {
itmMock := &itemsMock.Items{}
for _, act := range getActions() {
itmMock.On("Delete", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
itmMock.On("Delete", mock.Anything, spaceID, envID, "actions", act.ID).
Return(nil).
Once()
}
......@@ -984,7 +983,7 @@ func TestSetupUninstall(t *testing.T) {
del := args[1].(*items.Item)
require.Equal(t, spaceID, del.SpaceID)
require.Equal(t, envID, del.EnvID)
require.Equal(t, extension.ActionsCollectionID, del.CollectionID)
require.Equal(t, "actions", del.CollectionID)
require.Equal(t, act.ID, del.ID)
}).
Return(errors.New("can't delete item")).
......@@ -1047,7 +1046,7 @@ func TestSetupCheck(t *testing.T) {
mock.Anything,
spaceID,
envID,
extension.ActionsCollectionID,
"actions",
mock.MatchedBy(func(filter *items.Filter) bool { return data.Contains("act", filter.ID) }),
mock.MatchedBy(func(opt *items.FindOptions) bool { return opt.Regular && opt.Hidden && opt.Templates }),
).Return(getActions(), 0, nil).Once()
......@@ -1219,7 +1218,7 @@ func TestSetupCheck(t *testing.T) {
//
// itmMock := &itemsMock.Items{}
// for _, act := range getActions() {
// itmMock.On("Get", mock.Anything, spaceID, envID, extension.ActionsCollectionID, act.ID).
// itmMock.On("Get", mock.Anything, spaceID, envID, "actions", act.ID).
// Return(act, nil).
// Once()
// }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment