diff --git a/go.mod b/go.mod index 9691ca2fb..37b3384f2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/blang/semver/v4 v4.0.0 github.com/go-logr/logr v1.2.4 + github.com/google/go-cmp v0.5.9 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 @@ -72,7 +73,6 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/cel-go v0.12.6 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/internal/resolution/util/predicates/predicates_test.go b/internal/resolution/util/predicates/predicates_test.go index c47f0ce5d..2c034d1f0 100644 --- a/internal/resolution/util/predicates/predicates_test.go +++ b/internal/resolution/util/predicates/predicates_test.go @@ -5,8 +5,6 @@ import ( mmsemver "github.com/Masterminds/semver/v3" bsemver "github.com/blang/semver/v4" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy/input" "github.com/operator-framework/operator-registry/alpha/property" @@ -14,134 +12,211 @@ import ( "github.com/operator-framework/operator-controller/internal/resolution/util/predicates" ) -func TestPredicates(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Predicates Suite") +type testData struct { + entity map[string]string + value string + result bool } -var _ = Describe("Predicates", func() { - Describe("WithPackageName", func() { - It("should return true when the entity has the same package name", func() { - entity := input.NewEntity("test", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, - }) - Expect(predicates.WithPackageName("mypackage")(entity)).To(BeTrue()) - Expect(predicates.WithPackageName("notmypackage")(entity)).To(BeFalse()) - }) - It("should return false when the entity does not have a package name", func() { - entity := input.NewEntity("test", map[string]string{}) - Expect(predicates.WithPackageName("mypackage")(entity)).To(BeFalse()) - }) - }) - - Describe("InMastermindsSemverRange", func() { - It("should return true when the entity has the has version in the right range", func() { - entity := input.NewEntity("test", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, - }) - inRange, err := mmsemver.NewConstraint(">=1.0.0") - Expect(err).NotTo(HaveOccurred()) - notInRange, err := mmsemver.NewConstraint(">=2.0.0") - Expect(err).NotTo(HaveOccurred()) - Expect(predicates.InMastermindsSemverRange(inRange)(entity)).To(BeTrue()) - Expect(predicates.InMastermindsSemverRange(notInRange)(entity)).To(BeFalse()) - }) - It("should return false when the entity does not have a version", func() { - entity := input.NewEntity("test", map[string]string{}) - inRange, err := mmsemver.NewConstraint(">=1.0.0") - Expect(err).NotTo(HaveOccurred()) - Expect(predicates.InMastermindsSemverRange(inRange)(entity)).To(BeFalse()) - }) - }) - - Describe("InBlangSemverRange", func() { - It("should return true when the entity has the has version in the right range", func() { - entity := input.NewEntity("test", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, - }) - inRange := bsemver.MustParseRange(">=1.0.0") - notInRange := bsemver.MustParseRange(">=2.0.0") - Expect(predicates.InBlangSemverRange(inRange)(entity)).To(BeTrue()) - Expect(predicates.InBlangSemverRange(notInRange)(entity)).To(BeFalse()) - }) - It("should return false when the entity does not have a version", func() { - entity := input.NewEntity("test", map[string]string{}) - inRange := bsemver.MustParseRange(">=1.0.0") - Expect(predicates.InBlangSemverRange(inRange)(entity)).To(BeFalse()) - }) - }) - - Describe("InChannel", func() { - It("should return true when the entity comes from the specified channel", func() { - entity := input.NewEntity("test", map[string]string{ - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - Expect(predicates.InChannel("stable")(entity)).To(BeTrue()) - Expect(predicates.InChannel("unstable")(entity)).To(BeFalse()) - }) - It("should return false when the entity does not have a channel", func() { - entity := input.NewEntity("test", map[string]string{}) - Expect(predicates.InChannel("stable")(entity)).To(BeFalse()) - }) - }) - - Describe("ProvidesGVK", func() { - It("should return true when the entity provides the specified gvk", func() { - entity := input.NewEntity("test", map[string]string{ - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"},{"group":"bar.io","kind":"Bar","version":"v1"}]`, - }) - Expect(predicates.ProvidesGVK(&olmentity.GVK{ - Group: "foo.io", - Version: "v1", - Kind: "Foo", - })(entity)).To(BeTrue()) - Expect(predicates.ProvidesGVK(&olmentity.GVK{ - Group: "baz.io", - Version: "v1alpha1", - Kind: "Baz", - })(entity)).To(BeFalse()) +func TestPredicatesWithPackageName(t *testing.T) { + testData := []testData{ + {map[string]string{property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`}, + "mypackage", + true}, + {map[string]string{property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`}, + "notmypackage", + false}, + {map[string]string{}, + "mypackage", + false}, + } + + for _, d := range testData { + t.Run("InMastermindsSemverRange", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + if predicates.WithPackageName(d.value)(entity) != d.result { + if d.result { + t.Errorf("package %v should be in entity %v", d.value, entity) + } else { + t.Errorf("package %v should not be in entity %v", d.value, entity) + } + } }) - It("should return false when the entity does not provide a gvk", func() { - entity := input.NewEntity("test", map[string]string{}) - Expect(predicates.ProvidesGVK(&olmentity.GVK{ - Group: "foo.io", - Version: "v1", - Kind: "Foo", - })(entity)).To(BeFalse()) + } +} + +func TestPredicatesInMastermindsSemverRange(t *testing.T) { + testData := []testData{ + {map[string]string{property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`}, + ">=1.0.0", + true}, + {map[string]string{property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`}, + ">=2.0.0", + false}, + {map[string]string{}, + ">=1.0.0", + false}, + } + + for _, d := range testData { + t.Run("InMastermindsSemverRange", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + c, err := mmsemver.NewConstraint(d.value) + if err != nil { + t.Fatalf("unable to parse constraint '%v': %v", d.value, err) + } + if predicates.InMastermindsSemverRange(c)(entity) != d.result { + if d.result { + t.Errorf("version %v should be in entity %v", d.value, entity) + } else { + t.Errorf("version %v should not be in entity %v", d.value, entity) + } + } }) - }) - - Describe("WithBundleImage", func() { - It("should return true when the entity provides the same bundle image", func() { - entity := input.NewEntity("test", map[string]string{ - olmentity.PropertyBundlePath: `"registry.io/repo/image@sha256:1234567890"`, - }) - Expect(predicates.WithBundleImage("registry.io/repo/image@sha256:1234567890")(entity)).To(BeTrue()) - Expect(predicates.WithBundleImage("registry.io/repo/image@sha256:0987654321")(entity)).To(BeFalse()) + } +} + +func TestPredicatesInBlangSemverRange(t *testing.T) { + testData := []testData{ + {map[string]string{property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`}, + ">=1.0.0", + true}, + {map[string]string{property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`}, + ">=2.0.0", + false}, + {map[string]string{}, + ">=1.0.0", + false}, + } + + for _, d := range testData { + t.Run("InBlangSemverRange", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + r := bsemver.MustParseRange(d.value) + if predicates.InBlangSemverRange(r)(entity) != d.result { + if d.result { + t.Errorf("version %v should be in entity %v", d.value, entity) + } else { + t.Errorf("version %v should not be in entity %v", d.value, entity) + } + } }) - It("should return false when the entity does not provide a bundle image", func() { - entity := input.NewEntity("test", map[string]string{}) - Expect(predicates.WithBundleImage("registry.io/repo/image@sha256:1234567890")(entity)).To(BeFalse()) + } +} + +func TestPredicatesInChannel(t *testing.T) { + testData := []testData{ + {map[string]string{property.TypeChannel: `{"channelName":"stable","priority":0}`}, + "stable", + true}, + {map[string]string{property.TypeChannel: `{"channelName":"stable","priority":0}`}, + "unstable", + false}, + {map[string]string{}, + "stable", + false}, + } + + for _, d := range testData { + t.Run("InChannel", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + if predicates.InChannel(d.value)(entity) != d.result { + if d.result { + t.Errorf("channel %v should be in entity %v", d.value, entity) + } else { + t.Errorf("channel %v should not be in entity %v", d.value, entity) + } + } }) - }) - - Describe("Replaces", func() { - It("should return true when the entity provides the replaces property", func() { - entity := input.NewEntity("test", map[string]string{ - "olm.bundle.channelEntry": `{"replaces": "test.v0.2.0"}`, - }) - Expect(predicates.Replaces("test.v0.2.0")(entity)).To(BeTrue()) - Expect(predicates.Replaces("test.v0.1.0")(entity)).To(BeFalse()) + } +} + +func TestPredicatesWithBundleImage(t *testing.T) { + testData := []testData{ + {map[string]string{olmentity.PropertyBundlePath: `"registry.io/repo/image@sha256:1234567890"`}, + "registry.io/repo/image@sha256:1234567890", + true}, + {map[string]string{olmentity.PropertyBundlePath: `"registry.io/repo/image@sha256:1234567890"`}, + "registry.io/repo/image@sha256:0987654321", + false}, + {map[string]string{}, + "registry.io/repo/image@sha256:1234567890", + false}, + } + + for _, d := range testData { + t.Run("WithBundleImage", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + if predicates.WithBundleImage(d.value)(entity) != d.result { + if d.result { + t.Errorf("bundle %v should be in entity %v", d.value, entity) + } else { + t.Errorf("bundle %v should not be in entity %v", d.value, entity) + } + } }) - It("should return false when the entity does not provide a replaces property", func() { - entity := input.NewEntity("test", map[string]string{}) - Expect(predicates.Replaces("test.v0.2.0")(entity)).To(BeFalse()) + } +} + +type testGVK struct { + entity map[string]string + value *olmentity.GVK + result bool +} + +func TestProvidesGVK(t *testing.T) { + testData := []testGVK{ + {map[string]string{property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"},{"group":"bar.io","kind":"Bar","version":"v1"}]`}, + &olmentity.GVK{Group: "foo.io", Version: "v1", Kind: "Foo"}, + true}, + {map[string]string{property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"},{"group":"bar.io","kind":"Bar","version":"v1"}]`}, + &olmentity.GVK{Group: "baz.io", Version: "v1alpha1", Kind: "Baz"}, + false}, + {map[string]string{}, + &olmentity.GVK{Group: "foo.io", Version: "v1", Kind: "Foo"}, + false}, + } + + for _, d := range testData { + t.Run("WithBundleImage", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + if predicates.ProvidesGVK(d.value)(entity) != d.result { + if d.result { + t.Errorf("replaces %v should be in entity %v", d.value, entity) + } else { + t.Errorf("replaces %v should not be in entity %v", d.value, entity) + } + } }) - It("should return false when the replace value is not a valid json", func() { - entity := input.NewEntity("test", map[string]string{ - "olm.bundle.channelEntry": `{"replaces"}`, - }) - Expect(predicates.Replaces("test.v0.2.0")(entity)).To(BeFalse()) + } +} + +func TestPredicatesReplaces(t *testing.T) { + testData := []testData{ + {map[string]string{"olm.bundle.channelEntry": `{"replaces": "test.v0.2.0"}`}, + "test.v0.2.0", + true}, + {map[string]string{"olm.bundle.channelEntry": `{"replaces": "test.v0.2.0"}`}, + "test.v0.1.0", + false}, + {map[string]string{}, + "test.v0.2.0", + false}, + {map[string]string{"olm.bundle.channelEntry": `{"replaces"}`}, + "test.v0.2.0", + false}, + } + + for _, d := range testData { + t.Run("WithBundleImage", func(t *testing.T) { + entity := input.NewEntity("test", d.entity) + if predicates.Replaces(d.value)(entity) != d.result { + if d.result { + t.Errorf("replaces %v should be in entity %v", d.value, entity) + } else { + t.Errorf("replaces %v should not be in entity %v", d.value, entity) + } + } }) - }) -}) + } +} diff --git a/internal/resolution/util/sort/sort_test.go b/internal/resolution/util/sort/sort_test.go index 6f1677cfe..f3d5ec003 100644 --- a/internal/resolution/util/sort/sort_test.go +++ b/internal/resolution/util/sort/sort_test.go @@ -4,149 +4,173 @@ import ( "sort" "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/google/go-cmp/cmp" "github.com/operator-framework/deppy/pkg/deppy/input" "github.com/operator-framework/operator-registry/alpha/property" entitysort "github.com/operator-framework/operator-controller/internal/resolution/util/sort" ) -func TestSort(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Sort Suite") +func TestSortByPackageName(t *testing.T) { + e1 := input.NewEntity("test1", map[string]string{ + property.TypePackage: `{"packageName": "package1", "version": "1.0.0"}`, + }) + e2 := input.NewEntity("test2", map[string]string{ + property.TypePackage: `{"packageName": "package2", "version": "1.0.0"}`, + }) + e3 := input.NewEntity("test3", map[string]string{ + property.TypePackage: `{"packageName": "package3", "version": "1.0.0"}`, + }) + entities := []*input.Entity{e2, e3, e1} + + sort.Slice(entities, func(i, j int) bool { + return entitysort.ByChannelAndVersion(entities[i], entities[j]) + }) + + if diff := cmp.Diff(entities[0], e1); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[1], e2); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[2], e3); diff != "" { + t.Error(diff) + } +} + +func TestSortByChannelName(t *testing.T) { + e1 := input.NewEntity("test1", map[string]string{ + property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"stableA","priority":0}`, + }) + e2 := input.NewEntity("test2", map[string]string{ + property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"stableB","priority":0}`, + }) + e3 := input.NewEntity("test3", map[string]string{ + property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"stableC","priority":0}`, + }) + entities := []*input.Entity{e2, e3, e1} + + sort.Slice(entities, func(i, j int) bool { + return entitysort.ByChannelAndVersion(entities[i], entities[j]) + }) + + if diff := cmp.Diff(entities[0], e1); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[1], e2); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[2], e3); diff != "" { + t.Error(diff) + } +} + +func TestSortByVersionNumber(t *testing.T) { + e1 := input.NewEntity("test1", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e2 := input.NewEntity("test2", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "2.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e3 := input.NewEntity("test3", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + entities := []*input.Entity{e2, e3, e1} + + sort.Slice(entities, func(i, j int) bool { + return entitysort.ByChannelAndVersion(entities[i], entities[j]) + }) + + if diff := cmp.Diff(entities[0], e3); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[1], e2); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[2], e1); diff != "" { + t.Error(diff) + } } -var _ = Describe("Sort", func() { - Describe("ByChannelAndVersion", func() { - It("should order entities by package name", func() { - e1 := input.NewEntity("test1", map[string]string{ - property.TypePackage: `{"packageName": "package1", "version": "1.0.0"}`, - }) - e2 := input.NewEntity("test2", map[string]string{ - property.TypePackage: `{"packageName": "package2", "version": "1.0.0"}`, - }) - e3 := input.NewEntity("test3", map[string]string{ - property.TypePackage: `{"packageName": "package3", "version": "1.0.0"}`, - }) - entities := []*input.Entity{e2, e3, e1} - - sort.Slice(entities, func(i, j int) bool { - return entitysort.ByChannelAndVersion(entities[i], entities[j]) - }) - - Expect(entities[0]).To(Equal(e1)) - Expect(entities[1]).To(Equal(e2)) - Expect(entities[2]).To(Equal(e3)) - }) - - It("should order entities by channel name", func() { - e1 := input.NewEntity("test1", map[string]string{ - property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stableA","priority":0}`, - }) - e2 := input.NewEntity("test2", map[string]string{ - property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stableB","priority":0}`, - }) - e3 := input.NewEntity("test3", map[string]string{ - property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stableC","priority":0}`, - }) - entities := []*input.Entity{e2, e3, e1} - - sort.Slice(entities, func(i, j int) bool { - return entitysort.ByChannelAndVersion(entities[i], entities[j]) - }) - - Expect(entities[0]).To(Equal(e1)) - Expect(entities[1]).To(Equal(e2)) - Expect(entities[2]).To(Equal(e3)) - }) - - It("should order entities by version number (highest first)", func() { - e1 := input.NewEntity("test1", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e2 := input.NewEntity("test2", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e3 := input.NewEntity("test3", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - entities := []*input.Entity{e2, e3, e1} - - sort.Slice(entities, func(i, j int) bool { - return entitysort.ByChannelAndVersion(entities[i], entities[j]) - }) - - Expect(entities[0]).To(Equal(e3)) - Expect(entities[1]).To(Equal(e2)) - Expect(entities[2]).To(Equal(e1)) - }) - - It("should order entities by version number (highest first) and channel priority (lower value -> higher priority)", func() { - e1 := input.NewEntity("test1", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"beta","priority":1}`, - }) - e2 := input.NewEntity("test2", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e3 := input.NewEntity("test3", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"beta","priority":1}`, - }) - e4 := input.NewEntity("test4", map[string]string{ - property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"beta","priority":1}`, - }) - entities := []*input.Entity{e2, e3, e1, e4} - - sort.Slice(entities, func(i, j int) bool { - return entitysort.ByChannelAndVersion(entities[i], entities[j]) - }) - - Expect(entities[0]).To(Equal(e2)) - Expect(entities[1]).To(Equal(e4)) - Expect(entities[2]).To(Equal(e3)) - Expect(entities[3]).To(Equal(e1)) - }) - - It("should order entities missing a property after those that have it", func() { - e1 := input.NewEntity("test1", map[string]string{ - property.TypePackage: `{"packageName": "mypackageA", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e2 := input.NewEntity("test2", map[string]string{ - property.TypePackage: `{"packageName": "mypackageA"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e3 := input.NewEntity("test3", map[string]string{ - property.TypePackage: `{"packageName": "mypackageA", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e4 := input.NewEntity("test4", map[string]string{ - property.TypePackage: `{"packageName": "mypackageB", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }) - e5 := input.NewEntity("test5", map[string]string{}) - entities := []*input.Entity{e2, e3, e1, e4, e5} - - sort.Slice(entities, func(i, j int) bool { - return entitysort.ByChannelAndVersion(entities[i], entities[j]) - }) - - Expect(entities[0]).To(Equal(e3)) - Expect(entities[1]).To(Equal(e1)) - Expect(entities[2]).To(Equal(e4)) - Expect(entities[3]).To(Equal(e2)) // no version - Expect(entities[4]).To(Equal(e5)) // no package - or anything - }) - }) - -}) +func TestSortByVersionNumberAndChannelPriority(t *testing.T) { + e1 := input.NewEntity("test1", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"beta","priority":1}`, + }) + e2 := input.NewEntity("test2", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e3 := input.NewEntity("test3", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "2.0.0"}`, + property.TypeChannel: `{"channelName":"beta","priority":1}`, + }) + e4 := input.NewEntity("test4", map[string]string{ + property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`, + property.TypeChannel: `{"channelName":"beta","priority":1}`, + }) + entities := []*input.Entity{e2, e3, e1, e4} + + sort.Slice(entities, func(i, j int) bool { + return entitysort.ByChannelAndVersion(entities[i], entities[j]) + }) + + if diff := cmp.Diff(entities[0], e2); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[1], e4); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[2], e3); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[3], e1); diff != "" { + t.Error(diff) + } +} + +func TestSortMissingProperty(t *testing.T) { + e1 := input.NewEntity("test1", map[string]string{ + property.TypePackage: `{"packageName": "mypackageA", "version": "1.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e2 := input.NewEntity("test2", map[string]string{ + property.TypePackage: `{"packageName": "mypackageA"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e3 := input.NewEntity("test3", map[string]string{ + property.TypePackage: `{"packageName": "mypackageA", "version": "3.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e4 := input.NewEntity("test4", map[string]string{ + property.TypePackage: `{"packageName": "mypackageB", "version": "3.0.0"}`, + property.TypeChannel: `{"channelName":"stable","priority":0}`, + }) + e5 := input.NewEntity("test5", map[string]string{}) + entities := []*input.Entity{e2, e3, e1, e4, e5} + + sort.Slice(entities, func(i, j int) bool { + return entitysort.ByChannelAndVersion(entities[i], entities[j]) + }) + if diff := cmp.Diff(entities[0], e3); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[1], e1); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[2], e4); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(entities[3], e2); diff != "" { // no version + t.Error(diff) + } + if diff := cmp.Diff(entities[4], e5); diff != "" { // no package - or anything + t.Error(diff) + } +}