Repository: redhat-openshift-ecosystem/openshift-preflight
Pull request #623 (2022-05-10 15:33:06)

Increase coverage of the internal/bundle package
Changes made to increase coverage:

* Add TestBundle to enable the suite. 0% -> 40%.
* Add ValidateBundle tests. 40% -> 97.2%.
* Refactor to use io.Reader for easier testing
* Fixed bug in finding CSV files

Fixes #615

Signed-off-by: Brad P. Crochet <brad@redhat.com>

openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#" title="Author self-approved">bcrochet</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [bcrochet]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
dcibot: -  SUCCESS https://www.distributed-ci.io/jobs/b1a04fe3-3c5d-4694-811a-4795a75a949a/jobStates
 from https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#pullrequestreview-968074182" title="Approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#" title="Author self-approved">bcrochet</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [acornett21,bcrochet]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#pullrequestreview-968074182" title="Approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#" title="Author self-approved">bcrochet</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#pullrequestreview-968286573" title="LGTM">komish</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [acornett21,bcrochet,komish]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#pullrequestreview-968074182" title="Approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#" title="Author self-approved">bcrochet</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#pullrequestreview-969388609" title="Approved">jomkz</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/623#pullrequestreview-968286573" title="LGTM">komish</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [acornett21,bcrochet,jomkz,komish]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/certification/errors/errors.go b/certification/errors/errors.go
index c669fe8..6ce16d1 100644
--- a/certification/errors/errors.go
+++ b/certification/errors/errors.go
@@ -43,4 +43,5 @@ var (
 	ErrExtractingLayer                 = errors.New("could not extract layer")
 	ErrDetectingModifiedFiles          = errors.New("error detecting modified files")
 	ErrRetrievingProject               = errors.New("could not retrieve project")
+	ErrTooManyCSVs                     = errors.New("more than one CSV file detected in bundle")
 )
