From 0feae9dc09f6dc807a84f36fe566300174d697b9 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Tue, 12 Sep 2023 09:29:45 -0400 Subject: [PATCH 1/6] Remove ginkgo from internal/controller unit tests Fix #190 Signed-off-by: Todd Short --- internal/controllers/admission_test.go | 323 +-- .../controllers/operator_controller_test.go | 2112 +++++++++-------- internal/controllers/suite_test.go | 42 +- 3 files changed, 1311 insertions(+), 1166 deletions(-) diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index 60b106664..aef29fd3d 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -2,9 +2,9 @@ package controllers_test import ( "context" + "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" @@ -19,159 +19,178 @@ func operator(spec operatorsv1alpha1.OperatorSpec) *operatorsv1alpha1.Operator { } } -var _ = Describe("Operator Spec Validations", func() { - var ( - ctx context.Context - cancel context.CancelFunc - ) - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - }) - AfterEach(func() { - cancel() - }) - It("should fail if the spec is empty", func() { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{})) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'")) - }) - It("should fail if package name length is greater than 48 characters", func() { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "this-is-a-really-long-package-name-that-is-greater-than-48-characters", - })) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Too long: may not be longer than 48")) - }) - It("should fail if version is valid semver but length is greater than 64 characters", func() { +func TestOperatorSpecIsEmpty(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{})) + require.Error(t, err) + require.ErrorContains(t, err, "spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'") +} + +func TestOperatorLongPackageName(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "this-is-a-really-long-package-name-that-is-greater-than-48-characters", + })) + require.Error(t, err) + require.ErrorContains(t, err, "Too long: may not be longer than 48") +} + +func TestOperatorLongValidSemver(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Version: "1234567890.1234567890.12345678901234567890123456789012345678901234", + })) + require.Error(t, err) + require.ErrorContains(t, err, "Too long: may not be longer than 64") +} + +func TestOperatorInvalidSemver(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + invalidSemvers := []string{ + "1.2.3.4", + "1.02.3", + "1.2.03", + "1.2.3-beta!", + "1.2.3.alpha", + "1..2.3", + "1.2.3-pre+bad_metadata", + "1.2.-3", + ".1.2.3", + "<<1.2.3", + ">>1.2.3", + ">~1.2.3", + "==1.2.3", + "=!1.2.3", + "!1.2.3", + "1.Y", + ">1.2.3 && <2.3.4", + ">1.2.3;<2.3.4", + "1.2.3 - 2.3.4", + } + for _, invalidSemver := range invalidSemvers { err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", - Version: "1234567890.1234567890.12345678901234567890123456789012345678901234", + Version: invalidSemver, })) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Too long: may not be longer than 64")) - }) - It("should fail if an invalid semver is given", func() { - invalidSemvers := []string{ - "1.2.3.4", - "1.02.3", - "1.2.03", - "1.2.3-beta!", - "1.2.3.alpha", - "1..2.3", - "1.2.3-pre+bad_metadata", - "1.2.-3", - ".1.2.3", - "<<1.2.3", - ">>1.2.3", - ">~1.2.3", - "==1.2.3", - "=!1.2.3", - "!1.2.3", - "1.Y", - ">1.2.3 && <2.3.4", - ">1.2.3;<2.3.4", - "1.2.3 - 2.3.4", - } - for _, invalidSemver := range invalidSemvers { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Version: invalidSemver, - })) - - Expect(err).To(HaveOccurred(), "expected error for invalid semver %q", invalidSemver) - // Don't need to include the whole regex, this should be enough to match the MasterMinds regex - Expect(err.Error()).To(ContainSubstring("spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)")) - } - }) - It("should pass if a valid semver range given", func() { - validSemvers := []string{ - ">=1.2.3", - "=>1.2.3", - ">= 1.2.3", - ">=v1.2.3", - ">= v1.2.3", - "<=1.2.3", - "=<1.2.3", - "=1.2.3", - "!=1.2.3", - "<1.2.3", - ">1.2.3", - "~1.2.2", - "~>1.2.3", - "^1.2.3", - "1.2.3", - "v1.2.3", - "1.x", - "1.X", - "1.*", - "1.2.x", - "1.2.X", - "1.2.*", - ">=1.2.3 <2.3.4", - ">=1.2.3,<2.3.4", - ">=1.2.3, <2.3.4", - "<1.2.3||>2.3.4", - "<1.2.3|| >2.3.4", - "<1.2.3 ||>2.3.4", - "<1.2.3 || >2.3.4", - ">1.0.0,<1.2.3 || >2.1.0", - "<1.2.3-abc >2.3.4-def", - "<1.2.3-abc+def >2.3.4-ghi+jkl", - } - for _, validSemver := range validSemvers { - op := operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Version: validSemver, - }) - err := cl.Create(ctx, op) - Expect(err).NotTo(HaveOccurred(), "expected success for semver range '%q': %w", validSemver, err) - err = cl.Delete(ctx, op) - Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid semver '%q': %w", validSemver, err) - } - }) - It("should fail if an invalid channel name is given", func() { - invalidChannels := []string{ - "spaces spaces", - "Capitalized", - "camelCase", - "many/invalid$characters+in_name", - "-start-with-hyphen", - "end-with-hyphen-", - ".start-with-period", - "end-with-period.", - } - for _, invalidChannel := range invalidChannels { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Channel: invalidChannel, - })) - Expect(err).To(HaveOccurred(), "expected error for invalid channel '%q'", invalidChannel) - Expect(err.Error()).To(ContainSubstring("spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'")) - } - }) - It("should pass if a valid channel name is given", func() { - validChannels := []string{ - "hyphenated-name", - "dotted.name", - "channel-has-version-1.0.1", - } - for _, validChannel := range validChannels { - op := operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Channel: validChannel, - }) - err := cl.Create(ctx, op) - Expect(err).NotTo(HaveOccurred(), "unexpected error creating valid channel '%q': %w", validChannel, err) - err = cl.Delete(ctx, op) - Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid channel '%q': %w", validChannel, err) - } - }) - It("should fail if an invalid channel name length", func() { + require.Errorf(t, err, "expected error for invalid semver %q", invalidSemver) + // Don't need to include the whole regex, this should be enough to match the MasterMinds regex + require.ErrorContains(t, err, "spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)") + } +} + +func TestOperatorValidSemver(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + validSemvers := []string{ + ">=1.2.3", + "=>1.2.3", + ">= 1.2.3", + ">=v1.2.3", + ">= v1.2.3", + "<=1.2.3", + "=<1.2.3", + "=1.2.3", + "!=1.2.3", + "<1.2.3", + ">1.2.3", + "~1.2.2", + "~>1.2.3", + "^1.2.3", + "1.2.3", + "v1.2.3", + "1.x", + "1.X", + "1.*", + "1.2.x", + "1.2.X", + "1.2.*", + ">=1.2.3 <2.3.4", + ">=1.2.3,<2.3.4", + ">=1.2.3, <2.3.4", + "<1.2.3||>2.3.4", + "<1.2.3|| >2.3.4", + "<1.2.3 ||>2.3.4", + "<1.2.3 || >2.3.4", + ">1.0.0,<1.2.3 || >2.1.0", + "<1.2.3-abc >2.3.4-def", + "<1.2.3-abc+def >2.3.4-ghi+jkl", + } + for _, validSemver := range validSemvers { + op := operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Version: validSemver, + }) + err := cl.Create(ctx, op) + require.NoErrorf(t, err, "unexpected error for semver range '%q': %w", validSemver, err) + err = cl.Delete(ctx, op) + require.NoErrorf(t, err, "unexpected error deleting valid semver '%q': %w", validSemver, err) + } +} + +func TestOperatorInvalidChannel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + invalidChannels := []string{ + "spaces spaces", + "Capitalized", + "camelCase", + "many/invalid$characters+in_name", + "-start-with-hyphen", + "end-with-hyphen-", + ".start-with-period", + "end-with-period.", + } + for _, invalidChannel := range invalidChannels { err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", - Channel: "longname01234567890123456789012345678901234567890", + Channel: invalidChannel, })) - Expect(err).To(HaveOccurred(), "expected error for invalid channel length") - Expect(err.Error()).To(ContainSubstring("spec.channel: Too long: may not be longer than 48")) - }) -}) + require.Errorf(t, err, "expected error for invalid channel '%q'", invalidChannel) + require.ErrorContains(t, err, "spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'") + } +} + +func TestOperatorValidChannel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + validChannels := []string{ + "hyphenated-name", + "dotted.name", + "channel-has-version-1.0.1", + } + for _, validChannel := range validChannels { + op := operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Channel: validChannel, + }) + err := cl.Create(ctx, op) + require.NoErrorf(t, err, "unexpected error creating valid channel '%q': %w", validChannel, err) + err = cl.Delete(ctx, op) + require.NoErrorf(t, err, "unexpected error deleting valid channel '%q': %w", validChannel, err) + } +} + +func TestOperatorLongValidChannel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Channel: "longname01234567890123456789012345678901234567890", + })) + + require.Error(t, err) + require.ErrorContains(t, err, "spec.channel: Too long: may not be longer than 48") +} diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index ef0a8f54c..9ff260893 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -6,8 +6,6 @@ import ( "fmt" "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy/solver" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" @@ -32,1024 +30,1178 @@ import ( testutil "github.com/operator-framework/operator-controller/test/util" ) -var _ = Describe("Operator Controller Test", func() { - var ( - ctx context.Context - fakeCatalogClient testutil.FakeCatalogClient - reconciler *controllers.OperatorReconciler - ) - BeforeEach(func() { - ctx = context.Background() - fakeCatalogClient = testutil.NewFakeCatalogClient(testBundleList) - reconciler = &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } - }) - When("the operator does not exist", func() { - It("returns no error", func() { - res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - }) - }) - When("the operator exists", func() { - var ( - operator *operatorsv1alpha1.Operator - opKey types.NamespacedName - ) - BeforeEach(func() { - opKey = types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} - }) - AfterEach(func() { - verifyInvariants(ctx, reconciler.Client, operator) - Expect(cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})).To(Succeed()) - Expect(cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})).To(Succeed()) - }) - When("the operator specifies a non-existent package", func() { - var pkgName string - BeforeEach(func() { - By("initializing cluster state") - pkgName = fmt.Sprintf("non-existent-%s", rand.String(6)) - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q found", pkgName))) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +// Describe: Operator Controller Test +func TestOperatorDoesNotExist(t *testing.T) { + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + t.Log("When the operator does not exist") + t.Log("It returns no error") + res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(Equal(fmt.Sprintf("no package %q found", pkgName))) - }) - }) - When("the operator specifies a version that does not exist", func() { - var pkgName string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: "0.50.0", // this version of the package does not exist - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName))) +func TestOperatorNonExistantPackage(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a non-existent package") + t.Log("By initializing cluster state") + pkgName := fmt.Sprintf("non-existent-%s", rand.String(6)) + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q found", pkgName)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q found", pkgName), cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) +func TestOperatorNonExistantVersion(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a version that does not exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: "0.50.0", // this version of the package does not exist + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorBundleDeploymentDoesNotExist(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When the BundleDeployment does not exist") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("It results in the expected BundleDeployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + t.Log("It sets the resolvedBundleResource status field") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + + t.Log("It sets the InstalledBundleResource status field") + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("It sets the status on operator") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(Equal(fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName))) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as resolution failed")) - }) - }) - When("the operator specifies a valid available package", func() { - const pkgName = "prometheus" - BeforeEach(func() { - By("initializing cluster state") - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - When("the BundleDeployment does not exist", func() { - BeforeEach(func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - }) - It("results in the expected BundleDeployment", func() { - bd := &rukpakv1alpha1.BundleDeployment{} - err := cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd) - Expect(err).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the resolvedBundleResource status field", func() { - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the InstalledBundleResource status field", func() { - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - }) - It("sets the status on operator", func() { - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - }) - }) - When("the expected BundleDeployment already exists", func() { - var bd *rukpakv1alpha1.BundleDeployment - BeforeEach(func() { - By("patching the existing BD") - bd = &rukpakv1alpha1.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: opKey.Name, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: operatorsv1alpha1.GroupVersion.String(), - Kind: "Operator", - Name: operator.Name, - UID: operator.UID, - Controller: pointer.Bool(true), - BlockOwnerDeletion: pointer.Bool(true), - }, - }, - }, - Spec: rukpakv1alpha1.BundleDeploymentSpec{ - ProvisionerClassName: "core-rukpak-io-plain", - Template: &rukpakv1alpha1.BundleTemplate{ - Spec: rukpakv1alpha1.BundleSpec{ - ProvisionerClassName: "core-rukpak-io-registry", - Source: rukpakv1alpha1.BundleSource{ - Type: rukpakv1alpha1.SourceTypeImage, - Image: &rukpakv1alpha1.ImageSource{ - Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", - }, - }, - }, - }, - }, - } - }) - - When("the BundleDeployment spec is out of date", func() { - BeforeEach(func() { - By("modifying the BD spec and creating the object") - bd.Spec.ProvisionerClassName = "core-rukpak-io-helm" - err := cl.Create(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - }) - It("results in the expected BundleDeployment", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - - By("checking the expected BD spec") - bd := &rukpakv1alpha1.BundleDeployment{} - err = cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd) - Expect(err).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected status conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - }) - }) - - When("The BundleDeployment spec is up-to-date", func() { - BeforeEach(func() { - err := cl.Create(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - bd.Status.ObservedGeneration = bd.GetGeneration() - }) - - When("the BundleDeployment is not patched", func() { - PIt("does not patch the BundleDeployment", func() { - // TODO: verify that no patch call is made. - }) - }) - - When("The BundleDeployment status is mapped to the expected Operator status", func() { - It("verify operator status when bundle deployment is waiting to be created", func() { - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - }) - - It("verify operator status when `HasValidBundle` condition of rukpak is false", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionFalse, - Message: "failed to unpack", - Reason: rukpakv1alpha1.ReasonUnpackFailed, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - }) - - It("verify operator status when `InstallReady` condition of rukpak is false", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionFalse, - Message: "failed to install", - Reason: rukpakv1alpha1.ReasonInstallFailed, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(ContainSubstring(`failed to install`)) - }) - - It("verify operator status when `InstallReady` condition of rukpak is true", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionTrue, - Message: "operator installed successfully", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("installed from \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - }) - - It("verify any other unknown status of bundledeployment", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionUnknown, - Message: "unpacking", - Reason: rukpakv1alpha1.ReasonUnpackSuccessful, - }) - - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(Equal("bundledeployment not ready: installing")) - }) - - It("verify operator status when bundleDeployment installation status is unknown", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(Equal("bundledeployment not ready: installing")) - }) - - }) - - }) - }) - When("an out-of-date BundleDeployment exists", func() { - var bd *rukpakv1alpha1.BundleDeployment - BeforeEach(func() { - By("creating the expected BD") - bd = &rukpakv1alpha1.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: rukpakv1alpha1.BundleDeploymentSpec{ - ProvisionerClassName: "foo", - Template: &rukpakv1alpha1.BundleTemplate{ - Spec: rukpakv1alpha1.BundleSpec{ - ProvisionerClassName: "bar", - Source: rukpakv1alpha1.BundleSource{ - Type: rukpakv1alpha1.SourceTypeHTTP, - HTTP: &rukpakv1alpha1.HTTPSource{ - URL: "http://localhost:8080/", - }, - }, - }, - }, +func TestOperatorBundleDeploymentOutOfDate(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When the expected BundleDeployment already exists") + t.Log("When the BundleDeployment spec is out of date") + t.Log("By patching the existing BD") + bd := &rukpakv1alpha1.BundleDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: opKey.Name, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: operatorsv1alpha1.GroupVersion.String(), + Kind: "Operator", + Name: operator.Name, + UID: operator.UID, + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }, + }, + Spec: rukpakv1alpha1.BundleDeploymentSpec{ + ProvisionerClassName: "core-rukpak-io-plain", + Template: &rukpakv1alpha1.BundleTemplate{ + Spec: rukpakv1alpha1.BundleSpec{ + ProvisionerClassName: "core-rukpak-io-registry", + Source: rukpakv1alpha1.BundleSource{ + Type: rukpakv1alpha1.SourceTypeImage, + Image: &rukpakv1alpha1.ImageSource{ + Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", }, - } - err := cl.Create(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - }) - It("results in the expected BundleDeployment", func() { - bd := &rukpakv1alpha1.BundleDeployment{} - err := cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd) - Expect(err).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the resolvedBundleResource status field", func() { - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the InstalledBundleResource status field", func() { - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - }) - It("sets resolution to unknown status", func() { - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - }) - }) - }) - When("the operator specifies a duplicate package", func() { - const pkgName = "prometheus" - var dupOperator *operatorsv1alpha1.Operator - - BeforeEach(func() { - By("initializing cluster state") - dupOperator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("orig-%s", opKey.Name)}, - Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, - } - - err := cl.Create(ctx, dupOperator) - Expect(err).NotTo(HaveOccurred()) - - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, - } - err = cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(Equal(`duplicate identifier "required package prometheus" in input`))) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(Equal(`duplicate identifier "required package prometheus" in input`)) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as resolution failed")) - }) - }) - When("the operator specifies a channel with version that exist", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgVer = "1.0.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + }, + }, + }, + } - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake1.0.0")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By modifying the BD spec and creating the object") + bd.Spec.ProvisionerClassName = "core-rukpak-io-helm" + require.NoError(t, cl.Create(ctx, bd)) + + t.Log("It results in the expected BundleDeployment") + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the expected BD spec") + bd = &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected status conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - - By("fetching the bundled deployment") - bd := &rukpakv1alpha1.BundleDeployment{} - Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake1.0.0")) - }) - }) - When("the operator specifies a package that exists within a channel but no version specified", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, +func TestOperatorBundleDeploymentUpToDate(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When the expected BundleDeployment already exists") + t.Log("When the BundleDeployment spec is up-to-date") + t.Log("By patching the existing BD") + bd := &rukpakv1alpha1.BundleDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: opKey.Name, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: operatorsv1alpha1.GroupVersion.String(), + Kind: "Operator", + Name: operator.Name, + UID: operator.UID, + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }, + }, + Spec: rukpakv1alpha1.BundleDeploymentSpec{ + ProvisionerClassName: "core-rukpak-io-plain", + Template: &rukpakv1alpha1.BundleTemplate{ + Spec: rukpakv1alpha1.BundleSpec{ + ProvisionerClassName: "core-rukpak-io-registry", + Source: rukpakv1alpha1.BundleSource{ + Type: rukpakv1alpha1.SourceTypeImage, + Image: &rukpakv1alpha1.ImageSource{ + Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", + }, }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + }, + }, + }, + } - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + require.NoError(t, cl.Create(ctx, bd)) + bd.Status.ObservedGeneration = bd.GetGeneration() + + t.Log("When the BundleDeployment status is mapped to the expected Operator status") + t.Log("It verifies operator status when bundle deployment is waiting to be created") + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("It verifies operator status when `HasValidBundle` condition of rukpak is false") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionFalse, + Message: "failed to unpack", + Reason: rukpakv1alpha1.ReasonUnpackFailed, + }) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - - By("fetching the bundled deployment") - bd := &rukpakv1alpha1.BundleDeployment{} - Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - }) - When("the operator specifies a package version in a channel that does not exist", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgVer = "0.47.0" - pkgChan = "alpha" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Equal(t, "", op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("It verifies operator status when `InstallReady` condition of rukpak is false") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionFalse, + Message: "failed to install", + Reason: rukpakv1alpha1.ReasonInstallFailed, + }) - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + require.NoError(t, err) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Contains(t, cond.Message, `failed to install`) + + t.Log("It verifies operator status when `InstallReady` condition of rukpak is true") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionTrue, + Message: "operator installed successfully", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "installed from \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + t.Log("It verifies any other unknown status of bundledeployment") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionUnknown, + Message: "unpacking", + Reason: rukpakv1alpha1.ReasonUnpackSuccessful, + }) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(Equal(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as resolution failed")) - }) - }) - When("the operator specifies a package in a channel that does not exist", func() { - var pkgName string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgChan = "non-existent" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan))) + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionUnknown, + Message: "installing", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Equal(t, "bundledeployment not ready: installing", cond.Message) + + t.Log("It verifies operator status when bundleDeployment installation status is unknown") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionUnknown, + Message: "installing", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By cchecking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Equal(t, "bundledeployment not ready: installing", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(Equal(fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan))) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as resolution failed")) - }) - }) - When("the operator specifies a package version that does not exist in the channel", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgVer = "0.57.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, +func TestOperatorExpectedBundleDeployment(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When an out-of-date BundleDeployment exists") + t.Log("By creating the expected BD") + bd := &rukpakv1alpha1.BundleDeployment{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: rukpakv1alpha1.BundleDeploymentSpec{ + ProvisionerClassName: "foo", + Template: &rukpakv1alpha1.BundleTemplate{ + Spec: rukpakv1alpha1.BundleSpec{ + ProvisionerClassName: "bar", + Source: rukpakv1alpha1.BundleSource{ + Type: rukpakv1alpha1.SourceTypeHTTP, + HTTP: &rukpakv1alpha1.HTTPSource{ + URL: "http://localhost:8080/", + }, }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + }, + }, + }, + } + require.NoError(t, cl.Create(ctx, bd)) + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("It results in the expected BundleDeployment") + bd = &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + t.Log("It sets the resolvedBundleResource status field") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + + t.Log("It sets the InstalledBundleResource status field") + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("It sets resolution to unknown status") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorDuplicatePackage(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a duplicate package") + t.Log("By initializing cluster state") + dupOperator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("orig-%s", opKey.Name)}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, dupOperator)) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(Equal(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as resolution failed")) - }) - }) - When("the operator specifies a package with a plain+v0 bundle", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "plain" - pkgVer = "0.1.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, `duplicate identifier "required package prometheus" in input`) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, `duplicate identifier "required package prometheus" in input`, cond.Message) + + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhub/plain@sha256:plain")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorChannelVersionExists(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + err := cl.Create(ctx, operator) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("By fetching the bundled deployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhub/plain@sha256:plain\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("bundledeployment status is unknown")) - - By("fetching the bundled deployment") - bd := &rukpakv1alpha1.BundleDeployment{} - Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhub/plain@sha256:plain")) - }) - }) - When("the operator specifies a package with a bad bundle mediatype", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "badmedia" - pkgVer = "0.1.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("unknown bundle mediatype: badmedia+v1")) +func TestOperatorChannelExistsNoVersion(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package that exists within a channel but no version specified") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("By fetching the bundledeployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) +func TestOperatorVersionNoChannel(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package version in a channel that does not exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "0.47.0" + pkgChan := "alpha" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhub/badmedia@sha256:badmedia")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorNoChannel(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package in a channel that does not exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgChan := "non-existent" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhub/badmedia@sha256:badmedia\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(Equal("unknown bundle mediatype: badmedia+v1")) - }) - }) - When("an invalid semver is provided that bypasses the regex validation", func() { - var ( - pkgName string - fakeClient client.Client - ) - BeforeEach(func() { - opKey = types.NamespacedName{Name: fmt.Sprintf("operator-validation-test-%s", rand.String(8))} - - By("injecting creating a client with the bad operator CR") - pkgName = fmt.Sprintf("exists-%s", rand.String(6)) - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: "1.2.3-123abc_def", // bad semver that matches the regex on the CR validation - }, - } +func TestOperatorNoVersion(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package version that does not exist in the channel") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "0.57.0" + pkgChan := "non-existent" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - // this bypasses client/server-side CR validation and allows us to test the reconciler's validation - fakeClient = fake.NewClientBuilder().WithScheme(sch).WithObjects(operator).WithStatusSubresource(operator).Build() +func TestOperatorPlainV0Bundle(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package with a plain+v0 bundle") + t.Log("By initializing cluster state") + pkgName := "plain" + pkgVer := "0.1.0" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhub/plain@sha256:plain", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhub/plain@sha256:plain\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("By fetching the bundled deployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhub/plain@sha256:plain", bd.Spec.Template.Spec.Source.Image.Ref) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("changing the reconciler client to the fake client") - reconciler.Client = fakeClient - }) +func TestOperatorBadBundleMediaType(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package with a bad bundle mediatype") + t.Log("By initializing cluster state") + pkgName := "badmedia" + pkgVer := "0.1.0" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + require.ErrorContains(t, err, "unknown bundle mediatype: badmedia+v1") + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhub/badmedia@sha256:badmedia", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhub/badmedia@sha256:badmedia\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Equal(t, "unknown bundle mediatype: badmedia+v1", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - It("should add an invalid spec condition and *not* re-enqueue for reconciliation", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).ToNot(HaveOccurred()) +func TestOperatorInvalidSemverPastRegex(t *testing.T) { + ctx := context.Background() + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } - By("fetching updated operator after reconcile") - Expect(fakeClient.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + t.Log("When an invalid semver is provided that bypasses the regex validation") + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-validation-test-%s", rand.String(8))} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By injecting creating a client with the bad operator CR") + pkgName := fmt.Sprintf("exists-%s", rand.String(6)) + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: "1.2.3-123abc_def", // bad semver that matches the regex on the CR validation + }, + } - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionUnknown)) - Expect(cond.Message).To(Equal("validation has not been attempted as spec is invalid")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as spec is invalid")) - }) - }) - }) -}) + // this bypasses client/server-side CR validation and allows us to test the reconciler's validation + fakeClient := fake.NewClientBuilder().WithScheme(sch).WithObjects(operator).WithStatusSubresource(operator).Build() + + t.Log("By changing the reconciler client to the fake client") + reconciler.Client = fakeClient + + t.Log("It should add an invalid spec condition and *not* re-enqueue for reconciliation") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, fakeClient.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionUnknown, cond.Reason) + require.Equal(t, "validation has not been attempted as spec is invalid", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as spec is invalid", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} -func verifyInvariants(ctx context.Context, c client.Client, op *operatorsv1alpha1.Operator) { +func verifyInvariants(ctx context.Context, t *testing.T, c client.Client, op *operatorsv1alpha1.Operator) { key := client.ObjectKeyFromObject(op) - Expect(c.Get(ctx, key, op)).To(Succeed()) + require.NoError(t, c.Get(ctx, key, op)) - verifyConditionsInvariants(op) + verifyConditionsInvariants(t, op) } -func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) { +func verifyConditionsInvariants(t *testing.T, op *operatorsv1alpha1.Operator) { // Expect that the operator's set of conditions contains all defined // condition types for the Operator API. Every reconcile should always // ensure every condition type's status/reason/message reflects the state // read during _this_ reconcile call. - Expect(op.Status.Conditions).To(HaveLen(len(conditionsets.ConditionTypes))) - for _, t := range conditionsets.ConditionTypes { - cond := apimeta.FindStatusCondition(op.Status.Conditions, t) - Expect(cond).To(Not(BeNil())) - Expect(cond.Status).NotTo(BeEmpty()) - Expect(cond.Reason).To(BeElementOf(conditionsets.ConditionReasons)) - Expect(cond.ObservedGeneration).To(Equal(op.GetGeneration())) + require.Len(t, op.Status.Conditions, len(conditionsets.ConditionTypes)) + for _, tt := range conditionsets.ConditionTypes { + cond := apimeta.FindStatusCondition(op.Status.Conditions, tt) + require.NotNil(t, cond) + require.NotEmpty(t, cond.Status) + require.Contains(t, conditionsets.ConditionReasons, cond.Reason) + require.Equal(t, op.GetGeneration(), cond.ObservedGeneration) } } diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 835b37070..bee213e47 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -17,20 +17,15 @@ limitations under the License. package controllers_test import ( - "fmt" + "log" "os" "path/filepath" "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" @@ -42,21 +37,7 @@ var ( sch *runtime.Scheme ) -// Some of the tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -// We plan phase Ginkgo out for unit tests. -// See: https://github.com/operator-framework/operator-controller/issues/189 -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Controller Suite") -} - -// This setup allows for Ginkgo and standard Go tests to co-exist -// and use the same setup and teardown. func TestMain(m *testing.M) { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - // bootstrapping test environment testEnv := &envtest.Environment{ CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases"), @@ -65,9 +46,9 @@ func TestMain(m *testing.M) { } cfg, err := testEnv.Start() - if err != nil { - fmt.Println(err) - os.Exit(1) + utilruntime.Must(err) + if cfg == nil { + log.Panic("expected cfg to not be nil") } sch = runtime.NewScheme() @@ -75,19 +56,12 @@ func TestMain(m *testing.M) { utilruntime.Must(rukpakv1alpha1.AddToScheme(sch)) cl, err = client.New(cfg, client.Options{Scheme: sch}) - if err != nil { - fmt.Println(err) - os.Exit(1) + utilruntime.Must(err) + if cl == nil { + log.Panic("expected cl (client.New) to not be nil") } code := m.Run() - - // tearing down the test environment - err = testEnv.Stop() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - + utilruntime.Must(testEnv.Stop()) os.Exit(code) } From 1461eca736e6a039ead636f902dd38b6639c59bb Mon Sep 17 00:00:00 2001 From: Todd Short Date: Tue, 14 Nov 2023 13:45:51 -0500 Subject: [PATCH 2/6] Always allocate cl Signed-off-by: Todd Short --- internal/controllers/admission_test.go | 40 ++++++++++--- .../controllers/operator_controller_test.go | 57 ++++++++++++++++++- internal/controllers/suite_test.go | 16 +++--- 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index aef29fd3d..4213deeab 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -23,7 +23,10 @@ func TestOperatorSpecIsEmpty(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{})) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{})) require.Error(t, err) require.ErrorContains(t, err, "spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'") } @@ -32,7 +35,10 @@ func TestOperatorLongPackageName(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "this-is-a-really-long-package-name-that-is-greater-than-48-characters", })) require.Error(t, err) @@ -43,7 +49,10 @@ func TestOperatorLongValidSemver(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Version: "1234567890.1234567890.12345678901234567890123456789012345678901234", })) @@ -77,7 +86,10 @@ func TestOperatorInvalidSemver(t *testing.T) { "1.2.3 - 2.3.4", } for _, invalidSemver := range invalidSemvers { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Version: invalidSemver, })) @@ -130,7 +142,10 @@ func TestOperatorValidSemver(t *testing.T) { PackageName: "package", Version: validSemver, }) - err := cl.Create(ctx, op) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, op) require.NoErrorf(t, err, "unexpected error for semver range '%q': %w", validSemver, err) err = cl.Delete(ctx, op) require.NoErrorf(t, err, "unexpected error deleting valid semver '%q': %w", validSemver, err) @@ -152,7 +167,10 @@ func TestOperatorInvalidChannel(t *testing.T) { "end-with-period.", } for _, invalidChannel := range invalidChannels { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Channel: invalidChannel, })) @@ -175,7 +193,10 @@ func TestOperatorValidChannel(t *testing.T) { PackageName: "package", Channel: validChannel, }) - err := cl.Create(ctx, op) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, op) require.NoErrorf(t, err, "unexpected error creating valid channel '%q': %w", validChannel, err) err = cl.Delete(ctx, op) require.NoErrorf(t, err, "unexpected error deleting valid channel '%q': %w", validChannel, err) @@ -186,7 +207,10 @@ func TestOperatorLongValidChannel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Channel: "longname01234567890123456789012345678901234567890", })) diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index 9ff260893..a0fd2215f 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -32,6 +32,10 @@ import ( // Describe: Operator Controller Test func TestOperatorDoesNotExist(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ Client: cl, @@ -46,6 +50,9 @@ func TestOperatorDoesNotExist(t *testing.T) { } func TestOperatorNonExistantPackage(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -90,6 +97,9 @@ func TestOperatorNonExistantPackage(t *testing.T) { } func TestOperatorNonExistantVersion(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -142,6 +152,9 @@ func TestOperatorNonExistantVersion(t *testing.T) { } func TestOperatorBundleDeploymentDoesNotExist(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -203,6 +216,9 @@ func TestOperatorBundleDeploymentDoesNotExist(t *testing.T) { } func TestOperatorBundleDeploymentOutOfDate(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -299,6 +315,9 @@ func TestOperatorBundleDeploymentOutOfDate(t *testing.T) { } func TestOperatorBundleDeploymentUpToDate(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -579,6 +598,9 @@ func TestOperatorBundleDeploymentUpToDate(t *testing.T) { } func TestOperatorExpectedBundleDeployment(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -659,6 +681,9 @@ func TestOperatorExpectedBundleDeployment(t *testing.T) { } func TestOperatorDuplicatePackage(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -715,6 +740,9 @@ func TestOperatorDuplicatePackage(t *testing.T) { } func TestOperatorChannelVersionExists(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -737,7 +765,7 @@ func TestOperatorChannelVersionExists(t *testing.T) { Channel: pkgChan, }, } - err := cl.Create(ctx, operator) + err = cl.Create(ctx, operator) require.NoError(t, err) t.Log("It sets resolution success status") @@ -780,6 +808,9 @@ func TestOperatorChannelVersionExists(t *testing.T) { } func TestOperatorChannelExistsNoVersion(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -843,6 +874,9 @@ func TestOperatorChannelExistsNoVersion(t *testing.T) { } func TestOperatorVersionNoChannel(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -899,6 +933,9 @@ func TestOperatorVersionNoChannel(t *testing.T) { } func TestOperatorNoChannel(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -952,6 +989,9 @@ func TestOperatorNoChannel(t *testing.T) { } func TestOperatorNoVersion(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -1007,6 +1047,9 @@ func TestOperatorNoVersion(t *testing.T) { } func TestOperatorPlainV0Bundle(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -1070,6 +1113,9 @@ func TestOperatorPlainV0Bundle(t *testing.T) { } func TestOperatorBadBundleMediaType(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -1126,6 +1172,9 @@ func TestOperatorBadBundleMediaType(t *testing.T) { } func TestOperatorInvalidSemverPastRegex(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -1206,6 +1255,9 @@ func verifyConditionsInvariants(t *testing.T, op *operatorsv1alpha1.Operator) { } func TestOperatorUpgrade(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ @@ -1485,6 +1537,9 @@ func TestOperatorUpgrade(t *testing.T) { } func TestOperatorDowngrade(t *testing.T) { + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) ctx := context.Background() fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.OperatorReconciler{ diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index bee213e47..0569cbbf3 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -33,10 +34,14 @@ import ( ) var ( - cl client.Client sch *runtime.Scheme + cfg *rest.Config ) +func newClient() (client.Client, error) { + return client.New(cfg, client.Options{Scheme: sch}) +} + func TestMain(m *testing.M) { testEnv := &envtest.Environment{ CRDDirectoryPaths: []string{ @@ -45,7 +50,8 @@ func TestMain(m *testing.M) { ErrorIfCRDPathMissing: true, } - cfg, err := testEnv.Start() + var err error + cfg, err = testEnv.Start() utilruntime.Must(err) if cfg == nil { log.Panic("expected cfg to not be nil") @@ -55,12 +61,6 @@ func TestMain(m *testing.M) { utilruntime.Must(operatorsv1alpha1.AddToScheme(sch)) utilruntime.Must(rukpakv1alpha1.AddToScheme(sch)) - cl, err = client.New(cfg, client.Options{Scheme: sch}) - utilruntime.Must(err) - if cl == nil { - log.Panic("expected cl (client.New) to not be nil") - } - code := m.Run() utilruntime.Must(testEnv.Stop()) os.Exit(code) From 6205da4668bf04abf47b860c1e3890202ec21bc3 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Tue, 14 Nov 2023 14:09:21 -0500 Subject: [PATCH 3/6] Move admissions tests to tables Signed-off-by: Todd Short --- internal/controllers/admission_test.go | 91 ++++++++++++-------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index 4213deeab..28f3af74e 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -19,45 +19,56 @@ func operator(spec operatorsv1alpha1.OperatorSpec) *operatorsv1alpha1.Operator { } } -func TestOperatorSpecIsEmpty(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{})) - require.Error(t, err) - require.ErrorContains(t, err, "spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'") +type testData struct { + spec *operatorsv1alpha1.Operator + comment string + errMsg string } -func TestOperatorLongPackageName(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "this-is-a-really-long-package-name-that-is-greater-than-48-characters", - })) - require.Error(t, err) - require.ErrorContains(t, err, "Too long: may not be longer than 48") +var operatorData = []testData{ + { + operator(operatorsv1alpha1.OperatorSpec{}), + "operator spec is empty", + "spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'", + }, + { + operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "this-is-a-really-long-package-name-that-is-greater-than-48-characters", + }), + "long package name", + "spec.packageName: Too long: may not be longer than 48", + }, + { + operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Version: "1234567890.1234567890.12345678901234567890123456789012345678901234", + }), + "long valid semver", + "spec.version: Too long: may not be longer than 64", + }, + { + operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Channel: "longname01234567890123456789012345678901234567890", + }), + "long channel name", + "spec.channel: Too long: may not be longer than 48", + }, } -func TestOperatorLongValidSemver(t *testing.T) { +func TestOperatorSpecs(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Version: "1234567890.1234567890.12345678901234567890123456789012345678901234", - })) - require.Error(t, err) - require.ErrorContains(t, err, "Too long: may not be longer than 64") + for _, d := range operatorData { + t.Logf("Running %s", d.comment) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, d.spec) + require.Error(t, err) + require.ErrorContains(t, err, d.errMsg) + } } func TestOperatorInvalidSemver(t *testing.T) { @@ -202,19 +213,3 @@ func TestOperatorValidChannel(t *testing.T) { require.NoErrorf(t, err, "unexpected error deleting valid channel '%q': %w", validChannel, err) } } - -func TestOperatorLongValidChannel(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Channel: "longname01234567890123456789012345678901234567890", - })) - - require.Error(t, err) - require.ErrorContains(t, err, "spec.channel: Too long: may not be longer than 48") -} From af6d30bfcd8dfc8797404897874491c848ce76e0 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Tue, 14 Nov 2023 14:17:14 -0500 Subject: [PATCH 4/6] Use random name for operators Signed-off-by: Todd Short --- internal/controllers/admission_test.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index 28f3af74e..97c08ab18 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -13,7 +13,7 @@ import ( func operator(spec operatorsv1alpha1.OperatorSpec) *operatorsv1alpha1.Operator { return &operatorsv1alpha1.Operator{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-operator", + GenerateName: "test-operator", }, Spec: spec, } @@ -158,8 +158,6 @@ func TestOperatorValidSemver(t *testing.T) { require.NotNil(t, cl) err = cl.Create(ctx, op) require.NoErrorf(t, err, "unexpected error for semver range '%q': %w", validSemver, err) - err = cl.Delete(ctx, op) - require.NoErrorf(t, err, "unexpected error deleting valid semver '%q': %w", validSemver, err) } } @@ -209,7 +207,5 @@ func TestOperatorValidChannel(t *testing.T) { require.NotNil(t, cl) err = cl.Create(ctx, op) require.NoErrorf(t, err, "unexpected error creating valid channel '%q': %w", validChannel, err) - err = cl.Delete(ctx, op) - require.NoErrorf(t, err, "unexpected error deleting valid channel '%q': %w", validChannel, err) } } From bb80d1643862531980ffde2d879daaed93e86ab3 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Tue, 14 Nov 2023 16:19:46 -0500 Subject: [PATCH 5/6] Parallelize the admission tests Signed-off-by: Todd Short --- internal/controllers/admission_test.go | 137 +++++++++++++++---------- 1 file changed, 80 insertions(+), 57 deletions(-) diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index 97c08ab18..6e01e6692 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -19,13 +19,11 @@ func operator(spec operatorsv1alpha1.OperatorSpec) *operatorsv1alpha1.Operator { } } -type testData struct { +var operatorData = []struct { spec *operatorsv1alpha1.Operator comment string errMsg string -} - -var operatorData = []testData{ +}{ { operator(operatorsv1alpha1.OperatorSpec{}), "operator spec is empty", @@ -57,23 +55,29 @@ var operatorData = []testData{ } func TestOperatorSpecs(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - for _, d := range operatorData { - t.Logf("Running %s", d.comment) - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, d.spec) - require.Error(t, err) - require.ErrorContains(t, err, d.errMsg) + t.Cleanup(cancel) + + for _, od := range operatorData { + d := od + t.Run(d.comment, func(t *testing.T) { + t.Parallel() + t.Logf("Running %s", d.comment) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, d.spec) + require.Error(t, err) + require.ErrorContains(t, err, d.errMsg) + }) } } func TestOperatorInvalidSemver(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + t.Cleanup(cancel) invalidSemvers := []string{ "1.2.3.4", @@ -96,23 +100,28 @@ func TestOperatorInvalidSemver(t *testing.T) { ">1.2.3;<2.3.4", "1.2.3 - 2.3.4", } - for _, invalidSemver := range invalidSemvers { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Version: invalidSemver, - })) - require.Errorf(t, err, "expected error for invalid semver %q", invalidSemver) - // Don't need to include the whole regex, this should be enough to match the MasterMinds regex - require.ErrorContains(t, err, "spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)") + for _, sm := range invalidSemvers { + d := sm + t.Run(d, func(t *testing.T) { + t.Parallel() + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Version: d, + })) + require.Errorf(t, err, "expected error for invalid semver %q", d) + // Don't need to include the whole regex, this should be enough to match the MasterMinds regex + require.ErrorContains(t, err, "spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)") + }) } } func TestOperatorValidSemver(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + t.Cleanup(cancel) validSemvers := []string{ ">=1.2.3", @@ -148,22 +157,27 @@ func TestOperatorValidSemver(t *testing.T) { "<1.2.3-abc >2.3.4-def", "<1.2.3-abc+def >2.3.4-ghi+jkl", } - for _, validSemver := range validSemvers { - op := operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Version: validSemver, + for _, smx := range validSemvers { + d := smx + t.Run(d, func(t *testing.T) { + t.Parallel() + op := operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Version: d, + }) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, op) + require.NoErrorf(t, err, "unexpected error for semver range %q: %w", d, err) }) - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, op) - require.NoErrorf(t, err, "unexpected error for semver range '%q': %w", validSemver, err) } } func TestOperatorInvalidChannel(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + t.Cleanup(cancel) invalidChannels := []string{ "spaces spaces", @@ -175,37 +189,46 @@ func TestOperatorInvalidChannel(t *testing.T) { ".start-with-period", "end-with-period.", } - for _, invalidChannel := range invalidChannels { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Channel: invalidChannel, - })) - require.Errorf(t, err, "expected error for invalid channel '%q'", invalidChannel) - require.ErrorContains(t, err, "spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'") + for _, ch := range invalidChannels { + d := ch + t.Run(d, func(t *testing.T) { + t.Parallel() + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Channel: d, + })) + require.Errorf(t, err, "expected error for invalid channel %q", d) + require.ErrorContains(t, err, "spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'") + }) } } func TestOperatorValidChannel(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + t.Cleanup(cancel) validChannels := []string{ "hyphenated-name", "dotted.name", "channel-has-version-1.0.1", } - for _, validChannel := range validChannels { - op := operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Channel: validChannel, + for _, ch := range validChannels { + d := ch + t.Run(d, func(t *testing.T) { + t.Parallel() + op := operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Channel: d, + }) + cl, err := newClient() + require.NoError(t, err) + require.NotNil(t, cl) + err = cl.Create(ctx, op) + require.NoErrorf(t, err, "unexpected error creating valid channel %q: %w", d, err) }) - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, op) - require.NoErrorf(t, err, "unexpected error creating valid channel '%q': %w", validChannel, err) } } From bd389d8ac400d0672cb7c6dff0f88ef4323d6cc1 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Wed, 15 Nov 2023 16:33:55 -0500 Subject: [PATCH 6/6] Consolidate client/reconciler initialization Signed-off-by: Todd Short --- internal/controllers/admission_test.go | 31 +-- .../controllers/operator_controller_test.go | 186 ++---------------- internal/controllers/suite_test.go | 26 ++- 3 files changed, 51 insertions(+), 192 deletions(-) diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index 6e01e6692..6f0c41c93 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -63,11 +63,8 @@ func TestOperatorSpecs(t *testing.T) { d := od t.Run(d.comment, func(t *testing.T) { t.Parallel() - t.Logf("Running %s", d.comment) - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, d.spec) + cl := newClient(t) + err := cl.Create(ctx, d.spec) require.Error(t, err) require.ErrorContains(t, err, d.errMsg) }) @@ -104,10 +101,8 @@ func TestOperatorInvalidSemver(t *testing.T) { d := sm t.Run(d, func(t *testing.T) { t.Parallel() - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl := newClient(t) + err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Version: d, })) @@ -161,14 +156,12 @@ func TestOperatorValidSemver(t *testing.T) { d := smx t.Run(d, func(t *testing.T) { t.Parallel() + cl := newClient(t) op := operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Version: d, }) - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, op) + err := cl.Create(ctx, op) require.NoErrorf(t, err, "unexpected error for semver range %q: %w", d, err) }) } @@ -193,10 +186,8 @@ func TestOperatorInvalidChannel(t *testing.T) { d := ch t.Run(d, func(t *testing.T) { t.Parallel() - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + cl := newClient(t) + err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Channel: d, })) @@ -220,14 +211,12 @@ func TestOperatorValidChannel(t *testing.T) { d := ch t.Run(d, func(t *testing.T) { t.Parallel() + cl := newClient(t) op := operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Channel: d, }) - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) - err = cl.Create(ctx, op) + err := cl.Create(ctx, op) require.NoErrorf(t, err, "unexpected error creating valid channel %q: %w", d, err) }) } diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index a0fd2215f..bff247b5f 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -6,7 +6,6 @@ import ( "fmt" "testing" - "github.com/operator-framework/deppy/pkg/deppy/solver" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" @@ -25,23 +24,13 @@ import ( operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" "github.com/operator-framework/operator-controller/internal/conditionsets" - "github.com/operator-framework/operator-controller/internal/controllers" "github.com/operator-framework/operator-controller/pkg/features" - testutil "github.com/operator-framework/operator-controller/test/util" ) // Describe: Operator Controller Test func TestOperatorDoesNotExist(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + _, reconciler := newClientAndReconciler(t) - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } t.Log("When the operator does not exist") t.Log("It returns no error") res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) @@ -50,16 +39,8 @@ func TestOperatorDoesNotExist(t *testing.T) { } func TestOperatorNonExistantPackage(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a non-existent package") @@ -97,16 +78,8 @@ func TestOperatorNonExistantPackage(t *testing.T) { } func TestOperatorNonExistantVersion(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a version that does not exist") @@ -152,16 +125,8 @@ func TestOperatorNonExistantVersion(t *testing.T) { } func TestOperatorBundleDeploymentDoesNotExist(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} const pkgName = "prometheus" @@ -216,16 +181,8 @@ func TestOperatorBundleDeploymentDoesNotExist(t *testing.T) { } func TestOperatorBundleDeploymentOutOfDate(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} const pkgName = "prometheus" @@ -315,16 +272,8 @@ func TestOperatorBundleDeploymentOutOfDate(t *testing.T) { } func TestOperatorBundleDeploymentUpToDate(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} const pkgName = "prometheus" @@ -598,16 +547,8 @@ func TestOperatorBundleDeploymentUpToDate(t *testing.T) { } func TestOperatorExpectedBundleDeployment(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} const pkgName = "prometheus" @@ -681,16 +622,8 @@ func TestOperatorExpectedBundleDeployment(t *testing.T) { } func TestOperatorDuplicatePackage(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} const pkgName = "prometheus" @@ -740,16 +673,8 @@ func TestOperatorDuplicatePackage(t *testing.T) { } func TestOperatorChannelVersionExists(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a channel with version that exist") @@ -765,7 +690,7 @@ func TestOperatorChannelVersionExists(t *testing.T) { Channel: pkgChan, }, } - err = cl.Create(ctx, operator) + err := cl.Create(ctx, operator) require.NoError(t, err) t.Log("It sets resolution success status") @@ -808,16 +733,8 @@ func TestOperatorChannelVersionExists(t *testing.T) { } func TestOperatorChannelExistsNoVersion(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a package that exists within a channel but no version specified") @@ -874,16 +791,8 @@ func TestOperatorChannelExistsNoVersion(t *testing.T) { } func TestOperatorVersionNoChannel(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a package version in a channel that does not exist") @@ -933,16 +842,8 @@ func TestOperatorVersionNoChannel(t *testing.T) { } func TestOperatorNoChannel(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a package in a channel that does not exist") @@ -989,16 +890,8 @@ func TestOperatorNoChannel(t *testing.T) { } func TestOperatorNoVersion(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a package version that does not exist in the channel") @@ -1047,16 +940,8 @@ func TestOperatorNoVersion(t *testing.T) { } func TestOperatorPlainV0Bundle(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a package with a plain+v0 bundle") @@ -1113,16 +998,8 @@ func TestOperatorPlainV0Bundle(t *testing.T) { } func TestOperatorBadBundleMediaType(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} t.Log("When the operator specifies a package with a bad bundle mediatype") @@ -1172,17 +1049,8 @@ func TestOperatorBadBundleMediaType(t *testing.T) { } func TestOperatorInvalidSemverPastRegex(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } - t.Log("When an invalid semver is provided that bypasses the regex validation") opKey := types.NamespacedName{Name: fmt.Sprintf("operator-validation-test-%s", rand.String(8))} @@ -1255,16 +1123,8 @@ func verifyConditionsInvariants(t *testing.T, op *operatorsv1alpha1.Operator) { } func TestOperatorUpgrade(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } t.Run("semver upgrade constraints enforcement of upgrades within major version", func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)() @@ -1537,16 +1397,8 @@ func TestOperatorUpgrade(t *testing.T) { } func TestOperatorDowngrade(t *testing.T) { - cl, err := newClient() - require.NoError(t, err) - require.NotNil(t, cl) + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } t.Run("enforce upgrade constraints", func(t *testing.T) { for _, tt := range []struct { diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 0569cbbf3..3b10a032d 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -28,20 +28,38 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" + "github.com/operator-framework/deppy/pkg/deppy/solver" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" + "github.com/stretchr/testify/require" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + "github.com/operator-framework/operator-controller/internal/controllers" + testutil "github.com/operator-framework/operator-controller/test/util" ) +func newClient(t *testing.T) client.Client { + cl, err := client.New(cfg, client.Options{Scheme: sch}) + require.NoError(t, err) + require.NotNil(t, cl) + return cl +} + +func newClientAndReconciler(t *testing.T) (client.Client, *controllers.OperatorReconciler) { + cl := newClient(t) + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + return cl, reconciler +} + var ( sch *runtime.Scheme cfg *rest.Config ) -func newClient() (client.Client, error) { - return client.New(cfg, client.Options{Scheme: sch}) -} - func TestMain(m *testing.M) { testEnv := &envtest.Environment{ CRDDirectoryPaths: []string{