Repository: redhat-openshift-ecosystem/openshift-preflight
Pull request #136 (2021-07-28 20:43:59)

Implement package uniqueness checks
Extending original PR #132 (thanks @rocrisp for doing the leg work on this check!)
Fixes: #34 

## Context
This PR refactors the Package Uniqueness check, which has a goal of hitting an external API with the package name stored in the bundle's ClusterServiceVersion as a query string, and confirming that the API does not have an entry for the given package already.

## Summary
- This check uses the mounted engine approach, and mounts the bundle on the filesystem using `podman unshare`. It then  reads the `annotations.yaml` file from disk and extracts the appropriate key/value that contains the package name.
- The package name is then sent to the defined API with the appropriate query string
- The API's response body is parsed/extracted to determine if a package with the name already exists. It's possible this might change if our target API changes.
- This PR also adds an apiClient interface just to simplify testing against a fake client.

openshift-ci[bot]: @komish: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Patch:
diff --git a/certification/engine/engine.go b/certification/engine/engine.go
index 89009df..f1d9cea 100644
--- a/certification/engine/engine.go
+++ b/certification/engine/engine.go
@@ -88,6 +88,8 @@ var scorecardBasicSpecCheck certification.Check = &shell.ScorecardBasicSpecCheck
 var scorecardOlmSuiteCheck certification.Check = &shell.ScorecardOlmSuiteCheck{}
 var hasNoProhibitedMountedCheck certification.Check = &shell.HasNoProhibitedPackagesMountedCheck{}
 var relatedImageManifestSchemaVersionCheck certification.Check = &shell.RelatedImagesAreSchemaVersion2Check{}