diff --git a/certification/internal/bundle/bundle.go b/certification/internal/bundle/bundle.go
index d6a6c53..f449aa6 100644
--- a/certification/internal/bundle/bundle.go
+++ b/certification/internal/bundle/bundle.go
@@ -2,8 +2,9 @@ package bundle
 
 import (
 	"context"
+	"fmt"
+	"io"
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 
@@ -28,7 +29,16 @@ func ValidateBundle(ctx context.Context, engine cli.OperatorSdkEngine, imagePath
 		OutputFormat:    "json-alpha1",
 	}
 
-	annotations, err := GetAnnotations(ctx, imagePath)
+	log.Trace("reading annotations file from the bundle")
+	log.Debug("mounted directory is ", imagePath)
+	// retrieve the operator metadata from bundle image
+	annotationsFileName := filepath.Join(imagePath, "metadata", "annotations.yaml")
+	annotationsFile, err := os.Open(annotationsFileName)
+	if err != nil {
+		log.Error(fmt.Errorf("%w: could not open annotations.yaml", err))
+		return nil, err
+	}
+	annotations, err := GetAnnotations(ctx, annotationsFile)
 	if err != nil {
 		log.Error("unable to get annotations.yaml from the bundle")
 		return nil, err
@@ -118,12 +128,10 @@ func cleanStringToGetTheVersionToParse(value string) string {
 	return value
 }
 
-func GetAnnotations(ctx context.Context, mountedDir string) (map[string]string, error) {
-	log.Trace("reading annotations file from the bundle")
-	log.Debug("mounted directory is ", mountedDir)
-	annotationsFilePath := path.Join(mountedDir, "metadata", "annotations.yaml")
-
-	fileContents, err := os.ReadFile(annotationsFilePath)
+// GetAnnotations accepts a context, and an io.Reader that is expected to provide
+// the annotations.yaml, and parses the annotations from there
+func GetAnnotations(ctx context.Context, r io.Reader) (map[string]string, error) {
+	fileContents, err := io.ReadAll(r)
 	if err != nil {
 		log.Error("fail to read metadata/annotation.yaml file in bundle")
 		return nil, err
@@ -158,42 +166,36 @@ func ExtractAnnotationsBytes(ctx context.Context, annotationBytes []byte) (map[s
 	return bundleMeta.Annotations, nil
 }
 
-func getCsvFilePathFromBundle(mountedDir string) (string, error) {
+func GetCsvFilePathFromBundle(mountedDir string) (string, error) {
 	log.Trace("reading clusterserviceversion file from the bundle")
 	log.Debug("mounted directory is ", mountedDir)
 	matches, err := filepath.Glob(filepath.Join(mountedDir, "manifests", "*.clusterserviceversion.yaml"))
 	if err != nil {
-		log.Error("glob pattern is malformed: ", err)
+		log.Error(fmt.Errorf("%w: glob pattern is malformed", err))
 		return "", err
 	}
 	if len(matches) == 0 {
-		log.Error("unable to find clusterserviceversion file in the bundle image: ", err)
-		return "", err
+		log.Error("unable to find clusterserviceversion file in the bundle image")
+		return "", os.ErrNotExist
 	}
 	if len(matches) > 1 {
-		log.Error("found more than one clusterserviceversion file in the bundle image: ", err)
-		return "", err
+		log.Error("found more than one clusterserviceversion file in the bundle image")
+		return "", errors.ErrTooManyCSVs
 	}
 	log.Debugf("The path to csv file is %s", matches[0])
 	return matches[0], nil
 }
 
-func GetSupportedInstalledModes(ctx context.Context, mountedDir string) (map[string]bool, error) {
-	csvFilepath, err := getCsvFilePathFromBundle(mountedDir)
-	if err != nil {
-		return nil, err
-	}
-
-	csvFileReader, err := os.ReadFile(csvFilepath)
+func GetSupportedInstallModes(ctx context.Context, csvReader io.Reader) (map[string]bool, error) {
+	var csv ClusterServiceVersion
+	bts, err := io.ReadAll(csvReader)
 	if err != nil {
-		log.Error(err)
+		log.Error(fmt.Errorf("%w: could not get CSV from reader", err))
 		return nil, err
 	}
-
-	var csv ClusterServiceVersion
-	err = yaml.Unmarshal(csvFileReader, &csv)
+	err = yaml.Unmarshal(bts, &csv)
 	if err != nil {
-		log.Error(err)
+		log.Error(fmt.Errorf("%w: malformed CSV detected", err))
 		return nil, err
 	}
 
diff --git a/certification/internal/policy/operator/deployable_by_olm.go b/certification/internal/policy/operator/deployable_by_olm.go
index b9db9e4..07be2a1 100644
--- a/certification/internal/policy/operator/deployable_by_olm.go
+++ b/certification/internal/policy/operator/deployable_by_olm.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"path/filepath"
 	"strings"
 	"sync"
 	"time"
@@ -138,7 +139,13 @@ func checkImageSource(operatorImages []string) bool {
 
 func (p *DeployableByOlmCheck) operatorMetadata(ctx context.Context, bundleRef certification.ImageReference) (*OperatorData, error) {
 	// retrieve the operator metadata from bundle image
-	annotations, err := bundle.GetAnnotations(ctx, bundleRef.ImageFSPath)
+	annotationsFileName := filepath.Join(bundleRef.ImageFSPath, "metadata", "annotations.yaml")
+	annotationsFile, err := os.Open(annotationsFileName)
+	if err != nil {
+		log.Error(fmt.Errorf("%w: could not open annotations.yaml", err))
+		return nil, err
+	}
+	annotations, err := bundle.GetAnnotations(ctx, annotationsFile)
 	if err != nil {
 		log.Error("unable to get annotations.yaml from the bundle")
 		return nil, err
@@ -162,9 +169,20 @@ func (p *DeployableByOlmCheck) operatorMetadata(ctx context.Context, bundleRef c
 		return nil, err
 	}
 
-	installedModes, err := bundle.GetSupportedInstalledModes(ctx, bundleRef.ImageFSPath)
+	csvFilepath, err := bundle.GetCsvFilePathFromBundle(bundleRef.ImageFSPath)
+	if err != nil {
+		return nil, err
+	}
+
+	csvFileReader, err := os.Open(csvFilepath)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	installModes, err := bundle.GetSupportedInstallModes(ctx, csvFileReader)
 	if err != nil {
-		log.Error("unable to extract operator install modes from ClusterServicVersion: ", err)
+		log.Error(fmt.Errorf("%w: unable to extract operator install modes from ClusterServicVersion", err))
 		return nil, err
 	}
 
@@ -175,7 +193,7 @@ func (p *DeployableByOlmCheck) operatorMetadata(ctx context.Context, bundleRef c
 		App:              packageName,
 		InstallNamespace: packageName,
 		TargetNamespace:  packageName + "-target",
-		InstallModes:     installedModes,
+		InstallModes:     installModes,
 	}, nil
 }
 
diff --git a/certification/internal/policy/operator/operator_pkg_name_uniqueness.go b/certification/internal/policy/operator/operator_pkg_name_uniqueness.go
index ebab3bc..b9987cb 100644
--- a/certification/internal/policy/operator/operator_pkg_name_uniqueness.go
+++ b/certification/internal/policy/operator/operator_pkg_name_uniqueness.go
@@ -6,6 +6,8 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"os"
+	"path/filepath"
 	"strings"
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
@@ -38,7 +40,14 @@ type packageData struct {
 type OperatorPkgNameIsUniqueCheck struct{}
 
 func (p *OperatorPkgNameIsUniqueCheck) Validate(ctx context.Context, bundleRef certification.ImageReference) (bool, error) {
-	annotations, err := bundle.GetAnnotations(ctx, bundleRef.ImageFSPath)
+	// retrieve the operator metadata from bundle image
+	annotationsFileName := filepath.Join(bundleRef.ImageFSPath, "metadata", "annotations.yaml")
+	annotationsFile, err := os.Open(annotationsFileName)
+	if err != nil {
+		log.Error(fmt.Errorf("%w: could not open annotations.yaml", err))
+		return false, err
+	}
+	annotations, err := bundle.GetAnnotations(ctx, annotationsFile)
 	if err != nil {
 		log.Error("unable to get annotations.yaml from the bundle")
 		return false, err


Test patch:
diff --git a/certification/internal/bundle/bundle_suite_test.go b/certification/internal/bundle/bundle_suite_test.go
index 27aa573..287aa6d 100644
--- a/certification/internal/bundle/bundle_suite_test.go
+++ b/certification/internal/bundle/bundle_suite_test.go
@@ -1,6 +1,18 @@
 package bundle
 
-import "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/internal/cli"
+import (
+	"errors"
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2/dsl/core"
+	. "github.com/onsi/gomega"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/internal/cli"
+)
+
+func TestBundle(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Bundle Utils Suite")
+}
 
 type FakeOperatorSdkEngine struct {
 	OperatorSdkReport   cli.OperatorSdkScorecardReport
@@ -14,3 +26,10 @@ func (f FakeOperatorSdkEngine) BundleValidate(image string, opts cli.OperatorSdk
 func (f FakeOperatorSdkEngine) Scorecard(image string, opts cli.OperatorSdkScorecardOptions) (*cli.OperatorSdkScorecardReport, error) {
 	return &f.OperatorSdkReport, nil
 }
+
+// In order to test some negative paths, this io.Reader will just throw an error
+type errReader int
+
+func (errReader) Read(p []byte) (n int, err error) {
+	return 0, errors.New("test error")
+}
diff --git a/certification/internal/bundle/bundle_test.go b/certification/internal/bundle/bundle_test.go
index 92edc0a..23ce21c 100644
--- a/certification/internal/bundle/bundle_test.go
+++ b/certification/internal/bundle/bundle_test.go
@@ -1,6 +1,7 @@
 package bundle
 
 import (
+	"bytes"
 	"context"
 	"os"
 	"path/filepath"
@@ -8,21 +9,184 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/internal/cli"
 )
 
 var _ = Describe("BundleValidateCheck", func() {
 	const (
-		metadataDir        = "metadata"
-		annotationFilename = "annotations.yaml"
-		annotations        = `annotations:
+		clusterServiceVersionFilename = "myoperator.clusterserviceversion.yaml"
+		manifestsDir                  = "manifests"
+		metadataDir                   = "metadata"
+		annotationFilename            = "annotations.yaml"
+		annotations                   = `annotations:
   com.redhat.openshift.versions: "v4.6-v4.9"
   operators.operatorframework.io.bundle.package.v1: testPackage
   operators.operatorframework.io.bundle.channel.default.v1: testChannel
 `
 	)
 
-	// fakeEngine cli.OperatorSdkEngine
-	var imageRef certification.ImageReference
+	Describe("Bundle validation", func() {
+		var (
+			imageRef   certification.ImageReference
+			fakeEngine cli.OperatorSdkEngine
+		)
+
+		BeforeEach(func() {
+			// mock bundle directory
+			tmpDir, err := os.MkdirTemp("", "bundle-metadata-*")
+			Expect(err).ToNot(HaveOccurred())
+
+			err = os.Mkdir(filepath.Join(tmpDir, metadataDir), 0o755)
+			Expect(err).ToNot(HaveOccurred())
+
+			err = os.Mkdir(filepath.Join(tmpDir, manifestsDir), 0o755)
+			Expect(err).ToNot(HaveOccurred())
+
+			err = os.WriteFile(filepath.Join(tmpDir, metadataDir, annotationFilename), []byte(annotations), 0o644)
+			Expect(err).ToNot(HaveOccurred())
+
+			imageRef.ImageFSPath = tmpDir
+			fakeEngine = FakeOperatorSdkEngine{}
+		})
+
+		AfterEach(func() {
+			err := os.RemoveAll(imageRef.ImageFSPath)
+			Expect(err).ToNot(HaveOccurred())
+		})
+
+		Context("the annotations file is valid", func() {
+			It("should pass", func() {
+				report, err := ValidateBundle(context.Background(), fakeEngine, imageRef.ImageFSPath)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(report).ToNot(BeNil())
+			})
+		})
+
+		Context("the annotations file does not exist", func() {
+			JustBeforeEach(func() {
+				err := os.Remove(filepath.Join(imageRef.ImageFSPath, metadataDir, annotationFilename))
+				Expect(err).ToNot(HaveOccurred())
+			})
+			It("should error", func() {
+				report, err := ValidateBundle(context.Background(), fakeEngine, imageRef.ImageFSPath)
+				Expect(err).To(HaveOccurred())
+				Expect(report).To(BeNil())
+			})
+		})
+
+		Context("the annotations file is malformed", func() {
+			JustBeforeEach(func() {
+				err := os.WriteFile(filepath.Join(imageRef.ImageFSPath, metadataDir, annotationFilename), []byte("badAnnotations"), 0o644)
+				Expect(err).ToNot(HaveOccurred())
+			})
+			It("should error", func() {
+				report, err := ValidateBundle(context.Background(), fakeEngine, imageRef.ImageFSPath)
+				Expect(err).To(HaveOccurred())
+				Expect(report).To(BeNil())
+			})
+		})
+
+		Context("the annotations file is valid but has no annotations", func() {
+			JustBeforeEach(func() {
+				err := os.WriteFile(filepath.Join(imageRef.ImageFSPath, metadataDir, annotationFilename), []byte("annotations:"), 0o644)
+				Expect(err).ToNot(HaveOccurred())
+			})
+			It("should fail gracefully", func() {
+				report, err := ValidateBundle(context.Background(), fakeEngine, imageRef.ImageFSPath)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(report).ToNot(BeNil())
+			})
+		})
+
+		Context("getting the CSV file from the bundle", func() {
+			var manifestsPath string
+
+			BeforeEach(func() {
+				manifestsPath = filepath.Join(imageRef.ImageFSPath, manifestsDir)
+				err := os.WriteFile(filepath.Join(manifestsPath, clusterServiceVersionFilename), []byte(""), 0o644)
+				Expect(err).ToNot(HaveOccurred())
+			})
+			Context("the CSV exists by itself", func() {
+				It("should return the filename", func() {
+					filename, err := GetCsvFilePathFromBundle(imageRef.ImageFSPath)
+					Expect(err).ToNot(HaveOccurred())
+					Expect(filename).To(Equal(filepath.Join(manifestsPath, clusterServiceVersionFilename)))
+				})
+			})
+			Context("the CSV doesn't exist", func() {
+				JustBeforeEach(func() {
+					err := os.Remove(filepath.Join(manifestsPath, clusterServiceVersionFilename))
+					Expect(err).ToNot(HaveOccurred())
+				})
+				It("should return an error", func() {
+					filename, err := GetCsvFilePathFromBundle(imageRef.ImageFSPath)
+					Expect(err).To(HaveOccurred())
+					Expect(filename).To(Equal(""))
+				})
+			})
+			Context("there is more than one CSV", func() {
+				JustBeforeEach(func() {
+					err := os.WriteFile(filepath.Join(manifestsPath, "otheroperator.clusterserviceversion.yaml"), []byte(""), 0o664)
+					Expect(err).ToNot(HaveOccurred())
+				})
+				It("should return an error", func() {
+					filename, err := GetCsvFilePathFromBundle(imageRef.ImageFSPath)
+					Expect(err).To(HaveOccurred())
+					Expect(filename).To(Equal(""))
+				})
+			})
+			Context("there is a bad mount dir", func() {
+				It("should return an error", func() {
+					filename, err := GetCsvFilePathFromBundle("[]")
+					Expect(err).To(HaveOccurred())
+					Expect(err).To(Equal(filepath.ErrBadPattern))
+					Expect(filename).To(Equal(""))
+				})
+			})
+		})
+	})
+
+	Describe("Supported Install Modes", func() {
+		var csv string = `spec:
+  installModes:
+  - supported: true
+    type: OwnNamespace
+  - supported: true
+    type: SingleNamespace
+  - supported: false
+    type: MultiNamespace
+  - supported: true
+    type: AllNamespaces`
+
+		Context("CSV is valid", func() {
+			It("should return a map of 3", func() {
+				installModes, err := GetSupportedInstallModes(context.Background(), bytes.NewBufferString(csv))
+				Expect(err).ToNot(HaveOccurred())
+				Expect(installModes).ToNot(BeNil())
+				Expect(len(installModes)).To(Equal(3))
+				Expect("MultiNamespace").ToNot(BeElementOf(installModes))
+			})
+		})
+
+		Context("reader is not valid", func() {
+			It("should error", func() {
+				installModes, err := GetSupportedInstallModes(context.Background(), errReader(0))
+				Expect(err).To(HaveOccurred())
+				Expect(installModes).To(BeNil())
+			})
+		})
+
+		Context("CSV is invalid", func() {
+			JustBeforeEach(func() {
+				csv = `invalid`
+			})
+			It("should error", func() {
+				installModes, err := GetSupportedInstallModes(context.Background(), bytes.NewBufferString(csv))
+				Expect(err).To(HaveOccurred())
+				Expect(installModes).To(BeNil())
+			})
+		})
+	})
 
 	Describe("While ensuring that container util is working", func() {
 		// tests: extractAnnotationsBytes
@@ -54,36 +218,15 @@ var _ = Describe("BundleValidateCheck", func() {
 					Expect(err).To(HaveOccurred())
 				})
 			})
-		})
-	})
 
-	BeforeEach(func() {
-		// 		stdout := `{
-		// 	"passed": true,
-		// 	"outputs": null
-		// }`
-		// 		stderr := ""
-		// report := cli.OperatorSdkBundleValidateReport{
-		// 	Stdout:  stdout,
-		// 	Stderr:  stderr,
-		// 	Passed:  true,
-		// 	Outputs: []cli.OperatorSdkBundleValidateOutput{},
-		// }
-		// fakeEngine = FakeOperatorSdkEngine{
-		// 	OperatorSdkBVReport: report,
-		// }
-
-		// mock bundle directory
-		tmpDir, err := os.MkdirTemp("", "bundle-metadata-*")
-		Expect(err).ToNot(HaveOccurred())
-
-		err = os.Mkdir(filepath.Join(tmpDir, metadataDir), 0o755)
-		Expect(err).ToNot(HaveOccurred())
-
-		err = os.WriteFile(filepath.Join(tmpDir, metadataDir, annotationFilename), []byte(annotations), 0o644)
-		Expect(err).ToNot(HaveOccurred())
-
-		imageRef.ImageFSPath = tmpDir
+			Context("a bad reader is sent to GetAnnotations", func() {
+				It("should return an error", func() {
+					annotations, err := GetAnnotations(context.Background(), errReader(0))
+					Expect(err).To(HaveOccurred())
+					Expect(annotations).To(BeNil())
+				})
+			})
+		})
 	})
 
 	DescribeTable("Image Registry validation",
@@ -97,9 +240,8 @@ var _ = Describe("BundleValidateCheck", func() {
 		Entry("range 4.6 to 4.9", "v4.6-v4.9", true),
 		Entry(">= 4.8", "v4.8", true),
 		Entry(">= 4.9", "v4.9", true),
+		Entry("begins = with error", "=foo", false),
+		Entry("bare version with error", "vfoo", false),
+		Entry("range with error", "v4.6-vfoo", false),
 	)
-	AfterEach(func() {
-		err := os.RemoveAll(imageRef.ImageFSPath)
-		Expect(err).ToNot(HaveOccurred())
-	})
 })