diff --git a/pkg/extension/extension.go b/pkg/extension/extension.go index c4abb5d5504d123320d90a70dd065a246366b69b..771a945312dd8a6bdfb87b3ca6fa9970e8d28f0d 100644 --- a/pkg/extension/extension.go +++ b/pkg/extension/extension.go @@ -3,8 +3,11 @@ package extension import ( "context" + "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/schema" + "git.perx.ru/perxis/perxis-go/pkg/setup" pb "git.perx.ru/perxis/perxis-go/proto/extensions" ) @@ -14,7 +17,7 @@ const ( StateInProgress = pb.State_IN_PROGRESS StateFail = pb.State_FAIL - ExtensionMetadataKey = "extension" // todo: давайте переименуем -> MetadataKey? везде импорты extension.ExtensionMetadataKey + MetadataKey = "extension" ) type ( @@ -70,3 +73,24 @@ func CheckInstalled(ctx context.Context, content *content.Content, spaceID, envI } return status.Installed, nil } + +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 24a8661c57f133789b72b551dd58068dfe5eb636..a42c650d67f0d9290490ca157b55fbcce6c8c344 100644 --- a/pkg/setup/collection.go +++ b/pkg/setup/collection.go @@ -8,7 +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/schema" "go.uber.org/zap" ) @@ -79,42 +78,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"] == s2.Metadata["extension"] // todo: Setup ничего не должен знать про расширения + 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 af2352efc5de9a2c74ec5d396d5d6c68f38576d0..a06c117a3a5586b4a3afc9513bb6ac1333c556cf 100644 --- a/pkg/setup/collection_test.go +++ b/pkg/setup/collection_test.go @@ -106,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", "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", "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", "test-extension")}).Return(nil).Once() - svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata("extension", "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", "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", "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", "test-extension")}).Return(nil).Once() - svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata("extension", "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", "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", "test-extension")}).Return(nil).Once() - svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata("extension", "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"}}}, @@ -214,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", "test"), - schema.New("name", field.String()), - false, - }, - { - "Not equal #2 (different metadata)", - schema.New("name", field.String()).WithMetadata("extension", "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", "test"), - schema.New("name", field.String()).WithMetadata("extension", "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) - }) - } -}