+var operatorPkgNameIsUniqueMountedCheck certification.Check = &shell.OperatorPkgNameIsUniqueMountedCheck{}
+var operatorPkgNameIsUniqueCheck certification.Check = &shell.OperatorPkgNameIsUniqueCheck{}
 
 var containerPolicy = map[string]certification.Check{
 	runAsNonRootCheck.Name():              runAsNonRootCheck,
@@ -105,10 +107,15 @@ var operatorPolicy = map[string]certification.Check{
 	scorecardBasicSpecCheck.Name():                scorecardBasicSpecCheck,
 	scorecardOlmSuiteCheck.Name():                 scorecardOlmSuiteCheck,
 	relatedImageManifestSchemaVersionCheck.Name(): relatedImageManifestSchemaVersionCheck,
+	validateOperatorBundle.Name():                 validateOperatorBundle,
+	scorecardBasicSpecCheck.Name():                scorecardBasicSpecCheck,
+	scorecardOlmSuiteCheck.Name():                 scorecardOlmSuiteCheck,
+	operatorPkgNameIsUniqueCheck.Name():           operatorPkgNameIsUniqueCheck,
 }
 
 var mountedChecks = map[string]certification.Check{
-	hasNoProhibitedMountedCheck.Name(): hasNoProhibitedMountedCheck,
+	hasNoProhibitedMountedCheck.Name():         hasNoProhibitedMountedCheck,
+	operatorPkgNameIsUniqueMountedCheck.Name(): operatorPkgNameIsUniqueMountedCheck,
 }
 
 func makeCheckList(checkMap map[string]certification.Check) []string {
diff --git a/certification/internal/shell/operator_pkg_name_uniqueness.go b/certification/internal/shell/operator_pkg_name_uniqueness.go
new file mode 100644
index 0000000..d9bd93d
--- /dev/null
+++ b/certification/internal/shell/operator_pkg_name_uniqueness.go
@@ -0,0 +1,42 @@
+package shell
+
+import (
+	"os"
+
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
+	log "github.com/sirupsen/logrus"
+)
+
+type OperatorPkgNameIsUniqueCheck struct{}
+
+func (p *OperatorPkgNameIsUniqueCheck) Validate(image string) (bool, error) {
+	result, err := podmanEngine.UnshareWithCheck("OperatorPackageNameIsUniqueMounted", image, os.Args[0], "check", "run")
+	if err != nil {
+		log.Trace("unable to execute preflight in the unshare env")
+		log.Debugf("Stdout: %s", result.Stdout)
+		log.Debugf("Stderr: %s", result.Stderr)
+		return false, err
+	}
+
+	return result.PassedOverall, nil
+}
+
+func (p *OperatorPkgNameIsUniqueCheck) Name() string {
+	return "OperatorPackageNameIsUnique"
+}
+
+func (p *OperatorPkgNameIsUniqueCheck) Metadata() certification.Metadata {
+	return certification.Metadata{
+		Description:      "Validating Bundle image package name uniqueness",
+		Level:            "best",
+		KnowledgeBaseURL: "https://connect.redhat.com/zones/containers/container-certification-policy-guide",
+		CheckURL:         "https://connect.redhat.com/zones/containers/container-certification-policy-guide",
+	}
+}
+
+func (p *OperatorPkgNameIsUniqueCheck) Help() certification.HelpText {
+	return certification.HelpText{
+		Message:    "Check encountered an error. It is possible that the bundle package name already exist in the RedHat Catalog registry.",
+		Suggestion: "Bundle package name must be unique meaning that it doesn't already exist in the RedHat Catalog registry",
+	}
+}
diff --git a/certification/internal/shell/operator_pkg_name_uniqueness_mounted.go b/certification/internal/shell/operator_pkg_name_uniqueness_mounted.go
new file mode 100644
index 0000000..60e3afa
--- /dev/null
+++ b/certification/internal/shell/operator_pkg_name_uniqueness_mounted.go
@@ -0,0 +1,237 @@
+package shell
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"path"
+
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
+	log "github.com/sirupsen/logrus"
+	"gopkg.in/yaml.v2"
+)
+
+const (
+	// apiEndpoint is the endpoint used to query for package uniqueness.
+	apiEndpoint = "https://catalog.redhat.com/api/containers/v1/operators/packages"
+
+	// packageKey is the packageKey in annotations.yaml that contains the package name.
+	packageKey = "operators.operatorframework.io.bundle.package.v1"
+)
+
+// apiRespondData is the response received from the defined API
+type apiResponseData struct {
+	Data     []packageData `json:"data"`
+	Page     int           `json:"page"`
+	PageSize int           `json:"page_size"`
+	Total    int           `json:"total"`
+}
+
+// packageData represents a single package entry in the API response.
+type packageData struct {
+	Id             string `json:"_id"`
+	Association    string `json:"association"`
+	CreateDate     string `json:"creation_date"`
+	LastUpdateTime string `json:"last_update_date"`
+	PackageName    string `json:"package_name"`
+	Source         string `json:"source"`
+}
+
+// OperatorPkgNameIsUniqueMountedCheck finds the package name as defined in the operator bundle's annotations
+// and checks it against Red Hat APIs to confirm that the package name is unique at the time of the
+// check.
+type OperatorPkgNameIsUniqueMountedCheck struct{}
+
+func (p *OperatorPkgNameIsUniqueMountedCheck) Validate(bundle string) (bool, error) {
+	annotations, err := p.getAnnotationsFromBundle(bundle)
+	if err != nil {
+		log.Errorf("unable to get annotations.yaml from the bundle")
+		return false, err
+	}
+
+	packageName, err := p.getPackageName(annotations)
+	if err != nil {
+		log.Error("unable to extract package name from ClusterServicVersion", err)
+		return false, err
+	}
+
+	log.Debugf("operator package name is %s", packageName)
+
+	req, err := p.buildRequest(apiEndpoint, packageName)
+	if err != nil {
+		log.Error("unable to build API request structure", err)
+		return false, err
+	}
+
+	resp, err := p.queryAPI(http.DefaultClient, req)
+	if err != nil {
+		log.Error("unable to query package name validation API for uniqueness check", err)
+		return false, err
+	}
+
+	data, err := p.parseAPIResponse(resp)
+	if err != nil {
+		log.Error("unable to parse response provided by package name validation API", err)
+		return false, err
+	}
+
+	return p.validate(data)
+}
+
+// getAnnotationsFromBundle will search a bundle's metadata directory for a file called annotations.yaml, which is
+// expected to exist in every bundle. This file's contents are read and marshaled to a simple map, which is then
+// returned.
+func (p *OperatorPkgNameIsUniqueMountedCheck) getAnnotationsFromBundle(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)
+	if err != nil {
+		log.Error("fail to read metadata/annotation.yaml file in bundle")
+		return nil, err
+	}
+
+	annotations, err := p.extractAnnotationsBytes(fileContents)
+	if err != nil {
+		log.Error("metadata/annotations.yaml found but is malformed")
+		return nil, err
+	}
+
+	return annotations, nil
+}
+
+// extractAnnotationsBytes reads the annotation data read from a file and returns the expected format for that yaml
+// represented as a map[string]string.
+func (p *OperatorPkgNameIsUniqueMountedCheck) extractAnnotationsBytes(annotationBytes []byte) (map[string]string, error) {
+	if len(annotationBytes) == 0 {
+		return nil, errors.New("the annotations file was empty")
+	}
+
+	var bundleMeta metadata
+	if err := yaml.Unmarshal(annotationBytes, &bundleMeta); err != nil {
+		log.Error("metadata/annotations.yaml found but is malformed")
+		return nil, err
+	}
+
+	return bundleMeta.Annotations, nil
+}
+
+// getPackageName accepts the annotations map and searches for the specified annotation corresponding
+// with the complete bundle name for an operator, which is then returned.
+func (p *OperatorPkgNameIsUniqueMountedCheck) getPackageName(annotations map[string]string) (string, error) {
+	log.Tracef("searching for package key (%s) in bundle", packageKey)
+	log.Trace("bundle data: ", annotations)
+	pkg, found := annotations[packageKey]
+	if !found {
+		return "", fmt.Errorf("did not find package name at the key %s in the annotations.yaml", packageKey)
+	}
+
+	return pkg, nil
+}
+
+// buildRequest builds the http.Request using the input parameters and returns a client.
+func (p *OperatorPkgNameIsUniqueMountedCheck) buildRequest(apiURL, packageName string) (*http.Request, error) {
+	req, err := http.NewRequest("GET", apiURL, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// this endpoint supports a query string, and we use that to determine if a
+	// package with the name already exists.
+	queryString := req.URL.Query()
+	queryString.Add("filter", fmt.Sprintf("package_name==%s", packageName))
+	req.URL.RawQuery = queryString.Encode()
+
+	return req, nil
+}
+
+// queryAPI uses the provided client to query the remote API, and returns the response if it
+// response is successful, or an error if the response was unexpected in any way.
+func (p *OperatorPkgNameIsUniqueMountedCheck) queryAPI(client apiClient, request *http.Request) (*http.Response, error) {
+	log.Trace("making API request to ", request.URL.String())
+	resp, err := client.Do(request)
+	if err != nil {
+		return nil, err
+	}
+
+	log.Trace("response code: ", resp.Status)
+
+	// The Connect API returns a 200 regardless of whether the package was found or not. Until this
+	// assumption changes, we assume any non-200 response is invalid, or due to a malformed request.
+	if resp.StatusCode != 200 {
+		return nil, fmt.Errorf("received an unexpected status code for the request")
+	}
+
+	return resp, nil
+}
+
+// parseAPIResponse reads the response and checks the body for the expected contents, and then
+// returns the body content as apiResponseData.
+func (p *OperatorPkgNameIsUniqueMountedCheck) parseAPIResponse(resp *http.Response) (*apiResponseData, error) {
+	var data apiResponseData
+	defer resp.Body.Close()
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	log.Trace("response body: ", string(body))
+
+	err = json.Unmarshal(body, &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
+
+// validate checks the apiResponseData and confirms that the package is unique by confirming that the
+// API returned no packages using the same name.
+func (p *OperatorPkgNameIsUniqueMountedCheck) validate(resp *apiResponseData) (bool, error) {
+	// success case - the API returned no entries
+	if len(resp.Data) == 0 {
+		return true, nil
+	}
+
+	log.Error("a package already exists in the Red Hat ecosystem using the same name")
+	// we don't expect multiple entries, but resp.Data is a list so we will iterate.
+	for _, v := range resp.Data {
+		log.Error("found the following entry: ", v)
+	}
+
+	return false, nil
+}
+
+func (p *OperatorPkgNameIsUniqueMountedCheck) Name() string {
+	return "OperatorPackageNameIsUniqueMounted"
+}
+
+func (p *OperatorPkgNameIsUniqueMountedCheck) Metadata() certification.Metadata {
+	return certification.Metadata{
+		Description:      "Validating Bundle image package name uniqueness",
+		Level:            "best",
+		KnowledgeBaseURL: "https://connect.redhat.com/zones/containers/container-certification-policy-guide",
+		CheckURL:         "https://connect.redhat.com/zones/containers/container-certification-policy-guide",
+	}
+}
+
+func (p *OperatorPkgNameIsUniqueMountedCheck) Help() certification.HelpText {
+	return certification.HelpText{
+		Message:    "Check encountered an error. It is possible that the bundle package name already exist in the RedHat Catalog registry.",
+		Suggestion: "Bundle package name must be unique meaning that it doesn't already exist in the RedHat Catalog registry",
+	}
+}
+
+// apiClient is a simple interface encompassing the only http.Client method we utilize for preflight checks. This exists to
+// enable mock implementations for testing purposes.
+type apiClient interface {
+	Do(req *http.Request) (*http.Response, error)
+}
+
+type metadata struct {
+	Annotations map[string]string
+}
diff --git a/certification/internal/shell/podman.go b/certification/internal/shell/podman.go
index 392a5ce..e772e80 100644
--- a/certification/internal/shell/podman.go
+++ b/certification/internal/shell/podman.go
@@ -297,11 +297,15 @@ func (pe PodmanCLIEngine) Unshare(env map[string]string, command ...string) (*cl
 		return &cli.PodmanUnshareReport{Stdout: stdout.String(), Stderr: stderr.String()}, errors.ErrAlreadyInUnshare
 	}
 
+	loglevel := os.Getenv("PFLT_LOGLEVEL")
+
 	environ := []string{
 		fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
 		"PREFLIGHT_EXEC_RUN=1",
 		"PFLT_LOGFILE=preflight-unshare.log",
+		fmt.Sprintf("PFLT_LOGLEVEL=%s", loglevel),
 	}
+
 	for k, v := range env {
 		environ = append(environ, fmt.Sprintf("%s=%s", k, v))
 	}
diff --git a/certification/internal/utils/container/helper.go b/certification/internal/utils/container/helper.go
index 4d9caec..86a3f36 100644
--- a/certification/internal/utils/container/helper.go
+++ b/certification/internal/utils/container/helper.go
@@ -90,5 +90,5 @@ func RunInsideImageFS(podmanEngine cli.PodmanEngine, image string, containerFn C
 		}
 	}()
 
-	return containerFn(report.MountDir)
+	return containerFn(strings.TrimSpace(report.MountDir))
 }
diff --git a/go.mod b/go.mod
index dc1c245..d646a8d 100644
--- a/go.mod
+++ b/go.mod
@@ -15,4 +15,5 @@ require (
 	github.com/sirupsen/logrus v1.8.1
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/viper v1.8.1
+	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/go.sum b/go.sum
index 6735c0c..53bd0c8 100644
--- a/go.sum
+++ b/go.sum
@@ -397,10 +397,10 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8
 github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 h1:LoN2wx/aN8JPGebG+2DaUyk4M+xRcqJXfuIbs8AWHdE=
-github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 h1:LoN2wx/aN8JPGebG+2DaUyk4M+xRcqJXfuIbs8AWHdE=
+github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=


Test patch:
diff --git a/certification/internal/shell/operator_pkg_name_uniqueness_test.go b/certification/internal/shell/operator_pkg_name_uniqueness_test.go
new file mode 100644
index 0000000..3fd1245
--- /dev/null
+++ b/certification/internal/shell/operator_pkg_name_uniqueness_test.go
@@ -0,0 +1,177 @@
+package shell
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+// fakeAPIClient implements the necessary methods to test API query logic for this check, which includes
+// implementing the local apiClient interface as well as the io.ReadCloser, used for http.Response.Body.
+type fakeAPIClient struct {
+	response http.Response
+}
+
+func (c *fakeAPIClient) Do(req *http.Request) (*http.Response, error) {
+	return &c.response, nil
+}
+
+var _ = Describe("OperatorPkgNameIsUniqueCheck", func() {
+	check := OperatorPkgNameIsUniqueMountedCheck{}
+
+	Describe("While ensuring that an operator's package is unique", func() {
+		// tests: getPackageNAme
+		Context("with the annotations map", func() {
+			key := "operators.operatorframework.io.bundle.package.v1"
+			pkg := "my-custom-operator"
+
+			Context("that has the expected package key", func() {
+				goodMap := map[string]string{key: pkg}
+
+				It("should return the package name and no error", func() {
+					pkgName, err := check.getPackageName(goodMap)
+					Expect(err).ToNot(HaveOccurred())
+					Expect(pkgName).To(Equal(pkg))
+				})
+			})
+
+			Context("that does not have the expected key", func() {
+				mapWithoutKey := map[string]string{}
+
+				It("should return an error", func() {
+					_, err := check.getPackageName(mapWithoutKey)
+					Expect(err).To(HaveOccurred())
+				})
+			})
+
+			Context("that is nil", func() {
+				var nilMap map[string]string
+
+				It("should return an error", func() {
+					_, err := check.getPackageName(nilMap)
+					Expect(err).To(HaveOccurred())
+				})
+			})
+		})
+
+		// tests: extractAnnotationsBytes
+		Context("with an annotations yaml data read from disk", func() {
+			Context("with the correct format", func() {
+				data := []byte(`annotations:
+    foo: bar`)
+
+				It("should properly marshal to a map[string]string", func() {
+					annotations, err := check.extractAnnotationsBytes(data)
+					Expect(err).ToNot(HaveOccurred())
+					Expect(annotations["foo"]).To(Equal("bar"))
+				})
+			})
+
+			Context("containing no data read in from the yaml file", func() {
+				data := []byte{}
+
+				It("should return an error", func() {
+					_, err := check.extractAnnotationsBytes(data)
+					Expect(err).To(HaveOccurred())
+				})
+			})
+
+			Context("containing malformed or unexpected data", func() {
+				data := []byte(`malformed`)
+
+				It("should return an error", func() {
+					_, err := check.extractAnnotationsBytes(data)
+					Expect(err).To(HaveOccurred())
+				})
+			})
+		})
+
+		// tests: buildRequest
+		Context("and building the HTTP request parameters", func() {
+			url := "https://example.com"
+			packageName := "my-custom-package"
+
+			It("should accurately reflect the input url and package name in the request data", func() {
+				request, err := check.buildRequest(url, packageName)
+				Expect(fmt.Sprintf("%s://%s", request.URL.Scheme, request.URL.Host)).To(Equal(url))
+				Expect(request.URL.RawQuery).To(ContainSubstring(packageName))
+				Expect(err).ToNot(HaveOccurred())
+			})
+		})
+
+		// tests: queryAPI, parseAPIResponse, validate
+		Context("and making requests to the package uniqueness API", func() {
+			fakeRequest := http.Request{
+				URL: &url.URL{},
+			}
+
+			Context("and receiving a response that no package exists with the defined name", func() {
+				fakeRequest := http.Request{
+					URL: &url.URL{},
+				}
+				successBody := `{"data":[],"page":0,"page_size":100,"total":0}`
+
+				goodClient := fakeAPIClient{
+					response: http.Response{
+						StatusCode: http.StatusOK,
+						Body:       io.NopCloser(strings.NewReader(successBody)),
+					},
+				}
+
+				It("should not throw an error when the api responses with a 200 ok", func() {
+					goodResp, goodErr := check.queryAPI(&goodClient, &fakeRequest)
+					Expect(goodErr).ToNot(HaveOccurred())
+
+					data, err := check.parseAPIResponse(goodResp)
+					Expect(err).ToNot(HaveOccurred())
+
+					isValid, err := check.validate(data)
+					Expect(err).ToNot(HaveOccurred())
+					Expect(isValid).To(BeTrue())
+				})
+			})
+
+			Context("and receiving a response that no a exists with the defined name", func() {
+				failBody := `{"data":[{"_id":"someID","association":"some association","creation_date":"2021-08-02","last_update_date":"2021-08-02","package_name":"some-package","source":"some source"}],"page":0,"page_size":100,"total":1}`
+
+				failClient := fakeAPIClient{
+					response: http.Response{
+						StatusCode: http.StatusOK,
+						Body:       io.NopCloser(strings.NewReader(failBody)),
+					},
+				}
+
+				It("should not throw an error when the api responses with a 200 ok, but should not validate", func() {
+					failResp, failErr := check.queryAPI(&failClient, &fakeRequest)
+					Expect(failErr).ToNot(HaveOccurred())
+
+					data, err := check.parseAPIResponse(failResp)
+					Expect(err).ToNot(HaveOccurred())
+
+					isValid, err := check.validate(data)
+					Expect(err).ToNot(HaveOccurred())
+					Expect(isValid).To(BeFalse())
+				})
+			})
+
+			Context("and receiving a non-200 response", func() {
+				errClient := fakeAPIClient{
+					response: http.Response{
+						StatusCode: http.StatusInternalServerError,
+						Body:       io.NopCloser(strings.NewReader("")),
+					},
+				}
+
+				It("should throw an error", func() {
+					_, err := check.queryAPI(&errClient, &fakeRequest)
+					Expect(err).To(HaveOccurred())
+				})
+			})
+		})
+	})
+})