diff --git a/perxis-proto b/perxis-proto index 81c967842f55811b459e455572703631712d7d86..8c2633f87320a29c7abd9389cedda60a64f88bfa 160000 --- a/perxis-proto +++ b/perxis-proto @@ -1 +1 @@ -Subproject commit 81c967842f55811b459e455572703631712d7d86 +Subproject commit 8c2633f87320a29c7abd9389cedda60a64f88bfa diff --git a/pkg/extension/extension.go b/pkg/extension/extension.go index fb8044c7a2f4c76b775840c5628319196a0493ae..4af0a7734b6bb2368c2cbe3ffd2f6de008e3340b 100644 --- a/pkg/extension/extension.go +++ b/pkg/extension/extension.go @@ -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) +} diff --git a/pkg/extension/extension_test.go b/pkg/extension/extension_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4c552b394f7a67cc9de5bb4aa25e90d22fe6058a --- /dev/null +++ b/pkg/extension/extension_test.go @@ -0,0 +1,113 @@ +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 + } + }) + } +} diff --git a/pkg/setup/collection.go b/pkg/setup/collection.go index 494003d2f7cad1f8977a45913fe1b2ce8bb6c404..3b22ecf87118307d58ec942a5abef47aa439679b 100644 --- a/pkg/setup/collection.go +++ b/pkg/setup/collection.go @@ -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,42 +71,33 @@ func DeleteCollectionIfRemove() CollectionsOption { } } -func isMetadataExtensionEqual(s1, s2 *schema.Schema) bool { - if s1.Metadata == nil && s2.Metadata == nil { - return true +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...)) } - if s1.Metadata == nil || s2.Metadata == nil { - return false + var update, setSchema bool + update = collection.Name != exist.Name || collection.IsSingle() != exist.IsSingle() || collection.IsSystem() != exist.IsSystem() || + collection.IsNoData() != exist.IsNoData() || collection.Hidden != exist.Hidden || collection.IsView() != exist.IsView() && data.ElementsMatch(exist.Tags, collection.Tags) + + if exist.View != nil && collection.View != nil { + update = update && *exist.View == *collection.View } - return s1.Metadata[extension.ExtensionMetadataKey] == s2.Metadata[extension.ExtensionMetadataKey] + setSchema = !collection.IsView() && !reflect.DeepEqual(exist.Schema, collection.Schema) + + return collection, update, setSchema, nil } 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) - } - - if len(exist.Tags) > 0 { - collection.Tags = data.SetFromSlice(append(exist.Tags, collection.Tags...)) - } - - var update, setSchema bool - update = collection.Name != exist.Name || collection.IsSingle() != exist.IsSingle() || collection.IsSystem() != exist.IsSystem() || - collection.IsNoData() != exist.IsNoData() || collection.Hidden != exist.Hidden || collection.IsView() != exist.IsView() && data.ElementsMatch(exist.Tags, collection.Tags) - - if exist.View != nil && collection.View != nil { - update = update && *exist.View == *collection.View - } - - setSchema = !collection.IsView() && !reflect.DeepEqual(exist.Schema, collection.Schema) + c.UpdateFn = DefaultUpdateCollectionStrategyFn + } +} - return collection, update, setSchema, nil - } +func WithUpdateCollectionStrategy(fn UpdateCollectionFn) CollectionsOption { + return func(c *CollectionConfig) { + c.UpdateFn = fn } } diff --git a/pkg/setup/collection_test.go b/pkg/setup/collection_test.go index 78f414db18545994d8a15789c617c5f20c27674b..a06c117a3a5586b4a3afc9513bb6ac1333c556cf 100644 --- a/pkg/setup/collection_test.go +++ b/pkg/setup/collection_test.go @@ -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) - }) - } -} diff --git a/pkg/setup/setup_test.go b/pkg/setup/setup_test.go index 044d8294e6c7eb5cbdf28803c24064d39e3c7059..681158887cb53e81582615146b30e68cb2cdef67 100644 --- a/pkg/setup/setup_test.go +++ b/pkg/setup/setup_test.go @@ -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() // }