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