zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/search_functions_internal_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  //
     5  //nolint:dupl
     6  package client
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"io"
    12  	"os"
    13  	"regexp"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	godigest "github.com/opencontainers/go-digest"
    20  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    21  	. "github.com/smartystreets/goconvey/convey"
    22  	"github.com/spf13/cobra"
    23  
    24  	zerr "zotregistry.dev/zot/errors"
    25  	"zotregistry.dev/zot/pkg/common"
    26  )
    27  
    28  func TestSearchAllImages(t *testing.T) {
    29  	Convey("SearchAllImages", t, func() {
    30  		buff := bytes.NewBufferString("")
    31  		searchConfig := getMockSearchConfig(buff, mockService{
    32  			getAllImagesFn: func(ctx context.Context, config SearchConfig, username, password string,
    33  				channel chan stringResult, wtgrp *sync.WaitGroup,
    34  			) {
    35  				str, err := getMockImageStruct().stringPlainText(10, 10, 10, false)
    36  
    37  				channel <- stringResult{StrValue: str, Err: err}
    38  			},
    39  		})
    40  
    41  		err := SearchAllImages(searchConfig)
    42  		So(err, ShouldBeNil)
    43  		space := regexp.MustCompile(`\s+`)
    44  		str := space.ReplaceAllString(buff.String(), " ")
    45  		actual := strings.TrimSpace(str)
    46  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
    47  	})
    48  }
    49  
    50  func TestSearchAllImagesGQL(t *testing.T) {
    51  	Convey("SearchAllImagesGQL", t, func() {
    52  		buff := bytes.NewBufferString("")
    53  		searchConfig := getMockSearchConfig(buff, mockService{
    54  			getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string,
    55  			) (*common.ImageListResponse, error) {
    56  				return &common.ImageListResponse{ImageList: common.ImageList{
    57  					PaginatedImagesResult: common.PaginatedImagesResult{
    58  						Results: []common.ImageSummary{getMockImageSummary()},
    59  					},
    60  				}}, nil
    61  			},
    62  		})
    63  
    64  		err := SearchAllImagesGQL(searchConfig)
    65  		So(err, ShouldBeNil)
    66  		space := regexp.MustCompile(`\s+`)
    67  		str := space.ReplaceAllString(buff.String(), " ")
    68  		actual := strings.TrimSpace(str)
    69  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
    70  	})
    71  
    72  	Convey("SearchAllImagesGQL error", t, func() {
    73  		buff := bytes.NewBufferString("")
    74  		searchConfig := getMockSearchConfig(buff, mockService{
    75  			getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string,
    76  			) (*common.ImageListResponse, error) {
    77  				return &common.ImageListResponse{ImageList: common.ImageList{
    78  					PaginatedImagesResult: common.PaginatedImagesResult{
    79  						Results: []common.ImageSummary{getMockImageSummary()},
    80  					},
    81  				}}, zerr.ErrInjected
    82  			},
    83  		})
    84  
    85  		err := SearchAllImagesGQL(searchConfig)
    86  		So(err, ShouldNotBeNil)
    87  	})
    88  }
    89  
    90  func TestSearchImageByName(t *testing.T) {
    91  	Convey("SearchImageByName", t, func() {
    92  		buff := bytes.NewBufferString("")
    93  		searchConfig := getMockSearchConfig(buff, mockService{
    94  			getImageByNameFn: func(ctx context.Context, config SearchConfig, username string, password string, imageName string,
    95  				channel chan stringResult, wtgrp *sync.WaitGroup,
    96  			) {
    97  				str, err := getMockImageStruct().stringPlainText(10, 10, 10, false)
    98  
    99  				channel <- stringResult{StrValue: str, Err: err}
   100  			},
   101  		})
   102  
   103  		err := SearchImageByName(searchConfig, "repo")
   104  		So(err, ShouldBeNil)
   105  		space := regexp.MustCompile(`\s+`)
   106  		str := space.ReplaceAllString(buff.String(), " ")
   107  		actual := strings.TrimSpace(str)
   108  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   109  	})
   110  
   111  	Convey("SearchImageByName error", t, func() {
   112  		buff := bytes.NewBufferString("")
   113  		searchConfig := getMockSearchConfig(buff, mockService{
   114  			getImageByNameFn: func(ctx context.Context, config SearchConfig, username string, password string, imageName string,
   115  				channel chan stringResult, wtgrp *sync.WaitGroup,
   116  			) {
   117  				channel <- stringResult{StrValue: "", Err: zerr.ErrInjected}
   118  			},
   119  		})
   120  
   121  		err := SearchImageByName(searchConfig, "repo")
   122  		So(err, ShouldNotBeNil)
   123  	})
   124  }
   125  
   126  func TestSearchImageByNameGQL(t *testing.T) {
   127  	Convey("SearchImageByNameGQL", t, func() {
   128  		buff := bytes.NewBufferString("")
   129  		searchConfig := getMockSearchConfig(buff, mockService{
   130  			getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string,
   131  			) (*common.ImageListResponse, error) {
   132  				return &common.ImageListResponse{ImageList: common.ImageList{
   133  					PaginatedImagesResult: common.PaginatedImagesResult{
   134  						Results: []common.ImageSummary{getMockImageSummary()},
   135  					},
   136  				}}, nil
   137  			},
   138  		})
   139  
   140  		err := SearchImageByNameGQL(searchConfig, "repo")
   141  		So(err, ShouldBeNil)
   142  		space := regexp.MustCompile(`\s+`)
   143  		str := space.ReplaceAllString(buff.String(), " ")
   144  		actual := strings.TrimSpace(str)
   145  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   146  	})
   147  
   148  	Convey("SearchImageByNameGQL error", t, func() {
   149  		buff := bytes.NewBufferString("")
   150  		searchConfig := getMockSearchConfig(buff, mockService{
   151  			getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string,
   152  			) (*common.ImageListResponse, error) {
   153  				return &common.ImageListResponse{ImageList: common.ImageList{
   154  					PaginatedImagesResult: common.PaginatedImagesResult{
   155  						Results: []common.ImageSummary{getMockImageSummary()},
   156  					},
   157  				}}, zerr.ErrInjected
   158  			},
   159  		})
   160  
   161  		err := SearchImageByNameGQL(searchConfig, "repo")
   162  		So(err, ShouldNotBeNil)
   163  	})
   164  }
   165  
   166  func TestSearchImagesByDigest(t *testing.T) {
   167  	Convey("SearchImagesByDigest", t, func() {
   168  		buff := bytes.NewBufferString("")
   169  		searchConfig := getMockSearchConfig(buff, mockService{
   170  			getImagesByDigestFn: func(ctx context.Context, config SearchConfig, username string, password string, digest string,
   171  				rch chan stringResult, wtgrp *sync.WaitGroup,
   172  			) {
   173  				str, err := getMockImageStruct().stringPlainText(10, 10, 10, false)
   174  
   175  				rch <- stringResult{StrValue: str, Err: err}
   176  			},
   177  		})
   178  
   179  		err := SearchImagesByDigest(searchConfig, godigest.FromString("str").String())
   180  		So(err, ShouldBeNil)
   181  		space := regexp.MustCompile(`\s+`)
   182  		str := space.ReplaceAllString(buff.String(), " ")
   183  		actual := strings.TrimSpace(str)
   184  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   185  	})
   186  
   187  	Convey("SearchImagesByDigest error", t, func() {
   188  		buff := bytes.NewBufferString("")
   189  		searchConfig := getMockSearchConfig(buff, mockService{
   190  			getImagesByDigestFn: func(ctx context.Context, config SearchConfig, username string, password string, digest string,
   191  				rch chan stringResult, wtgrp *sync.WaitGroup,
   192  			) {
   193  				rch <- stringResult{StrValue: "", Err: zerr.ErrInjected}
   194  			},
   195  		})
   196  
   197  		err := SearchImagesByDigest(searchConfig, godigest.FromString("str").String())
   198  		So(err, ShouldNotBeNil)
   199  	})
   200  }
   201  
   202  func TestSearchDerivedImageListGQL(t *testing.T) {
   203  	Convey("SearchDerivedImageListGQL", t, func() {
   204  		buff := bytes.NewBufferString("")
   205  		searchConfig := getMockSearchConfig(buff, mockService{
   206  			getDerivedImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   207  				derivedImage string) (*common.DerivedImageListResponse, error,
   208  			) {
   209  				return &common.DerivedImageListResponse{DerivedImageList: common.DerivedImageList{
   210  					PaginatedImagesResult: common.PaginatedImagesResult{
   211  						Results: []common.ImageSummary{
   212  							getMockImageSummary(),
   213  						},
   214  					},
   215  				}}, nil
   216  			},
   217  		})
   218  
   219  		err := SearchDerivedImageListGQL(searchConfig, "repo:tag")
   220  		So(err, ShouldBeNil)
   221  		space := regexp.MustCompile(`\s+`)
   222  		str := space.ReplaceAllString(buff.String(), " ")
   223  		actual := strings.TrimSpace(str)
   224  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   225  	})
   226  
   227  	Convey("SearchDerivedImageListGQL error", t, func() {
   228  		buff := bytes.NewBufferString("")
   229  		searchConfig := getMockSearchConfig(buff, mockService{
   230  			getDerivedImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   231  				derivedImage string) (*common.DerivedImageListResponse, error,
   232  			) {
   233  				return &common.DerivedImageListResponse{DerivedImageList: common.DerivedImageList{
   234  					PaginatedImagesResult: common.PaginatedImagesResult{Results: []common.ImageSummary{}},
   235  				}}, zerr.ErrInjected
   236  			},
   237  		})
   238  
   239  		err := SearchDerivedImageListGQL(searchConfig, "repo:tag")
   240  		So(err, ShouldNotBeNil)
   241  	})
   242  }
   243  
   244  func TestSearchBaseImageListGQL(t *testing.T) {
   245  	Convey("SearchBaseImageListGQL", t, func() {
   246  		buff := bytes.NewBufferString("")
   247  		searchConfig := getMockSearchConfig(buff, mockService{
   248  			getBaseImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   249  				derivedImage string) (*common.BaseImageListResponse, error,
   250  			) {
   251  				return &common.BaseImageListResponse{BaseImageList: common.BaseImageList{
   252  					PaginatedImagesResult: common.PaginatedImagesResult{Results: []common.ImageSummary{
   253  						getMockImageSummary(),
   254  					}},
   255  				}}, nil
   256  			},
   257  		})
   258  
   259  		err := SearchBaseImageListGQL(searchConfig, "repo:tag")
   260  		So(err, ShouldBeNil)
   261  		space := regexp.MustCompile(`\s+`)
   262  		str := space.ReplaceAllString(buff.String(), " ")
   263  		actual := strings.TrimSpace(str)
   264  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   265  	})
   266  
   267  	Convey("SearchBaseImageListGQL error", t, func() {
   268  		buff := bytes.NewBufferString("")
   269  		searchConfig := getMockSearchConfig(buff, mockService{
   270  			getBaseImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   271  				derivedImage string) (*common.BaseImageListResponse, error,
   272  			) {
   273  				return &common.BaseImageListResponse{BaseImageList: common.BaseImageList{
   274  					PaginatedImagesResult: common.PaginatedImagesResult{Results: []common.ImageSummary{}},
   275  				}}, zerr.ErrInjected
   276  			},
   277  		})
   278  
   279  		err := SearchBaseImageListGQL(searchConfig, "repo:tag")
   280  		So(err, ShouldNotBeNil)
   281  	})
   282  }
   283  
   284  func TestSearchImagesForDigestGQL(t *testing.T) {
   285  	Convey("SearchImagesForDigestGQL", t, func() {
   286  		buff := bytes.NewBufferString("")
   287  		searchConfig := getMockSearchConfig(buff, mockService{
   288  			getImagesForDigestGQLFn: func(ctx context.Context, config SearchConfig, username string,
   289  				password string, digest string) (*common.ImagesForDigest, error,
   290  			) {
   291  				return &common.ImagesForDigest{ImagesForDigestList: common.ImagesForDigestList{
   292  					PaginatedImagesResult: common.PaginatedImagesResult{
   293  						Results: []common.ImageSummary{getMockImageSummary()},
   294  					},
   295  				}}, nil
   296  			},
   297  		})
   298  
   299  		err := SearchImagesForDigestGQL(searchConfig, "digest")
   300  		So(err, ShouldBeNil)
   301  		space := regexp.MustCompile(`\s+`)
   302  		str := space.ReplaceAllString(buff.String(), " ")
   303  		actual := strings.TrimSpace(str)
   304  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   305  	})
   306  
   307  	Convey("SearchImagesForDigestGQL error", t, func() {
   308  		buff := bytes.NewBufferString("")
   309  		searchConfig := getMockSearchConfig(buff, mockService{
   310  			getImagesForDigestGQLFn: func(ctx context.Context, config SearchConfig, username string,
   311  				password string, digest string) (*common.ImagesForDigest, error,
   312  			) {
   313  				return &common.ImagesForDigest{ImagesForDigestList: common.ImagesForDigestList{
   314  					PaginatedImagesResult: common.PaginatedImagesResult{},
   315  				}}, zerr.ErrInjected
   316  			},
   317  		})
   318  
   319  		err := SearchImagesForDigestGQL(searchConfig, "digest")
   320  		So(err, ShouldNotBeNil)
   321  	})
   322  }
   323  
   324  func TestSearchCVEForImageGQL(t *testing.T) {
   325  	Convey("SearchCVEForImageGQL normal mode", t, func() {
   326  		buff := bytes.NewBufferString("")
   327  		searchConfig := getMockSearchConfig(buff, mockService{
   328  			getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   329  				imageName string, searchedCVE string) (*cveResult, error,
   330  			) {
   331  				return &cveResult{
   332  					Data: cveData{
   333  						CVEListForImage: cveListForImage{
   334  							CVEList: []cve{
   335  								{
   336  									ID:          "dummyCVEID",
   337  									Description: "Description of the CVE",
   338  									Title:       "Title of that CVE",
   339  									Severity:    "HIGH",
   340  									PackageList: []packageList{
   341  										{
   342  											Name:             "packagename",
   343  											FixedVersion:     "fixedver",
   344  											InstalledVersion: "installedver",
   345  										},
   346  									},
   347  								},
   348  								{
   349  									ID:          "test-cve-id2",
   350  									Description: "Test CVE ID 2",
   351  									Title:       "Test CVE 2",
   352  									Severity:    "HIGH",
   353  									PackageList: []packageList{
   354  										{
   355  											Name:             "packagename",
   356  											PackagePath:      "/usr/bin/dummy.jar",
   357  											FixedVersion:     "fixedver",
   358  											InstalledVersion: "installedver",
   359  										},
   360  										{
   361  											Name:             "packagename",
   362  											PackagePath:      "/usr/bin/dummy.gem",
   363  											FixedVersion:     "fixedver",
   364  											InstalledVersion: "installedver",
   365  										},
   366  									},
   367  								},
   368  							},
   369  							Summary: common.ImageVulnerabilitySummary{
   370  								Count:         2,
   371  								UnknownCount:  0,
   372  								LowCount:      0,
   373  								MediumCount:   0,
   374  								HighCount:     2,
   375  								CriticalCount: 0,
   376  								MaxSeverity:   "HIGH",
   377  							},
   378  						},
   379  					},
   380  				}, nil
   381  			},
   382  		})
   383  
   384  		err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
   385  		So(err, ShouldBeNil)
   386  		bufferContent := buff.String()
   387  		bufferLines := strings.Split(bufferContent, "\n")
   388  
   389  		// Expected result - each row indicates a row of the table with reduced spaces
   390  		expected := []string{
   391  			"CRITICAL 0, HIGH 2, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 2",
   392  			"",
   393  			"ID SEVERITY TITLE",
   394  			"dummyCVEID HIGH Title of that CVE",
   395  			"test-cve-id2 HIGH Test CVE 2",
   396  		}
   397  
   398  		space := regexp.MustCompile(`\s+`)
   399  
   400  		for lineIndex := 0; lineIndex < len(expected); lineIndex++ {
   401  			line := space.ReplaceAllString(bufferLines[lineIndex], " ")
   402  			So(line, ShouldEqualTrimSpace, expected[lineIndex])
   403  		}
   404  	})
   405  
   406  	Convey("SearchCVEForImageGQL verbose mode", t, func() {
   407  		buff := bytes.NewBufferString("")
   408  		searchConfig := getMockSearchConfig(buff, mockService{
   409  			getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   410  				imageName string, searchedCVE string) (*cveResult, error,
   411  			) {
   412  				return &cveResult{
   413  					Data: cveData{
   414  						CVEListForImage: cveListForImage{
   415  							CVEList: []cve{
   416  								{
   417  									ID:          "CVE-100",
   418  									Description: "",
   419  									Title:       "CVE-100 Title",
   420  									Severity:    "HIGH",
   421  									PackageList: []packageList{},
   422  								},
   423  								{
   424  									ID:          "CVE-101",
   425  									Description: "Desc 101\n",
   426  									Title:       "CVE-101 Title",
   427  									Severity:    "HIGH",
   428  									PackageList: []packageList{
   429  										{
   430  											Name:             "Pkg1",
   431  											FixedVersion:     "2.0.0",
   432  											InstalledVersion: "1.0.0",
   433  										},
   434  									},
   435  								},
   436  								{
   437  									ID:          "CVE-102",
   438  									Description: "Desc 102",
   439  									Title:       "CVE-102 Title",
   440  									Severity:    "HIGH",
   441  									PackageList: []packageList{
   442  										{
   443  											Name:             "dummy-java",
   444  											PackagePath:      "/usr/bin/dummy.jar",
   445  											FixedVersion:     "4.0.0",
   446  											InstalledVersion: "3.0.0",
   447  										},
   448  										{
   449  											Name:             "dummy-ruby",
   450  											PackagePath:      "/usr/bin/dummy.gem",
   451  											FixedVersion:     "5.0.0",
   452  											InstalledVersion: "1.0.0",
   453  										},
   454  									},
   455  								},
   456  							},
   457  							Summary: common.ImageVulnerabilitySummary{
   458  								Count:         3,
   459  								UnknownCount:  0,
   460  								LowCount:      0,
   461  								MediumCount:   0,
   462  								HighCount:     3,
   463  								CriticalCount: 0,
   464  								MaxSeverity:   "HIGH",
   465  							},
   466  						},
   467  					},
   468  				}, nil
   469  			},
   470  		})
   471  
   472  		searchConfig.Verbose = true
   473  		err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
   474  		So(err, ShouldBeNil)
   475  		bufferContent := buff.String()
   476  		bufferLines := strings.Split(bufferContent, "\n")
   477  
   478  		// Expected result - each row indicates a line in the output
   479  		expected := []string{
   480  			"CRITICAL 0, HIGH 3, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 3",
   481  			"",
   482  			"CVE-100",
   483  			"Severity: HIGH",
   484  			"Title: CVE-100 Title",
   485  			"Description:",
   486  			"Not Specified",
   487  			"",
   488  			"Vulnerable Packages:",
   489  			"No Vulnerable Packages",
   490  			"",
   491  			"",
   492  			"CVE-101",
   493  			"Severity: HIGH",
   494  			"Title: CVE-101 Title",
   495  			"Description:",
   496  			"Desc 101",
   497  			"",
   498  			"Vulnerable Packages:",
   499  			" Package Name: Pkg1",
   500  			" Package Path: ",
   501  			" Installed Version: 1.0.0",
   502  			" Fixed Version: 2.0.0",
   503  			"",
   504  			"",
   505  			"CVE-102",
   506  			"Severity: HIGH",
   507  			"Title: CVE-102 Title",
   508  			"Description:",
   509  			"Desc 102",
   510  			"",
   511  			"Vulnerable Packages:",
   512  			" Package Name: dummy-java",
   513  			" Package Path: /usr/bin/dummy.jar",
   514  			" Installed Version: 3.0.0",
   515  			" Fixed Version: 4.0.0",
   516  			"",
   517  			" Package Name: dummy-ruby",
   518  			" Package Path: /usr/bin/dummy.gem",
   519  			" Installed Version: 1.0.0",
   520  			" Fixed Version: 5.0.0",
   521  			"",
   522  			"",
   523  		}
   524  
   525  		for index, expectedLine := range expected {
   526  			So(bufferLines[index], ShouldEqual, expectedLine)
   527  		}
   528  	})
   529  
   530  	Convey("SearchCVEForImageGQL with injected error", t, func() {
   531  		buff := bytes.NewBufferString("")
   532  		searchConfig := getMockSearchConfig(buff, mockService{
   533  			getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
   534  				imageName string, searchedCVE string) (*cveResult, error,
   535  			) {
   536  				return &cveResult{}, zerr.ErrInjected
   537  			},
   538  		})
   539  
   540  		err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
   541  		So(err, ShouldNotBeNil)
   542  	})
   543  }
   544  
   545  func TestSearchImagesByCVEIDGQL(t *testing.T) {
   546  	Convey("SearchImagesByCVEIDGQL", t, func() {
   547  		buff := bytes.NewBufferString("")
   548  		searchConfig := getMockSearchConfig(buff, mockService{
   549  			getTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   550  				imageName, cveID string) (*common.ImagesForCve, error,
   551  			) {
   552  				return &common.ImagesForCve{
   553  					ImagesForCVEList: common.ImagesForCVEList{
   554  						PaginatedImagesResult: common.PaginatedImagesResult{
   555  							Results: []common.ImageSummary{
   556  								getMockImageSummary(),
   557  							},
   558  						},
   559  					},
   560  				}, nil
   561  			},
   562  		})
   563  
   564  		err := SearchImagesByCVEIDGQL(searchConfig, "repo", "CVE-12345")
   565  		So(err, ShouldBeNil)
   566  		space := regexp.MustCompile(`\s+`)
   567  		str := space.ReplaceAllString(buff.String(), " ")
   568  		actual := strings.TrimSpace(str)
   569  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   570  	})
   571  
   572  	Convey("SearchImagesByCVEIDGQL error", t, func() {
   573  		buff := bytes.NewBufferString("")
   574  		searchConfig := getMockSearchConfig(buff, mockService{
   575  			getTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   576  				imageName, cveID string) (*common.ImagesForCve, error,
   577  			) {
   578  				return &common.ImagesForCve{
   579  					ImagesForCVEList: common.ImagesForCVEList{
   580  						PaginatedImagesResult: common.PaginatedImagesResult{},
   581  					},
   582  				}, zerr.ErrInjected
   583  			},
   584  		})
   585  
   586  		err := SearchImagesByCVEIDGQL(searchConfig, "repo", "CVE-12345")
   587  		So(err, ShouldNotBeNil)
   588  	})
   589  }
   590  
   591  func TestSearchFixedTagsGQL(t *testing.T) {
   592  	Convey("SearchFixedTagsGQL", t, func() {
   593  		buff := bytes.NewBufferString("")
   594  		searchConfig := getMockSearchConfig(buff, mockService{
   595  			getFixedTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   596  				imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error,
   597  			) {
   598  				return &common.ImageListWithCVEFixedResponse{
   599  					ImageListWithCVEFixed: common.ImageListWithCVEFixed{
   600  						PaginatedImagesResult: common.PaginatedImagesResult{
   601  							Results: []common.ImageSummary{getMockImageSummary()},
   602  						},
   603  					},
   604  				}, nil
   605  			},
   606  		})
   607  
   608  		err := SearchFixedTagsGQL(searchConfig, "repo", "CVE-12345")
   609  		So(err, ShouldBeNil)
   610  		space := regexp.MustCompile(`\s+`)
   611  		str := space.ReplaceAllString(buff.String(), " ")
   612  		actual := strings.TrimSpace(str)
   613  		So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
   614  	})
   615  
   616  	Convey("SearchFixedTagsGQL error", t, func() {
   617  		buff := bytes.NewBufferString("")
   618  		searchConfig := getMockSearchConfig(buff, mockService{
   619  			getFixedTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   620  				imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error,
   621  			) {
   622  				return &common.ImageListWithCVEFixedResponse{
   623  					ImageListWithCVEFixed: common.ImageListWithCVEFixed{
   624  						PaginatedImagesResult: common.PaginatedImagesResult{},
   625  					},
   626  				}, zerr.ErrInjected
   627  			},
   628  		})
   629  
   630  		err := SearchFixedTagsGQL(searchConfig, "repo", "CVE-12345")
   631  		So(err, ShouldNotBeNil)
   632  	})
   633  }
   634  
   635  func TestSearchReferrersGQL(t *testing.T) {
   636  	Convey("SearchReferrersGQL", t, func() {
   637  		buff := bytes.NewBufferString("")
   638  		searchConfig := getMockSearchConfig(buff, mockService{
   639  			getReferrersGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   640  				repo, digest string) (*common.ReferrersResp, error,
   641  			) {
   642  				return &common.ReferrersResp{
   643  					ReferrersResult: common.ReferrersResult{
   644  						Referrers: []common.Referrer{{
   645  							MediaType:    ispec.MediaTypeImageManifest,
   646  							Size:         100,
   647  							ArtifactType: "art.type",
   648  							Digest:       godigest.FromString("123").String(),
   649  						}},
   650  					},
   651  				}, nil
   652  			},
   653  		})
   654  
   655  		err := SearchReferrersGQL(searchConfig, "repo@"+godigest.FromString("str").String())
   656  		So(err, ShouldBeNil)
   657  		space := regexp.MustCompile(`\s+`)
   658  		str := space.ReplaceAllString(buff.String(), " ")
   659  		actual := strings.TrimSpace(str)
   660  		So(actual, ShouldContainSubstring,
   661  			"art.type 100 B sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3")
   662  	})
   663  
   664  	Convey("SearchReferrersGQL error", t, func() {
   665  		buff := bytes.NewBufferString("")
   666  		searchConfig := getMockSearchConfig(buff, mockService{
   667  			getReferrersGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   668  				repo, digest string) (*common.ReferrersResp, error,
   669  			) {
   670  				return &common.ReferrersResp{}, zerr.ErrInjected
   671  			},
   672  		})
   673  
   674  		err := SearchReferrersGQL(searchConfig, "repo@"+godigest.FromString("str").String())
   675  		So(err, ShouldNotBeNil)
   676  	})
   677  }
   678  
   679  func TestGlobalSearchGQL(t *testing.T) {
   680  	Convey("GlobalSearchGQL", t, func() {
   681  		buff := bytes.NewBufferString("")
   682  		searchConfig := getMockSearchConfig(buff, mockService{
   683  			globalSearchGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   684  				query string) (*common.GlobalSearch, error,
   685  			) {
   686  				return &common.GlobalSearch{
   687  					Repos: []common.RepoSummary{{
   688  						Name:        "repo",
   689  						Size:        "100",
   690  						LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC),
   691  					}},
   692  				}, nil
   693  			},
   694  		})
   695  
   696  		err := GlobalSearchGQL(searchConfig, "repo")
   697  		So(err, ShouldBeNil)
   698  		space := regexp.MustCompile(`\s+`)
   699  		str := space.ReplaceAllString(buff.String(), " ")
   700  		actual := strings.TrimSpace(str)
   701  		So(actual, ShouldContainSubstring,
   702  			"repo ")
   703  	})
   704  
   705  	Convey("GlobalSearchGQL error", t, func() {
   706  		buff := bytes.NewBufferString("")
   707  		searchConfig := getMockSearchConfig(buff, mockService{
   708  			globalSearchGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   709  				query string) (*common.GlobalSearch, error,
   710  			) {
   711  				return &common.GlobalSearch{}, zerr.ErrInjected
   712  			},
   713  		})
   714  
   715  		err := GlobalSearchGQL(searchConfig, "repo")
   716  		So(err, ShouldNotBeNil)
   717  	})
   718  }
   719  
   720  func TestSearchReferrers(t *testing.T) {
   721  	Convey("SearchReferrers", t, func() {
   722  		buff := bytes.NewBufferString("")
   723  		searchConfig := getMockSearchConfig(buff, mockService{
   724  			getReferrersFn: func(ctx context.Context, config SearchConfig, username string, password string,
   725  				repo string, digest string) (referrersResult, error,
   726  			) {
   727  				return referrersResult([]common.Referrer{
   728  					{
   729  						MediaType:    ispec.MediaTypeImageManifest,
   730  						Size:         100,
   731  						ArtifactType: "art.type",
   732  						Digest:       godigest.FromString("123").String(),
   733  					},
   734  				}), nil
   735  			},
   736  		})
   737  
   738  		err := SearchReferrers(searchConfig, "repo@"+godigest.FromString("str").String())
   739  		So(err, ShouldBeNil)
   740  		space := regexp.MustCompile(`\s+`)
   741  		str := space.ReplaceAllString(buff.String(), " ")
   742  		actual := strings.TrimSpace(str)
   743  		So(actual, ShouldContainSubstring,
   744  			"art.type 100 B sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3")
   745  	})
   746  
   747  	Convey("SearchReferrers error", t, func() {
   748  		buff := bytes.NewBufferString("")
   749  		searchConfig := getMockSearchConfig(buff, mockService{
   750  			getReferrersFn: func(ctx context.Context, config SearchConfig, username string, password string,
   751  				repo string, digest string) (referrersResult, error,
   752  			) {
   753  				return referrersResult{}, zerr.ErrInjected
   754  			},
   755  		})
   756  
   757  		err := SearchReferrers(searchConfig, "repo@"+godigest.FromString("str").String())
   758  		So(err, ShouldNotBeNil)
   759  	})
   760  }
   761  
   762  func TestSearchRepos(t *testing.T) {
   763  	Convey("SearchRepos", t, func() {
   764  		buff := bytes.NewBufferString("")
   765  		searchConfig := getMockSearchConfig(buff, mockService{})
   766  
   767  		err := SearchRepos(searchConfig)
   768  		So(err, ShouldBeNil)
   769  		space := regexp.MustCompile(`\s+`)
   770  		str := space.ReplaceAllString(buff.String(), " ")
   771  		actual := strings.TrimSpace(str)
   772  		So(actual, ShouldContainSubstring, "repo1")
   773  		So(actual, ShouldContainSubstring, "repo2")
   774  	})
   775  }
   776  
   777  func getMockSearchConfig(buff *bytes.Buffer, mockService mockService) SearchConfig {
   778  	return SearchConfig{
   779  		ResultWriter:  buff,
   780  		User:          "",
   781  		SearchService: mockService,
   782  		ServURL:       "http://127.0.0.1:8000",
   783  		OutputFormat:  "",
   784  		VerifyTLS:     false,
   785  		FixedFlag:     false,
   786  		Verbose:       false,
   787  		Debug:         false,
   788  	}
   789  }
   790  
   791  func getMockImageStruct() imageStruct {
   792  	return imageStruct(common.ImageSummary{
   793  		RepoName: "repo", Tag: "tag",
   794  		MediaType: ispec.MediaTypeImageManifest,
   795  		Digest:    godigest.FromString("str").String(),
   796  		Size:      "100",
   797  		Manifests: []common.ManifestSummary{{
   798  			Size:         "100",
   799  			Platform:     common.Platform{Os: "os", Arch: "arch"},
   800  			Digest:       godigest.FromString("str").String(),
   801  			ConfigDigest: godigest.FromString("str").String(),
   802  		}},
   803  	})
   804  }
   805  
   806  func getMockImageSummary() common.ImageSummary {
   807  	return common.ImageSummary{
   808  		RepoName: "repo", Tag: "tag",
   809  		MediaType: ispec.MediaTypeImageManifest,
   810  		Digest:    godigest.FromString("str").String(),
   811  		Size:      "100",
   812  		Manifests: []common.ManifestSummary{{
   813  			Size:         "100",
   814  			Platform:     common.Platform{Os: "os", Arch: "arch"},
   815  			Digest:       godigest.FromString("str").String(),
   816  			ConfigDigest: godigest.FromString("str").String(),
   817  		}},
   818  	}
   819  }
   820  
   821  func TestUtils(t *testing.T) {
   822  	Convey("Utils", t, func() {
   823  		ok := haveSameArgs(field{"query", []struct {
   824  			Name string `json:"name"`
   825  		}{
   826  			{Name: "arg1"}, {Name: "arg2"},
   827  		}}, GQLQuery{
   828  			Name: "query", Args: []string{"arg1"},
   829  		})
   830  		So(ok, ShouldBeFalse)
   831  
   832  		ok = haveSameArgs(field{"query", []struct {
   833  			Name string `json:"name"`
   834  		}{
   835  			{Name: "arg1"}, {Name: "arg2"},
   836  		}}, GQLQuery{
   837  			Name: "query", Args: []string{"arg1", "arg3"},
   838  		})
   839  		So(ok, ShouldBeFalse)
   840  
   841  		err := containsGQLQueryWithParams(
   842  			[]field{
   843  				{Name: "query"},
   844  			},
   845  			[]typeInfo{},
   846  			GQLQuery{Name: "other-name"},
   847  		)
   848  		So(err, ShouldNotBeNil)
   849  	})
   850  
   851  	Convey("GetConfigOptions", t, func() {
   852  		// no flags
   853  		cmd := &cobra.Command{}
   854  		isSpinner, verifyTLS, err := GetCliConfigOptions(cmd)
   855  		So(err, ShouldNotBeNil)
   856  		So(isSpinner, ShouldBeFalse)
   857  		So(verifyTLS, ShouldBeFalse)
   858  
   859  		// bad showspinner
   860  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":"bad", "verify-tls": false}]}`)
   861  		cmd = &cobra.Command{}
   862  		cmd.Flags().String(ConfigFlag, "imagetest", "")
   863  		isSpinner, verifyTLS, err = GetCliConfigOptions(cmd)
   864  		So(err, ShouldNotBeNil)
   865  		So(isSpinner, ShouldBeFalse)
   866  		So(verifyTLS, ShouldBeFalse)
   867  		os.Remove(configPath)
   868  
   869  		// bad verify-tls
   870  		configPath = makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false, "verify-tls": "bad"}]}`)
   871  		cmd = &cobra.Command{}
   872  		cmd.Flags().String(ConfigFlag, "imagetest", "")
   873  		isSpinner, verifyTLS, err = GetCliConfigOptions(cmd)
   874  		So(err, ShouldNotBeNil)
   875  		So(isSpinner, ShouldBeFalse)
   876  		So(verifyTLS, ShouldBeFalse)
   877  		os.Remove(configPath)
   878  	})
   879  
   880  	Convey("GetServerURLFromFlags", t, func() {
   881  		cmd := &cobra.Command{}
   882  		cmd.Flags().String(URLFlag, "url", "")
   883  		url, err := GetServerURLFromFlags(cmd)
   884  		So(url, ShouldResemble, "url")
   885  		So(err, ShouldBeNil)
   886  
   887  		// err no config or url
   888  		cmd = &cobra.Command{}
   889  		url, err = GetServerURLFromFlags(cmd)
   890  		So(url, ShouldResemble, "")
   891  		So(err, ShouldNotBeNil)
   892  
   893  		// err ulr from config is empty
   894  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest"}]}`)
   895  		cmd = &cobra.Command{}
   896  		cmd.Flags().String(ConfigFlag, "imagetest", "")
   897  		url, err = GetServerURLFromFlags(cmd)
   898  		So(url, ShouldResemble, "")
   899  		So(err, ShouldNotBeNil)
   900  		os.Remove(configPath)
   901  
   902  		// err reading the server url from config
   903  		configPath = makeConfigFile("{}")
   904  		cmd = &cobra.Command{}
   905  		cmd.Flags().String(ConfigFlag, "imagetest", "")
   906  		url, err = GetServerURLFromFlags(cmd)
   907  		So(url, ShouldResemble, "")
   908  		So(err, ShouldNotBeNil)
   909  		os.Remove(configPath)
   910  	})
   911  
   912  	Convey("CheckExtEndPointQuery", t, func() {
   913  		// invalid url
   914  		err := CheckExtEndPointQuery(SearchConfig{
   915  			User:    "",
   916  			ServURL: "bad-url",
   917  		})
   918  		So(err, ShouldNotBeNil)
   919  
   920  		// good url but no connection
   921  		err = CheckExtEndPointQuery(SearchConfig{
   922  			User:         "",
   923  			ServURL:      "http://127.0.0.1:5000",
   924  			VerifyTLS:    false,
   925  			Debug:        false,
   926  			ResultWriter: io.Discard,
   927  		})
   928  		So(err, ShouldNotBeNil)
   929  	})
   930  }