zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/search/convert/convert_test.go (about)

     1  //go:build search
     2  
     3  package convert_test
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"testing"
     9  	"time"
    10  
    11  	godigest "github.com/opencontainers/go-digest"
    12  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    13  	. "github.com/smartystreets/goconvey/convey"
    14  
    15  	"zotregistry.io/zot/pkg/extensions/search/convert"
    16  	"zotregistry.io/zot/pkg/extensions/search/gql_generated"
    17  	"zotregistry.io/zot/pkg/extensions/search/pagination"
    18  	"zotregistry.io/zot/pkg/log"
    19  	"zotregistry.io/zot/pkg/meta/boltdb"
    20  	mTypes "zotregistry.io/zot/pkg/meta/types"
    21  	. "zotregistry.io/zot/pkg/test/image-utils"
    22  	"zotregistry.io/zot/pkg/test/mocks"
    23  	ociutils "zotregistry.io/zot/pkg/test/oci-utils"
    24  )
    25  
    26  var ErrTestError = errors.New("TestError")
    27  
    28  func TestUpdateLastUpdatedTimestamp(t *testing.T) {
    29  	Convey("Image summary is the first image checked for the repo", t, func() {
    30  		before := time.Time{}
    31  		after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC)
    32  		img := convert.UpdateLastUpdatedTimestamp(
    33  			&before,
    34  			&gql_generated.ImageSummary{LastUpdated: &before},
    35  			&gql_generated.ImageSummary{LastUpdated: &after},
    36  		)
    37  
    38  		So(*img.LastUpdated, ShouldResemble, after)
    39  	})
    40  
    41  	Convey("Image summary is updated after the current latest image", t, func() {
    42  		before := time.Date(2022, time.April, 1, 11, 0, 0, 0, time.UTC)
    43  		after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC)
    44  		img := convert.UpdateLastUpdatedTimestamp(
    45  			&before,
    46  			&gql_generated.ImageSummary{LastUpdated: &before},
    47  			&gql_generated.ImageSummary{LastUpdated: &after},
    48  		)
    49  
    50  		So(*img.LastUpdated, ShouldResemble, after)
    51  	})
    52  
    53  	Convey("Image summary is updated before the current latest image", t, func() {
    54  		before := time.Date(2022, time.April, 1, 11, 0, 0, 0, time.UTC)
    55  		after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC)
    56  		img := convert.UpdateLastUpdatedTimestamp(
    57  			&after,
    58  			&gql_generated.ImageSummary{LastUpdated: &after},
    59  			&gql_generated.ImageSummary{LastUpdated: &before},
    60  		)
    61  
    62  		So(*img.LastUpdated, ShouldResemble, after)
    63  	})
    64  }
    65  
    66  func TestLabels(t *testing.T) {
    67  	Convey("Test labels", t, func() {
    68  		// Test various labels
    69  		labels := make(map[string]string)
    70  
    71  		desc := convert.GetDescription(labels)
    72  		So(desc, ShouldEqual, "")
    73  
    74  		license := convert.GetLicenses(labels)
    75  		So(license, ShouldEqual, "")
    76  
    77  		vendor := convert.GetVendor(labels)
    78  		So(vendor, ShouldEqual, "")
    79  
    80  		categories := convert.GetCategories(labels)
    81  		So(categories, ShouldEqual, "")
    82  
    83  		labels[ispec.AnnotationVendor] = "zot"
    84  		labels[ispec.AnnotationDescription] = "zot-desc"
    85  		labels[ispec.AnnotationLicenses] = "zot-license"
    86  		labels[convert.AnnotationLabels] = "zot-labels"
    87  
    88  		desc = convert.GetDescription(labels)
    89  		So(desc, ShouldEqual, "zot-desc")
    90  
    91  		license = convert.GetLicenses(labels)
    92  		So(license, ShouldEqual, "zot-license")
    93  
    94  		vendor = convert.GetVendor(labels)
    95  		So(vendor, ShouldEqual, "zot")
    96  
    97  		categories = convert.GetCategories(labels)
    98  		So(categories, ShouldEqual, "zot-labels")
    99  
   100  		labels = make(map[string]string)
   101  
   102  		// Use diff key
   103  		labels[convert.LabelAnnotationVendor] = "zot-vendor"
   104  		labels[convert.LabelAnnotationDescription] = "zot-label-desc"
   105  		labels[ispec.AnnotationLicenses] = "zot-label-license"
   106  
   107  		desc = convert.GetDescription(labels)
   108  		So(desc, ShouldEqual, "zot-label-desc")
   109  
   110  		license = convert.GetLicenses(labels)
   111  		So(license, ShouldEqual, "zot-label-license")
   112  
   113  		vendor = convert.GetVendor(labels)
   114  		So(vendor, ShouldEqual, "zot-vendor")
   115  	})
   116  }
   117  
   118  func TestGetSignaturesInfo(t *testing.T) {
   119  	Convey("Test get signatures info - cosign", t, func() {
   120  		digest := godigest.FromString("dig")
   121  		signatures := map[string]mTypes.ManifestSignatures{
   122  			digest.String(): {
   123  				"cosign": []mTypes.SignatureInfo{
   124  					{
   125  						LayersInfo: []mTypes.LayerInfo{
   126  							{
   127  								LayerContent: []byte{},
   128  								LayerDigest:  "",
   129  								SignatureKey: "",
   130  								Signer:       "author",
   131  							},
   132  						},
   133  					},
   134  				},
   135  			},
   136  		}
   137  
   138  		signaturesSummary := convert.GetSignaturesInfo(true, signatures[digest.String()])
   139  		So(signaturesSummary, ShouldNotBeEmpty)
   140  		So(*signaturesSummary[0].Author, ShouldEqual, "author")
   141  		So(*signaturesSummary[0].IsTrusted, ShouldEqual, true)
   142  		So(*signaturesSummary[0].Tool, ShouldEqual, "cosign")
   143  	})
   144  
   145  	Convey("Test get signatures info - notation", t, func() {
   146  		digest := godigest.FromString("dig")
   147  		signatures := map[string]mTypes.ManifestSignatures{
   148  			digest.String(): {
   149  				"notation": []mTypes.SignatureInfo{
   150  					{
   151  						LayersInfo: []mTypes.LayerInfo{
   152  							{
   153  								LayerContent: []byte{},
   154  								LayerDigest:  "",
   155  								SignatureKey: "",
   156  								Signer:       "author",
   157  								Date:         time.Now().AddDate(0, 0, -1),
   158  							},
   159  						},
   160  					},
   161  				},
   162  			},
   163  		}
   164  
   165  		signaturesSummary := convert.GetSignaturesInfo(true, signatures[digest.String()])
   166  		So(signaturesSummary, ShouldNotBeEmpty)
   167  		So(*signaturesSummary[0].Author, ShouldEqual, "author")
   168  		So(*signaturesSummary[0].IsTrusted, ShouldEqual, false)
   169  		So(*signaturesSummary[0].Tool, ShouldEqual, "notation")
   170  	})
   171  }
   172  
   173  func TestAcceptedByFilter(t *testing.T) {
   174  	Convey("Images", t, func() {
   175  		Convey("Os not found", func() {
   176  			found := convert.ImgSumAcceptedByFilter(
   177  				&gql_generated.ImageSummary{
   178  					Manifests: []*gql_generated.ManifestSummary{
   179  						{Platform: &gql_generated.Platform{Os: ref("os1")}},
   180  						{Platform: &gql_generated.Platform{Os: ref("os2")}},
   181  					},
   182  				},
   183  				mTypes.Filter{Os: []*string{ref("os3")}},
   184  			)
   185  
   186  			So(found, ShouldBeFalse)
   187  		})
   188  
   189  		Convey("Has to be signed ", func() {
   190  			found := convert.ImgSumAcceptedByFilter(
   191  				&gql_generated.ImageSummary{
   192  					Manifests: []*gql_generated.ManifestSummary{
   193  						{IsSigned: ref(false)},
   194  					},
   195  					IsSigned: ref(false),
   196  				},
   197  				mTypes.Filter{HasToBeSigned: ref(true)},
   198  			)
   199  
   200  			So(found, ShouldBeFalse)
   201  		})
   202  	})
   203  
   204  	Convey("Repos", t, func() {
   205  		Convey("Os not found", func() {
   206  			found := convert.RepoSumAcceptedByFilter(
   207  				&gql_generated.RepoSummary{
   208  					Platforms: []*gql_generated.Platform{
   209  						{Os: ref("os1")},
   210  						{Os: ref("os2")},
   211  					},
   212  				},
   213  				mTypes.Filter{Os: []*string{ref("os3")}},
   214  			)
   215  
   216  			So(found, ShouldBeFalse)
   217  		})
   218  
   219  		Convey("Arch not found", func() {
   220  			found := convert.RepoSumAcceptedByFilter(
   221  				&gql_generated.RepoSummary{
   222  					Platforms: []*gql_generated.Platform{
   223  						{Arch: ref("Arch")},
   224  					},
   225  				},
   226  				mTypes.Filter{Arch: []*string{ref("arch_not_found")}},
   227  			)
   228  
   229  			So(found, ShouldBeFalse)
   230  		})
   231  
   232  		Convey("Has to be signed ", func() {
   233  			found := convert.ImgSumAcceptedByFilter(
   234  				&gql_generated.ImageSummary{
   235  					Manifests: []*gql_generated.ManifestSummary{
   236  						{IsSigned: ref(false)},
   237  					},
   238  					IsSigned: ref(false),
   239  				},
   240  				mTypes.Filter{HasToBeSigned: ref(true)},
   241  			)
   242  
   243  			So(found, ShouldBeFalse)
   244  		})
   245  	})
   246  }
   247  
   248  func ref[T any](val T) *T {
   249  	ref := val
   250  
   251  	return &ref
   252  }
   253  
   254  func TestPaginatedConvert(t *testing.T) {
   255  	ctx := context.Background()
   256  
   257  	tempDir := t.TempDir()
   258  
   259  	driver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: tempDir})
   260  	if err != nil {
   261  		t.FailNow()
   262  	}
   263  
   264  	metaDB, err := boltdb.New(driver, log.NewLogger("debug", ""))
   265  	if err != nil {
   266  		t.FailNow()
   267  	}
   268  
   269  	var (
   270  		badBothImage = CreateImageWith().DefaultLayers().ImageConfig(
   271  			ispec.Image{Platform: ispec.Platform{OS: "bad-os", Architecture: "bad-arch"}}).Build()
   272  		badOsImage = CreateImageWith().DefaultLayers().ImageConfig(
   273  			ispec.Image{Platform: ispec.Platform{OS: "bad-os", Architecture: "good-arch"}}).Build()
   274  		badArchImage = CreateImageWith().DefaultLayers().ImageConfig(
   275  			ispec.Image{Platform: ispec.Platform{OS: "good-os", Architecture: "bad-arch"}}).Build()
   276  		goodImage = CreateImageWith().DefaultLayers().ImageConfig(
   277  			ispec.Image{Platform: ispec.Platform{OS: "good-os", Architecture: "good-arch"}}).Build()
   278  
   279  		randomImage1    = CreateRandomImage()
   280  		randomImage2    = CreateRandomImage()
   281  		signatureDigest = godigest.FromString("signature")
   282  
   283  		badMultiArch = CreateMultiarchWith().Images(
   284  			[]Image{badBothImage, badOsImage, badArchImage, randomImage1}).Build()
   285  		goodMultiArch = CreateMultiarchWith().Images(
   286  			[]Image{badOsImage, badArchImage, randomImage2, goodImage}).Build()
   287  	)
   288  
   289  	ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB,
   290  		ociutils.Repo{
   291  			Name: "repo1-only-images",
   292  			Images: []ociutils.RepoImage{
   293  				{Image: goodImage, Reference: "goodImage"},
   294  				{Image: badOsImage, Reference: "badOsImage"},
   295  				{Image: badArchImage, Reference: "badArchImage"},
   296  				{Image: badBothImage, Reference: "badBothImage"},
   297  			},
   298  			IsBookmarked: true,
   299  			IsStarred:    true,
   300  		},
   301  		ociutils.Repo{
   302  			Name: "repo2-only-bad-images",
   303  			Images: []ociutils.RepoImage{
   304  				{Image: randomImage1, Reference: "randomImage1"},
   305  				{Image: randomImage2, Reference: "randomImage2"},
   306  				{Image: badBothImage, Reference: "badBothImage"},
   307  			},
   308  			IsBookmarked: true,
   309  			IsStarred:    true,
   310  		},
   311  		ociutils.Repo{
   312  			Name: "repo3-only-multiarch",
   313  			MultiArchImages: []ociutils.RepoMultiArchImage{
   314  				{MultiarchImage: badMultiArch, Reference: "badMultiArch"},
   315  				{MultiarchImage: goodMultiArch, Reference: "goodMultiArch"},
   316  			},
   317  			IsBookmarked: true,
   318  			IsStarred:    true,
   319  		},
   320  		ociutils.Repo{
   321  			Name: "repo4-not-bookmarked-or-starred",
   322  			Images: []ociutils.RepoImage{
   323  				{Image: goodImage, Reference: "goodImage"},
   324  			},
   325  			MultiArchImages: []ociutils.RepoMultiArchImage{
   326  				{MultiarchImage: goodMultiArch, Reference: "goodMultiArch"},
   327  			},
   328  		},
   329  		ociutils.Repo{
   330  			Name: "repo5-signed",
   331  			Images: []ociutils.RepoImage{
   332  				{Image: goodImage, Reference: "goodImage"}, // is fake signed by the image below
   333  			},
   334  			Signatures: map[string]mTypes.ManifestSignatures{
   335  				goodImage.DigestStr(): ociutils.GetFakeSignatureInfo(signatureDigest.String()),
   336  			},
   337  		},
   338  	)
   339  	if err != nil {
   340  		t.FailNow()
   341  	}
   342  
   343  	skipCVE := convert.SkipQGLField{Vulnerabilities: true}
   344  
   345  	Convey("PaginatedRepoMeta2RepoSummaries filtering and sorting", t, func() {
   346  		// Test different combinations of the filter
   347  		repoMetaList, err := metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta)
   348  		So(err, ShouldBeNil)
   349  		imageMeta, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   350  		So(err, ShouldBeNil)
   351  
   352  		reposSum, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(
   353  			ctx, repoMetaList, imageMeta,
   354  			mTypes.Filter{
   355  				Os:           []*string{ref("good-os")},
   356  				Arch:         []*string{ref("good-arch")},
   357  				IsBookmarked: ref(true),
   358  				IsStarred:    ref(true),
   359  			},
   360  			pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE,
   361  		)
   362  		So(err, ShouldBeNil)
   363  		So(len(reposSum), ShouldEqual, 2)
   364  		So(*reposSum[0].Name, ShouldResemble, "repo1-only-images")
   365  		So(*reposSum[1].Name, ShouldResemble, "repo3-only-multiarch")
   366  		So(pageInfo.ItemCount, ShouldEqual, 2)
   367  		So(pageInfo.ItemCount, ShouldEqual, 2)
   368  		So(pageInfo.ItemCount, ShouldEqual, 2)
   369  		So(pageInfo.ItemCount, ShouldEqual, 2)
   370  
   371  		reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries(
   372  			ctx, repoMetaList, imageMeta,
   373  			mTypes.Filter{
   374  				Os:            []*string{ref("good-os")},
   375  				Arch:          []*string{ref("good-arch")},
   376  				IsBookmarked:  ref(true),
   377  				IsStarred:     ref(true),
   378  				HasToBeSigned: ref(true),
   379  			},
   380  			pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE,
   381  		)
   382  		So(err, ShouldBeNil)
   383  		So(len(reposSum), ShouldEqual, 0)
   384  		So(pageInfo.ItemCount, ShouldEqual, 0)
   385  
   386  		reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries(
   387  			ctx, repoMetaList, imageMeta,
   388  			mTypes.Filter{
   389  				HasToBeSigned: ref(true),
   390  			},
   391  			pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE,
   392  		)
   393  		So(err, ShouldBeNil)
   394  		So(len(reposSum), ShouldEqual, 1)
   395  		So(*reposSum[0].Name, ShouldResemble, "repo5-signed")
   396  		So(pageInfo.ItemCount, ShouldEqual, 1)
   397  
   398  		// no filter
   399  		reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries(
   400  			ctx, repoMetaList, imageMeta,
   401  			mTypes.Filter{}, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE,
   402  		)
   403  		So(err, ShouldBeNil)
   404  		So(len(reposSum), ShouldEqual, 5)
   405  		So(*reposSum[0].Name, ShouldResemble, "repo1-only-images")
   406  		So(*reposSum[1].Name, ShouldResemble, "repo2-only-bad-images")
   407  		So(*reposSum[2].Name, ShouldResemble, "repo3-only-multiarch")
   408  		So(*reposSum[3].Name, ShouldResemble, "repo4-not-bookmarked-or-starred")
   409  		So(*reposSum[4].Name, ShouldResemble, "repo5-signed")
   410  		So(pageInfo.ItemCount, ShouldEqual, 5)
   411  
   412  		// no filter opposite sorting
   413  		reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries(
   414  			ctx, repoMetaList, imageMeta,
   415  			mTypes.Filter{}, pagination.PageInput{SortBy: pagination.AlphabeticDsc}, mocks.CveInfoMock{}, skipCVE,
   416  		)
   417  		So(err, ShouldBeNil)
   418  		So(len(reposSum), ShouldEqual, 5)
   419  		So(*reposSum[0].Name, ShouldResemble, "repo5-signed")
   420  		So(*reposSum[1].Name, ShouldResemble, "repo4-not-bookmarked-or-starred")
   421  		So(*reposSum[2].Name, ShouldResemble, "repo3-only-multiarch")
   422  		So(*reposSum[3].Name, ShouldResemble, "repo2-only-bad-images")
   423  		So(*reposSum[4].Name, ShouldResemble, "repo1-only-images")
   424  		So(pageInfo.ItemCount, ShouldEqual, 5)
   425  
   426  		// add pagination
   427  		reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries(
   428  			ctx, repoMetaList, imageMeta,
   429  			mTypes.Filter{
   430  				Os:           []*string{ref("good-os")},
   431  				Arch:         []*string{ref("good-arch")},
   432  				IsBookmarked: ref(true),
   433  				IsStarred:    ref(true),
   434  			},
   435  			pagination.PageInput{Limit: 1, Offset: 0, SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE,
   436  		)
   437  		So(err, ShouldBeNil)
   438  		So(len(reposSum), ShouldEqual, 1)
   439  		So(*reposSum[0].Name, ShouldResemble, "repo1-only-images")
   440  		So(pageInfo.ItemCount, ShouldEqual, 1)
   441  		So(pageInfo.TotalCount, ShouldEqual, 2)
   442  
   443  		reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries(
   444  			ctx, repoMetaList, imageMeta,
   445  			mTypes.Filter{
   446  				Os:           []*string{ref("good-os")},
   447  				Arch:         []*string{ref("good-arch")},
   448  				IsBookmarked: ref(true),
   449  				IsStarred:    ref(true),
   450  			},
   451  			pagination.PageInput{Limit: 1, Offset: 1, SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE,
   452  		)
   453  		So(err, ShouldBeNil)
   454  		So(len(reposSum), ShouldEqual, 1)
   455  		So(*reposSum[0].Name, ShouldResemble, "repo3-only-multiarch")
   456  		So(pageInfo.ItemCount, ShouldEqual, 1)
   457  		So(pageInfo.TotalCount, ShouldEqual, 2)
   458  	})
   459  
   460  	Convey("PaginatedRepoMeta2ImageSummaries filtering and sorting", t, func() {
   461  		fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta)
   462  		So(err, ShouldBeNil)
   463  
   464  		imgSum, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(
   465  			ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{},
   466  			mTypes.Filter{
   467  				Os:   []*string{ref("good-os")},
   468  				Arch: []*string{ref("good-arch")},
   469  			},
   470  			pagination.PageInput{SortBy: pagination.AlphabeticAsc},
   471  		)
   472  		So(err, ShouldBeNil)
   473  		So(len(imgSum), ShouldEqual, 5)
   474  		So(*imgSum[0].RepoName, ShouldResemble, "repo1-only-images")
   475  		So(*imgSum[0].Tag, ShouldResemble, "goodImage")
   476  		So(*imgSum[1].RepoName, ShouldResemble, "repo3-only-multiarch")
   477  		So(*imgSum[1].Tag, ShouldResemble, "goodMultiArch")
   478  		So(*imgSum[2].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred")
   479  		So(*imgSum[2].Tag, ShouldResemble, "goodImage")
   480  		So(*imgSum[3].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred")
   481  		So(*imgSum[3].Tag, ShouldResemble, "goodMultiArch")
   482  		So(*imgSum[4].RepoName, ShouldResemble, "repo5-signed")
   483  		So(*imgSum[4].Tag, ShouldResemble, "goodImage")
   484  		So(pageInfo.ItemCount, ShouldEqual, 5)
   485  
   486  		// add page of size 2
   487  		imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries(
   488  			ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{},
   489  			mTypes.Filter{
   490  				Os:   []*string{ref("good-os")},
   491  				Arch: []*string{ref("good-arch")},
   492  			},
   493  			pagination.PageInput{Limit: 2, Offset: 0, SortBy: pagination.AlphabeticAsc},
   494  		)
   495  		So(err, ShouldBeNil)
   496  		So(len(imgSum), ShouldEqual, 2)
   497  		So(*imgSum[0].RepoName, ShouldResemble, "repo1-only-images")
   498  		So(*imgSum[0].Tag, ShouldResemble, "goodImage")
   499  		So(*imgSum[1].RepoName, ShouldResemble, "repo3-only-multiarch")
   500  		So(*imgSum[1].Tag, ShouldResemble, "goodMultiArch")
   501  		So(pageInfo.ItemCount, ShouldEqual, 2)
   502  		So(pageInfo.TotalCount, ShouldEqual, 5)
   503  
   504  		// next page
   505  		imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries(
   506  			ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{},
   507  			mTypes.Filter{
   508  				Os:   []*string{ref("good-os")},
   509  				Arch: []*string{ref("good-arch")},
   510  			},
   511  			pagination.PageInput{Limit: 2, Offset: 2, SortBy: pagination.AlphabeticAsc},
   512  		)
   513  		So(err, ShouldBeNil)
   514  		So(len(imgSum), ShouldEqual, 2)
   515  		So(*imgSum[0].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred")
   516  		So(*imgSum[0].Tag, ShouldResemble, "goodImage")
   517  		So(*imgSum[1].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred")
   518  		So(*imgSum[1].Tag, ShouldResemble, "goodMultiArch")
   519  		So(pageInfo.ItemCount, ShouldEqual, 2)
   520  		So(pageInfo.TotalCount, ShouldEqual, 5)
   521  
   522  		// last page
   523  		imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries(
   524  			ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{},
   525  			mTypes.Filter{
   526  				Os:   []*string{ref("good-os")},
   527  				Arch: []*string{ref("good-arch")},
   528  			},
   529  			pagination.PageInput{Limit: 2, Offset: 4, SortBy: pagination.AlphabeticAsc},
   530  		)
   531  		So(err, ShouldBeNil)
   532  		So(len(imgSum), ShouldEqual, 1)
   533  		So(*imgSum[0].RepoName, ShouldResemble, "repo5-signed")
   534  		So(*imgSum[0].Tag, ShouldResemble, "goodImage")
   535  		So(pageInfo.ItemCount, ShouldEqual, 1)
   536  		So(pageInfo.TotalCount, ShouldEqual, 5)
   537  
   538  		// has to be signed
   539  		imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries(
   540  			ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{},
   541  			mTypes.Filter{
   542  				Os:            []*string{ref("good-os")},
   543  				Arch:          []*string{ref("good-arch")},
   544  				HasToBeSigned: ref(true),
   545  			},
   546  			pagination.PageInput{SortBy: pagination.AlphabeticAsc},
   547  		)
   548  		So(err, ShouldBeNil)
   549  		So(len(imgSum), ShouldEqual, 1)
   550  		So(*imgSum[0].RepoName, ShouldResemble, "repo5-signed")
   551  		So(*imgSum[0].Tag, ShouldResemble, "goodImage")
   552  		So(pageInfo.ItemCount, ShouldEqual, 1)
   553  	})
   554  }
   555  
   556  func TestIndexAnnotations(t *testing.T) {
   557  	Convey("Test ImageIndex2ImageSummary annotations logic", t, func() {
   558  		ctx := context.Background()
   559  
   560  		tempDir := t.TempDir()
   561  
   562  		driver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: tempDir})
   563  		if err != nil {
   564  			t.FailNow()
   565  		}
   566  
   567  		metaDB, err := boltdb.New(driver, log.NewLogger("debug", ""))
   568  		So(err, ShouldBeNil)
   569  
   570  		configLabels := map[string]string{
   571  			ispec.AnnotationDescription:   "ConfigDescription",
   572  			ispec.AnnotationLicenses:      "ConfigLicenses",
   573  			ispec.AnnotationVendor:        "ConfigVendor",
   574  			ispec.AnnotationAuthors:       "ConfigAuthors",
   575  			ispec.AnnotationTitle:         "ConfigTitle",
   576  			ispec.AnnotationDocumentation: "ConfigDocumentation",
   577  			ispec.AnnotationSource:        "ConfigSource",
   578  		}
   579  
   580  		manifestAnnotations := map[string]string{
   581  			ispec.AnnotationDescription:   "ManifestDescription",
   582  			ispec.AnnotationLicenses:      "ManifestLicenses",
   583  			ispec.AnnotationVendor:        "ManifestVendor",
   584  			ispec.AnnotationAuthors:       "ManifestAuthors",
   585  			ispec.AnnotationTitle:         "ManifestTitle",
   586  			ispec.AnnotationDocumentation: "ManifestDocumentation",
   587  			ispec.AnnotationSource:        "ManifestSource",
   588  		}
   589  
   590  		indexAnnotations := map[string]string{
   591  			ispec.AnnotationDescription:   "IndexDescription",
   592  			ispec.AnnotationLicenses:      "IndexLicenses",
   593  			ispec.AnnotationVendor:        "IndexVendor",
   594  			ispec.AnnotationAuthors:       "IndexAuthors",
   595  			ispec.AnnotationTitle:         "IndexTitle",
   596  			ispec.AnnotationDocumentation: "IndexDocumentation",
   597  			ispec.AnnotationSource:        "IndexSource",
   598  		}
   599  
   600  		imageWithConfigAnnotations := CreateImageWith().DefaultLayers().
   601  			ImageConfig(ispec.Image{
   602  				Config: ispec.ImageConfig{
   603  					Labels: configLabels,
   604  				},
   605  			}).Build()
   606  
   607  		imageWithManifestAndConfigAnnotations := CreateImageWith().DefaultLayers().
   608  			ImageConfig(ispec.Image{
   609  				Config: ispec.ImageConfig{
   610  					Labels: configLabels,
   611  				},
   612  			}).Annotations(manifestAnnotations).Build()
   613  
   614  		// --------------------------------------------------------
   615  		indexWithAnnotations := CreateMultiarchWith().Images(
   616  			[]Image{imageWithManifestAndConfigAnnotations},
   617  		).Annotations(indexAnnotations).Build()
   618  
   619  		ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB,
   620  			ociutils.Repo{
   621  				Name: "repo",
   622  				MultiArchImages: []ociutils.RepoMultiArchImage{
   623  					{MultiarchImage: indexWithAnnotations, Reference: "tag"},
   624  				},
   625  			})
   626  		So(err, ShouldBeNil)
   627  
   628  		repoMeta, err := metaDB.GetRepoMeta(ctx, "repo")
   629  		So(err, ShouldBeNil)
   630  		imageMeta, err := metaDB.FilterImageMeta(ctx, []string{indexWithAnnotations.DigestStr()})
   631  		So(err, ShouldBeNil)
   632  
   633  		imageSummary, _, err := convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
   634  			imageMeta[indexWithAnnotations.DigestStr()]))
   635  		So(err, ShouldBeNil)
   636  		So(*imageSummary.Description, ShouldResemble, "IndexDescription")
   637  		So(*imageSummary.Licenses, ShouldResemble, "IndexLicenses")
   638  		So(*imageSummary.Title, ShouldResemble, "IndexTitle")
   639  		So(*imageSummary.Source, ShouldResemble, "IndexSource")
   640  		So(*imageSummary.Documentation, ShouldResemble, "IndexDocumentation")
   641  		So(*imageSummary.Vendor, ShouldResemble, "IndexVendor")
   642  		So(*imageSummary.Authors, ShouldResemble, "IndexAuthors")
   643  
   644  		err = metaDB.ResetDB()
   645  		So(err, ShouldBeNil)
   646  		// --------------------------------------------------------
   647  		indexWithManifestAndConfigAnnotations := CreateMultiarchWith().Images(
   648  			[]Image{imageWithManifestAndConfigAnnotations, CreateRandomImage(), CreateRandomImage()},
   649  		).Build()
   650  
   651  		ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{
   652  			Name: "repo",
   653  			MultiArchImages: []ociutils.RepoMultiArchImage{
   654  				{MultiarchImage: indexWithManifestAndConfigAnnotations, Reference: "tag"},
   655  			},
   656  		})
   657  		So(err, ShouldBeNil)
   658  
   659  		digest := indexWithManifestAndConfigAnnotations.DigestStr()
   660  
   661  		repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
   662  		So(err, ShouldBeNil)
   663  		imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest})
   664  		So(err, ShouldBeNil)
   665  
   666  		imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
   667  			imageMeta[digest]))
   668  		So(err, ShouldBeNil)
   669  		So(*imageSummary.Description, ShouldResemble, "ManifestDescription")
   670  		So(*imageSummary.Licenses, ShouldResemble, "ManifestLicenses")
   671  		So(*imageSummary.Title, ShouldResemble, "ManifestTitle")
   672  		So(*imageSummary.Source, ShouldResemble, "ManifestSource")
   673  		So(*imageSummary.Documentation, ShouldResemble, "ManifestDocumentation")
   674  		So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor")
   675  		So(*imageSummary.Authors, ShouldResemble, "ManifestAuthors")
   676  
   677  		err = metaDB.ResetDB()
   678  		So(err, ShouldBeNil)
   679  		// --------------------------------------------------------
   680  		indexWithConfigAnnotations := CreateMultiarchWith().Images(
   681  			[]Image{imageWithConfigAnnotations, CreateRandomImage(), CreateRandomImage()},
   682  		).Build()
   683  
   684  		ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{
   685  			Name: "repo",
   686  			MultiArchImages: []ociutils.RepoMultiArchImage{
   687  				{MultiarchImage: indexWithConfigAnnotations, Reference: "tag"},
   688  			},
   689  		})
   690  		So(err, ShouldBeNil)
   691  
   692  		digest = indexWithConfigAnnotations.DigestStr()
   693  
   694  		repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
   695  		So(err, ShouldBeNil)
   696  		imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest})
   697  		So(err, ShouldBeNil)
   698  
   699  		imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
   700  			imageMeta[digest]))
   701  		So(err, ShouldBeNil)
   702  		So(*imageSummary.Description, ShouldResemble, "ConfigDescription")
   703  		So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses")
   704  		So(*imageSummary.Title, ShouldResemble, "ConfigTitle")
   705  		So(*imageSummary.Source, ShouldResemble, "ConfigSource")
   706  		So(*imageSummary.Documentation, ShouldResemble, "ConfigDocumentation")
   707  		So(*imageSummary.Vendor, ShouldResemble, "ConfigVendor")
   708  		So(*imageSummary.Authors, ShouldResemble, "ConfigAuthors")
   709  
   710  		err = metaDB.ResetDB()
   711  		So(err, ShouldBeNil)
   712  		//--------------------------------------------------------
   713  
   714  		indexWithMixAnnotations := CreateMultiarchWith().Images(
   715  			[]Image{
   716  				CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{
   717  					Config: ispec.ImageConfig{
   718  						Labels: map[string]string{
   719  							ispec.AnnotationDescription: "ConfigDescription",
   720  							ispec.AnnotationLicenses:    "ConfigLicenses",
   721  						},
   722  					},
   723  				}).Annotations(map[string]string{
   724  					ispec.AnnotationVendor:  "ManifestVendor",
   725  					ispec.AnnotationAuthors: "ManifestAuthors",
   726  				}).Build(),
   727  				CreateRandomImage(),
   728  				CreateRandomImage(),
   729  			},
   730  		).Annotations(
   731  			map[string]string{
   732  				ispec.AnnotationTitle:         "IndexTitle",
   733  				ispec.AnnotationDocumentation: "IndexDocumentation",
   734  				ispec.AnnotationSource:        "IndexSource",
   735  			},
   736  		).Build()
   737  
   738  		ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{
   739  			Name: "repo",
   740  			MultiArchImages: []ociutils.RepoMultiArchImage{
   741  				{MultiarchImage: indexWithMixAnnotations, Reference: "tag"},
   742  			},
   743  		})
   744  		So(err, ShouldBeNil)
   745  
   746  		digest = indexWithMixAnnotations.DigestStr()
   747  
   748  		repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
   749  		So(err, ShouldBeNil)
   750  		imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest})
   751  		So(err, ShouldBeNil)
   752  
   753  		imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
   754  			imageMeta[digest]))
   755  		So(err, ShouldBeNil)
   756  		So(*imageSummary.Description, ShouldResemble, "ConfigDescription")
   757  		So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses")
   758  		So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor")
   759  		So(*imageSummary.Authors, ShouldResemble, "ManifestAuthors")
   760  		So(*imageSummary.Title, ShouldResemble, "IndexTitle")
   761  		So(*imageSummary.Documentation, ShouldResemble, "IndexDocumentation")
   762  		So(*imageSummary.Source, ShouldResemble, "IndexSource")
   763  
   764  		err = metaDB.ResetDB()
   765  		So(err, ShouldBeNil)
   766  		//--------------------------------------------------------
   767  		indexWithNoAnnotations := CreateRandomMultiarch()
   768  
   769  		ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{
   770  			Name: "repo",
   771  			MultiArchImages: []ociutils.RepoMultiArchImage{
   772  				{MultiarchImage: indexWithNoAnnotations, Reference: "tag"},
   773  			},
   774  		})
   775  		So(err, ShouldBeNil)
   776  
   777  		digest = indexWithNoAnnotations.DigestStr()
   778  
   779  		repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
   780  		So(err, ShouldBeNil)
   781  		imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest})
   782  		So(err, ShouldBeNil)
   783  
   784  		imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
   785  			imageMeta[digest]))
   786  		So(err, ShouldBeNil)
   787  		So(*imageSummary.Description, ShouldBeBlank)
   788  		So(*imageSummary.Licenses, ShouldBeBlank)
   789  		So(*imageSummary.Vendor, ShouldBeBlank)
   790  		So(*imageSummary.Authors, ShouldBeBlank)
   791  		So(*imageSummary.Title, ShouldBeBlank)
   792  		So(*imageSummary.Documentation, ShouldBeBlank)
   793  		So(*imageSummary.Source, ShouldBeBlank)
   794  
   795  		err = metaDB.ResetDB()
   796  		So(err, ShouldBeNil)
   797  	})
   798  }
   799  
   800  func TestConvertErrors(t *testing.T) {
   801  	ctx := context.Background()
   802  	log := log.NewLogger("debug", "")
   803  
   804  	Convey("Errors", t, func() {
   805  		Convey("RepoMeta2ExpandedRepoInfo", func() {
   806  			_, imgSums := convert.RepoMeta2ExpandedRepoInfo(ctx,
   807  				mTypes.RepoMeta{
   808  					Tags: map[string]mTypes.Descriptor{"tag": {MediaType: "bad-type", Digest: "digest"}},
   809  				},
   810  				map[string]mTypes.ImageMeta{
   811  					"digest": {},
   812  				},
   813  				convert.SkipQGLField{}, nil,
   814  				log,
   815  			)
   816  			So(len(imgSums), ShouldEqual, 0)
   817  		})
   818  	})
   819  }