zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/resolver_test.go (about)

     1  //go:build search
     2  
     3  package search //nolint
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/99designs/gqlgen/graphql"
    13  	godigest "github.com/opencontainers/go-digest"
    14  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  	. "github.com/smartystreets/goconvey/convey"
    16  
    17  	zerr "zotregistry.dev/zot/errors"
    18  	"zotregistry.dev/zot/pkg/common"
    19  	"zotregistry.dev/zot/pkg/extensions/search/convert"
    20  	cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
    21  	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
    22  	"zotregistry.dev/zot/pkg/extensions/search/gql_generated"
    23  	"zotregistry.dev/zot/pkg/log"
    24  	"zotregistry.dev/zot/pkg/meta/boltdb"
    25  	mConvert "zotregistry.dev/zot/pkg/meta/convert"
    26  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    27  	reqCtx "zotregistry.dev/zot/pkg/requestcontext"
    28  	"zotregistry.dev/zot/pkg/storage"
    29  	. "zotregistry.dev/zot/pkg/test/image-utils"
    30  	"zotregistry.dev/zot/pkg/test/mocks"
    31  	ociutils "zotregistry.dev/zot/pkg/test/oci-utils"
    32  )
    33  
    34  var ErrTestError = errors.New("TestError")
    35  
    36  func TestResolverGlobalSearch(t *testing.T) {
    37  	Convey("globalSearch", t, func() {
    38  		const query = "repo1"
    39  		Convey("MetaDB SearchRepos error", func() {
    40  			mockMetaDB := mocks.MetaDBMock{
    41  				SearchReposFn: func(ctx context.Context, searchText string,
    42  				) ([]mTypes.RepoMeta, error) {
    43  					return []mTypes.RepoMeta{}, ErrTestError
    44  				},
    45  			}
    46  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
    47  				graphql.DefaultRecover)
    48  			mockCve := mocks.CveInfoMock{}
    49  			repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
    50  				&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
    51  			So(err, ShouldNotBeNil)
    52  			So(images, ShouldBeEmpty)
    53  			So(layers, ShouldBeEmpty)
    54  			So(repos.Results, ShouldBeEmpty)
    55  		})
    56  
    57  		Convey("paginated fail", func() {
    58  			pageInput := &gql_generated.PageInput{
    59  				Limit: ref(-1),
    60  			}
    61  
    62  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
    63  				graphql.DefaultRecover)
    64  
    65  			_, _, _, err := globalSearch(responseContext, "repo", mocks.MetaDBMock{}, &gql_generated.Filter{},
    66  				pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
    67  			So(err, ShouldNotBeNil)
    68  
    69  			_, _, _, err = globalSearch(responseContext, "repo:tag", mocks.MetaDBMock{}, &gql_generated.Filter{},
    70  				pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
    71  			So(err, ShouldNotBeNil)
    72  		})
    73  
    74  		Convey("MetaDB SearchTags gives error", func() {
    75  			mockMetaDB := mocks.MetaDBMock{
    76  				SearchTagsFn: func(ctx context.Context, searchText string) ([]mTypes.FullImageMeta, error) {
    77  					return []mTypes.FullImageMeta{}, ErrTestError
    78  				},
    79  			}
    80  			const query = "repo1:1.0.1"
    81  			mockCve := mocks.CveInfoMock{}
    82  
    83  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
    84  				graphql.DefaultRecover)
    85  			repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
    86  				&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
    87  			So(err, ShouldNotBeNil)
    88  			So(images, ShouldBeEmpty)
    89  			So(layers, ShouldBeEmpty)
    90  			So(repos.Results, ShouldBeEmpty)
    91  		})
    92  
    93  		Convey("Searching by digest", func() {
    94  			ctx := context.Background()
    95  			query := "sha256:aabb12341baf2"
    96  			mockMetaDB := mocks.MetaDBMock{
    97  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
    98  					filterFunc mTypes.FilterFunc,
    99  				) ([]mTypes.FullImageMeta, error) {
   100  					return []mTypes.FullImageMeta{}, ErrTestError
   101  				},
   102  			}
   103  
   104  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
   105  			repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
   106  				&gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
   107  			So(err, ShouldNotBeNil)
   108  			So(images, ShouldBeEmpty)
   109  			So(layers, ShouldBeEmpty)
   110  			So(repos.Results, ShouldBeEmpty)
   111  		})
   112  
   113  		Convey("Searching by digest with bad pagination", func() {
   114  			ctx := context.Background()
   115  			query := "sha256:aabb12341baf2"
   116  
   117  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
   118  			repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{},
   119  				&gql_generated.PageInput{Limit: ref(-10)}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
   120  			So(err, ShouldNotBeNil)
   121  			So(images, ShouldBeEmpty)
   122  			So(layers, ShouldBeEmpty)
   123  			So(repos.Results, ShouldBeEmpty)
   124  		})
   125  
   126  		Convey("Searching by tag returns a filter error", func() {
   127  			ctx := context.Background()
   128  			query := ":test"
   129  			mockMetaDB := mocks.MetaDBMock{
   130  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
   131  					filterFunc mTypes.FilterFunc,
   132  				) ([]mTypes.FullImageMeta, error) {
   133  					return []mTypes.FullImageMeta{}, ErrTestError
   134  				},
   135  			}
   136  
   137  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
   138  			repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
   139  				&gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
   140  			So(err, ShouldNotBeNil)
   141  			So(images, ShouldBeEmpty)
   142  			So(layers, ShouldBeEmpty)
   143  			So(repos.Results, ShouldBeEmpty)
   144  		})
   145  
   146  		Convey("Searching by tag returns a pagination error", func() {
   147  			ctx := context.Background()
   148  			query := ":test"
   149  
   150  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
   151  			repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{},
   152  				&gql_generated.PageInput{Limit: ref(-10)}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
   153  			So(err, ShouldNotBeNil)
   154  			So(images, ShouldBeEmpty)
   155  			So(layers, ShouldBeEmpty)
   156  			So(repos.Results, ShouldBeEmpty)
   157  		})
   158  
   159  		Convey("Searching with a bad query", func() {
   160  			ctx := context.Background()
   161  			query := ":"
   162  
   163  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
   164  			repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{},
   165  				&gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
   166  			So(err, ShouldNotBeNil)
   167  			So(images, ShouldBeEmpty)
   168  			So(layers, ShouldBeEmpty)
   169  			So(repos.Results, ShouldBeEmpty)
   170  		})
   171  	})
   172  }
   173  
   174  func TestRepoListWithNewestImage(t *testing.T) {
   175  	Convey("RepoListWithNewestImage", t, func() {
   176  		Convey("MetaDB SearchRepos error", func() {
   177  			mockMetaDB := mocks.MetaDBMock{
   178  				SearchReposFn: func(ctx context.Context, searchText string) ([]mTypes.RepoMeta, error) {
   179  					return []mTypes.RepoMeta{}, ErrTestError
   180  				},
   181  			}
   182  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   183  				graphql.DefaultRecover)
   184  			mockCve := mocks.CveInfoMock{}
   185  
   186  			pageInput := gql_generated.PageInput{
   187  				Limit:  ref(1),
   188  				Offset: ref(0),
   189  				SortBy: ref(gql_generated.SortCriteriaUpdateTime),
   190  			}
   191  			repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), &pageInput, mockMetaDB)
   192  			So(err, ShouldNotBeNil)
   193  			So(repos.Results, ShouldBeEmpty)
   194  		})
   195  
   196  		Convey("paginated fail", func() {
   197  			pageInput := &gql_generated.PageInput{
   198  				Limit: ref(-1),
   199  			}
   200  
   201  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   202  				graphql.DefaultRecover)
   203  
   204  			_, err := repoListWithNewestImage(responseContext, mocks.CveInfoMock{}, log.NewLogger("debug", ""),
   205  				pageInput, mocks.MetaDBMock{})
   206  			So(err, ShouldNotBeNil)
   207  		})
   208  
   209  		Convey("Working SearchRepo function", func() {
   210  			createTime := time.Now()
   211  			createTime2 := createTime.Add(time.Second)
   212  			img1 := CreateImageWith().DefaultLayers().
   213  				ImageConfig(ispec.Image{
   214  					Config: ispec.ImageConfig{
   215  						Labels: map[string]string{},
   216  					},
   217  					Created: &createTime,
   218  				}).Build()
   219  
   220  			img2 := CreateImageWith().DefaultLayers().
   221  				ImageConfig(ispec.Image{
   222  					Config: ispec.ImageConfig{
   223  						Labels: map[string]string{},
   224  					},
   225  					Created: &createTime2,
   226  				}).Build()
   227  
   228  			mockMetaDB := mocks.MetaDBMock{
   229  				SearchReposFn: func(ctx context.Context, searchText string) ([]mTypes.RepoMeta, error) {
   230  					repos := []mTypes.RepoMeta{
   231  						{
   232  							Name: "repo1",
   233  							Tags: map[mTypes.Tag]mTypes.Descriptor{
   234  								"1.0.1": {
   235  									Digest:    img1.DigestStr(),
   236  									MediaType: ispec.MediaTypeImageManifest,
   237  								},
   238  							},
   239  							Signatures: map[mTypes.ImageDigest]mTypes.ManifestSignatures{
   240  								img1.DigestStr(): {
   241  									"cosign": []mTypes.SignatureInfo{
   242  										{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
   243  									},
   244  								},
   245  							},
   246  							StarCount: 100,
   247  							LastUpdatedImage: &mTypes.LastUpdatedImage{
   248  								Descriptor: mTypes.Descriptor{
   249  									Digest:    img1.DigestStr(),
   250  									MediaType: ispec.MediaTypeImageManifest,
   251  								},
   252  								Tag:         "1.0.1",
   253  								LastUpdated: &createTime,
   254  							},
   255  						},
   256  						{
   257  							Name: "repo2",
   258  							Tags: map[mTypes.Tag]mTypes.Descriptor{
   259  								"1.0.2": {
   260  									Digest:    img2.DigestStr(),
   261  									MediaType: ispec.MediaTypeImageManifest,
   262  								},
   263  							},
   264  							Signatures: map[mTypes.ImageDigest]mTypes.ManifestSignatures{
   265  								img1.DigestStr(): {
   266  									"cosign": []mTypes.SignatureInfo{
   267  										{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
   268  									},
   269  								},
   270  							},
   271  							StarCount: 100,
   272  							LastUpdatedImage: &mTypes.LastUpdatedImage{
   273  								Descriptor: mTypes.Descriptor{
   274  									Digest:    img2.DigestStr(),
   275  									MediaType: ispec.MediaTypeImageManifest,
   276  								},
   277  								Tag:         "1.0.2",
   278  								LastUpdated: &createTime2,
   279  							},
   280  						},
   281  					}
   282  
   283  					return repos, nil
   284  				},
   285  				FilterImageMetaFn: func(ctx context.Context, digests []string,
   286  				) (map[string]mTypes.ImageMeta, error) {
   287  					return map[string]mTypes.ImageMeta{
   288  						img1.DigestStr(): mConvert.GetImageManifestMeta(img1.Manifest, img1.Config,
   289  							img1.ManifestDescriptor.Size, img1.ManifestDescriptor.Digest),
   290  						img2.DigestStr(): mConvert.GetImageManifestMeta(img2.Manifest, img2.Config,
   291  							img2.ManifestDescriptor.Size, img2.ManifestDescriptor.Digest),
   292  					}, nil
   293  				},
   294  			}
   295  			Convey("MetaDB missing requestedPage", func() {
   296  				responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   297  					graphql.DefaultRecover)
   298  				mockCve := mocks.CveInfoMock{}
   299  				repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), nil, mockMetaDB)
   300  				So(err, ShouldBeNil)
   301  				So(repos.Results, ShouldNotBeEmpty)
   302  			})
   303  
   304  			Convey("MetaDB SearchRepo is successful", func() {
   305  				pageInput := gql_generated.PageInput{
   306  					Limit:  ref(2),
   307  					Offset: ref(0),
   308  					SortBy: ref(gql_generated.SortCriteriaUpdateTime),
   309  				}
   310  
   311  				responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   312  					graphql.DefaultRecover)
   313  
   314  				mockCve := mocks.CveInfoMock{}
   315  				repos, err := repoListWithNewestImage(responseContext, mockCve,
   316  					log.NewLogger("debug", ""), &pageInput, mockMetaDB)
   317  				So(err, ShouldBeNil)
   318  				So(repos, ShouldNotBeEmpty)
   319  				So(len(repos.Results), ShouldEqual, 2)
   320  				So(*repos.Results[0].Name, ShouldEqual, "repo2")
   321  				So(*repos.Results[0].LastUpdated, ShouldEqual, createTime2)
   322  			})
   323  		})
   324  	})
   325  }
   326  
   327  func TestGetFilteredPaginatedRepos(t *testing.T) {
   328  	ctx := context.Background()
   329  	log := log.NewLogger("debug", "")
   330  
   331  	Convey("getFilteredPaginatedRepos", t, func() {
   332  		metaDB := mocks.MetaDBMock{}
   333  
   334  		Convey("FilterRepos", func() {
   335  			metaDB.FilterReposFn = func(ctx context.Context, rankName mTypes.FilterRepoNameFunc,
   336  				filterFunc mTypes.FilterFullRepoFunc,
   337  			) ([]mTypes.RepoMeta, error) {
   338  				return nil, ErrTestError
   339  			}
   340  			_, err := getFilteredPaginatedRepos(ctx, nil, func(repo string) bool { return true }, log,
   341  				&gql_generated.PageInput{}, metaDB)
   342  			So(err, ShouldNotBeNil)
   343  		})
   344  		Convey("FilterImageMeta", func() {
   345  			metaDB.FilterImageMetaFn = func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
   346  				return nil, ErrTestError
   347  			}
   348  			_, err := getFilteredPaginatedRepos(ctx, nil, func(repo string) bool { return true }, log,
   349  				&gql_generated.PageInput{}, metaDB)
   350  			So(err, ShouldNotBeNil)
   351  		})
   352  		Convey("PaginatedRepoMeta2RepoSummaries", func() {
   353  			_, err := getFilteredPaginatedRepos(ctx, nil, func(repo string) bool { return true }, log,
   354  				&gql_generated.PageInput{Limit: ref(-10)}, metaDB)
   355  			So(err, ShouldNotBeNil)
   356  		})
   357  	})
   358  }
   359  
   360  func TestGetBookmarkedRepos(t *testing.T) {
   361  	Convey("getBookmarkedRepos", t, func() {
   362  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   363  			graphql.DefaultRecover)
   364  		_, err := getBookmarkedRepos(
   365  			responseContext,
   366  			mocks.CveInfoMock{},
   367  			log.NewLogger("debug", ""),
   368  			nil,
   369  			mocks.MetaDBMock{
   370  				GetBookmarkedReposFn: func(ctx context.Context) ([]string, error) {
   371  					return []string{}, ErrTestError
   372  				},
   373  			},
   374  		)
   375  		So(err, ShouldNotBeNil)
   376  	})
   377  }
   378  
   379  func TestGetStarredRepos(t *testing.T) {
   380  	Convey("getStarredRepos", t, func() {
   381  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   382  			graphql.DefaultRecover)
   383  		_, err := getStarredRepos(
   384  			responseContext,
   385  			mocks.CveInfoMock{},
   386  			log.NewLogger("debug", ""),
   387  			nil,
   388  			mocks.MetaDBMock{
   389  				GetStarredReposFn: func(ctx context.Context) ([]string, error) {
   390  					return []string{}, ErrTestError
   391  				},
   392  			},
   393  		)
   394  		So(err, ShouldNotBeNil)
   395  	})
   396  }
   397  
   398  func getTestRepoMetaWithImages(repo string, images []Image) mTypes.RepoMeta {
   399  	tags := map[mTypes.Tag]mTypes.Descriptor{"": {}}
   400  	statistics := map[mTypes.Tag]mTypes.DescriptorStatistics{"": {}}
   401  	signatures := map[mTypes.ImageDigest]mTypes.ManifestSignatures{"": {}}
   402  	referrers := map[string][]mTypes.ReferrerInfo{"": {}}
   403  
   404  	for i := range images {
   405  		tags[images[i].DigestStr()] = mTypes.Descriptor{}
   406  		statistics[images[i].DigestStr()] = mTypes.DescriptorStatistics{}
   407  		signatures[images[i].DigestStr()] = mTypes.ManifestSignatures{}
   408  		referrers[images[i].DigestStr()] = []mTypes.ReferrerInfo{}
   409  	}
   410  
   411  	return mTypes.RepoMeta{
   412  		Name:       repo,
   413  		Tags:       tags,
   414  		Statistics: statistics,
   415  		Signatures: signatures,
   416  		Referrers:  referrers,
   417  	}
   418  }
   419  
   420  func TestImageListForDigest(t *testing.T) {
   421  	Convey("getImageList", t, func() {
   422  		Convey("no page requested, FilterTagsFn returns error", func() {
   423  			mockMetaDB := mocks.MetaDBMock{
   424  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
   425  					filterFunc mTypes.FilterFunc,
   426  				) ([]mTypes.FullImageMeta, error) {
   427  					return []mTypes.FullImageMeta{}, ErrTestError
   428  				},
   429  			}
   430  
   431  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   432  				graphql.DefaultRecover)
   433  			_, err := getImageListForDigest(responseContext, "invalid", mockMetaDB, mocks.CveInfoMock{}, nil)
   434  			So(err, ShouldNotBeNil)
   435  		})
   436  
   437  		Convey("Paginated convert fails", func() {
   438  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   439  				graphql.DefaultRecover)
   440  			_, err := getImageListForDigest(responseContext, "invalid", mocks.MetaDBMock{}, mocks.CveInfoMock{},
   441  				&gql_generated.PageInput{Limit: ref(-1)})
   442  			So(err, ShouldNotBeNil)
   443  		})
   444  
   445  		Convey("valid imageListForDigest returned for matching manifest digest", func() {
   446  			img1, img2 := CreateRandomImage(), CreateRandomImage()
   447  			mockMetaDB := mocks.MetaDBMock{
   448  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
   449  					filterFunc mTypes.FilterFunc,
   450  				) ([]mTypes.FullImageMeta, error) {
   451  					fullImageMetaList := []mTypes.ImageMeta{img1.AsImageMeta(), img2.AsImageMeta()}
   452  					repoMeta := getTestRepoMetaWithImages("repo", []Image{img1, img2})
   453  					tags := []string{"tag1", "tag2"}
   454  
   455  					acceptedImages := []mTypes.FullImageMeta{}
   456  
   457  					for i := range fullImageMetaList {
   458  						if filterFunc(repoMeta, fullImageMetaList[i]) {
   459  							acceptedImages = append(acceptedImages,
   460  								convert.GetFullImageMeta(tags[i], repoMeta, fullImageMetaList[i]))
   461  
   462  							continue
   463  						}
   464  					}
   465  
   466  					return acceptedImages, nil
   467  				},
   468  			}
   469  
   470  			pageInput := gql_generated.PageInput{
   471  				Limit:  ref(1),
   472  				Offset: ref(0),
   473  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
   474  			}
   475  
   476  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   477  				graphql.DefaultRecover)
   478  			imageSummaries, err := getImageListForDigest(responseContext, img1.DigestStr(),
   479  				mockMetaDB, mocks.CveInfoMock{}, &pageInput)
   480  			So(err, ShouldBeNil)
   481  			So(len(imageSummaries.Results), ShouldEqual, 1)
   482  
   483  			imageSummaries, err = getImageListForDigest(responseContext, "invalid",
   484  				mockMetaDB, mocks.CveInfoMock{}, &pageInput)
   485  			So(err, ShouldBeNil)
   486  			So(len(imageSummaries.Results), ShouldEqual, 0)
   487  
   488  			imageSummaries, err = getImageListForDigest(responseContext, img1.Manifest.Config.Digest.String(),
   489  				mockMetaDB, mocks.CveInfoMock{}, &pageInput)
   490  			So(err, ShouldBeNil)
   491  			So(len(imageSummaries.Results), ShouldEqual, 1)
   492  
   493  			imageSummaries, err = getImageListForDigest(responseContext, img1.Manifest.Layers[0].Digest.String(),
   494  				mockMetaDB, mocks.CveInfoMock{}, &pageInput)
   495  			So(err, ShouldBeNil)
   496  			So(len(imageSummaries.Results), ShouldEqual, 1)
   497  		})
   498  
   499  		Convey("valid imageListForDigest, multiple matching tags", func() {
   500  			img1 := CreateRandomImage()
   501  
   502  			mockMetaDB := mocks.MetaDBMock{
   503  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
   504  					filterFunc mTypes.FilterFunc,
   505  				) ([]mTypes.FullImageMeta, error) {
   506  					fullImageMetaList := []mTypes.ImageMeta{img1.AsImageMeta()}
   507  					repoMeta := getTestRepoMetaWithImages("repo", []Image{img1, img1})
   508  					tags := []string{"tag1", "tag2"}
   509  
   510  					acceptedImages := []mTypes.FullImageMeta{}
   511  
   512  					for i := range fullImageMetaList {
   513  						if filterFunc(repoMeta, fullImageMetaList[i]) {
   514  							acceptedImages = append(acceptedImages,
   515  								convert.GetFullImageMeta(tags[i], repoMeta, fullImageMetaList[i]))
   516  
   517  							continue
   518  						}
   519  					}
   520  
   521  					return acceptedImages, nil
   522  				},
   523  			}
   524  
   525  			pageInput := gql_generated.PageInput{
   526  				Limit:  ref(1),
   527  				Offset: ref(0),
   528  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
   529  			}
   530  
   531  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   532  				graphql.DefaultRecover)
   533  
   534  			imageSummaries, err := getImageListForDigest(responseContext, img1.DigestStr(),
   535  				mockMetaDB, mocks.CveInfoMock{}, &pageInput)
   536  			So(err, ShouldBeNil)
   537  			So(len(imageSummaries.Results), ShouldEqual, 1)
   538  		})
   539  	})
   540  }
   541  
   542  func TestGetImageSummaryError(t *testing.T) {
   543  	Convey("getImageSummary", t, func() {
   544  		metaDB := mocks.MetaDBMock{
   545  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   546  				return mTypes.RepoMeta{Tags: map[mTypes.Tag]mTypes.Descriptor{"tag": {}}}, nil
   547  			},
   548  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
   549  				return nil, ErrTestError
   550  			},
   551  		}
   552  		log := log.NewLogger("debug", "")
   553  
   554  		_, err := getImageSummary(context.Background(), "repo", "tag", nil, convert.SkipQGLField{},
   555  			metaDB, nil, log)
   556  		So(err, ShouldNotBeNil)
   557  	})
   558  }
   559  
   560  func TestImageListError(t *testing.T) {
   561  	Convey("getImageList", t, func() {
   562  		testLogger := log.NewLogger("debug", "/dev/null")
   563  		Convey("no page requested, SearchRepoFn returns error", func() {
   564  			mockMetaDB := mocks.MetaDBMock{
   565  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
   566  				) ([]mTypes.FullImageMeta, error) {
   567  					return []mTypes.FullImageMeta{}, ErrTestError
   568  				},
   569  			}
   570  
   571  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   572  				graphql.DefaultRecover)
   573  
   574  			_, err := getImageList(responseContext, "test", mockMetaDB, mocks.CveInfoMock{}, nil, testLogger)
   575  			So(err, ShouldNotBeNil)
   576  		})
   577  
   578  		Convey("Paginated convert fails", func() {
   579  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   580  				graphql.DefaultRecover)
   581  
   582  			_, err := getImageList(responseContext, "test", mocks.MetaDBMock{}, mocks.CveInfoMock{},
   583  				&gql_generated.PageInput{Limit: ref(-1)}, log.NewLogger("debug", ""))
   584  
   585  			So(err, ShouldNotBeNil)
   586  		})
   587  
   588  		Convey("valid repoList returned", func() {
   589  			mockMetaDB := mocks.MetaDBMock{
   590  				FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
   591  				) ([]mTypes.FullImageMeta, error) {
   592  					repoName := "correct-repo"
   593  
   594  					if !filterRepoTag(repoName, "tag") {
   595  						return []mTypes.FullImageMeta{}, nil
   596  					}
   597  
   598  					image := CreateDefaultImage()
   599  					repoMeta := mTypes.RepoMeta{
   600  						Name: "repo",
   601  						Tags: map[mTypes.Tag]mTypes.Descriptor{image.DigestStr(): {
   602  							Digest:    image.DigestStr(),
   603  							MediaType: ispec.MediaTypeImageManifest,
   604  						}},
   605  					}
   606  
   607  					return []mTypes.FullImageMeta{convert.GetFullImageMeta("tag", repoMeta, image.AsImageMeta())}, nil
   608  				},
   609  			}
   610  
   611  			pageInput := gql_generated.PageInput{
   612  				Limit:  ref(1),
   613  				Offset: ref(0),
   614  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
   615  			}
   616  
   617  			responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   618  				graphql.DefaultRecover)
   619  
   620  			imageSummaries, err := getImageList(responseContext, "correct-repo", mockMetaDB,
   621  				mocks.CveInfoMock{}, &pageInput, testLogger)
   622  			So(err, ShouldBeNil)
   623  			So(len(imageSummaries.Results), ShouldEqual, 1)
   624  
   625  			imageSummaries, err = getImageList(responseContext, "invalid", mockMetaDB,
   626  				mocks.CveInfoMock{}, &pageInput, testLogger)
   627  			So(err, ShouldBeNil)
   628  			So(len(imageSummaries.Results), ShouldEqual, 0)
   629  		})
   630  	})
   631  }
   632  
   633  func TestGetReferrers(t *testing.T) {
   634  	Convey("getReferrers", t, func() {
   635  		referredDigest := godigest.FromString("t").String()
   636  
   637  		Convey("referredDigest is empty", func() {
   638  			testLogger := log.NewLogger("debug", "")
   639  
   640  			_, err := getReferrers(mocks.MetaDBMock{}, "test", "", nil, testLogger)
   641  			So(err, ShouldNotBeNil)
   642  		})
   643  
   644  		Convey("GetReferrers returns error", func() {
   645  			testLogger := log.NewLogger("debug", "")
   646  			mockedStore := mocks.MetaDBMock{
   647  				GetReferrersInfoFn: func(repo string, referredDigest godigest.Digest, artifactTypes []string,
   648  				) ([]mTypes.ReferrerInfo, error) {
   649  					return nil, ErrTestError
   650  				},
   651  			}
   652  
   653  			_, err := getReferrers(mockedStore, "test", referredDigest, nil, testLogger)
   654  			So(err, ShouldNotBeNil)
   655  		})
   656  
   657  		Convey("GetReferrers return index of descriptors", func() {
   658  			testLogger := log.NewLogger("debug", "")
   659  			referrerDescriptor := ispec.Descriptor{
   660  				MediaType:    ispec.MediaTypeImageManifest,
   661  				ArtifactType: "com.artifact.test",
   662  				Size:         403,
   663  				Digest:       godigest.FromString("test"),
   664  				Annotations: map[string]string{
   665  					"key": "value",
   666  				},
   667  			}
   668  			mockedStore := mocks.MetaDBMock{
   669  				GetReferrersInfoFn: func(repo string, referredDigest godigest.Digest, artifactTypes []string,
   670  				) ([]mTypes.ReferrerInfo, error) {
   671  					return []mTypes.ReferrerInfo{
   672  						{
   673  							Digest:       referrerDescriptor.Digest.String(),
   674  							MediaType:    referrerDescriptor.MediaType,
   675  							ArtifactType: referrerDescriptor.ArtifactType,
   676  							Size:         int(referrerDescriptor.Size),
   677  							Annotations:  referrerDescriptor.Annotations,
   678  						},
   679  					}, nil
   680  				},
   681  			}
   682  
   683  			referrers, err := getReferrers(mockedStore, "test", referredDigest, nil, testLogger)
   684  			So(err, ShouldBeNil)
   685  			So(*referrers[0].ArtifactType, ShouldEqual, referrerDescriptor.ArtifactType)
   686  			So(*referrers[0].MediaType, ShouldEqual, referrerDescriptor.MediaType)
   687  			So(*referrers[0].Size, ShouldEqual, referrerDescriptor.Size)
   688  			So(*referrers[0].Digest, ShouldEqual, referrerDescriptor.Digest.String())
   689  			So(*referrers[0].Annotations[0].Value, ShouldEqual, referrerDescriptor.Annotations["key"])
   690  		})
   691  	})
   692  }
   693  
   694  func TestQueryResolverErrors(t *testing.T) {
   695  	Convey("Errors", t, func() {
   696  		log := log.NewLogger("debug", "")
   697  		ctx := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
   698  			graphql.DefaultRecover)
   699  
   700  		Convey("GlobalSearch error bad requested page", func() {
   701  			resolverConfig := NewResolver(log, storage.StoreController{}, mocks.MetaDBMock{}, mocks.CveInfoMock{})
   702  			resolver := queryResolver{resolverConfig}
   703  			pageInput := gql_generated.PageInput{
   704  				Limit:  ref(-1),
   705  				Offset: ref(0),
   706  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
   707  			}
   708  
   709  			_, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput)
   710  			So(err, ShouldNotBeNil)
   711  
   712  			pageInput = gql_generated.PageInput{
   713  				Limit:  ref(0),
   714  				Offset: ref(-1),
   715  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
   716  			}
   717  
   718  			_, err = resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput)
   719  			So(err, ShouldNotBeNil)
   720  		})
   721  
   722  		Convey("GlobalSearch error filte image meta", func() {
   723  			resolverConfig := NewResolver(log, storage.StoreController{}, mocks.MetaDBMock{
   724  				FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
   725  					return nil, ErrTestError
   726  				},
   727  			}, mocks.CveInfoMock{})
   728  			resolver := queryResolver{resolverConfig}
   729  
   730  			_, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, getGQLPageInput(1, 1))
   731  			So(err, ShouldNotBeNil)
   732  		})
   733  
   734  		Convey("CVEDiffListForImages nill cveinfo", func() {
   735  			resolverConfig := NewResolver(
   736  				log,
   737  				storage.StoreController{
   738  					DefaultStore: mocks.MockedImageStore{},
   739  				},
   740  				mocks.MetaDBMock{
   741  					GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
   742  					) ([]mTypes.RepoMeta, error) {
   743  						return []mTypes.RepoMeta{}, ErrTestError
   744  					},
   745  				},
   746  				nil,
   747  			)
   748  
   749  			qr := queryResolver{resolverConfig}
   750  
   751  			_, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{},
   752  				&gql_generated.PageInput{}, nil, nil)
   753  			So(err, ShouldNotBeNil)
   754  		})
   755  
   756  		Convey("CVEDiffListForImages error", func() {
   757  			resolverConfig := NewResolver(
   758  				log,
   759  				storage.StoreController{
   760  					DefaultStore: mocks.MockedImageStore{},
   761  				},
   762  				mocks.MetaDBMock{
   763  					GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
   764  					) ([]mTypes.RepoMeta, error) {
   765  						return []mTypes.RepoMeta{}, ErrTestError
   766  					},
   767  				},
   768  				mocks.CveInfoMock{},
   769  			)
   770  
   771  			qr := queryResolver{resolverConfig}
   772  
   773  			_, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{},
   774  				&gql_generated.PageInput{}, nil, nil)
   775  			So(err, ShouldNotBeNil)
   776  		})
   777  
   778  		Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
   779  			resolverConfig := NewResolver(
   780  				log,
   781  				storage.StoreController{
   782  					DefaultStore: mocks.MockedImageStore{},
   783  				},
   784  				mocks.MetaDBMock{
   785  					GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
   786  					) ([]mTypes.RepoMeta, error) {
   787  						return []mTypes.RepoMeta{}, ErrTestError
   788  					},
   789  				},
   790  				mocks.CveInfoMock{},
   791  			)
   792  
   793  			qr := queryResolver{resolverConfig}
   794  
   795  			_, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.Filter{}, &gql_generated.PageInput{})
   796  			So(err, ShouldNotBeNil)
   797  		})
   798  
   799  		Convey("ImageListForCve error in FilterTags", func() {
   800  			resolverConfig := NewResolver(
   801  				log,
   802  				storage.StoreController{
   803  					DefaultStore: mocks.MockedImageStore{},
   804  				},
   805  				mocks.MetaDBMock{
   806  					FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
   807  					) ([]mTypes.FullImageMeta, error) {
   808  						return []mTypes.FullImageMeta{}, ErrTestError
   809  					},
   810  				},
   811  				mocks.CveInfoMock{},
   812  			)
   813  
   814  			qr := queryResolver{resolverConfig}
   815  
   816  			_, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.Filter{}, &gql_generated.PageInput{})
   817  			So(err, ShouldNotBeNil)
   818  		})
   819  
   820  		Convey("ImageListWithCVEFixed error in FilterTags", func() {
   821  			resolverConfig := NewResolver(
   822  				log,
   823  				storage.StoreController{
   824  					DefaultStore: mocks.MockedImageStore{},
   825  				},
   826  				mocks.MetaDBMock{
   827  					FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
   828  					) ([]mTypes.FullImageMeta, error) {
   829  						return []mTypes.FullImageMeta{}, ErrTestError
   830  					},
   831  				},
   832  				mocks.CveInfoMock{},
   833  			)
   834  
   835  			qr := queryResolver{resolverConfig}
   836  
   837  			_, err := qr.ImageListWithCVEFixed(ctx, "cve1", "image", &gql_generated.Filter{}, &gql_generated.PageInput{})
   838  			So(err, ShouldNotBeNil)
   839  		})
   840  
   841  		Convey("RepoListWithNewestImage repoListWithNewestImage() filter image meta error", func() {
   842  			resolverConfig := NewResolver(
   843  				log,
   844  				storage.StoreController{
   845  					DefaultStore: mocks.MockedImageStore{},
   846  				},
   847  				mocks.MetaDBMock{
   848  					SearchReposFn: func(ctx context.Context, searchText string,
   849  					) ([]mTypes.RepoMeta, error) {
   850  						return []mTypes.RepoMeta{}, nil
   851  					},
   852  					FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
   853  						return nil, ErrTestError
   854  					},
   855  				},
   856  				mocks.CveInfoMock{},
   857  			)
   858  
   859  			qr := queryResolver{resolverConfig}
   860  
   861  			_, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{})
   862  			So(err, ShouldNotBeNil)
   863  		})
   864  
   865  		Convey("RepoListWithNewestImage repoListWithNewestImage() errors mocked StoreController", func() {
   866  			resolverConfig := NewResolver(
   867  				log,
   868  				storage.StoreController{
   869  					DefaultStore: mocks.MockedImageStore{},
   870  				},
   871  				mocks.MetaDBMock{
   872  					SearchReposFn: func(ctx context.Context, searchText string,
   873  					) ([]mTypes.RepoMeta, error) {
   874  						return nil, ErrTestError
   875  					},
   876  				},
   877  				mocks.CveInfoMock{},
   878  			)
   879  
   880  			qr := queryResolver{resolverConfig}
   881  
   882  			_, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{})
   883  			So(err, ShouldNotBeNil)
   884  		})
   885  
   886  		Convey("RepoListWithNewestImage repoListWithNewestImage() errors valid StoreController", func() {
   887  			resolverConfig := NewResolver(
   888  				log,
   889  				storage.StoreController{},
   890  				mocks.MetaDBMock{
   891  					SearchReposFn: func(ctx context.Context, searchText string,
   892  					) ([]mTypes.RepoMeta, error) {
   893  						return nil, ErrTestError
   894  					},
   895  				},
   896  				mocks.CveInfoMock{},
   897  			)
   898  
   899  			qr := queryResolver{resolverConfig}
   900  
   901  			_, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{})
   902  			So(err, ShouldNotBeNil)
   903  		})
   904  
   905  		Convey("ImageList getImageList() errors", func() {
   906  			resolverConfig := NewResolver(
   907  				log,
   908  				storage.StoreController{},
   909  				mocks.MetaDBMock{
   910  					FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
   911  					) ([]mTypes.FullImageMeta, error) {
   912  						return []mTypes.FullImageMeta{}, ErrTestError
   913  					},
   914  				},
   915  				mocks.CveInfoMock{},
   916  			)
   917  
   918  			qr := queryResolver{resolverConfig}
   919  
   920  			_, err := qr.ImageList(ctx, "repo", &gql_generated.PageInput{})
   921  			So(err, ShouldNotBeNil)
   922  		})
   923  
   924  		Convey("DerivedImageList ExpandedRepoInfo() errors", func() {
   925  			resolverConfig := NewResolver(
   926  				log,
   927  				storage.StoreController{
   928  					DefaultStore: mocks.MockedImageStore{
   929  						GetRepositoriesFn: func() ([]string, error) {
   930  							return []string{"sub1/repo"}, nil
   931  						},
   932  						GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
   933  							return []byte("{}"), "digest", "str", nil
   934  						},
   935  					},
   936  				},
   937  				mocks.MetaDBMock{
   938  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   939  						return mTypes.RepoMeta{}, ErrTestError
   940  					},
   941  				},
   942  				mocks.CveInfoMock{},
   943  			)
   944  
   945  			qr := queryResolver{resolverConfig}
   946  
   947  			_, err := qr.DerivedImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
   948  			So(err, ShouldNotBeNil)
   949  		})
   950  
   951  		Convey("BaseImageList ExpandedRepoInfo() errors", func() {
   952  			resolverConfig := NewResolver(
   953  				log,
   954  				storage.StoreController{
   955  					DefaultStore: mocks.MockedImageStore{
   956  						GetRepositoriesFn: func() ([]string, error) {
   957  							return []string{"sub1/repo"}, nil
   958  						},
   959  						GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
   960  							return []byte("{}"), "digest", "str", nil
   961  						},
   962  					},
   963  				},
   964  				mocks.MetaDBMock{
   965  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   966  						return mTypes.RepoMeta{}, ErrTestError
   967  					},
   968  				},
   969  				mocks.CveInfoMock{},
   970  			)
   971  
   972  			qr := queryResolver{resolverConfig}
   973  
   974  			_, err := qr.BaseImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
   975  			So(err, ShouldNotBeNil)
   976  		})
   977  
   978  		Convey("DerivedImageList and BaseImage List FilterTags() errors", func() {
   979  			image := CreateDefaultImage()
   980  
   981  			resolverConfig := NewResolver(
   982  				log,
   983  				storage.StoreController{},
   984  				mocks.MetaDBMock{
   985  					FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
   986  					) ([]mTypes.FullImageMeta, error) {
   987  						return []mTypes.FullImageMeta{}, ErrTestError
   988  					},
   989  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   990  						return mTypes.RepoMeta{
   991  							Name: "repo",
   992  							Tags: map[mTypes.Tag]mTypes.Descriptor{
   993  								"tag": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
   994  							},
   995  						}, nil
   996  					},
   997  					GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
   998  						return image.AsImageMeta(), nil
   999  					},
  1000  				},
  1001  				mocks.CveInfoMock{},
  1002  			)
  1003  
  1004  			resolver := queryResolver{resolverConfig}
  1005  
  1006  			_, err := resolver.DerivedImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
  1007  			So(err, ShouldNotBeNil)
  1008  
  1009  			_, err = resolver.BaseImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
  1010  			So(err, ShouldNotBeNil)
  1011  		})
  1012  
  1013  		Convey("GetReferrers error", func() {
  1014  			resolverConfig := NewResolver(
  1015  				log,
  1016  				storage.StoreController{
  1017  					DefaultStore: mocks.MockedImageStore{
  1018  						GetReferrersFn: func(repo string, digest godigest.Digest, artifactTypes []string) (ispec.Index, error) {
  1019  							return ispec.Index{}, ErrTestError
  1020  						},
  1021  					},
  1022  				},
  1023  				mocks.MetaDBMock{},
  1024  				mocks.CveInfoMock{},
  1025  			)
  1026  
  1027  			qr := queryResolver{resolverConfig}
  1028  
  1029  			_, err := qr.Referrers(ctx, "repo", "", nil)
  1030  			So(err, ShouldNotBeNil)
  1031  		})
  1032  	})
  1033  }
  1034  
  1035  func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
  1036  	ctx := context.Background()
  1037  	log := log.NewLogger("debug", "")
  1038  	LINUX := "linux"
  1039  	AMD := "amd"
  1040  	ARM := "arm64"
  1041  
  1042  	boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: t.TempDir()})
  1043  	if err != nil {
  1044  		panic(err)
  1045  	}
  1046  
  1047  	metaDB, err := boltdb.New(boltDriver, log)
  1048  	if err != nil {
  1049  		panic(err)
  1050  	}
  1051  
  1052  	image1 := CreateImageWith().RandomLayers(5, 2).ImageConfig(ispec.Image{
  1053  		Created: DateRef(2008, 1, 1, 12, 0, 0, 0, time.UTC),
  1054  		Platform: ispec.Platform{
  1055  			Architecture: AMD,
  1056  			OS:           LINUX,
  1057  		},
  1058  	}).Build()
  1059  	digest1 := image1.Digest()
  1060  
  1061  	image2 := CreateImageWith().RandomLayers(5, 2).ImageConfig(ispec.Image{
  1062  		Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC),
  1063  		Platform: ispec.Platform{
  1064  			Architecture: AMD,
  1065  			OS:           LINUX,
  1066  		},
  1067  	}).Build()
  1068  	digest2 := image2.Digest()
  1069  
  1070  	image3 := CreateImageWith().RandomLayers(5, 2).ImageConfig(ispec.Image{
  1071  		Created: DateRef(2010, 1, 1, 12, 0, 0, 0, time.UTC),
  1072  		Platform: ispec.Platform{
  1073  			Architecture: ARM,
  1074  			OS:           LINUX,
  1075  		},
  1076  	}).Build()
  1077  	digest3 := image3.Digest()
  1078  
  1079  	ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB,
  1080  		ociutils.Repo{
  1081  			Name: "repo1", Images: []ociutils.RepoImage{
  1082  				{Image: image1, Reference: "1.0.0"},
  1083  				{Image: image2, Reference: "1.0.1"},
  1084  				{Image: image3, Reference: "1.1.0"},
  1085  				{Image: image3, Reference: "latest"},
  1086  			},
  1087  		},
  1088  		ociutils.Repo{
  1089  			Name: "repo2", Images: []ociutils.RepoImage{
  1090  				{Image: image1, Reference: "2.0.0"},
  1091  				{Image: image2, Reference: "2.0.1"},
  1092  				{Image: image3, Reference: "2.1.0"},
  1093  				{Image: image3, Reference: "latest"},
  1094  			},
  1095  		},
  1096  		ociutils.Repo{
  1097  			Name: "repo3", Images: []ociutils.RepoImage{
  1098  				{Image: image2, Reference: "3.0.1"},
  1099  				{Image: image3, Reference: "3.1.0"},
  1100  				{Image: image3, Reference: "latest"},
  1101  			},
  1102  		},
  1103  	)
  1104  	if err != nil {
  1105  		panic(err)
  1106  	}
  1107  
  1108  	getCveResults := func(digestStr string) map[string]cvemodel.CVE {
  1109  		if digestStr == digest1.String() {
  1110  			return map[string]cvemodel.CVE{
  1111  				"CVE1": {
  1112  					ID:          "CVE1",
  1113  					Severity:    "HIGH",
  1114  					Title:       "Title CVE1",
  1115  					Description: "Description CVE1",
  1116  				},
  1117  				"CVE2": {
  1118  					ID:          "CVE2",
  1119  					Severity:    "MEDIUM",
  1120  					Title:       "Title CVE2",
  1121  					Description: "Description CVE2",
  1122  				},
  1123  				"CVE3": {
  1124  					ID:          "CVE3",
  1125  					Severity:    "LOW",
  1126  					Title:       "Title CVE3",
  1127  					Description: "Description CVE3",
  1128  				},
  1129  				"CVE34": {
  1130  					ID:          "CVE34",
  1131  					Severity:    "LOW",
  1132  					Title:       "Title for CVE34",
  1133  					Description: "Description CVE34",
  1134  				},
  1135  			}
  1136  		}
  1137  
  1138  		if digestStr == digest2.String() {
  1139  			return map[string]cvemodel.CVE{
  1140  				"CVE2": {
  1141  					ID:          "CVE2",
  1142  					Severity:    "MEDIUM",
  1143  					Title:       "Title CVE2",
  1144  					Description: "Description CVE2",
  1145  				},
  1146  				"CVE3": {
  1147  					ID:          "CVE3",
  1148  					Severity:    "LOW",
  1149  					Title:       "Title CVE3",
  1150  					Description: "Description CVE3",
  1151  				},
  1152  			}
  1153  		}
  1154  
  1155  		if digestStr == digest3.String() {
  1156  			return map[string]cvemodel.CVE{
  1157  				"CVE3": {
  1158  					ID:          "CVE3",
  1159  					Severity:    "LOW",
  1160  					Title:       "Title CVE3",
  1161  					Description: "Description CVE3",
  1162  				},
  1163  			}
  1164  		}
  1165  
  1166  		// By default the image has no vulnerabilities
  1167  		return map[string]cvemodel.CVE{}
  1168  	}
  1169  
  1170  	// MetaDB loaded with initial data, now mock the scanner
  1171  	// Setup test CVE data in mock scanner
  1172  	scanner := mocks.CveScannerMock{
  1173  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
  1174  			repo, ref, _, _ := common.GetRepoReference(image)
  1175  
  1176  			if common.IsDigest(ref) {
  1177  				return getCveResults(ref), nil
  1178  			}
  1179  
  1180  			repoMeta, _ := metaDB.GetRepoMeta(ctx, repo)
  1181  
  1182  			if _, ok := repoMeta.Tags[ref]; !ok {
  1183  				panic("unexpected tag '" + ref + "', test might be wrong")
  1184  			}
  1185  
  1186  			return getCveResults(repoMeta.Tags[ref].Digest), nil
  1187  		},
  1188  		GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE {
  1189  			return getCveResults(digestStr)
  1190  		},
  1191  		IsResultCachedFn: func(digestStr string) bool {
  1192  			return true
  1193  		},
  1194  	}
  1195  
  1196  	cveInfo := &cveinfo.BaseCveInfo{
  1197  		Log:     log,
  1198  		Scanner: scanner,
  1199  		MetaDB:  metaDB,
  1200  	}
  1201  
  1202  	Convey("Get CVE list for image ", t, func() {
  1203  		Convey("Unpaginated request to get all CVEs in an image", func() {
  1204  			pageInput := &gql_generated.PageInput{
  1205  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
  1206  			}
  1207  
  1208  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1209  				graphql.DefaultRecover)
  1210  
  1211  			dig := godigest.FromString("dig")
  1212  			repoWithDigestRef := fmt.Sprintf("repo@%s", dig)
  1213  
  1214  			_, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, "", "", "", log)
  1215  			So(err, ShouldBeNil)
  1216  
  1217  			cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", "", "", log)
  1218  			So(err, ShouldBeNil)
  1219  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1220  
  1221  			expectedCves := []string{"CVE1", "CVE2", "CVE3", "CVE34"}
  1222  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1223  
  1224  			for _, cve := range cveResult.CVEList {
  1225  				So(expectedCves, ShouldContain, *cve.ID)
  1226  			}
  1227  
  1228  			// test searching CVE by id in results
  1229  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE3", "", "", log)
  1230  			So(err, ShouldBeNil)
  1231  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1232  
  1233  			expectedCves = []string{"CVE3", "CVE34"}
  1234  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1235  
  1236  			for _, cve := range cveResult.CVEList {
  1237  				So(expectedCves, ShouldContain, *cve.ID)
  1238  			}
  1239  
  1240  			// test searching CVE by id in results - no matches
  1241  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE100", "", "", log)
  1242  			So(err, ShouldBeNil)
  1243  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1244  			So(len(cveResult.CVEList), ShouldEqual, 0)
  1245  
  1246  			// test searching CVE by id in results - partial name
  1247  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "VE3", "", "", log)
  1248  			So(err, ShouldBeNil)
  1249  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1250  
  1251  			expectedCves = []string{"CVE3", "CVE34"}
  1252  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1253  
  1254  			for _, cve := range cveResult.CVEList {
  1255  				So(expectedCves, ShouldContain, *cve.ID)
  1256  			}
  1257  
  1258  			// test searching CVE by title in results
  1259  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Title CVE", "", "", log)
  1260  			So(err, ShouldBeNil)
  1261  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1262  
  1263  			expectedCves = []string{"CVE1", "CVE2", "CVE3"}
  1264  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1265  
  1266  			for _, cve := range cveResult.CVEList {
  1267  				So(expectedCves, ShouldContain, *cve.ID)
  1268  			}
  1269  
  1270  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, pageInput, "", "", "", log)
  1271  			So(err, ShouldBeNil)
  1272  			So(*cveResult.Tag, ShouldEqual, "1.0.1")
  1273  
  1274  			expectedCves = []string{"CVE2", "CVE3"}
  1275  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1276  
  1277  			for _, cve := range cveResult.CVEList {
  1278  				So(expectedCves, ShouldContain, *cve.ID)
  1279  			}
  1280  
  1281  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", "", "", log)
  1282  			So(err, ShouldBeNil)
  1283  			So(*cveResult.Tag, ShouldEqual, "1.1.0")
  1284  
  1285  			expectedCves = []string{"CVE3"}
  1286  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1287  
  1288  			for _, cve := range cveResult.CVEList {
  1289  				So(expectedCves, ShouldContain, *cve.ID)
  1290  			}
  1291  		})
  1292  
  1293  		Convey("Unpaginated request to get all CVEs in an image excluding some", func() {
  1294  			pageInput := &gql_generated.PageInput{
  1295  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
  1296  			}
  1297  
  1298  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1299  				graphql.DefaultRecover)
  1300  
  1301  			cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Title CVE",
  1302  				"Title CVE2", "", log)
  1303  			So(err, ShouldBeNil)
  1304  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1305  
  1306  			expectedCves := []string{"CVE1", "CVE3"}
  1307  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1308  
  1309  			for _, cve := range cveResult.CVEList {
  1310  				So(expectedCves, ShouldContain, *cve.ID)
  1311  			}
  1312  
  1313  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Description",
  1314  				"Description CVE2", "", log)
  1315  			So(err, ShouldBeNil)
  1316  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1317  
  1318  			expectedCves = []string{"CVE1", "CVE3", "CVE34"}
  1319  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1320  
  1321  			for _, cve := range cveResult.CVEList {
  1322  				So(expectedCves, ShouldContain, *cve.ID)
  1323  			}
  1324  		})
  1325  
  1326  		Convey("Unpaginated request to get all CVEs in an image filtered by severity", func() {
  1327  			pageInput := &gql_generated.PageInput{
  1328  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
  1329  			}
  1330  
  1331  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1332  				graphql.DefaultRecover)
  1333  
  1334  			cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "",
  1335  				"", "HIGH", log)
  1336  			So(err, ShouldBeNil)
  1337  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1338  
  1339  			expectedCves := []string{"CVE1"}
  1340  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1341  
  1342  			for _, cve := range cveResult.CVEList {
  1343  				So(expectedCves, ShouldContain, *cve.ID)
  1344  			}
  1345  
  1346  			cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Description",
  1347  				"Description CVE2", "LOW", log)
  1348  			So(err, ShouldBeNil)
  1349  			So(*cveResult.Tag, ShouldEqual, "1.0.0")
  1350  
  1351  			expectedCves = []string{"CVE3", "CVE34"}
  1352  			So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
  1353  
  1354  			for _, cve := range cveResult.CVEList {
  1355  				So(expectedCves, ShouldContain, *cve.ID)
  1356  			}
  1357  		})
  1358  
  1359  		Convey("paginated fail", func() {
  1360  			pageInput := &gql_generated.PageInput{
  1361  				Limit: ref(-1),
  1362  			}
  1363  
  1364  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1365  				graphql.DefaultRecover)
  1366  
  1367  			_, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", "", "", log)
  1368  			So(err, ShouldNotBeNil)
  1369  		})
  1370  	})
  1371  
  1372  	Convey("Get a list of images affected by a particular CVE ", t, func() {
  1373  		Convey("Unpaginated request", func() {
  1374  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1375  				graphql.DefaultRecover)
  1376  
  1377  			images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, nil, metaDB, log)
  1378  			So(err, ShouldBeNil)
  1379  
  1380  			expectedImages := []string{
  1381  				"repo1:1.0.0",
  1382  				"repo2:2.0.0",
  1383  			}
  1384  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1385  
  1386  			for _, image := range images.Results {
  1387  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1388  			}
  1389  
  1390  			images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, nil, metaDB, log)
  1391  			So(err, ShouldBeNil)
  1392  
  1393  			expectedImages = []string{
  1394  				"repo1:1.0.0", "repo1:1.0.1",
  1395  				"repo2:2.0.0", "repo2:2.0.1",
  1396  				"repo3:3.0.1",
  1397  			}
  1398  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1399  
  1400  			for _, image := range images.Results {
  1401  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1402  			}
  1403  
  1404  			images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, nil, metaDB, log)
  1405  			So(err, ShouldBeNil)
  1406  
  1407  			expectedImages = []string{
  1408  				"repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
  1409  				"repo2:2.0.0", "repo2:2.0.1", "repo2:2.1.0", "repo2:latest",
  1410  				"repo3:3.0.1", "repo3:3.1.0", "repo3:latest",
  1411  			}
  1412  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1413  
  1414  			for _, image := range images.Results {
  1415  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1416  			}
  1417  		})
  1418  
  1419  		Convey("paginated fail", func() {
  1420  			pageInput := &gql_generated.PageInput{
  1421  				Limit: ref(-1),
  1422  			}
  1423  
  1424  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1425  				graphql.DefaultRecover)
  1426  
  1427  			_, err = getImageListForCVE(responseContext, "repo1:1.1.0", cveInfo, &gql_generated.Filter{},
  1428  				pageInput, mocks.MetaDBMock{}, log)
  1429  			So(err, ShouldNotBeNil)
  1430  		})
  1431  
  1432  		Convey("context done", func() {
  1433  			pageInput := getGQLPageInput(1, 0)
  1434  
  1435  			ctx, cancel := context.WithCancel(ctx)
  1436  			cancel()
  1437  
  1438  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1439  				graphql.DefaultRecover)
  1440  
  1441  			canceledScanner := scanner
  1442  
  1443  			canceledScanner.ScanImageFn = func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
  1444  				return nil, ctx.Err()
  1445  			}
  1446  
  1447  			cveInfo.Scanner = canceledScanner
  1448  
  1449  			defer func() {
  1450  				cveInfo.Scanner = scanner
  1451  			}()
  1452  
  1453  			_, err = getImageListForCVE(responseContext, "repo1:1.1.0", cveInfo, &gql_generated.Filter{},
  1454  				pageInput, metaDB, log)
  1455  			So(err, ShouldEqual, ctx.Err())
  1456  		})
  1457  
  1458  		Convey("Paginated requests", func() {
  1459  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1460  				graphql.DefaultRecover,
  1461  			)
  1462  
  1463  			pageInput := getGQLPageInput(1, 0)
  1464  
  1465  			images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1466  			So(err, ShouldBeNil)
  1467  
  1468  			expectedImages := []string{
  1469  				"repo1:1.0.0",
  1470  			}
  1471  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1472  
  1473  			for _, image := range images.Results {
  1474  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1475  			}
  1476  
  1477  			pageInput = getGQLPageInput(1, 1)
  1478  
  1479  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1480  			So(err, ShouldBeNil)
  1481  
  1482  			expectedImages = []string{
  1483  				"repo2:2.0.0",
  1484  			}
  1485  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1486  
  1487  			for _, image := range images.Results {
  1488  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1489  			}
  1490  
  1491  			pageInput = getGQLPageInput(1, 2)
  1492  
  1493  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1494  			So(err, ShouldBeNil)
  1495  			So(len(images.Results), ShouldEqual, 0)
  1496  
  1497  			pageInput = getGQLPageInput(1, 5)
  1498  
  1499  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1500  			So(err, ShouldBeNil)
  1501  			So(len(images.Results), ShouldEqual, 0)
  1502  
  1503  			pageInput = getGQLPageInput(2, 0)
  1504  
  1505  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1506  			So(err, ShouldBeNil)
  1507  
  1508  			expectedImages = []string{
  1509  				"repo1:1.0.0",
  1510  				"repo2:2.0.0",
  1511  			}
  1512  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1513  
  1514  			for _, image := range images.Results {
  1515  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1516  			}
  1517  
  1518  			pageInput = getGQLPageInput(5, 0)
  1519  
  1520  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1521  			So(err, ShouldBeNil)
  1522  
  1523  			expectedImages = []string{
  1524  				"repo1:1.0.0",
  1525  				"repo2:2.0.0",
  1526  			}
  1527  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1528  
  1529  			for _, image := range images.Results {
  1530  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1531  			}
  1532  
  1533  			pageInput = getGQLPageInput(5, 1)
  1534  
  1535  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1536  			So(err, ShouldBeNil)
  1537  
  1538  			expectedImages = []string{
  1539  				"repo2:2.0.0",
  1540  			}
  1541  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1542  
  1543  			for _, image := range images.Results {
  1544  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1545  			}
  1546  
  1547  			pageInput = getGQLPageInput(5, 2)
  1548  
  1549  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1550  			So(err, ShouldBeNil)
  1551  			So(len(images.Results), ShouldEqual, 0)
  1552  
  1553  			pageInput = getGQLPageInput(5, 5)
  1554  
  1555  			images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
  1556  			So(err, ShouldBeNil)
  1557  			So(len(images.Results), ShouldEqual, 0)
  1558  
  1559  			pageInput = getGQLPageInput(5, 0)
  1560  
  1561  			images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log)
  1562  			So(err, ShouldBeNil)
  1563  
  1564  			expectedImages = []string{
  1565  				"repo1:1.0.0", "repo1:1.0.1",
  1566  				"repo2:2.0.0", "repo2:2.0.1",
  1567  				"repo3:3.0.1",
  1568  			}
  1569  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1570  
  1571  			for _, image := range images.Results {
  1572  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1573  			}
  1574  
  1575  			pageInput = getGQLPageInput(5, 3)
  1576  
  1577  			images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log)
  1578  			So(err, ShouldBeNil)
  1579  
  1580  			expectedImages = []string{
  1581  				"repo2:2.0.1",
  1582  				"repo3:3.0.1",
  1583  			}
  1584  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1585  
  1586  			for _, image := range images.Results {
  1587  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1588  			}
  1589  
  1590  			pageInput = getGQLPageInput(5, 0)
  1591  
  1592  			images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
  1593  			So(err, ShouldBeNil)
  1594  
  1595  			expectedImages = []string{
  1596  				"repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
  1597  				"repo2:2.0.0",
  1598  			}
  1599  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1600  
  1601  			for _, image := range images.Results {
  1602  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1603  			}
  1604  
  1605  			pageInput = getGQLPageInput(5, 5)
  1606  
  1607  			images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
  1608  			So(err, ShouldBeNil)
  1609  
  1610  			expectedImages = []string{
  1611  				"repo2:2.0.1", "repo2:2.1.0", "repo2:latest",
  1612  				"repo3:3.0.1", "repo3:3.1.0",
  1613  			}
  1614  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1615  
  1616  			for _, image := range images.Results {
  1617  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1618  			}
  1619  
  1620  			pageInput = getGQLPageInput(5, 10)
  1621  
  1622  			images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
  1623  			So(err, ShouldBeNil)
  1624  
  1625  			expectedImages = []string{
  1626  				"repo3:latest",
  1627  			}
  1628  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1629  
  1630  			for _, image := range images.Results {
  1631  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1632  			}
  1633  
  1634  			amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}}
  1635  			pageInput = getGQLPageInput(5, 0)
  1636  
  1637  			images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log)
  1638  			So(err, ShouldBeNil)
  1639  
  1640  			expectedImages = []string{
  1641  				"repo1:1.0.0", "repo1:1.0.1",
  1642  				"repo2:2.0.0", "repo2:2.0.1",
  1643  				"repo3:3.0.1",
  1644  			}
  1645  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1646  
  1647  			for _, image := range images.Results {
  1648  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1649  			}
  1650  
  1651  			pageInput = getGQLPageInput(2, 2)
  1652  
  1653  			images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log)
  1654  			So(err, ShouldBeNil)
  1655  
  1656  			expectedImages = []string{
  1657  				"repo2:2.0.0", "repo2:2.0.1",
  1658  			}
  1659  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1660  
  1661  			for _, image := range images.Results {
  1662  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1663  			}
  1664  		})
  1665  	})
  1666  
  1667  	Convey("Get a list of images where a particular CVE is fixed", t, func() {
  1668  		Convey("Unpaginated request", func() {
  1669  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1670  				graphql.DefaultRecover)
  1671  
  1672  			images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, nil, metaDB, log)
  1673  			So(err, ShouldBeNil)
  1674  
  1675  			expectedImages := []string{
  1676  				"repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
  1677  			}
  1678  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1679  
  1680  			for _, image := range images.Results {
  1681  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1682  			}
  1683  
  1684  			images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, nil, metaDB, log)
  1685  			So(err, ShouldBeNil)
  1686  
  1687  			expectedImages = []string{
  1688  				"repo1:1.1.0", "repo1:latest",
  1689  			}
  1690  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1691  
  1692  			for _, image := range images.Results {
  1693  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1694  			}
  1695  
  1696  			images, err = getImageListWithCVEFixed(responseContext, "CVE3", "repo1", cveInfo, nil, nil, metaDB, log)
  1697  			So(err, ShouldBeNil)
  1698  			So(len(images.Results), ShouldEqual, 0)
  1699  		})
  1700  
  1701  		Convey("paginated fail", func() {
  1702  			pageInput := &gql_generated.PageInput{
  1703  				Limit: ref(-1),
  1704  			}
  1705  
  1706  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1707  				graphql.DefaultRecover)
  1708  
  1709  			_, err = getImageListWithCVEFixed(responseContext, "cve", "repo1:1.1.0", cveInfo, &gql_generated.Filter{},
  1710  				pageInput, mocks.MetaDBMock{
  1711  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  1712  						return mTypes.RepoMeta{
  1713  							Tags: map[mTypes.Tag]mTypes.Descriptor{
  1714  								"1.1.0": {
  1715  									Digest:    godigest.FromString("str").String(),
  1716  									MediaType: ispec.MediaTypeImageManifest,
  1717  								},
  1718  							},
  1719  						}, nil
  1720  					},
  1721  				}, log)
  1722  			So(err, ShouldNotBeNil)
  1723  		})
  1724  
  1725  		Convey("context done", func() {
  1726  			ctx, cancel := context.WithCancel(ctx)
  1727  			cancel()
  1728  
  1729  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1730  				graphql.DefaultRecover)
  1731  
  1732  			_, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, nil, metaDB, log)
  1733  			So(err, ShouldNotBeNil)
  1734  		})
  1735  
  1736  		Convey("Paginated requests", func() {
  1737  			responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  1738  				graphql.DefaultRecover,
  1739  			)
  1740  
  1741  			pageInput := getGQLPageInput(1, 0)
  1742  
  1743  			images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1744  			So(err, ShouldBeNil)
  1745  
  1746  			expectedImages := []string{
  1747  				"repo1:1.0.1",
  1748  			}
  1749  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1750  
  1751  			for _, image := range images.Results {
  1752  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1753  			}
  1754  
  1755  			pageInput = getGQLPageInput(1, 1)
  1756  
  1757  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1758  			So(err, ShouldBeNil)
  1759  
  1760  			expectedImages = []string{
  1761  				"repo1:1.1.0",
  1762  			}
  1763  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1764  
  1765  			for _, image := range images.Results {
  1766  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1767  			}
  1768  
  1769  			pageInput = getGQLPageInput(1, 2)
  1770  
  1771  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1772  			So(err, ShouldBeNil)
  1773  
  1774  			expectedImages = []string{
  1775  				"repo1:latest",
  1776  			}
  1777  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1778  
  1779  			for _, image := range images.Results {
  1780  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1781  			}
  1782  
  1783  			pageInput = getGQLPageInput(1, 3)
  1784  
  1785  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1786  			So(err, ShouldBeNil)
  1787  			So(len(images.Results), ShouldEqual, 0)
  1788  
  1789  			pageInput = getGQLPageInput(1, 10)
  1790  
  1791  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1792  			So(err, ShouldBeNil)
  1793  			So(len(images.Results), ShouldEqual, 0)
  1794  
  1795  			pageInput = getGQLPageInput(2, 0)
  1796  
  1797  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1798  			So(err, ShouldBeNil)
  1799  
  1800  			expectedImages = []string{
  1801  				"repo1:1.0.1", "repo1:1.1.0",
  1802  			}
  1803  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1804  
  1805  			for _, image := range images.Results {
  1806  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1807  			}
  1808  
  1809  			pageInput = getGQLPageInput(2, 1)
  1810  
  1811  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1812  			So(err, ShouldBeNil)
  1813  
  1814  			expectedImages = []string{
  1815  				"repo1:1.1.0", "repo1:latest",
  1816  			}
  1817  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1818  
  1819  			for _, image := range images.Results {
  1820  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1821  			}
  1822  
  1823  			pageInput = getGQLPageInput(2, 2)
  1824  
  1825  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1826  			So(err, ShouldBeNil)
  1827  
  1828  			expectedImages = []string{
  1829  				"repo1:latest",
  1830  			}
  1831  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1832  
  1833  			for _, image := range images.Results {
  1834  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1835  			}
  1836  
  1837  			pageInput = getGQLPageInput(5, 0)
  1838  
  1839  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1840  			So(err, ShouldBeNil)
  1841  
  1842  			expectedImages = []string{
  1843  				"repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
  1844  			}
  1845  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1846  
  1847  			for _, image := range images.Results {
  1848  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1849  			}
  1850  
  1851  			pageInput = getGQLPageInput(5, 0)
  1852  
  1853  			images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1854  			So(err, ShouldBeNil)
  1855  
  1856  			expectedImages = []string{
  1857  				"repo1:1.1.0", "repo1:latest",
  1858  			}
  1859  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1860  
  1861  			for _, image := range images.Results {
  1862  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1863  			}
  1864  
  1865  			pageInput = getGQLPageInput(5, 2)
  1866  
  1867  			images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log)
  1868  			So(err, ShouldBeNil)
  1869  			So(len(images.Results), ShouldEqual, 0)
  1870  
  1871  			amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}}
  1872  			armFilter := &gql_generated.Filter{Arch: []*string{&ARM}}
  1873  
  1874  			pageInput = getGQLPageInput(3, 0)
  1875  
  1876  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, amdFilter, pageInput, metaDB, log)
  1877  			So(err, ShouldBeNil)
  1878  
  1879  			expectedImages = []string{"repo1:1.0.1"}
  1880  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1881  
  1882  			for _, image := range images.Results {
  1883  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1884  			}
  1885  
  1886  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log)
  1887  			So(err, ShouldBeNil)
  1888  
  1889  			expectedImages = []string{"repo1:1.1.0", "repo1:latest"}
  1890  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1891  
  1892  			for _, image := range images.Results {
  1893  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1894  			}
  1895  
  1896  			pageInput = getGQLPageInput(1, 1)
  1897  
  1898  			images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log)
  1899  			So(err, ShouldBeNil)
  1900  
  1901  			expectedImages = []string{"repo1:latest"}
  1902  			So(len(images.Results), ShouldEqual, len(expectedImages))
  1903  
  1904  			for _, image := range images.Results {
  1905  				So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
  1906  			}
  1907  		})
  1908  	})
  1909  
  1910  	Convey("Errors for cve resolvers", t, func() {
  1911  		_, err := getImageListForCVE(
  1912  			ctx,
  1913  			"id",
  1914  			mocks.CveInfoMock{
  1915  				GetImageListForCVEFn: func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) {
  1916  					return []cvemodel.TagInfo{}, ErrTestError
  1917  				},
  1918  			},
  1919  			nil,
  1920  			nil,
  1921  			mocks.MetaDBMock{
  1922  				GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
  1923  				) ([]mTypes.RepoMeta, error) {
  1924  					return []mTypes.RepoMeta{{}}, nil
  1925  				},
  1926  			},
  1927  			log,
  1928  		)
  1929  		So(err, ShouldNotBeNil)
  1930  	})
  1931  
  1932  	Convey("CVE Diff between images", t, func() {
  1933  		// image := "image:tag"
  1934  		// baseImage := "base:basetag"
  1935  		ctx := context.Background()
  1936  		pageInput := &gql_generated.PageInput{
  1937  			SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
  1938  		}
  1939  
  1940  		boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: t.TempDir()})
  1941  		if err != nil {
  1942  			panic(err)
  1943  		}
  1944  
  1945  		metaDB, err := boltdb.New(boltDriver, log)
  1946  		if err != nil {
  1947  			panic(err)
  1948  		}
  1949  
  1950  		layer1 := []byte{10, 20, 30}
  1951  		layer2 := []byte{11, 21, 31}
  1952  		layer3 := []byte{12, 22, 23}
  1953  
  1954  		otherImage := CreateImageWith().LayerBlobs([][]byte{
  1955  			layer1,
  1956  		}).DefaultConfig().Build()
  1957  
  1958  		baseImage := CreateImageWith().LayerBlobs([][]byte{
  1959  			layer1,
  1960  			layer2,
  1961  		}).PlatformConfig("testArch", "testOs").Build()
  1962  
  1963  		image := CreateImageWith().LayerBlobs([][]byte{
  1964  			layer1,
  1965  			layer2,
  1966  			layer3,
  1967  		}).PlatformConfig("testArch", "testOs").Build()
  1968  
  1969  		multiArchBase := CreateMultiarchWith().Images([]Image{baseImage, CreateRandomImage(), CreateRandomImage()}).
  1970  			Build()
  1971  		multiArchImage := CreateMultiarchWith().Images([]Image{image, CreateRandomImage(), CreateRandomImage()}).
  1972  			Build()
  1973  
  1974  		getCveResults := func(digestStr string) map[string]cvemodel.CVE {
  1975  			switch digestStr {
  1976  			case image.DigestStr():
  1977  				return map[string]cvemodel.CVE{
  1978  					"CVE1": {
  1979  						ID:          "CVE1",
  1980  						Severity:    "HIGH",
  1981  						Title:       "Title CVE1",
  1982  						Description: "Description CVE1",
  1983  						PackageList: []cvemodel.Package{{}},
  1984  					},
  1985  					"CVE2": {
  1986  						ID:          "CVE2",
  1987  						Severity:    "MEDIUM",
  1988  						Title:       "Title CVE2",
  1989  						Description: "Description CVE2",
  1990  						PackageList: []cvemodel.Package{{}},
  1991  					},
  1992  					"CVE3": {
  1993  						ID:          "CVE3",
  1994  						Severity:    "LOW",
  1995  						Title:       "Title CVE3",
  1996  						Description: "Description CVE3",
  1997  						PackageList: []cvemodel.Package{{}},
  1998  					},
  1999  				}
  2000  			case baseImage.DigestStr():
  2001  				return map[string]cvemodel.CVE{
  2002  					"CVE1": {
  2003  						ID:          "CVE1",
  2004  						Severity:    "HIGH",
  2005  						Title:       "Title CVE1",
  2006  						Description: "Description CVE1",
  2007  						PackageList: []cvemodel.Package{{}},
  2008  					},
  2009  					"CVE2": {
  2010  						ID:          "CVE2",
  2011  						Severity:    "MEDIUM",
  2012  						Title:       "Title CVE2",
  2013  						Description: "Description CVE2",
  2014  						PackageList: []cvemodel.Package{{}},
  2015  					},
  2016  				}
  2017  			case otherImage.DigestStr():
  2018  				return map[string]cvemodel.CVE{
  2019  					"CVE1": {
  2020  						ID:          "CVE1",
  2021  						Severity:    "HIGH",
  2022  						Title:       "Title CVE1",
  2023  						Description: "Description CVE1",
  2024  						PackageList: []cvemodel.Package{{}},
  2025  					},
  2026  				}
  2027  			}
  2028  
  2029  			// By default the image has no vulnerabilities
  2030  			return map[string]cvemodel.CVE{}
  2031  		}
  2032  
  2033  		// MetaDB loaded with initial data, now mock the scanner
  2034  		// Setup test CVE data in mock scanner
  2035  		scanner := mocks.CveScannerMock{
  2036  			ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
  2037  				repo, ref, _, _ := common.GetRepoReference(image)
  2038  
  2039  				if common.IsDigest(ref) {
  2040  					return getCveResults(ref), nil
  2041  				}
  2042  
  2043  				repoMeta, _ := metaDB.GetRepoMeta(ctx, repo)
  2044  
  2045  				if _, ok := repoMeta.Tags[ref]; !ok {
  2046  					panic("unexpected tag '" + ref + "', test might be wrong")
  2047  				}
  2048  
  2049  				return getCveResults(repoMeta.Tags[ref].Digest), nil
  2050  			},
  2051  			GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE {
  2052  				return getCveResults(digestStr)
  2053  			},
  2054  			IsResultCachedFn: func(digestStr string) bool {
  2055  				return true
  2056  			},
  2057  		}
  2058  
  2059  		cveInfo := &cveinfo.BaseCveInfo{
  2060  			Log:     log,
  2061  			Scanner: scanner,
  2062  			MetaDB:  metaDB,
  2063  		}
  2064  
  2065  		ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB,
  2066  			ociutils.Repo{
  2067  				Name: "repo",
  2068  				Images: []ociutils.RepoImage{
  2069  					{Image: otherImage, Reference: "other-image"},
  2070  					{Image: baseImage, Reference: "base-image"},
  2071  					{Image: image, Reference: "image"},
  2072  				},
  2073  			},
  2074  			ociutils.Repo{
  2075  				Name: "repo-multi",
  2076  				MultiArchImages: []ociutils.RepoMultiArchImage{
  2077  					{MultiarchImage: CreateRandomMultiarch(), Reference: "multi-rand"},
  2078  					{MultiarchImage: multiArchBase, Reference: "multi-base"},
  2079  					{MultiarchImage: multiArchImage, Reference: "multi-img"},
  2080  				},
  2081  			},
  2082  		)
  2083  		So(err, ShouldBeNil)
  2084  
  2085  		minuend := gql_generated.ImageInput{Repo: "repo", Tag: "image"}
  2086  		subtrahend := gql_generated.ImageInput{Repo: "repo", Tag: "image"}
  2087  		diffResult, err := getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2088  		So(err, ShouldBeNil)
  2089  		So(len(diffResult.CVEList), ShouldEqual, 0)
  2090  
  2091  		minuend = gql_generated.ImageInput{Repo: "repo", Tag: "image"}
  2092  		subtrahend = gql_generated.ImageInput{}
  2093  		diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2094  		So(err, ShouldBeNil)
  2095  		So(len(diffResult.CVEList), ShouldEqual, 1)
  2096  
  2097  		minuend = gql_generated.ImageInput{Repo: "repo", Tag: "base-image"}
  2098  		subtrahend = gql_generated.ImageInput{Repo: "repo", Tag: "image"}
  2099  		diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2100  		So(err, ShouldBeNil)
  2101  		So(len(diffResult.CVEList), ShouldEqual, 0)
  2102  
  2103  		minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{}}
  2104  		subtrahend = gql_generated.ImageInput{}
  2105  		_, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2106  		So(err, ShouldNotBeNil)
  2107  
  2108  		minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{
  2109  			Os:   ref("testOs"),
  2110  			Arch: ref("testArch"),
  2111  		}}
  2112  		subtrahend = gql_generated.ImageInput{}
  2113  		diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2114  		So(err, ShouldBeNil)
  2115  		So(len(diffResult.CVEList), ShouldEqual, 1)
  2116  		So(diffResult.Subtrahend.Repo, ShouldEqual, "repo-multi")
  2117  		So(diffResult.Subtrahend.Tag, ShouldEqual, "multi-base")
  2118  		So(dderef(diffResult.Subtrahend.Platform.Os), ShouldResemble, "testOs")
  2119  		So(dderef(diffResult.Subtrahend.Platform.Arch), ShouldResemble, "testArch")
  2120  
  2121  		minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{
  2122  			Os:   ref("testOs"),
  2123  			Arch: ref("testArch"),
  2124  		}}
  2125  		subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{
  2126  			Os:   ref("testOs"),
  2127  			Arch: ref("testArch"),
  2128  		}}
  2129  		diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2130  		So(err, ShouldBeNil)
  2131  		So(len(diffResult.CVEList), ShouldEqual, 1)
  2132  
  2133  		minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{
  2134  			Os:   ref("testOs"),
  2135  			Arch: ref("testArch"),
  2136  		}}
  2137  		subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{}}
  2138  		_, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log)
  2139  		So(err, ShouldNotBeNil)
  2140  	})
  2141  
  2142  	Convey("CVE Diff Errors", t, func() {
  2143  		ctx := context.Background()
  2144  		metaDB := mocks.MetaDBMock{}
  2145  		cveInfo := mocks.CveInfoMock{}
  2146  		emptyImage := gql_generated.ImageInput{}
  2147  
  2148  		Convey("minuend is empty", func() {
  2149  			_, err := getCVEDiffListForImages(ctx, emptyImage, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
  2150  			So(err, ShouldNotBeNil)
  2151  		})
  2152  
  2153  		Convey("no ", func() {
  2154  			minuend := gql_generated.ImageInput{Repo: "repo", Tag: "bad-tag"}
  2155  			_, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
  2156  			So(err, ShouldNotBeNil)
  2157  		})
  2158  
  2159  		Convey("getImageSummary for subtrahend errors", func() {
  2160  			metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2161  				return mTypes.RepoMeta{}, ErrTestError
  2162  			}
  2163  			minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"}
  2164  			_, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
  2165  			So(err, ShouldNotBeNil)
  2166  
  2167  			metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2168  				return mTypes.RepoMeta{}, zerr.ErrRepoMetaNotFound
  2169  			}
  2170  			minuend = gql_generated.ImageInput{Repo: "test", Tag: "img"}
  2171  			_, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
  2172  			So(err, ShouldNotBeNil)
  2173  		})
  2174  
  2175  		Convey("FilterTags for subtrahend errors", func() {
  2176  			metaDB.FilterTagsFn = func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2177  			) ([]mTypes.FullImageMeta, error) {
  2178  				return nil, ErrTestError
  2179  			}
  2180  			minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"}
  2181  			_, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
  2182  			So(err, ShouldNotBeNil)
  2183  		})
  2184  
  2185  		Convey("GetCVEDiffListForImages errors", func() {
  2186  			cveInfo.GetCVEDiffListForImagesFn = func(ctx context.Context, minuend, subtrahend, searchedCVE, excluded string,
  2187  				pageInput cvemodel.PageInput,
  2188  			) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) {
  2189  				return nil, cvemodel.ImageCVESummary{}, common.PageInfo{}, ErrTestError
  2190  			}
  2191  			minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"}
  2192  			subtrahend := gql_generated.ImageInput{Repo: "sub", Tag: "img"}
  2193  			_, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log)
  2194  			So(err, ShouldNotBeNil)
  2195  		})
  2196  	})
  2197  }
  2198  
  2199  func TestMockedDerivedImageList(t *testing.T) {
  2200  	Convey("MetaDB FilterTags error", t, func() {
  2201  		log := log.NewLogger("debug", "/dev/null")
  2202  
  2203  		image := CreateRandomImage()
  2204  		mockMetaDB := mocks.MetaDBMock{
  2205  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2206  			) ([]mTypes.FullImageMeta, error) {
  2207  				return []mTypes.FullImageMeta{}, ErrTestError
  2208  			},
  2209  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2210  				return mTypes.RepoMeta{}, ErrTestError
  2211  			},
  2212  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2213  				return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil
  2214  			},
  2215  		}
  2216  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2217  			graphql.DefaultRecover)
  2218  
  2219  		mockCve := mocks.CveInfoMock{}
  2220  		images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &gql_generated.PageInput{},
  2221  			mockCve, log)
  2222  		So(err, ShouldNotBeNil)
  2223  		So(images.Results, ShouldBeEmpty)
  2224  	})
  2225  
  2226  	Convey("paginated fail", t, func() {
  2227  		log := log.NewLogger("debug", "/dev/null")
  2228  		image := CreateRandomImage()
  2229  		pageInput := &gql_generated.PageInput{
  2230  			Limit: ref(-1),
  2231  		}
  2232  
  2233  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2234  			graphql.DefaultRecover)
  2235  
  2236  		_, err := derivedImageList(responseContext, "repo1:1.0.1", nil,
  2237  			mocks.MetaDBMock{
  2238  				GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2239  					return mTypes.RepoMeta{
  2240  						Tags: map[mTypes.Tag]mTypes.Descriptor{
  2241  							"1.0.1": {
  2242  								Digest:    image.DigestStr(),
  2243  								MediaType: ispec.MediaTypeImageManifest,
  2244  							},
  2245  						},
  2246  					}, nil
  2247  				},
  2248  				FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2249  					return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil
  2250  				},
  2251  			},
  2252  			pageInput,
  2253  			mocks.CveInfoMock{}, log)
  2254  		So(err, ShouldNotBeNil)
  2255  	})
  2256  
  2257  	//nolint: dupl
  2258  	Convey("MetaDB FilterTags no repo available", t, func() {
  2259  		log := log.NewLogger("debug", "/dev/null")
  2260  		image := CreateDefaultImage()
  2261  
  2262  		mockMetaDB := mocks.MetaDBMock{
  2263  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2264  			) ([]mTypes.FullImageMeta, error) {
  2265  				return []mTypes.FullImageMeta{}, nil
  2266  			},
  2267  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2268  				return mTypes.RepoMeta{
  2269  					Name: "repo1",
  2270  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2271  						"1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2272  					},
  2273  				}, nil
  2274  			},
  2275  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2276  				return map[string]mTypes.ImageMeta{
  2277  					digests[0]: image.AsImageMeta(),
  2278  				}, nil
  2279  			},
  2280  			GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
  2281  				return image.AsImageMeta(), nil
  2282  			},
  2283  		}
  2284  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2285  			graphql.DefaultRecover)
  2286  
  2287  		mockCve := mocks.CveInfoMock{}
  2288  		images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &gql_generated.PageInput{},
  2289  			mockCve, log)
  2290  		So(err, ShouldBeNil)
  2291  		So(images.Results, ShouldBeEmpty)
  2292  	})
  2293  
  2294  	//nolint: dupl
  2295  	Convey("derived image list working", t, func() {
  2296  		log := log.NewLogger("debug", "/dev/null")
  2297  		layer1 := []byte{10, 11, 10, 11}
  2298  		layer2 := []byte{11, 11, 11, 11}
  2299  		layer3 := []byte{10, 10, 10, 11}
  2300  		layer4 := []byte{13, 14, 15, 11}
  2301  
  2302  		image := CreateImageWith().
  2303  			LayerBlobs([][]byte{
  2304  				layer1,
  2305  				layer2,
  2306  				layer3,
  2307  			}).DefaultConfig().Build()
  2308  
  2309  		derivedImage := CreateImageWith().
  2310  			LayerBlobs([][]byte{
  2311  				layer1,
  2312  				layer2,
  2313  				layer3,
  2314  				layer4,
  2315  			}).DefaultConfig().Build()
  2316  
  2317  		imageMetaMap := map[string]mTypes.ImageMeta{
  2318  			image.DigestStr():        image.AsImageMeta(),
  2319  			derivedImage.DigestStr(): derivedImage.AsImageMeta(),
  2320  		}
  2321  
  2322  		mockMetaDB := mocks.MetaDBMock{
  2323  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2324  				return mTypes.RepoMeta{
  2325  					Name: "repo1",
  2326  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2327  						"1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2328  					},
  2329  				}, nil
  2330  			},
  2331  			GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
  2332  				return imageMetaMap[digest.String()], nil
  2333  			},
  2334  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2335  				result := map[string]mTypes.ImageMeta{}
  2336  
  2337  				for _, digest := range digests {
  2338  					result[digest] = imageMetaMap[digest]
  2339  				}
  2340  
  2341  				return result, nil
  2342  			},
  2343  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2344  			) ([]mTypes.FullImageMeta, error) {
  2345  				fullImageMetaList := []mTypes.FullImageMeta{}
  2346  				repos := []mTypes.RepoMeta{{
  2347  					Name: "repo1",
  2348  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2349  						"1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2350  						"1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2351  						"1.0.3": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2352  					},
  2353  				}}
  2354  
  2355  				for _, repo := range repos {
  2356  					for tag, descriptor := range repo.Tags {
  2357  						if filterFunc(repo, imageMetaMap[descriptor.Digest]) {
  2358  							fullImageMetaList = append(fullImageMetaList,
  2359  								convert.GetFullImageMeta(tag, repo, imageMetaMap[descriptor.Digest]))
  2360  						}
  2361  					}
  2362  				}
  2363  
  2364  				return fullImageMetaList, nil
  2365  			},
  2366  		}
  2367  
  2368  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2369  			graphql.DefaultRecover)
  2370  
  2371  		mockCve := mocks.CveInfoMock{}
  2372  
  2373  		Convey("valid derivedImageList, results not affected by pageInput", func() {
  2374  			images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &gql_generated.PageInput{},
  2375  				mockCve, log)
  2376  			So(err, ShouldBeNil)
  2377  			So(images.Results, ShouldNotBeEmpty)
  2378  			So(len(images.Results), ShouldEqual, 2)
  2379  		})
  2380  
  2381  		Convey("valid derivedImageList, results affected by pageInput", func() {
  2382  			pageInput := gql_generated.PageInput{
  2383  				Limit:  ref(1),
  2384  				Offset: ref(0),
  2385  				SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc),
  2386  			}
  2387  
  2388  			images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &pageInput,
  2389  				mockCve, log)
  2390  			So(err, ShouldBeNil)
  2391  			So(images.Results, ShouldNotBeEmpty)
  2392  			So(len(images.Results), ShouldEqual, 1)
  2393  		})
  2394  	})
  2395  }
  2396  
  2397  func TestMockedBaseImageList(t *testing.T) {
  2398  	Convey("MetaDB FilterTags error", t, func() {
  2399  		mockMetaDB := mocks.MetaDBMock{
  2400  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2401  			) ([]mTypes.FullImageMeta, error) {
  2402  				return []mTypes.FullImageMeta{}, ErrTestError
  2403  			},
  2404  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2405  				return mTypes.RepoMeta{}, ErrTestError
  2406  			},
  2407  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2408  				return map[string]mTypes.ImageMeta{}, ErrTestError
  2409  			},
  2410  		}
  2411  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2412  			graphql.DefaultRecover)
  2413  
  2414  		mockCve := mocks.CveInfoMock{}
  2415  		images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, &gql_generated.PageInput{},
  2416  			mockCve, log.NewLogger("debug", ""))
  2417  		So(err, ShouldNotBeNil)
  2418  		So(images.Results, ShouldBeEmpty)
  2419  	})
  2420  
  2421  	Convey("paginated fail", t, func() {
  2422  		image := CreateDefaultImage()
  2423  		pageInput := &gql_generated.PageInput{Limit: ref(-1)}
  2424  
  2425  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2426  			graphql.DefaultRecover)
  2427  		_, err := baseImageList(responseContext, "repo1:1.0.2", nil,
  2428  			mocks.MetaDBMock{
  2429  				GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2430  					return mTypes.RepoMeta{
  2431  						Tags: map[mTypes.Tag]mTypes.Descriptor{
  2432  							"1.0.2": {
  2433  								Digest:    image.DigestStr(),
  2434  								MediaType: ispec.MediaTypeImageManifest,
  2435  							},
  2436  						},
  2437  					}, nil
  2438  				},
  2439  				FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2440  					return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil
  2441  				},
  2442  			},
  2443  			pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
  2444  		So(err, ShouldNotBeNil)
  2445  	})
  2446  
  2447  	//nolint: dupl
  2448  	Convey("MetaDB FilterTags no repo available", t, func() {
  2449  		image := CreateDefaultImage()
  2450  
  2451  		mockMetaDB := mocks.MetaDBMock{
  2452  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2453  			) ([]mTypes.FullImageMeta, error) {
  2454  				return []mTypes.FullImageMeta{}, nil
  2455  			},
  2456  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2457  				return mTypes.RepoMeta{
  2458  					Name: "repo1",
  2459  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2460  						"1.0.2": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2461  					},
  2462  				}, nil
  2463  			},
  2464  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2465  				return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil
  2466  			},
  2467  		}
  2468  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2469  			graphql.DefaultRecover)
  2470  
  2471  		mockCve := mocks.CveInfoMock{}
  2472  		images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, &gql_generated.PageInput{},
  2473  			mockCve, log.NewLogger("debug", ""))
  2474  		So(err, ShouldBeNil)
  2475  		So(images.Results, ShouldBeEmpty)
  2476  	})
  2477  
  2478  	//nolint: dupl
  2479  	Convey("base image list working", t, func() {
  2480  		layer1 := []byte{10, 11, 10, 11}
  2481  		layer2 := []byte{11, 11, 11, 11}
  2482  		layer3 := []byte{10, 10, 10, 11}
  2483  		layer4 := []byte{13, 14, 15, 11}
  2484  
  2485  		image := CreateImageWith().
  2486  			LayerBlobs([][]byte{
  2487  				layer1,
  2488  				layer2,
  2489  				layer3,
  2490  			}).DefaultConfig().Build()
  2491  
  2492  		derivedImage := CreateImageWith().
  2493  			LayerBlobs([][]byte{
  2494  				layer1,
  2495  				layer2,
  2496  				layer3,
  2497  				layer4,
  2498  			}).DefaultConfig().Build()
  2499  
  2500  		imageMetaMap := map[string]mTypes.ImageMeta{
  2501  			image.DigestStr():        image.AsImageMeta(),
  2502  			derivedImage.DigestStr(): derivedImage.AsImageMeta(),
  2503  		}
  2504  
  2505  		mockMetaDB := mocks.MetaDBMock{
  2506  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2507  				return mTypes.RepoMeta{
  2508  					Name: "repo1",
  2509  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2510  						"1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2511  					},
  2512  				}, nil
  2513  			},
  2514  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2515  				return imageMetaMap, nil
  2516  			},
  2517  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2518  			) ([]mTypes.FullImageMeta, error) {
  2519  				fullImageMetaList := []mTypes.FullImageMeta{}
  2520  				repos := []mTypes.RepoMeta{{
  2521  					Name: "repo1",
  2522  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2523  						"1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2524  						"1.0.3": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2525  						"1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2526  					},
  2527  				}}
  2528  
  2529  				for _, repo := range repos {
  2530  					for tag, descriptor := range repo.Tags {
  2531  						if filterFunc(repo, imageMetaMap[descriptor.Digest]) {
  2532  							fullImageMetaList = append(fullImageMetaList,
  2533  								convert.GetFullImageMeta(tag, repo, imageMetaMap[descriptor.Digest]))
  2534  						}
  2535  					}
  2536  				}
  2537  
  2538  				return fullImageMetaList, nil
  2539  			},
  2540  		}
  2541  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2542  			graphql.DefaultRecover)
  2543  
  2544  		mockCve := mocks.CveInfoMock{}
  2545  
  2546  		Convey("valid baseImageList, results not affected by pageInput", func() {
  2547  			images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB,
  2548  				&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
  2549  			So(err, ShouldBeNil)
  2550  			So(images.Results, ShouldNotBeEmpty)
  2551  			So(len(images.Results), ShouldEqual, 2)
  2552  			expectedTags := []string{"1.0.1", "1.0.3"}
  2553  			So(expectedTags, ShouldContain, *images.Results[0].Tag)
  2554  			So(expectedTags, ShouldContain, *images.Results[1].Tag)
  2555  		})
  2556  
  2557  		Convey("valid baseImageList, results affected by pageInput", func() {
  2558  			limit := 1
  2559  			offset := 0
  2560  			sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
  2561  			pageInput := gql_generated.PageInput{
  2562  				Limit:  &limit,
  2563  				Offset: &offset,
  2564  				SortBy: &sortCriteria,
  2565  			}
  2566  
  2567  			images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB,
  2568  				&pageInput, mockCve, log.NewLogger("debug", ""))
  2569  			So(err, ShouldBeNil)
  2570  			So(images.Results, ShouldNotBeEmpty)
  2571  			So(len(images.Results), ShouldEqual, limit)
  2572  			So(*images.Results[0].Tag, ShouldEqual, "1.0.1")
  2573  		})
  2574  	})
  2575  
  2576  	//nolint: dupl
  2577  	Convey("filterTags working, no base image list found", t, func() {
  2578  		layer1 := []byte{10, 11, 10, 11}
  2579  		layer2 := []byte{11, 11, 11, 11}
  2580  		layer3 := []byte{10, 10, 10, 11}
  2581  		layer4 := []byte{13, 14, 15, 11}
  2582  
  2583  		image := CreateImageWith().
  2584  			LayerBlobs([][]byte{
  2585  				layer1,
  2586  				layer2,
  2587  				layer3,
  2588  			}).DefaultConfig().Build()
  2589  
  2590  		derivedImage := CreateImageWith().
  2591  			LayerBlobs([][]byte{
  2592  				layer4,
  2593  			}).DefaultConfig().Build()
  2594  
  2595  		imageMetaMap := map[string]mTypes.ImageMeta{
  2596  			image.DigestStr():        image.AsImageMeta(),
  2597  			derivedImage.DigestStr(): derivedImage.AsImageMeta(),
  2598  		}
  2599  
  2600  		mockMetaDB := mocks.MetaDBMock{
  2601  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2602  				return mTypes.RepoMeta{
  2603  					Name: "repo1",
  2604  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2605  						"1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2606  					},
  2607  				}, nil
  2608  			},
  2609  			FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) {
  2610  				return imageMetaMap, nil
  2611  			},
  2612  			FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc,
  2613  			) ([]mTypes.FullImageMeta, error) {
  2614  				fullImageMetaList := []mTypes.FullImageMeta{}
  2615  				repos := []mTypes.RepoMeta{{
  2616  					Name: "repo1",
  2617  					Tags: map[mTypes.Tag]mTypes.Descriptor{
  2618  						"1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2619  						"1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest},
  2620  					},
  2621  				}}
  2622  
  2623  				for _, repo := range repos {
  2624  					for tag, descriptor := range repo.Tags {
  2625  						if filterFunc(repo, imageMetaMap[descriptor.Digest]) {
  2626  							fullImageMetaList = append(fullImageMetaList,
  2627  								convert.GetFullImageMeta(tag, repo, imageMetaMap[descriptor.Digest]))
  2628  						}
  2629  					}
  2630  				}
  2631  
  2632  				return fullImageMetaList, nil
  2633  			},
  2634  		}
  2635  		responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
  2636  			graphql.DefaultRecover)
  2637  
  2638  		mockCve := mocks.CveInfoMock{}
  2639  		images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, &gql_generated.PageInput{},
  2640  			mockCve, log.NewLogger("debug", ""))
  2641  		So(err, ShouldBeNil)
  2642  		So(images.Results, ShouldBeEmpty)
  2643  	})
  2644  }
  2645  
  2646  func TestExpandedRepoInfoErrors(t *testing.T) {
  2647  	log := log.NewLogger("debug", "")
  2648  
  2649  	Convey("Access error", t, func() {
  2650  		userAc := reqCtx.NewUserAccessControl()
  2651  		userAc.SetUsername("user")
  2652  		userAc.SetGlobPatterns("read", map[string]bool{
  2653  			"repo": false,
  2654  		})
  2655  
  2656  		ctx := userAc.DeriveContext(context.Background())
  2657  
  2658  		responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
  2659  			graphql.DefaultRecover)
  2660  
  2661  		_, err := expandedRepoInfo(responseContext, "repo", mocks.MetaDBMock{}, mocks.CveInfoMock{}, log)
  2662  		So(err, ShouldBeNil)
  2663  	})
  2664  }
  2665  
  2666  func TestUtils(t *testing.T) {
  2667  	Convey("utils", t, func() {
  2668  		Convey("", func() {
  2669  			So(isMatchingPlatform(ispec.Platform{OS: "test"}, gql_generated.PlatformInput{Os: ref("t")}), ShouldBeFalse)
  2670  			So(getArch(ispec.Platform{OS: "t", Architecture: "e", Variant: "st"}), ShouldResemble, "e/st")
  2671  		})
  2672  
  2673  		Convey("checkImageInput", func() {
  2674  			_, err := resolveImageData(context.Background(), gql_generated.ImageInput{Repo: "test"}, mocks.MetaDBMock{})
  2675  			So(err, ShouldNotBeNil)
  2676  		})
  2677  
  2678  		Convey("checkImageInput can't find index data", func() {
  2679  			_, err := resolveImageData(context.Background(), gql_generated.ImageInput{
  2680  				Repo: "test", Tag: "test", Digest: ref("dig"),
  2681  			},
  2682  				mocks.MetaDBMock{
  2683  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2684  						return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{
  2685  							"test": {MediaType: ispec.MediaTypeImageIndex},
  2686  						}}, nil
  2687  					},
  2688  					GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
  2689  						return mTypes.ImageMeta{}, ErrTestError
  2690  					},
  2691  				})
  2692  			So(err, ShouldNotBeNil)
  2693  		})
  2694  		Convey("checkImageInput image meta not found", func() {
  2695  			_, err := resolveImageData(context.Background(), gql_generated.ImageInput{
  2696  				Repo: "test", Tag: "test", Digest: ref("dig"),
  2697  			},
  2698  				mocks.MetaDBMock{
  2699  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2700  						return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{
  2701  							"test": {MediaType: ispec.MediaTypeImageIndex},
  2702  						}}, nil
  2703  					},
  2704  					GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
  2705  						return mTypes.ImageMeta{}, nil
  2706  					},
  2707  				})
  2708  			So(err, ShouldNotBeNil)
  2709  		})
  2710  		Convey("checkImageInput image meta bad media type", func() {
  2711  			_, err := resolveImageData(context.Background(), gql_generated.ImageInput{
  2712  				Repo: "test", Tag: "test", Digest: ref("dig"),
  2713  			},
  2714  				mocks.MetaDBMock{
  2715  					GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
  2716  						return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{
  2717  							"test": {MediaType: "bad-type"},
  2718  						}}, nil
  2719  					},
  2720  					GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
  2721  						return mTypes.ImageMeta{}, nil
  2722  					},
  2723  				})
  2724  			So(err, ShouldBeNil)
  2725  		})
  2726  	})
  2727  }
  2728  
  2729  func getGQLPageInput(limit int, offset int) *gql_generated.PageInput {
  2730  	sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
  2731  
  2732  	return &gql_generated.PageInput{
  2733  		Limit:  &limit,
  2734  		Offset: &offset,
  2735  		SortBy: &sortCriteria,
  2736  	}
  2737  }