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