//go:build imagetrust
// +build imagetrust

package meta_test

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2"
	guuid "github.com/gofrs/uuid"
	"github.com/notaryproject/notation-core-go/signature/jws"
	"github.com/notaryproject/notation-go"
	"github.com/notaryproject/notation-go/signer"
	godigest "github.com/opencontainers/go-digest"
	"github.com/opencontainers/image-spec/specs-go"
	ispec "github.com/opencontainers/image-spec/specs-go/v1"
	. "github.com/smartystreets/goconvey/convey"

	zerr "zotregistry.dev/zot/errors"
	"zotregistry.dev/zot/pkg/api/config"
	rediscfg "zotregistry.dev/zot/pkg/api/config/redis"
	zcommon "zotregistry.dev/zot/pkg/common"
	"zotregistry.dev/zot/pkg/extensions/imagetrust"
	"zotregistry.dev/zot/pkg/extensions/search/convert"
	"zotregistry.dev/zot/pkg/log"
	"zotregistry.dev/zot/pkg/meta"
	"zotregistry.dev/zot/pkg/meta/boltdb"
	"zotregistry.dev/zot/pkg/meta/common"
	mdynamodb "zotregistry.dev/zot/pkg/meta/dynamodb"
	"zotregistry.dev/zot/pkg/meta/redis"
	mTypes "zotregistry.dev/zot/pkg/meta/types"
	reqCtx "zotregistry.dev/zot/pkg/requestcontext"
	tCommon "zotregistry.dev/zot/pkg/test/common"
	. "zotregistry.dev/zot/pkg/test/image-utils"
	"zotregistry.dev/zot/pkg/test/signature"
	tskip "zotregistry.dev/zot/pkg/test/skip"
)

const (
	LINUX   = "linux"
	WINDOWS = "windows"
	AMD     = "amd"
	ARM     = "arm64"
)

func getManifestDigest(md mTypes.ManifestMeta) string { return md.Digest.String() }

func TestBoltDB(t *testing.T) {
	Convey("BoltDB creation", t, func() {
		boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()}

		repoDBPath := path.Join(boltDBParams.RootDir, "meta.db")
		defer os.Remove(repoDBPath)

		boltDriver, err := boltdb.GetBoltDriver(boltDBParams)
		So(err, ShouldBeNil)

		log := log.NewLogger("debug", "")

		metaDB, err := boltdb.New(boltDriver, log)
		So(metaDB, ShouldNotBeNil)
		So(err, ShouldBeNil)

		err = os.Chmod(repoDBPath, 0o200)
		So(err, ShouldBeNil)

		_, err = boltdb.GetBoltDriver(boltDBParams)
		So(err, ShouldNotBeNil)

		err = os.Chmod(repoDBPath, 0o600)
		So(err, ShouldBeNil)
	})

	Convey("BoltDB Wrapper", t, func() {
		boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()}
		boltDriver, err := boltdb.GetBoltDriver(boltDBParams)
		So(err, ShouldBeNil)

		log := log.NewLogger("debug", "")

		imgTrustStore, err := imagetrust.NewLocalImageTrustStore(boltDBParams.RootDir)
		So(err, ShouldBeNil)

		boltdbWrapper, err := boltdb.New(boltDriver, log)

		boltdbWrapper.SetImageTrustStore(imgTrustStore)

		defer func() {
			os.Remove(path.Join(boltDBParams.RootDir, "meta.db"))
			os.RemoveAll(path.Join(boltDBParams.RootDir, "_cosign"))
			os.RemoveAll(path.Join(boltDBParams.RootDir, "_notation"))
		}()

		So(boltdbWrapper, ShouldNotBeNil)
		So(err, ShouldBeNil)

		RunMetaDBTests(t, boltdbWrapper)
	})
}

func TestDynamoDBWrapper(t *testing.T) {
	tskip.SkipDynamo(t)

	uuid, err := guuid.NewV4()
	if err != nil {
		panic(err)
	}

	repoMetaTablename := "RepoMetadataTable" + uuid.String()
	versionTablename := "Version" + uuid.String()
	userDataTablename := "UserDataTable" + uuid.String()
	apiKeyTablename := "ApiKeyTable" + uuid.String()
	imageMetaTablename := "ImageMeta" + uuid.String()
	repoBlobsTablename := "RepoBlobs" + uuid.String()

	Convey("DynamoDB Wrapper", t, func() {
		dynamoDBDriverParams := mdynamodb.DBDriverParameters{
			Endpoint:               os.Getenv("DYNAMODBMOCK_ENDPOINT"),
			RepoMetaTablename:      repoMetaTablename,
			RepoBlobsInfoTablename: repoBlobsTablename,
			ImageMetaTablename:     imageMetaTablename,
			VersionTablename:       versionTablename,
			UserDataTablename:      userDataTablename,
			APIKeyTablename:        apiKeyTablename,
			Region:                 "us-east-2",
		}

		dynamoClient, err := mdynamodb.GetDynamoClient(dynamoDBDriverParams)
		So(err, ShouldBeNil)

		log := log.NewLogger("debug", "")

		dynamoDriver, err := mdynamodb.New(dynamoClient, dynamoDBDriverParams, log)
		So(dynamoDriver, ShouldNotBeNil)
		So(err, ShouldBeNil)

		imgTrustStore, err := imagetrust.NewAWSImageTrustStore(dynamoDBDriverParams.Region, dynamoDBDriverParams.Endpoint)
		So(err, ShouldBeNil)

		dynamoDriver.SetImageTrustStore(imgTrustStore)

		resetDynamoDBTables := func() error {
			err := dynamoDriver.ResetTable(dynamoDriver.RepoMetaTablename)
			if err != nil {
				return err
			}

			err = dynamoDriver.ResetTable(dynamoDriver.ImageMetaTablename)
			if err != nil {
				return err
			}

			err = dynamoDriver.ResetTable(dynamoDriver.RepoBlobsTablename)
			if err != nil {
				return err
			}

			// Note: Tests are very slow if we reset the UserData table every new convey. We'll reset it as needed

			return err
		}

		RunMetaDBTests(t, dynamoDriver, resetDynamoDBTables)
	})
}

func TestRedisDB(t *testing.T) {
	miniRedis := miniredis.RunT(t)

	Convey("RedisDB Wrapper", t, func() {
		rootDir := t.TempDir()
		log := log.NewLogger("debug", "")

		params := redis.DBDriverParameters{KeyPrefix: "zot"}
		driverConfig := map[string]interface{}{"url": "redis://" + miniRedis.Addr()}

		redisDriver, err := rediscfg.GetRedisClient(driverConfig, log)
		So(err, ShouldBeNil)

		metaDB, err := redis.New(redisDriver, params, log)
		So(metaDB, ShouldNotBeNil)
		So(err, ShouldBeNil)

		imgTrustStore, err := imagetrust.NewLocalImageTrustStore(rootDir)
		So(err, ShouldBeNil)

		metaDB.SetImageTrustStore(imgTrustStore)

		defer func() {
			metaDB.ResetDB() //nolint: errcheck
			os.RemoveAll(path.Join(rootDir, "_cosign"))
			os.RemoveAll(path.Join(rootDir, "_notation"))
		}()

		RunMetaDBTests(t, metaDB)
	})
}

func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func() error) { //nolint: thelper,gocyclo
	ctx := context.Background()

	Convey("Test MetaDB Interface implementation", func() {
		for _, prepFunc := range preparationFuncs {
			err := prepFunc()
			So(err, ShouldBeNil)
		}

		Convey("Test CRUD operations on UserData and API keys", func() {
			hashKey1 := "id"
			label1 := "apiKey1"

			apiKeys := make(map[string]mTypes.APIKeyDetails)
			apiKeyDetails := mTypes.APIKeyDetails{
				Label:  label1,
				Scopes: []string{"repo"},
				UUID:   hashKey1,
			}

			apiKeys[hashKey1] = apiKeyDetails

			userProfileSrc := mTypes.UserData{
				Groups:  []string{"group1", "group2"},
				APIKeys: apiKeys,
			}

			Convey("Test basic operations on API keys", func() {
				hashKey2 := "key"
				label2 := "apiKey2"

				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("test")

				ctx := userAc.DeriveContext(context.Background())

				err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
				So(err, ShouldBeNil)

				isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
				So(isExpired, ShouldBeFalse)
				So(err, ShouldBeNil)

				storedAPIKeys, err := metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 1)
				So(storedAPIKeys[0], ShouldResemble, apiKeyDetails)

				userProfile, err := metaDB.GetUserData(ctx)
				So(err, ShouldBeNil)
				So(userProfile.APIKeys, ShouldContainKey, hashKey1)
				So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label)
				So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes)

				err = metaDB.SetUserData(ctx, userProfileSrc)
				So(err, ShouldBeNil)

				userProfile, err = metaDB.GetUserData(ctx)
				So(err, ShouldBeNil)
				So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups)
				So(userProfile.APIKeys, ShouldContainKey, hashKey1)
				So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label)
				So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 1)
				So(storedAPIKeys[0], ShouldResemble, apiKeyDetails)

				lastUsed := userProfile.APIKeys[hashKey1].LastUsed

				err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
				So(err, ShouldBeNil)

				userProfile, err = metaDB.GetUserData(ctx)
				So(err, ShouldBeNil)
				So(userProfile.APIKeys[hashKey1].LastUsed, ShouldHappenAfter, lastUsed)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 1)
				So(storedAPIKeys[0].LastUsed, ShouldHappenAfter, lastUsed)

				userGroups, err := metaDB.GetUserGroups(ctx)
				So(err, ShouldBeNil)
				So(userGroups, ShouldResemble, userProfileSrc.Groups)

				apiKeyDetails.UUID = hashKey2
				apiKeyDetails.Label = label2
				err = metaDB.AddUserAPIKey(ctx, hashKey2, &apiKeyDetails)
				So(err, ShouldBeNil)

				userProfile, err = metaDB.GetUserData(ctx)
				So(err, ShouldBeNil)
				So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups)
				So(userProfile.APIKeys, ShouldContainKey, hashKey2)
				So(userProfile.APIKeys[hashKey2].Label, ShouldEqual, apiKeyDetails.Label)
				So(userProfile.APIKeys[hashKey2].Scopes, ShouldResemble, apiKeyDetails.Scopes)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 2)
				So(storedAPIKeys[0].Scopes, ShouldResemble, apiKeyDetails.Scopes)
				So(storedAPIKeys[1].Scopes, ShouldResemble, apiKeyDetails.Scopes)
				scopes := []string{storedAPIKeys[0].Label, storedAPIKeys[1].Label}
				// order is not preserved when getting api keys from db
				So(scopes, ShouldContain, label1)
				So(scopes, ShouldContain, label2)

				email, err := metaDB.GetUserAPIKeyInfo(hashKey2)
				So(err, ShouldBeNil)
				So(email, ShouldEqual, "test")

				email, err = metaDB.GetUserAPIKeyInfo(hashKey1)
				So(err, ShouldBeNil)
				So(email, ShouldEqual, "test")

				err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
				So(err, ShouldBeNil)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 1)
				So(storedAPIKeys[0].Label, ShouldEqual, label2)

				userProfile, err = metaDB.GetUserData(ctx)
				So(err, ShouldBeNil)
				So(len(userProfile.APIKeys), ShouldEqual, 1)

				err = metaDB.DeleteUserAPIKey(ctx, hashKey2)
				So(err, ShouldBeNil)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 0)

				userProfile, err = metaDB.GetUserData(ctx)
				So(err, ShouldBeNil)
				So(len(userProfile.APIKeys), ShouldEqual, 0)
				So(userProfile.APIKeys, ShouldNotContainKey, hashKey2)

				// delete non existent api key
				err = metaDB.DeleteUserAPIKey(ctx, hashKey2)
				So(err, ShouldBeNil)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 0)

				err = metaDB.DeleteUserData(ctx)
				So(err, ShouldBeNil)

				storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 0)

				email, err = metaDB.GetUserAPIKeyInfo(hashKey2)
				So(err, ShouldNotBeNil)
				So(email, ShouldBeEmpty)

				email, err = metaDB.GetUserAPIKeyInfo(hashKey1)
				So(err, ShouldNotBeNil)
				So(email, ShouldBeEmpty)

				_, err = metaDB.GetUserData(ctx)
				So(err, ShouldNotBeNil)

				userGroups, err = metaDB.GetUserGroups(ctx)
				So(err, ShouldNotBeNil)
				So(userGroups, ShouldBeEmpty)

				err = metaDB.SetUserGroups(ctx, userProfileSrc.Groups)
				So(err, ShouldBeNil)

				userGroups, err = metaDB.GetUserGroups(ctx)
				So(err, ShouldBeNil)
				So(userGroups, ShouldResemble, userProfileSrc.Groups)
			})

			Convey("Test API keys operations with invalid access control context", func() {
				var invalid struct{}

				key := reqCtx.GetContextKey()
				ctx := context.WithValue(context.Background(), key, invalid)

				_, err := metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldNotBeNil)

				err = metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
				So(err, ShouldNotBeNil)

				isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
				So(isExpired, ShouldBeFalse)
				So(err, ShouldNotBeNil)

				err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
				So(err, ShouldNotBeNil)

				_, err = metaDB.GetUserData(ctx)
				So(err, ShouldNotBeNil)

				_, err = metaDB.GetUserGroups(ctx)
				So(err, ShouldNotBeNil)

				_, err = metaDB.GetUserAPIKeyInfo(hashKey1)
				So(err, ShouldNotBeNil)

				err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
				So(err, ShouldNotBeNil)

				err = metaDB.SetUserData(ctx, userProfileSrc)
				So(err, ShouldNotBeNil)

				err = metaDB.SetUserGroups(ctx, []string{"group1", "groups2"})
				So(err, ShouldNotBeNil)

				err = metaDB.DeleteUserData(ctx)
				So(err, ShouldNotBeNil)
			})

			Convey("Test API keys operations with empty userid", func() {
				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("")

				ctx := userAc.DeriveContext(context.Background())

				_, err := metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldNotBeNil)

				isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
				So(isExpired, ShouldBeFalse)
				So(err, ShouldNotBeNil)

				err = metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
				So(err, ShouldNotBeNil)

				err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
				So(err, ShouldNotBeNil)

				_, err = metaDB.GetUserData(ctx)
				So(err, ShouldNotBeNil)

				_, err = metaDB.GetUserGroups(ctx)
				So(err, ShouldNotBeNil)

				_, err = metaDB.GetUserAPIKeyInfo(hashKey1)
				So(err, ShouldNotBeNil)

				err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
				So(err, ShouldNotBeNil)

				err = metaDB.SetUserData(ctx, userProfileSrc)
				So(err, ShouldNotBeNil)

				err = metaDB.SetUserGroups(ctx, []string{"group1", "groups2"})
				So(err, ShouldNotBeNil)

				err = metaDB.DeleteUserData(ctx)
				So(err, ShouldNotBeNil)
			})

			Convey("Test API keys with short expiration date", func() {
				expirationDate := time.Now().Add(1 * time.Second)
				apiKeyDetails.ExpirationDate = expirationDate

				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("test")

				ctx := userAc.DeriveContext(context.Background())

				err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
				So(err, ShouldBeNil)

				storedAPIKeys, err := metaDB.GetUserAPIKeys(ctx)
				So(err, ShouldBeNil)
				So(len(storedAPIKeys), ShouldEqual, 1)
				So(storedAPIKeys[0].ExpirationDate, ShouldResemble, expirationDate)
				So(storedAPIKeys[0].Label, ShouldEqual, apiKeyDetails.Label)
				So(storedAPIKeys[0].Scopes, ShouldResemble, apiKeyDetails.Scopes)

				isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
				So(isExpired, ShouldBeFalse)
				So(err, ShouldBeNil)

				time.Sleep(1 * time.Second)

				Convey("GetUserAPIKeys detects api key expired", func() {
					storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
					So(err, ShouldBeNil)
					So(len(storedAPIKeys), ShouldEqual, 1)
					So(storedAPIKeys[0].IsExpired, ShouldBeTrue)

					isExpired, err = metaDB.IsAPIKeyExpired(ctx, hashKey1)
					So(isExpired, ShouldBeTrue)
					So(err, ShouldBeNil)
				})

				Convey("IsAPIKeyExpired detects api key expired", func() {
					isExpired, err = metaDB.IsAPIKeyExpired(ctx, hashKey1)
					So(isExpired, ShouldBeTrue)
					So(err, ShouldBeNil)

					storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
					So(err, ShouldBeNil)
					So(len(storedAPIKeys), ShouldEqual, 1)
					So(storedAPIKeys[0].IsExpired, ShouldBeTrue)
				})
			})
		})

		Convey("Test Setting an image by tag and retrieving data", func() {
			imgData := CreateImageWith().
				DefaultLayers().
				ImageConfig(ispec.Image{
					Created: DateRef(2000, 10, 10, 10, 10, 10, 10, time.UTC),
					Author:  "author",
					Platform: ispec.Platform{
						Architecture: "arch",
						OS:           "os",
						OSVersion:    "os-vers",
						OSFeatures:   []string{"os-features"},
						Variant:      "variant",
					},
					Config: ispec.ImageConfig{
						Labels:       map[string]string{"test": "test"},
						Env:          []string{"test"},
						ExposedPorts: map[string]struct{}{"test": {}},
						Volumes:      map[string]struct{}{"test": {}},
					},
					RootFS: ispec.RootFS{
						DiffIDs: []godigest.Digest{godigest.FromString("test")},
					},
				}).Build().AsImageMeta()

			err := metaDB.SetImageMeta(imgData.Digest, imgData)
			So(err, ShouldBeNil)

			retrievedImgData, err := metaDB.GetImageMeta(imgData.Digest)
			So(err, ShouldBeNil)
			So(imgData, ShouldResemble, retrievedImgData)

			imgMulti := CreateRandomMultiarch()

			for i := range imgMulti.Images {
				err = metaDB.SetImageMeta(imgMulti.Images[i].Digest(), imgMulti.Images[i].AsImageMeta())
				So(err, ShouldBeNil)
			}

			err = metaDB.SetImageMeta(imgMulti.Digest(), imgMulti.AsImageMeta())
			So(err, ShouldBeNil)

			retrievedImgMultiData, err := metaDB.GetImageMeta(imgMulti.Digest())
			So(err, ShouldBeNil)
			So(imgMulti.AsImageMeta(), ShouldEqual, retrievedImgMultiData)
		})

		Convey("GetFullImageMeta", func() {
			img1 := CreateRandomImage()
			multi := CreateMultiarchWith().Images([]Image{img1}).Build()

			err := metaDB.SetRepoReference(ctx, "repo", img1.Digest().String(), img1.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, "repo", "tag", multi.AsImageMeta())
			So(err, ShouldBeNil)

			fullImageMeta, err := metaDB.GetFullImageMeta(ctx, "repo", "tag")
			So(err, ShouldBeNil)
			So(fullImageMeta.Digest.String(), ShouldResemble, multi.DigestStr())
		})

		Convey("Set/Get RepoMeta", func() {
			err := metaDB.SetRepoMeta("repo", mTypes.RepoMeta{
				Name: "repo",
				Tags: map[mTypes.Tag]mTypes.Descriptor{"tag": {Digest: "dig"}},

				Statistics: map[mTypes.ImageDigest]mTypes.DescriptorStatistics{},
				Signatures: map[mTypes.ImageDigest]mTypes.ManifestSignatures{},
				Referrers:  map[mTypes.ImageDigest][]mTypes.ReferrerInfo{"digest": {{Digest: "dig"}}},
			})
			So(err, ShouldBeNil)
			repoMeta, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(repoMeta.Name, ShouldResemble, "repo")
			So(repoMeta.Tags, ShouldContainKey, "tag")
		})

		Convey("Test SetRepoReference", func() {
			var (
				repo1 = "repo1"
				repo2 = "repo2"
				tag1  = "0.0.1"
				tag2  = "0.0.2"
			)

			img1 := CreateImageWith().RandomLayers(2, 10).RandomConfig().
				Annotations(map[string]string{"test": "annotation"}).Build()
			imgData1 := img1.AsImageMeta()
			img1Size := img1.ConfigDescriptor.Size + img1.ManifestDescriptor.Size + 2*10

			img2 := CreateImageWith().LayerBlobs(img1.Layers).RandomConfig().
				Annotations(map[string]string{"test": "annotation"}).Build()
			imgData2 := img2.AsImageMeta()
			img2Size := img2.ConfigDescriptor.Size + img2.ManifestDescriptor.Size + 2*10

			multiImages := []Image{
				CreateImageWith().RandomLayers(2, 10).
					ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "multi-os1", Architecture: "multi-arch1"}}).
					Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}).
					Build(),
				CreateImageWith().RandomLayers(2, 10).
					ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "multi-os2", Architecture: "multi-arch2"}}).
					Annotations(map[string]string{ispec.AnnotationVendor: "vendor2"}).
					Build(),
			}

			imgMulti := CreateMultiarchWith().
				Images(multiImages).
				Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}).Build()

			Convey("Setting a good repo", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, imgData1)
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				So(repoMeta.Name, ShouldResemble, repo1)
				So(repoMeta.Tags[tag1].Digest, ShouldEqual, img1.DigestStr())
			})

			Convey("Setting an index with it's manifests", func() {
				_, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldNotBeNil)

				for i := range imgMulti.Images {
					err := metaDB.SetRepoReference(ctx, repo1, imgMulti.Images[i].DigestStr(),
						imgMulti.Images[i].AsImageMeta())
					So(err, ShouldBeNil)
				}

				err = metaDB.SetRepoReference(ctx, repo1, tag1, imgMulti.AsImageMeta())
				So(err, ShouldBeNil)

				image1TotalSize := multiImages[0].ManifestDescriptor.Size + multiImages[0].ConfigDescriptor.Size + 2*10
				image2TotalSize := multiImages[1].ManifestDescriptor.Size + multiImages[1].ConfigDescriptor.Size + 2*10
				indexTotalSize := image1TotalSize + image2TotalSize + imgMulti.IndexDescriptor.Size

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				So(repoMeta.Name, ShouldResemble, repo1)
				So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "multi-os1", Architecture: "multi-arch1"})
				So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "multi-os2", Architecture: "multi-arch2"})
				So(repoMeta.Vendors, ShouldContain, "vendor1")
				So(repoMeta.Vendors, ShouldContain, "vendor2")
				So(repoMeta.Size, ShouldEqual, indexTotalSize)
			})

			Convey("Set multiple repos", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, imgData1)
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, repo2, tag1, imgData2)
				So(err, ShouldBeNil)

				repoMeta1, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				repoMeta2, err := metaDB.GetRepoMeta(ctx, repo2)
				So(err, ShouldBeNil)

				So(repoMeta1.Tags[tag1].Digest, ShouldResemble, imgData1.Digest.String())
				So(repoMeta2.Tags[tag1].Digest, ShouldResemble, imgData2.Digest.String())
				So(repoMeta1.Size, ShouldEqual, img1Size)
				So(repoMeta2.Size, ShouldEqual, img2Size)
			})

			Convey("Set, delete and set again", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, imgData1)
				So(err, ShouldBeNil)

				err = metaDB.RemoveRepoReference(repo1, tag1, imgData1.Digest)
				So(err, ShouldBeNil)

				err = metaDB.SetRepoReference(ctx, repo1, tag1, imgData1)
				So(err, ShouldBeNil)
			})

			Convey("Check repo blobs info for manifest image", func() {
				image1 := CreateImageWith().RandomLayers(2, 10).
					ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os1", Architecture: "arch1"}}).
					Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}).
					Build()
				imageMeta1 := image1.AsImageMeta()

				layersSize := int64(2 * 10)
				image1Size := imageMeta1.Manifests[0].Size + imageMeta1.Manifests[0].Manifest.Config.Size + layersSize

				err := metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta1)
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				So(repoMeta.Vendors, ShouldContain, "vendor1")
				So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "os1", Architecture: "arch1"})
				So(repoMeta.Size, ShouldEqual, image1Size)

				image2 := CreateImageWith().
					LayerBlobs(image1.Layers).
					ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os2", Architecture: "arch2"}}).
					Annotations(map[string]string{ispec.AnnotationVendor: "vendor2"}).
					Build()
				imageMeta2 := image2.AsImageMeta()

				// the layers are the same so we add them once
				repoSize := image1Size + image2.ManifestDescriptor.Size + image2.ConfigDescriptor.Size

				err = metaDB.SetRepoReference(ctx, repo1, tag2, imageMeta2)
				So(err, ShouldBeNil)

				repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				So(repoMeta.Vendors, ShouldContain, "vendor1")
				So(repoMeta.Vendors, ShouldContain, "vendor2")
				So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "os1", Architecture: "arch1"})
				So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "os2", Architecture: "arch2"})
				So(repoMeta.Size, ShouldEqual, repoSize)
			})

			Convey("Set with a bad reference", func() {
				err := metaDB.SetRepoReference(ctx, "repo", "", imgData1)
				So(err, ShouldNotBeNil)

				err = metaDB.SetRepoReference(ctx, "", "tag", imgData1)
				So(err, ShouldNotBeNil)
			})

			Convey("Check last updated for indexes", func() {
				config1 := GetDefaultConfig()
				config1.Created = DateRef(2009, 2, 1, 12, 0, 0, 0, time.UTC)

				config2 := GetDefaultConfig()
				config2.Created = DateRef(2011, 2, 1, 12, 0, 0, 0, time.UTC)

				config3 := GetDefaultConfig()
				config3.Created = DateRef(2011, 3, 1, 12, 0, 0, 0, time.UTC)

				image1 := CreateMultiarchWith().Images([]Image{
					CreateImageWith().RandomLayers(1, 10).ImageConfig(config1).Build(),
				}).Build()
				image2 := CreateMultiarchWith().Images([]Image{
					CreateImageWith().RandomLayers(1, 10).ImageConfig(config2).Build(),
					CreateImageWith().RandomLayers(1, 10).ImageConfig(config3).Build(),
				}).Build()

				_, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldNotBeNil)

				for i := range image1.Images {
					err := metaDB.SetRepoReference(ctx, repo1, image1.Images[i].DigestStr(),
						image1.Images[i].AsImageMeta())
					So(err, ShouldBeNil)
				}

				err = metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)

				for i := range image2.Images {
					err := metaDB.SetRepoReference(ctx, repo1, image2.Images[i].DigestStr(),
						image2.Images[i].AsImageMeta())
					So(err, ShouldBeNil)
				}

				err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta())
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				So(*repoMeta.LastUpdatedImage.LastUpdated, ShouldEqual, time.Date(2011, 3, 1, 12, 0, 0, 0, time.UTC))
			})
		})

		Convey("Test RemoveRepoReference", func() {
			var (
				repo = "repo1"
				tag1 = "0.0.1"
				tag2 = "0.0.2"
			)

			layersSize := int64(2 * 10)

			image1 := CreateImageWith().
				RandomLayers(2, 10).
				ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os1", Architecture: "arch1"}}).
				Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}).
				Build()
			imageMeta1 := image1.AsImageMeta()
			image1Size := imageMeta1.Manifests[0].Size + imageMeta1.Manifests[0].Manifest.Config.Size + layersSize

			image2 := CreateImageWith().
				LayerBlobs(image1.Layers).
				ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os2", Architecture: "arch2"}}).
				Annotations(map[string]string{ispec.AnnotationVendor: "vendor2", "annotation": "test"}).
				Build()
			imageMeta2 := image2.AsImageMeta()
			image2Size := imageMeta2.Manifests[0].Size + imageMeta2.Manifests[0].Manifest.Config.Size + layersSize

			totalRepoSize := image1Size + image2Size - layersSize

			err := metaDB.SetRepoReference(ctx, repo, tag1, imageMeta1)
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo, tag2, imageMeta2)
			So(err, ShouldBeNil)

			Convey("Delete reference from repo", func() {
				err = metaDB.RemoveRepoReference(repo, tag1, imageMeta1.Digest)
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
				So(err, ShouldBeNil)

				_, ok := repoMeta.Tags[tag1]
				So(ok, ShouldBeFalse)
				So(repoMeta.Size, ShouldEqual, image2Size)
				So(repoMeta.Platforms, ShouldNotContain, ispec.Platform{OS: "os1", Architecture: "arch1"})
				So(repoMeta.Vendors, ShouldNotContain, "vendor1")
			})

			Convey("Delete a digest from repo", func() {
				err = metaDB.RemoveRepoReference(repo, tag2, imageMeta2.Digest)
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
				So(err, ShouldBeNil)

				_, ok := repoMeta.Tags[tag2]
				So(ok, ShouldBeFalse)
				So(repoMeta.Size, ShouldEqual, image1Size)
				So(repoMeta.Platforms, ShouldNotContain, ispec.Platform{OS: "os2", Architecture: "arch2"})
				So(repoMeta.Vendors, ShouldNotContain, "vendor2")
			})

			Convey("Delete inexistent reference from repo", func() {
				inexistentDigest := godigest.FromBytes([]byte("inexistent"))
				err := metaDB.RemoveRepoReference(repo, inexistentDigest.String(), inexistentDigest)
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
				So(err, ShouldBeNil)

				So(repoMeta.Tags[tag1].Digest, ShouldResemble, imageMeta1.Digest.String())
				So(repoMeta.Tags[tag2].Digest, ShouldResemble, imageMeta2.Digest.String())
				So(repoMeta.Size, ShouldEqual, totalRepoSize)
			})

			Convey("Delete reference from inexistent repo", func() {
				inexistentDigest := godigest.FromBytes([]byte("inexistent"))

				err := metaDB.RemoveRepoReference("InexistentRepo", inexistentDigest.String(), inexistentDigest)
				So(err, ShouldBeNil)

				repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
				So(err, ShouldBeNil)

				So(repoMeta.Tags[tag1].Digest, ShouldResemble, imageMeta1.Digest.String())
				So(repoMeta.Tags[tag2].Digest, ShouldResemble, imageMeta2.Digest.String())
				So(repoMeta.Size, ShouldEqual, totalRepoSize)
			})
		})

		Convey("Test GetMultipleRepoMeta", func() {
			var (
				repo1 = "repo1"
				repo2 = "repo2"
				repo3 = "repo3"
				tag1  = "0.0.1"
				tag2  = "0.0.2"
				tag3  = "0.0.3"
			)

			userAc := reqCtx.NewUserAccessControl()
			userAc.SetUsername("username")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
				repo3: false,
			})

			ctx := userAc.DeriveContext(context.Background())

			image1 := CreateImageWith().
				RandomLayers(2, 10).
				ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os1", Architecture: "arch1"}}).
				Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}).
				Build()
			imageMeta1 := image1.AsImageMeta()

			image2 := CreateImageWith().
				LayerBlobs(image1.Layers).
				ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os2", Architecture: "arch2"}}).
				Annotations(map[string]string{ispec.AnnotationVendor: "vendor2"}).
				Build()
			imageMeta2 := image2.AsImageMeta()

			image3 := CreateImageWith().
				LayerBlobs(image1.Layers).
				ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os3", Architecture: "arch3"}}).
				Annotations(map[string]string{ispec.AnnotationVendor: "vendor3"}).
				Build()
			imageMeta3 := image3.AsImageMeta()

			err := metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta1)
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo1, tag2, imageMeta2)
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo2, tag2, imageMeta2)
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo3, tag3, imageMeta3)
			So(err, ShouldBeNil)

			Convey("Get all RepoMeta", func() {
				repoMetaSlice, err := metaDB.GetMultipleRepoMeta(context.TODO(), func(repoMeta mTypes.RepoMeta) bool {
					return true
				})
				So(err, ShouldBeNil)
				So(len(repoMetaSlice), ShouldEqual, 3)
			})

			Convey("Get all RepoMeta for user with restricted permissions", func() {
				repoMetaSlice, err := metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool {
					return true
				})
				So(err, ShouldBeNil)
				So(len(repoMetaSlice), ShouldEqual, 2)
			})

			Convey("Get repo with a tag", func() {
				repoMetaSlice, err := metaDB.GetMultipleRepoMeta(context.TODO(), func(repoMeta mTypes.RepoMeta) bool {
					for tag := range repoMeta.Tags {
						if tag == tag1 {
							return true
						}
					}

					return false
				})
				So(err, ShouldBeNil)
				So(len(repoMetaSlice), ShouldEqual, 1)
				So(repoMetaSlice[0].Tags[tag1].Digest == imageMeta1.Digest.String(), ShouldBeTrue)
			})
		})

		Convey("Test IncrementRepoStars", func() {
			var (
				repo1     = "repo1"
				tag1      = "0.0.1"
				imageMeta = CreateDefaultImage().AsImageMeta()
			)

			err := metaDB.IncrementRepoStars("missing-repo")
			So(err, ShouldNotBeNil)

			err = metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta)
			So(err, ShouldBeNil)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 2)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 3)
		})

		Convey("Test DecrementRepoStars", func() {
			var (
				repo1     = "repo1"
				tag1      = "0.0.1"
				imageMeta = CreateDefaultImage().AsImageMeta()
			)

			err := metaDB.IncrementRepoStars("missing-repo")
			So(err, ShouldNotBeNil)

			err = metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta)
			So(err, ShouldBeNil)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			err = metaDB.DecrementRepoStars(repo1)
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 0)

			err = metaDB.DecrementRepoStars(repo1)
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 0)

			_, err = metaDB.GetRepoMeta(ctx, "badRepo")
			So(err, ShouldNotBeNil)
		})

		Convey("Test Repo Stars", func() {
			var (
				repo1 = "repo1"
				tag1  = "0.0.1"
			)

			err := metaDB.SetRepoReference(ctx, repo1, tag1, CreateDefaultImage().AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)
			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)
			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 2)

			err = metaDB.IncrementRepoStars(repo1)
			So(err, ShouldBeNil)
			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 3)
		})

		Convey("Test bookmarked/starred behavior when user context is not present/invalid", func() {
			// Context has no user access control data
			ctx := context.Background()

			toggleState, err := metaDB.ToggleBookmarkRepo(ctx, repo)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			toggleState, err = metaDB.ToggleStarRepo(ctx, repo)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			// Context has invalid user access control data
			var invalid struct{}

			key := reqCtx.GetContextKey()
			ctx = context.WithValue(context.Background(), key, invalid)

			toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			toggleState, err = metaDB.ToggleStarRepo(ctx, repo)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			// Context has user access control data, but it is not completely initialized
			userAc := reqCtx.NewUserAccessControl()
			ctx = userAc.DeriveContext(context.Background())

			toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			toggleState, err = metaDB.ToggleStarRepo(ctx, repo)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			// Context has user access control data with all fields initialized
			userAc.SetUsername("user1")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo: true,
			})

			ctx = userAc.DeriveContext(context.Background())

			toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Removed)

			toggleState, err = metaDB.ToggleStarRepo(ctx, repo)
			So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
			So(toggleState, ShouldEqual, mTypes.NotChanged)
		})

		Convey("Test repo stars for user", func() {
			var (
				repo1 = "repo1"
				tag1  = "0.0.1"
				repo2 = "repo2"
			)

			userAc := reqCtx.NewUserAccessControl()
			userAc.SetUsername("user1")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
			})

			// "user1"
			ctx1 := userAc.DeriveContext(ctx)

			userAc = reqCtx.NewUserAccessControl()
			userAc.SetUsername("user2")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
			})

			// "user2"
			ctx2 := userAc.DeriveContext(ctx)

			userAc = reqCtx.NewUserAccessControl()
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
			})

			// anonymous user
			ctx3 := userAc.DeriveContext(ctx)

			err := metaDB.SetRepoReference(ctx, repo1, tag1, CreateDefaultImage().AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo2, tag1, CreateDefaultImage().AsImageMeta())
			So(err, ShouldBeNil)

			repos, err := metaDB.GetStarredRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			repos, err = metaDB.GetStarredRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 bookmarks repo 1, User 2 has no stars
			toggleState, err := metaDB.ToggleStarRepo(ctx1, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			repos, err = metaDB.GetStarredRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetStarredRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 and User 2 star only repo 1
			toggleState, err = metaDB.ToggleStarRepo(ctx2, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 2)

			repos, err = metaDB.GetStarredRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetStarredRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 stars repos 1 and 2, and User 2 stars only repo 1
			toggleState, err = metaDB.ToggleStarRepo(ctx1, repo2)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo2)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			repos, err = metaDB.GetStarredRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 2)
			So(repos, ShouldContain, repo1)
			So(repos, ShouldContain, repo2)

			repos, err = metaDB.GetStarredRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 stars only repo 2, and User 2 stars only repo 1
			toggleState, err = metaDB.ToggleStarRepo(ctx1, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Removed)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			repos, err = metaDB.GetStarredRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo2)

			repos, err = metaDB.GetStarredRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 stars both repos 1 and 2, and User 2 removes all stars
			toggleState, err = metaDB.ToggleStarRepo(ctx1, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			toggleState, err = metaDB.ToggleStarRepo(ctx2, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Removed)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo2)
			So(err, ShouldBeNil)
			So(repoMeta.StarCount, ShouldEqual, 1)

			repos, err = metaDB.GetStarredRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 2)
			So(repos, ShouldContain, repo1)
			So(repos, ShouldContain, repo2)

			repos, err = metaDB.GetStarredRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// Anonymous user attempts to toggle a star
			toggleState, err = metaDB.ToggleStarRepo(ctx3, repo1)
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 stars just repo 1
			toggleState, err = metaDB.ToggleStarRepo(ctx1, repo2)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Removed)

			repos, err = metaDB.GetStarredRepos(ctx3)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)
		})

		//nolint: contextcheck
		Convey("Test repo bookmarks for user", func() {
			var (
				repo1  = "repo1"
				tag1   = "0.0.1"
				repo2  = "repo2"
				image1 = CreateRandomImage().AsImageMeta()
			)

			userAc := reqCtx.NewUserAccessControl()
			userAc.SetUsername("user1")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
			})

			// "user1"
			ctx1 := userAc.DeriveContext(context.Background())

			userAc = reqCtx.NewUserAccessControl()
			userAc.SetUsername("user2")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
			})

			// "user2"
			ctx2 := userAc.DeriveContext(context.Background())

			userAc = reqCtx.NewUserAccessControl()
			userAc.SetGlobPatterns("read", map[string]bool{
				repo1: true,
				repo2: true,
			})

			// anonymous user
			ctx3 := userAc.DeriveContext(context.Background())

			err := metaDB.SetRepoReference(ctx, repo1, tag1, image1)
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo2, tag1, image1)
			So(err, ShouldBeNil)

			repos, err := metaDB.GetBookmarkedRepos(ctx1) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			repos, err = metaDB.GetBookmarkedRepos(ctx2) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// anonymous cannot use bookmarks
			repos, err = metaDB.GetBookmarkedRepos(ctx3) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			toggleState, err := metaDB.ToggleBookmarkRepo(ctx3, repo1) //nolint: contextcheck
			So(err, ShouldNotBeNil)
			So(toggleState, ShouldEqual, mTypes.NotChanged)

			repos, err = metaDB.GetBookmarkedRepos(ctx3) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 bookmarks repo 1, User 2 has no bookmarks
			toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo1) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			repos, err = metaDB.GetBookmarkedRepos(ctx1) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetBookmarkedRepos(ctx2) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)

			// User 1 and User 2 bookmark only repo 1
			toggleState, err = metaDB.ToggleBookmarkRepo(ctx2, repo1) //nolint: contextcheck
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			repos, err = metaDB.GetBookmarkedRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			repos, err = metaDB.GetBookmarkedRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			// User 1 bookmarks repos 1 and 2, and User 2 bookmarks only repo 1
			toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo2)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			repos, err = metaDB.GetBookmarkedRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 2)
			So(repos, ShouldContain, repo1)
			So(repos, ShouldContain, repo2)

			repos, err = metaDB.GetBookmarkedRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			// User 1 bookmarks only repo 2, and User 2 bookmarks only repo 1
			toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Removed)

			repos, err = metaDB.GetBookmarkedRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo2)

			repos, err = metaDB.GetBookmarkedRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 1)
			So(repos, ShouldContain, repo1)

			// User 1 bookmarks both repos 1 and 2, and User 2 removes all bookmarks
			toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Added)

			toggleState, err = metaDB.ToggleBookmarkRepo(ctx2, repo1)
			So(err, ShouldBeNil)
			So(toggleState, ShouldEqual, mTypes.Removed)

			repos, err = metaDB.GetBookmarkedRepos(ctx1)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 2)
			So(repos, ShouldContain, repo1)
			So(repos, ShouldContain, repo2)

			repos, err = metaDB.GetBookmarkedRepos(ctx2)
			So(err, ShouldBeNil)
			So(len(repos), ShouldEqual, 0)
		})

		Convey("Test UpdateStatsOnDownload", func() {
			var (
				repo1  = "repo1"
				tag1   = "0.0.1"
				image1 = CreateRandomImage().AsImageMeta()
			)

			err := metaDB.SetRepoReference(ctx, repo1, tag1, image1)
			So(err, ShouldBeNil)

			err = metaDB.UpdateStatsOnDownload(repo1, tag1)
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)

			So(repoMeta.Statistics[image1.Digest.String()].DownloadCount, ShouldEqual, 1)

			err = metaDB.UpdateStatsOnDownload(repo1, tag1)
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)

			So(repoMeta.Statistics[image1.Digest.String()].DownloadCount, ShouldEqual, 2)
			So(time.Now(), ShouldHappenAfter, repoMeta.Statistics[image1.Digest.String()].LastPullTimestamp)
		})

		Convey("Test AddImageSignature", func() {
			var (
				repo1  = "repo1"
				tag1   = "0.0.1"
				image1 = CreateRandomImage().AsImageMeta()
			)

			err := metaDB.SetRepoReference(ctx, repo1, tag1, image1)
			So(err, ShouldBeNil)

			err = metaDB.AddManifestSignature(repo1, image1.Digest, mTypes.SignatureMetadata{
				SignatureType:   "cosign",
				SignatureDigest: "digest",
				LayersInfo:      []mTypes.LayerInfo{{LayerDigest: "layer-digest", LayerContent: []byte{10}}},
			})
			So(err, ShouldBeNil)

			err = metaDB.AddManifestSignature(repo1, image1.Digest, mTypes.SignatureMetadata{
				SignatureType:   "cosign",
				SignatureTag:    fmt.Sprintf("sha256-%s.sig", image1.Digest.Encoded()),
				SignatureDigest: "digesttag",
				LayersInfo:      []mTypes.LayerInfo{{LayerDigest: "layer-digest", LayerContent: []byte{10}}},
			})
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.Signatures[image1.Digest.String()]["cosign"][0].SignatureManifestDigest,
				ShouldResemble, "digesttag")
			So(repoMeta.Signatures[image1.Digest.String()]["cosign"][1].SignatureManifestDigest,
				ShouldResemble, "digest")

			imageMeta, err := metaDB.GetImageMeta(image1.Digest)
			fullImageMeta := convert.GetFullImageMeta(tag1, repoMeta, imageMeta)

			So(err, ShouldBeNil)
			So(fullImageMeta.Signatures["cosign"][0].SignatureManifestDigest, ShouldResemble, "digesttag")
			So(fullImageMeta.Signatures["cosign"][0].LayersInfo[0].LayerDigest, ShouldResemble, "layer-digest")
			So(fullImageMeta.Signatures["cosign"][0].LayersInfo[0].LayerContent[0], ShouldEqual, 10)
			So(fullImageMeta.Signatures["cosign"][1].SignatureManifestDigest, ShouldResemble, "digest")
			So(fullImageMeta.Signatures["cosign"][1].LayersInfo[0].LayerDigest, ShouldResemble, "layer-digest")
			So(fullImageMeta.Signatures["cosign"][1].LayersInfo[0].LayerContent[0], ShouldEqual, 10)
		})

		Convey("Test UpdateSignaturesValidity", func() {
			Convey("untrusted signature", func() {
				var (
					repo1  = "repo1"
					tag1   = "0.0.1"
					image1 = CreateRandomImage()
				)

				err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)

				layerInfo := mTypes.LayerInfo{LayerDigest: "", LayerContent: []byte{}, SignatureKey: ""}

				err = metaDB.AddManifestSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{
					SignatureType:   "cosign",
					SignatureDigest: image1.DigestStr(),
					SignatureTag:    fmt.Sprintf("sha256-%s.sig", image1.Digest().Encoded()),
					LayersInfo:      []mTypes.LayerInfo{layerInfo},
				})
				So(err, ShouldBeNil)

				err = metaDB.UpdateSignaturesValidity(ctx, repo1, image1.Digest())
				So(err, ShouldBeNil)

				repoData, err := metaDB.GetRepoMeta(ctx, repo1)
				So(err, ShouldBeNil)
				So(repoData.Signatures[image1.DigestStr()]["cosign"][0].LayersInfo[0].Signer,
					ShouldBeEmpty)
				So(repoData.Signatures[image1.DigestStr()]["cosign"][0].LayersInfo[0].Date,
					ShouldBeZeroValue)

				Convey("with context done", func() {
					ctx, cancel := context.WithCancel(context.Background())
					cancel()

					err = metaDB.UpdateSignaturesValidity(ctx, repo1, image1.Digest())
					So(err, ShouldNotBeNil)
				})
			})

			//nolint: contextcheck
			Convey("trusted signature", func() {
				image1 := CreateRandomImage()
				repo := "repo1"
				tag := "0.0.1"

				err := metaDB.SetRepoReference(ctx, repo, tag, image1.AsImageMeta())
				So(err, ShouldBeNil)

				mediaType := jws.MediaTypeEnvelope

				signOpts := notation.SignerSignOptions{
					SignatureMediaType: mediaType,
					PluginConfig:       map[string]string{},
					ExpiryDuration:     24 * time.Hour,
				}

				tdir := t.TempDir()
				uuid, err := guuid.NewV4()
				So(err, ShouldBeNil)

				keyName := fmt.Sprintf("notation-sign-test-%s", uuid)

				signature.NotationPathLock.Lock()
				defer signature.NotationPathLock.Unlock()

				signature.LoadNotationPath(tdir)

				err = signature.GenerateNotationCerts(tdir, keyName)
				So(err, ShouldBeNil)

				// getSigner
				var newSigner notation.Signer

				// ResolveKey
				signingKeys, err := signature.LoadNotationSigningkeys(tdir)
				So(err, ShouldBeNil)

				idx := tCommon.Index(signingKeys.Keys, keyName)
				So(idx, ShouldBeGreaterThanOrEqualTo, 0)

				key := signingKeys.Keys[idx]

				if key.X509KeyPair != nil {
					newSigner, err = signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath)
					So(err, ShouldBeNil)
				}

				descToSign := ispec.Descriptor{
					MediaType: image1.Manifest.MediaType,
					Digest:    image1.Digest(),
					Size:      image1.ManifestDescriptor.Size,
				}

				ctx := context.Background()

				sig, _, err := newSigner.Sign(ctx, descToSign, signOpts)
				So(err, ShouldBeNil)

				layerInfo := mTypes.LayerInfo{
					LayerDigest:  string(godigest.FromBytes(sig)),
					LayerContent: sig, SignatureKey: mediaType,
				}

				err = metaDB.AddManifestSignature(repo, image1.Digest(), mTypes.SignatureMetadata{
					SignatureType:   "notation",
					SignatureDigest: string(godigest.FromString("signature digest")),
					LayersInfo:      []mTypes.LayerInfo{layerInfo},
				})
				So(err, ShouldBeNil)

				certificateContent, err := os.ReadFile(path.Join(
					tdir,
					"notation/localkeys",
					keyName+".crt",
				))
				So(err, ShouldBeNil)
				So(certificateContent, ShouldNotBeNil)

				imgTrustStore, ok := metaDB.ImageTrustStore().(*imagetrust.ImageTrustStore)
				So(ok, ShouldBeTrue)

				err = imagetrust.UploadCertificate(imgTrustStore.NotationStorage, certificateContent, "ca")
				So(err, ShouldBeNil)

				err = metaDB.UpdateSignaturesValidity(ctx, repo, image1.Digest()) //nolint:contextcheck
				So(err, ShouldBeNil)

				repoData, err := metaDB.GetRepoMeta(ctx, repo)
				So(err, ShouldBeNil)

				So(repoData.Signatures[image1.DigestStr()]["notation"][0].LayersInfo[0].Signer,
					ShouldNotBeEmpty)
				So(repoData.Signatures[image1.DigestStr()]["notation"][0].LayersInfo[0].Date,
					ShouldNotBeZeroValue)
			})
		})

		Convey("Test AddImageSignature with inverted order", func() {
			var (
				repo1  = "repo1"
				tag1   = "0.0.1"
				image1 = CreateRandomImage()
			)

			err := metaDB.AddManifestSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{
				SignatureType:   "cosign",
				SignatureTag:    fmt.Sprintf("sha256-%s.sig", image1.Digest().Encoded()),
				SignatureDigest: "digest",
			})
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.Signatures[image1.DigestStr()]["cosign"][0].SignatureManifestDigest,
				ShouldResemble, "digest")
		})

		Convey("Test DeleteSignature", func() {
			var (
				repo1  = "repo1"
				tag1   = "0.0.1"
				image1 = CreateRandomImage()
			)

			err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.AddManifestSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{
				SignatureType:   "cosign",
				SignatureTag:    fmt.Sprintf("sha256-%s.sig", image1.Digest().Encoded()),
				SignatureDigest: "digest",
			})
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.Signatures[image1.DigestStr()]["cosign"][0].SignatureManifestDigest,
				ShouldResemble, "digest")

			err = metaDB.DeleteSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{
				SignatureType:   "cosign",
				SignatureDigest: "digest",
			})
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, repo1)
			So(err, ShouldBeNil)
			So(repoMeta.Signatures[image1.DigestStr()]["cosign"], ShouldBeEmpty)

			err = metaDB.DeleteSignature(repo1, "badDigest", mTypes.SignatureMetadata{
				SignatureType:   "cosign",
				SignatureDigest: "digest",
			})
			So(err, ShouldNotBeNil)
		})

		Convey("Test SearchRepos", func() {
			var (
				repo1  = "repo1"
				repo2  = "repo2"
				repo3  = "repo3"
				tag1   = "0.0.1"
				tag2   = "0.0.2"
				tag3   = "0.0.3"
				image1 = CreateRandomImage()
				image2 = CreateRandomImage()
				image3 = CreateRandomImage()
				ctx    = context.Background()
			)

			_ = repo3

			Convey("Search all repos", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, repo2, tag3, image3.AsImageMeta())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, "")
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 2)

				repos := map[string]map[string]string{}
				for _, repoMeta := range repoMetaList {
					if _, exists := repos[repoMeta.Name]; !exists {
						repos[repoMeta.Name] = map[string]string{}
					}

					for tag, descriptor := range repoMeta.Tags {
						repos[repoMeta.Name][tag] = descriptor.Digest
					}
				}

				So(repos[repo1][tag1], ShouldEqual, image1.DigestStr())
				So(repos[repo1][tag2], ShouldEqual, image2.DigestStr())
				So(repos[repo2][tag3], ShouldEqual, image3.DigestStr())
			})

			Convey("Search repos including empty ones", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta())
				So(err, ShouldBeNil)
				// We need to add a new reference and then remove it in order to obtain the empty repo
				err = metaDB.SetRepoReference(ctx, repo2, tag3, image3.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.RemoveRepoReference(repo2, tag3, image3.Digest())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, "")
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 1) // Empty repos are not returned

				repos := map[string]map[string]string{}
				for _, repoMeta := range repoMetaList {
					if _, exists := repos[repoMeta.Name]; !exists {
						repos[repoMeta.Name] = map[string]string{}
					}

					for tag, descriptor := range repoMeta.Tags {
						repos[repoMeta.Name][tag] = descriptor.Digest
					}
				}

				So(repos[repo1][tag1], ShouldEqual, image1.DigestStr())
				So(repos[repo1][tag2], ShouldEqual, image2.DigestStr())
				So(len(repos[repo2]), ShouldEqual, 0)
			})

			Convey("Search a repo by name", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, repo1)
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 1)
				So(repoMetaList[0].Tags[tag1].Digest, ShouldResemble, image1.DigestStr())
			})

			Convey("Search non-existing repo by name", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)

				err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, "RepoThatDoesntExist")
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 0)
			})

			Convey("Search with partial match", func() {
				err := metaDB.SetRepoReference(ctx, "alpine", tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "pine", tag2, image2.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "golang", tag3, image3.AsImageMeta())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, "pine")
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 2)
			})

			Convey("Search multiple repos that share manifests", func() {
				err := metaDB.SetRepoReference(ctx, "alpine", tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "pine", tag2, image1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "golang", tag3, image1.AsImageMeta())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, "")
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 3)
			})

			Convey("Search repos with access control", func() {
				err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, repo2, tag2, image2.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, repo3, tag3, image3.AsImageMeta())
				So(err, ShouldBeNil)

				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("username")
				userAc.SetGlobPatterns("read", map[string]bool{
					repo1: true,
					repo2: true,
				})

				ctx := userAc.DeriveContext(context.Background()) //nolint: contextcheck

				repoMetaList, err := metaDB.SearchRepos(ctx, "repo") //nolint: contextcheck
				So(err, ShouldBeNil)
				So(len(repoMetaList), ShouldEqual, 2)

				for _, k := range repoMetaList {
					So(k.Name, ShouldBeIn, []string{repo1, repo2})
				}
			})

			Convey("Search Repos with Indexes", func() {
				var (
					tag4      = "0.0.4"
					subImage1 = CreateRandomImage()
					subImage2 = CreateRandomImage()
					multiarch = CreateMultiarchWith().Images([]Image{subImage1, subImage2}).Build()

					tag5   = "0.0.5"
					image1 = CreateRandomImage()
				)

				err := metaDB.SetRepoReference(ctx, "repo", subImage1.DigestStr(), subImage1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "repo", subImage2.DigestStr(), subImage2.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "repo", tag4, multiarch.AsImageMeta())
				So(err, ShouldBeNil)

				err = metaDB.SetRepoReference(ctx, "repo", tag5, image1.AsImageMeta())
				So(err, ShouldBeNil)

				repoMetaList, err := metaDB.SearchRepos(ctx, "repo")
				So(err, ShouldBeNil)

				So(len(repoMetaList), ShouldEqual, 1)
				So(repoMetaList[0].Name, ShouldResemble, "repo")
				So(repoMetaList[0].Tags, ShouldContainKey, tag4)
				So(repoMetaList[0].Tags[tag4].MediaType, ShouldResemble, ispec.MediaTypeImageIndex)
				So(repoMetaList[0].Tags, ShouldContainKey, tag5)
				So(repoMetaList[0].Tags[tag5].MediaType, ShouldResemble, ispec.MediaTypeImageManifest)
			})
		})

		Convey("Test SearchTags", func() {
			var (
				repo1  = "repo1"
				repo2  = "repo2"
				image1 = CreateRandomImage()
				image2 = CreateRandomImage()
				image3 = CreateRandomImage()
				ctx    = context.Background()
			)

			err := metaDB.SetRepoReference(ctx, repo1, "0.0.1", image1.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "0.0.2", image3.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "0.1.0", image2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "1.0.0", image2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "1.0.1", image2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo2, "0.0.1", image3.AsImageMeta())
			So(err, ShouldBeNil)

			Convey("With exact match", func() {
				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:0.0.1")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 1)
				So(fullImageMetaList[0].Digest.String(), ShouldResemble, image1.DigestStr())
			})

			Convey("With no match", func() {
				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:badtag")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 0)
			})

			Convey("With no permission", func() {
				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("user1")
				userAc.SetGlobPatterns("read",
					map[string]bool{
						repo1: false,
						repo2: false,
					},
				)
				ctx1 := userAc.DeriveContext(context.Background())

				fullImageMetaList, err := metaDB.SearchTags(ctx1, "repo1:0.0.1")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 0)
			})

			Convey("With partial repo path", func() {
				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo:0.0.1")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 0)
			})

			Convey("With partial tag", func() {
				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:0.0")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 2)

				tags := map[string]struct{}{}
				for _, imageMeta := range fullImageMetaList {
					tags[imageMeta.Tag] = struct{}{}
				}

				So(tags, ShouldContainKey, "0.0.2")
				So(tags, ShouldContainKey, "0.0.1")

				fullImageMetaList, err = metaDB.SearchTags(ctx, "repo1:0.")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 3)

				tags = map[string]struct{}{}
				for _, imageMeta := range fullImageMetaList {
					tags[imageMeta.Tag] = struct{}{}
				}

				So(tags, ShouldContainKey, "0.0.1")
				So(tags, ShouldContainKey, "0.0.2")
				So(tags, ShouldContainKey, "0.1.0")
			})

			Convey("With bad query", func() {
				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo:0.0.1:test")
				So(err, ShouldNotBeNil)
				So(len(fullImageMetaList), ShouldEqual, 0)
			})

			Convey("Search with access control", func() {
				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("username")
				userAc.SetGlobPatterns("read", map[string]bool{
					repo1: true,
					repo2: false,
				})

				ctx := userAc.DeriveContext(context.Background())

				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 5)
				So(fullImageMetaList[0].Repo, ShouldResemble, repo1)

				fullImageMetaList, err = metaDB.SearchTags(ctx, "repo2:")
				So(err, ShouldBeNil)
				So(fullImageMetaList, ShouldBeEmpty)
			})

			Convey("Search Tags with Indexes", func() {
				var (
					tag4      = "0.0.4"
					subImage1 = CreateRandomImage()
					subImage2 = CreateRandomImage()
					multiarch = CreateMultiarchWith().Images([]Image{subImage1, subImage2}).Build()

					tag5   = "0.0.5"
					image5 = CreateRandomImage()

					tag6   = "6.0.0"
					image6 = CreateRandomImage()
				)

				err = metaDB.SetRepoReference(ctx, "repo", subImage1.DigestStr(), subImage1.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "repo", subImage2.DigestStr(), subImage2.AsImageMeta())
				So(err, ShouldBeNil)
				err = metaDB.SetRepoReference(ctx, "repo", tag4, multiarch.AsImageMeta())
				So(err, ShouldBeNil)

				err = metaDB.SetRepoReference(ctx, "repo", tag5, image5.AsImageMeta())
				So(err, ShouldBeNil)

				err = metaDB.SetRepoReference(ctx, "repo", tag6, image6.AsImageMeta())
				So(err, ShouldBeNil)

				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo:0.0")
				So(err, ShouldBeNil)

				tags := map[string]struct{}{}
				for _, imageMeta := range fullImageMetaList {
					tags[imageMeta.Tag] = struct{}{}
				}

				So(len(fullImageMetaList), ShouldEqual, 2)
				So(tags, ShouldContainKey, tag4)
				So(tags, ShouldContainKey, tag5)
				So(tags, ShouldNotContainKey, tag6)

				multiarchImageMeta := mTypes.FullImageMeta{}
				found := false

				for _, imageMeta := range fullImageMetaList {
					if imageMeta.MediaType == ispec.MediaTypeImageIndex {
						multiarchImageMeta = imageMeta
						found = true
					}
				}

				So(found, ShouldBeTrue)
				So(len(multiarchImageMeta.Manifests), ShouldEqual, 2)

				digests := []string{}
				for _, manifest := range multiarchImageMeta.Manifests {
					digests = append(digests, manifest.Digest.String())
				}

				So(digests, ShouldContain, subImage1.DigestStr())
				So(digests, ShouldContain, subImage2.DigestStr())
			})

			Convey("With referrer", func() {
				refImage := CreateRandomImageWith().Subject(image1.DescriptorRef()).Build()
				err := metaDB.SetRepoReference(ctx, repo1, "ref-tag", refImage.AsImageMeta())
				So(err, ShouldBeNil)

				fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:0.0.1")
				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 1)
				So(len(fullImageMetaList[0].Referrers), ShouldEqual, 1)
				So(fullImageMetaList[0].Referrers[0].Digest, ShouldResemble, refImage.DigestStr())
			})
		})

		Convey("Test FilterTags", func() {
			var (
				repo1  = "repo1"
				repo2  = "repo2"
				image1 = CreateRandomImage()
				image2 = CreateRandomImage()
				image3 = CreateRandomImage()

				subImage1 = CreateRandomImage()
				subImage2 = CreateRandomImage()
				multiarch = CreateMultiarchWith().Images([]Image{subImage1, subImage2}).Build()
				ctx       = context.Background()
			)

			err := metaDB.SetRepoReference(ctx, repo1, subImage1.DigestStr(), subImage1.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, subImage2.DigestStr(), subImage2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "2.0.0", multiarch.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, repo1, "0.0.1", image1.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "0.0.2", image3.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "0.1.0", image2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "1.0.0", image2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo1, "1.0.1", image2.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo2, "0.0.1", image3.AsImageMeta())
			So(err, ShouldBeNil)

			Convey("Return all tags", func() {
				fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta)

				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 7)

				tags := []string{}
				indexImage := mTypes.FullImageMeta{}

				for _, imageMeta := range fullImageMetaList {
					tags = append(tags, imageMeta.Tag)

					if imageMeta.MediaType == ispec.MediaTypeImageIndex {
						indexImage = imageMeta
					}
				}

				So(zcommon.Contains(tags, "0.0.1"), ShouldBeTrue)
				So(zcommon.Contains(tags, "0.0.2"), ShouldBeTrue)
				So(zcommon.Contains(tags, "0.1.0"), ShouldBeTrue)
				So(zcommon.Contains(tags, "1.0.0"), ShouldBeTrue)
				So(zcommon.Contains(tags, "1.0.1"), ShouldBeTrue)
				So(zcommon.Contains(tags, "2.0.0"), ShouldBeTrue)
				So(zcommon.Contains(tags, "0.0.1"), ShouldBeTrue)

				So(indexImage.Digest.String(), ShouldResemble, multiarch.DigestStr())

				digests := []string{}
				for _, manifest := range indexImage.Manifests {
					digests = append(digests, manifest.Digest.String())
				}

				So(digests, ShouldContain, subImage1.DigestStr())
				So(digests, ShouldContain, subImage2.DigestStr())
			})

			Convey("Return all tags in a specific repo", func() {
				fullImageMetaList, err := metaDB.FilterTags(ctx, func(repo, tag string) bool { return repo == repo1 },
					mTypes.AcceptAllImageMeta)

				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 6)

				tags := map[string]struct{}{}
				indexImage := mTypes.FullImageMeta{}

				for _, imageMeta := range fullImageMetaList {
					tags[imageMeta.Tag] = struct{}{}

					if imageMeta.MediaType == ispec.MediaTypeImageIndex {
						indexImage = imageMeta
					}
				}

				So(tags, ShouldContainKey, "0.0.1")
				So(tags, ShouldContainKey, "0.0.2")
				So(tags, ShouldContainKey, "0.1.0")
				So(tags, ShouldContainKey, "1.0.0")
				So(tags, ShouldContainKey, "1.0.1")
				So(tags, ShouldContainKey, "2.0.0")

				So(indexImage.Digest.String(), ShouldResemble, multiarch.DigestStr())

				digests := []string{}
				for _, manifest := range indexImage.Manifests {
					digests = append(digests, manifest.Digest.String())
				}

				So(digests, ShouldContain, subImage1.DigestStr())
				So(digests, ShouldContain, subImage2.DigestStr())
			})

			Convey("Filter everything out", func() {
				fullImageMetaList, err := metaDB.FilterTags(ctx,
					func(repo, tag string) bool { return false },
					func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { return false },
				)

				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 0)
			})

			Convey("Search with access control", func() {
				userAc := reqCtx.NewUserAccessControl()
				userAc.SetUsername("username")
				userAc.SetGlobPatterns("read", map[string]bool{
					repo1: false,
					repo2: true,
				})

				ctx := userAc.DeriveContext(context.Background())

				fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta)

				So(err, ShouldBeNil)
				So(len(fullImageMetaList), ShouldEqual, 1)
				So(fullImageMetaList[0].Repo, ShouldResemble, repo2)
				So(fullImageMetaList[0].Tag, ShouldResemble, "0.0.1")
			})
		})

		Convey("Test Referrers", func() {
			image1 := CreateRandomImage()

			err := metaDB.SetRepoReference(ctx, "repo", "tag", image1.AsImageMeta())
			So(err, ShouldBeNil)

			// Artifact 1 with artifact type in Manifest
			artifact1 := CreateImageWith().
				RandomLayers(10, 2).DefaultConfig().
				ArtifactType("art-type1").
				Subject(image1.DescriptorRef()).
				Build()

			err = metaDB.SetRepoReference(ctx, "repo", artifact1.DigestStr(), artifact1.AsImageMeta())
			So(err, ShouldBeNil)

			// Artifact 2 with artifact type in Config media type
			artifact2 := CreateImageWith().
				RandomLayers(10, 2).
				ArtifactConfig("art-type2").
				Subject(image1.DescriptorRef()).
				Build()

			err = metaDB.SetRepoReference(ctx, "repo", artifact2.DigestStr(), artifact2.AsImageMeta())
			So(err, ShouldBeNil)

			// GetReferrers
			referrers, err := metaDB.GetReferrersInfo("repo", image1.Digest(), nil)
			So(len(referrers), ShouldEqual, 2)
			So(referrers, ShouldContain, mTypes.ReferrerInfo{
				Digest:       artifact1.DigestStr(),
				MediaType:    ispec.MediaTypeImageManifest,
				ArtifactType: "art-type1",
				Size:         int(artifact1.ManifestDescriptor.Size),
			})
			So(referrers, ShouldContain, mTypes.ReferrerInfo{
				Digest:       artifact2.DigestStr(),
				MediaType:    ispec.MediaTypeImageManifest,
				ArtifactType: "art-type2",
				Size:         int(artifact2.ManifestDescriptor.Size),
			})
			So(err, ShouldBeNil)

			// Delete the Referrers
			err = metaDB.RemoveRepoReference("repo", artifact1.DigestStr(), artifact1.Digest())
			So(err, ShouldBeNil)

			referrers, err = metaDB.GetReferrersInfo("repo", image1.Digest(), nil)
			So(err, ShouldBeNil)
			So(len(referrers), ShouldEqual, 1)

			err = metaDB.RemoveRepoReference("repo", artifact2.DigestStr(), artifact2.Digest())
			So(err, ShouldBeNil)

			referrers, err = metaDB.GetReferrersInfo("repo", image1.Digest(), nil)
			So(err, ShouldBeNil)
			So(len(referrers), ShouldEqual, 0)
		})

		Convey("Test Referrers add same one twice but with different tags - delete by tag then digest", func() {
			_, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldNotBeNil)

			tag := "tag1"
			refTag := "refTag"
			image := CreateRandomImage()
			referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build()

			err = metaDB.SetRepoReference(ctx, "repo", tag, image.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, "repo", refTag, referrer.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta())
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1)

			err = metaDB.RemoveRepoReference("repo", refTag, referrer.Digest())
			So(err, ShouldBeNil)

			// we still have the untagged manifest
			repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1)

			err = metaDB.RemoveRepoReference("repo", referrer.DigestStr(), referrer.Digest())
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 0)
		})

		Convey("Test Referrers add same one twice but with different tags - delete by digest", func() {
			_, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldNotBeNil)

			tag := "tag-1"
			refTag := "refTag"
			image := CreateRandomImage()
			referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build()

			err = metaDB.SetRepoReference(ctx, "repo", tag, image.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, "repo", refTag, referrer.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta())
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1)

			// this should delete all references
			err = metaDB.RemoveRepoReference("repo", referrer.DigestStr(), referrer.Digest())
			So(err, ShouldBeNil)

			repoMeta, err = metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 0)
		})

		Convey("Test Referrers add same one twice", func() {
			_, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldNotBeNil)

			tag := "tag-ref"
			image := CreateRandomImage()
			referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build()

			err = metaDB.SetRepoReference(ctx, "repo", tag, image.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta())
			So(err, ShouldBeNil)

			err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta())
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1)
		})

		Convey("GetReferrersInfo", func() {
			repo := "repo"
			tag := "test-tag"

			image := CreateRandomImage()
			err := metaDB.SetRepoReference(ctx, repo, tag, image.AsImageMeta())
			So(err, ShouldBeNil)

			referrerWantedType := CreateRandomImageWith().
				ArtifactType("wanted-type").
				Subject(image.DescriptorRef()).Build()

			referrerNotWantedType := CreateRandomImageWith().
				ArtifactType("not-wanted-type").
				Subject(image.DescriptorRef()).Build()

			err = metaDB.SetRepoReference(ctx, repo, referrerWantedType.DigestStr(),
				referrerWantedType.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo, referrerNotWantedType.DigestStr(),
				referrerNotWantedType.AsImageMeta())
			So(err, ShouldBeNil)

			referrerInfo, err := metaDB.GetReferrersInfo("repo", image.Digest(), []string{"wanted-type"})
			So(err, ShouldBeNil)
			So(len(referrerInfo), ShouldEqual, 1)
			So(referrerInfo[0].ArtifactType, ShouldResemble, "wanted-type")
			So(referrerInfo[0].Digest, ShouldResemble, referrerWantedType.DigestStr())
		})

		Convey("FilterImageMeta", func() {
			repo := "repo"
			tag := "tag"

			Convey("Just manifests", func() {
				image := CreateRandomImage()
				err := metaDB.SetRepoReference(ctx, repo, tag, image.AsImageMeta())
				So(err, ShouldBeNil)

				imageMeta, err := metaDB.FilterImageMeta(ctx, []string{image.DigestStr()})
				So(err, ShouldBeNil)
				So(imageMeta, ShouldContainKey, image.DigestStr())

				_, err = metaDB.FilterImageMeta(ctx, []string{image.DigestStr(), "bad-digest"})
				So(err, ShouldNotBeNil)
			})

			Convey("Index", func() {
				multi := CreateRandomMultiarch()
				digests := []string{}

				for i := range multi.Images {
					err := metaDB.SetRepoReference(ctx, repo, multi.Images[i].DigestStr(),
						multi.Images[i].AsImageMeta())
					So(err, ShouldBeNil)

					digests = append(digests, multi.Images[i].DigestStr())
				}

				err := metaDB.SetRepoReference(ctx, repo, tag, multi.AsImageMeta())
				So(err, ShouldBeNil)

				imageMeta, err := metaDB.FilterImageMeta(ctx, []string{multi.DigestStr()})
				So(err, ShouldBeNil)
				So(imageMeta, ShouldContainKey, multi.DigestStr())

				actualDigests := tCommon.AccumulateField(imageMeta[multi.DigestStr()].Manifests, getManifestDigest)
				So(tCommon.ContainSameElements(actualDigests, digests), ShouldBeTrue)
			})
		})

		Convey("ResetDB", func() {
			repo := "repo-reset"
			tag := "tag-reset"

			image := CreateRandomImage()
			referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build()
			err := metaDB.SetRepoReference(ctx, repo, tag, image.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo, tag, referrer.AsImageMeta())
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
			So(err, ShouldBeNil)
			So(repoMeta.Tags, ShouldNotBeEmpty)
			So(repoMeta.Statistics, ShouldNotBeEmpty)
			So(repoMeta.Referrers, ShouldNotBeEmpty)

			err = metaDB.ResetDB()
			So(err, ShouldBeNil)

			_, err = metaDB.GetRepoMeta(ctx, repo)
			So(err, ShouldNotBeNil)
		})

		Convey("FilterRepos", func() {
			repo := "repoFilter"
			repoUnmatched := "repoExcluded"
			tag1 := "tag1"
			tag2 := "tag22"
			tag3 := "tag3"
			tag4 := "tag44"

			userAc := reqCtx.NewUserAccessControl()
			userAc.SetUsername("username")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo:          true,
				repoUnmatched: false,
			})

			ctx := userAc.DeriveContext(context.Background())

			image := CreateImageWith().DefaultLayers().PlatformConfig("image-platform", "image-os").Build()
			err := metaDB.SetRepoReference(ctx, repo, tag1, image.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoUnmatched, tag3, image.AsImageMeta())
			So(err, ShouldBeNil)

			multiarch := CreateMultiarchWith().
				Images([]Image{
					CreateImageWith().DefaultLayers().PlatformConfig("sub-platform1", "sub-os1").Build(),
					CreateImageWith().DefaultLayers().PlatformConfig("sub-platform2", "sub-os2").Build(),
				}).Build()

			for _, img := range multiarch.Images {
				err := metaDB.SetRepoReference(ctx, repo, img.DigestStr(), img.AsImageMeta())
				So(err, ShouldBeNil)
			}

			err = metaDB.SetRepoReference(ctx, repo, tag2, multiarch.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoUnmatched, tag4, multiarch.AsImageMeta())
			So(err, ShouldBeNil)

			//nolint: contextcheck
			repoMetaList, err := metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames,
				mTypes.AcceptAllRepoMeta)
			So(err, ShouldBeNil)
			So(len(repoMetaList), ShouldEqual, 1)
			repoMeta := repoMetaList[0]
			So(repoMeta.Platforms, ShouldContain, ispec.Platform{Architecture: "image-platform", OS: "image-os"})
			So(repoMeta.Platforms, ShouldContain, ispec.Platform{Architecture: "sub-platform1", OS: "sub-os1"})
			So(repoMeta.Platforms, ShouldContain, ispec.Platform{Architecture: "sub-platform2", OS: "sub-os2"})
		})

		Convey("Test bookmarked/starred field present in returned RepoMeta", func() {
			repo99 := "repo99"
			userAc := reqCtx.NewUserAccessControl()
			userAc.SetUsername("user1")
			userAc.SetGlobPatterns("read", map[string]bool{
				repo99: true,
			})

			ctx := userAc.DeriveContext(context.Background())

			image := CreateRandomImage()

			err := metaDB.SetRepoReference(ctx, repo99, "tag", image.AsImageMeta())
			So(err, ShouldBeNil)

			repoMetaList, err := metaDB.SearchRepos(ctx, repo99)
			So(err, ShouldBeNil)
			So(len(repoMetaList), ShouldEqual, 1)
			So(repoMetaList[0].IsBookmarked, ShouldBeFalse)
			So(repoMetaList[0].IsStarred, ShouldBeFalse)

			fullImageMetaList, err := metaDB.SearchTags(ctx, repo99+":")
			So(err, ShouldBeNil)
			So(len(fullImageMetaList), ShouldEqual, 1)
			So(fullImageMetaList[0].IsBookmarked, ShouldBeFalse)
			So(fullImageMetaList[0].IsStarred, ShouldBeFalse)

			repoMetaList, err = metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta)
			So(err, ShouldBeNil)
			So(len(repoMetaList), ShouldEqual, 1)
			So(repoMetaList[0].IsBookmarked, ShouldBeFalse)
			So(repoMetaList[0].IsStarred, ShouldBeFalse)

			fullImageMetaList, err = metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta)
			So(err, ShouldBeNil)
			So(len(fullImageMetaList), ShouldEqual, 1)
			So(fullImageMetaList[0].IsBookmarked, ShouldBeFalse)
			So(fullImageMetaList[0].IsStarred, ShouldBeFalse)

			_, err = metaDB.ToggleBookmarkRepo(ctx, repo99)
			So(err, ShouldBeNil)

			_, err = metaDB.ToggleStarRepo(ctx, repo99)
			So(err, ShouldBeNil)

			repoMetaList, err = metaDB.SearchRepos(ctx, repo99)
			So(err, ShouldBeNil)
			So(len(repoMetaList), ShouldEqual, 1)
			So(repoMetaList[0].IsBookmarked, ShouldBeTrue)
			So(repoMetaList[0].IsStarred, ShouldBeTrue)

			fullImageMetaList, err = metaDB.SearchTags(ctx, repo99+":")
			So(err, ShouldBeNil)
			So(len(fullImageMetaList), ShouldEqual, 1)
			So(fullImageMetaList[0].IsBookmarked, ShouldBeTrue)
			So(fullImageMetaList[0].IsStarred, ShouldBeTrue)

			repoMetaList, err = metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta)
			So(err, ShouldBeNil)
			So(len(repoMetaList), ShouldEqual, 1)
			So(repoMetaList[0].IsBookmarked, ShouldBeTrue)
			So(repoMetaList[0].IsStarred, ShouldBeTrue)

			fullImageMetaList, err = metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta)
			So(err, ShouldBeNil)
			So(len(fullImageMetaList), ShouldEqual, 1)
			So(fullImageMetaList[0].IsBookmarked, ShouldBeTrue)
			So(fullImageMetaList[0].IsStarred, ShouldBeTrue)
		})

		Convey("Test GetUserRepoMeta", func() {
			err := metaDB.ResetDB()
			So(err, ShouldBeNil)

			userAc := reqCtx.NewUserAccessControl()
			userAc.SetUsername("user1")
			userAc.SetGlobPatterns("read", map[string]bool{
				"repo": true,
			})

			ctx := userAc.DeriveContext(context.Background())

			err = metaDB.SetRepoReference(ctx, "repo", "tag", CreateDefaultImage().AsImageMeta())
			So(err, ShouldBeNil)

			_, err = metaDB.ToggleBookmarkRepo(ctx, "repo")
			So(err, ShouldBeNil)

			_, err = metaDB.ToggleStarRepo(ctx, "repo")
			So(err, ShouldBeNil)

			repoMeta, err := metaDB.GetRepoMeta(ctx, "repo")
			So(err, ShouldBeNil)
			So(repoMeta.IsBookmarked, ShouldBeTrue)
			So(repoMeta.IsStarred, ShouldBeTrue)
			So(repoMeta.Tags, ShouldContainKey, "tag")
		})

		Convey("GetAllRepoNames", func() {
			repo1 := "repo1"
			repo2 := "repo2"
			repo3 := "repo3"
			imageMeta := CreateRandomImage().AsImageMeta()

			err := metaDB.SetRepoReference(ctx, repo1, "tag", imageMeta)
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo2, "tag", imageMeta)
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repo3, "tag", imageMeta)
			So(err, ShouldBeNil)

			repos, err := metaDB.GetAllRepoNames()
			So(err, ShouldBeNil)
			So(repos, ShouldContain, repo1)
			So(repos, ShouldContain, repo2)
			So(repos, ShouldContain, repo3)

			err = metaDB.DeleteRepoMeta(repo1)
			So(err, ShouldBeNil)

			repos, err = metaDB.GetAllRepoNames()
			So(err, ShouldBeNil)
			So(repos, ShouldNotContain, repo1)
			So(repos, ShouldContain, repo2)
			So(repos, ShouldContain, repo3)

			err = metaDB.DeleteRepoMeta(repo2)
			So(err, ShouldBeNil)

			repos, err = metaDB.GetAllRepoNames()
			So(err, ShouldBeNil)
			So(repos, ShouldNotContain, repo1)
			So(repos, ShouldNotContain, repo2)
			So(repos, ShouldContain, repo3)

			err = metaDB.SetRepoReference(ctx, repo1, "tag", imageMeta)
			So(err, ShouldBeNil)

			repos, err = metaDB.GetAllRepoNames()
			So(err, ShouldBeNil)
			So(repos, ShouldContain, repo1)
			So(repos, ShouldNotContain, repo2)
			So(repos, ShouldContain, repo3)

			err = metaDB.DeleteRepoMeta(repo1)
			So(err, ShouldBeNil)

			repos, err = metaDB.GetAllRepoNames()
			So(err, ShouldBeNil)
			So(repos, ShouldNotContain, repo1)
			So(repos, ShouldNotContain, repo2)
			So(repos, ShouldContain, repo3)

			err = metaDB.DeleteRepoMeta(repo3)
			So(err, ShouldBeNil)

			repos, err = metaDB.GetAllRepoNames()
			So(err, ShouldBeNil)
			So(repos, ShouldNotContain, repo1)
			So(repos, ShouldNotContain, repo2)
			So(repos, ShouldNotContain, repo3)
		})

		Convey("Test Nested Indexes", func() {
			// nested manifest/indexes:
			// image111 -> multiArchBottom11 -> multiArchMiddle1 -> multiArchTop
			// image112 -> multiArchBottom11 -> multiArchMiddle1 -> multiArchTop
			// image121 -> multiArchBottom12 -> multiArchMiddle1 -> multiArchTop
			// image122 -> multiArchBottom12 -> multiArchMiddle1 -> multiArchTop
			// image211 -> multiArchBottom21 -> multiArchMiddle2 -> multiArchTop
			// image212 -> multiArchBottom21 -> multiArchMiddle2 -> multiArchTop
			// image31 -> multiArchMiddle3 -> multiArchTop
			// image32 -> multiArchMiddle3 -> multiArchTop
			repoName := "nested"

			image111 := CreateRandomImage()
			image112 := CreateRandomImage()
			multiArchBottom11 := CreateMultiarchWith().Images([]Image{image111, image112}).Build()

			err := metaDB.SetRepoReference(ctx, repoName, image111.DigestStr(), image111.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, image112.DigestStr(), image112.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom11.DigestStr(), multiArchBottom11.AsImageMeta())
			So(err, ShouldBeNil)

			image121 := CreateRandomImage()
			image122 := CreateRandomImage()
			multiArchBottom12 := CreateMultiarchWith().Images([]Image{image121, image122}).Build()

			err = metaDB.SetRepoReference(ctx, repoName, image121.DigestStr(), image121.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, image122.DigestStr(), image122.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom12.DigestStr(), multiArchBottom12.AsImageMeta())
			So(err, ShouldBeNil)

			indexMultiArchMiddle1 := ispec.Index{
				Versioned: specs.Versioned{SchemaVersion: 2},
				MediaType: ispec.MediaTypeImageIndex,
				Manifests: []ispec.Descriptor{
					{
						Digest:    multiArchBottom11.IndexDescriptor.Digest,
						Size:      multiArchBottom11.IndexDescriptor.Size,
						MediaType: ispec.MediaTypeImageIndex,
					},
					{
						Digest:    multiArchBottom12.IndexDescriptor.Digest,
						Size:      multiArchBottom12.IndexDescriptor.Size,
						MediaType: ispec.MediaTypeImageIndex,
					},
				},
			}

			indexMultiArchMiddle1Blob, err := json.Marshal(indexMultiArchMiddle1)
			So(err, ShouldBeNil)
			indexMultiArchMiddle1Digest := godigest.FromBytes(indexMultiArchMiddle1Blob)

			indexMultiArchMiddle1Meta := mTypes.ImageMeta{
				MediaType: ispec.MediaTypeImageIndex,
				Digest:    indexMultiArchMiddle1Digest,
				Size:      int64(len(indexMultiArchMiddle1Blob)),
				Index:     &indexMultiArchMiddle1,
			}

			err = metaDB.SetRepoReference(ctx, repoName, "multiArchMiddle1", indexMultiArchMiddle1Meta)
			So(err, ShouldBeNil)

			image211 := CreateRandomImage()
			image212 := CreateRandomImage()
			multiArchBottom21 := CreateMultiarchWith().Images([]Image{image211, image212}).Build()

			err = metaDB.SetRepoReference(ctx, repoName, image211.DigestStr(), image211.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, image212.DigestStr(), image212.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom21.DigestStr(), multiArchBottom21.AsImageMeta())
			So(err, ShouldBeNil)

			indexMultiArchMiddle2 := ispec.Index{
				Versioned: specs.Versioned{SchemaVersion: 2},
				MediaType: ispec.MediaTypeImageIndex,
				Manifests: []ispec.Descriptor{
					{
						Digest:    multiArchBottom21.IndexDescriptor.Digest,
						Size:      multiArchBottom21.IndexDescriptor.Size,
						MediaType: ispec.MediaTypeImageIndex,
					},
				},
			}

			indexMultiArchMiddle2Blob, err := json.Marshal(indexMultiArchMiddle2)
			So(err, ShouldBeNil)

			indexMultiArchMiddle2Digest := godigest.FromBytes(indexMultiArchMiddle2Blob)

			indexMultiArchMiddle2Meta := mTypes.ImageMeta{
				MediaType: ispec.MediaTypeImageIndex,
				Digest:    indexMultiArchMiddle2Digest,
				Size:      int64(len(indexMultiArchMiddle2Blob)),
				Index:     &indexMultiArchMiddle2,
			}

			err = metaDB.SetRepoReference(ctx, repoName, "multiArchMiddle2", indexMultiArchMiddle2Meta)
			So(err, ShouldBeNil)

			image31 := CreateRandomImage()
			image32 := CreateRandomImage()
			multiArchBottom3 := CreateMultiarchWith().Images([]Image{image31, image32}).Build()

			err = metaDB.SetRepoReference(ctx, repoName, image31.DigestStr(), image31.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, image32.DigestStr(), image32.AsImageMeta())
			So(err, ShouldBeNil)
			err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom3.DigestStr(), multiArchBottom3.AsImageMeta())
			So(err, ShouldBeNil)

			indexMultiArchTop := ispec.Index{
				Versioned: specs.Versioned{SchemaVersion: 2},
				MediaType: ispec.MediaTypeImageIndex,
				Manifests: []ispec.Descriptor{
					{
						Digest:    indexMultiArchMiddle1Digest,
						Size:      int64(len(indexMultiArchMiddle1Blob)),
						MediaType: ispec.MediaTypeImageIndex,
					},
					{
						Digest:    indexMultiArchMiddle2Digest,
						Size:      int64(len(indexMultiArchMiddle2Blob)),
						MediaType: ispec.MediaTypeImageIndex,
					},
					{
						Digest:    multiArchBottom3.IndexDescriptor.Digest,
						Size:      multiArchBottom3.IndexDescriptor.Size,
						MediaType: ispec.MediaTypeImageIndex,
					},
				},
			}

			indexMultiArchTopBlob, err := json.Marshal(indexMultiArchTop)
			So(err, ShouldBeNil)

			indexMultiArchTopBlobDigest := godigest.FromBytes(indexMultiArchTopBlob)

			indexMultiArchTopMeta := mTypes.ImageMeta{
				MediaType: ispec.MediaTypeImageIndex,
				Digest:    indexMultiArchTopBlobDigest,
				Size:      int64(len(indexMultiArchTopBlob)),
				Index:     &indexMultiArchTop,
			}

			err = metaDB.SetRepoReference(ctx, repoName, "multiArchTop", indexMultiArchTopMeta)
			So(err, ShouldBeNil)

			Convey("Test searchTags", func() {
				fullImageMetaList, err := metaDB.SearchTags(ctx, repoName+":"+"multiArch")
				So(err, ShouldBeNil)

				So(len(fullImageMetaList), ShouldEqual, 3)

				tags := map[string][]string{}

				for _, imageMeta := range fullImageMetaList {
					So(imageMeta.MediaType, ShouldEqual, ispec.MediaTypeImageIndex)

					digests := []string{}
					for _, manifest := range imageMeta.Manifests {
						digests = append(digests, manifest.Digest.String())
					}

					tags[imageMeta.Tag] = digests
				}

				So(tags, ShouldContainKey, "multiArchMiddle1")
				So(tags, ShouldContainKey, "multiArchMiddle2")
				So(tags, ShouldContainKey, "multiArchTop")

				So(len(tags["multiArchMiddle1"]), ShouldEqual, 4)
				So(len(tags["multiArchMiddle2"]), ShouldEqual, 2)
				So(len(tags["multiArchTop"]), ShouldEqual, 8)
			})
		})
	})
}

func TestRelevanceSorting(t *testing.T) {
	Convey("Test Relevance Sorting", t, func() {
		So(common.RankRepoName("alpine", "alpine"), ShouldEqual, 0)
		So(common.RankRepoName("test/alpine", "test/alpine"), ShouldEqual, 0)
		So(common.RankRepoName("test/alpine", "alpine"), ShouldEqual, -1)
		So(common.RankRepoName("alpine", "test/alpine"), ShouldEqual, 1)
		So(common.RankRepoName("test", "test/alpine"), ShouldEqual, 10)
		So(common.RankRepoName("pine", "test/alpine"), ShouldEqual, 3)
		So(common.RankRepoName("pine", "alpine/alpine"), ShouldEqual, 3)
		So(common.RankRepoName("pine", "alpine/test"), ShouldEqual, 30)
		So(common.RankRepoName("test/pine", "alpine"), ShouldEqual, -1)
		So(common.RankRepoName("repo/test", "repo/test/alpine"), ShouldEqual, 10)
		So(common.RankRepoName("repo/test/golang", "repo/test2/alpine"), ShouldEqual, -1)
		So(common.RankRepoName("repo/test/pine", "repo/test/alpine"), ShouldEqual, 3)
		So(common.RankRepoName("debian", "c3/debian/base-amd64"), ShouldEqual, 400)
		So(common.RankRepoName("debian/base-amd64", "c3/debian/base-amd64"), ShouldEqual, 400)
		So(common.RankRepoName("debian/base-amd64", "c3/aux/debian/base-amd64"), ShouldEqual, 800)
		So(common.RankRepoName("aux/debian", "c3/aux/debian/base-amd64"), ShouldEqual, 400)
	})
}

func TestCreateBoltDB(t *testing.T) {
	Convey("New() succeeds", t, func() {
		rootDir := t.TempDir()

		conf := config.New()
		conf.Storage.RootDirectory = rootDir

		log := log.NewLogger("debug", "")
		So(log, ShouldNotBeNil)

		Convey("Test New() with unspecified driver", func() {
			conf.Storage.CacheDriver = map[string]interface{}{}
		})

		Convey("Test New() with bad driver", func() {
			// we default to bolt in case of misconfiguration
			conf.Storage.CacheDriver = map[string]interface{}{"name": "somedriver"}
		})

		Convey("Test New() with specified driver", func() {
			conf.Storage.CacheDriver = map[string]interface{}{"name": "cache"}
		})

		repoDBPath := path.Join(rootDir, "meta.db")
		defer os.Remove(repoDBPath)

		metaDB, err := meta.New(conf.Storage.StorageConfig, log)
		So(err, ShouldBeNil)
		So(metaDB, ShouldNotBeNil)

		err = os.Chmod(repoDBPath, 0o200)
		So(err, ShouldBeNil)

		metaDB, err = meta.New(conf.Storage.StorageConfig, log)
		So(err, ShouldNotBeNil)
		So(metaDB, ShouldBeNil)

		err = os.Chmod(repoDBPath, 0o600)
		So(err, ShouldBeNil)
	})
}

func TestCreateRedisDB(t *testing.T) {
	Convey("Test New()", t, func() {
		conf := config.New()
		conf.Storage.RemoteCache = true

		log := log.NewLogger("debug", "")
		So(log, ShouldNotBeNil)

		Convey("Succeeds with default key prefix", func() {
			miniRedis := miniredis.RunT(t)

			cacheDriverParams := map[string]interface{}{
				"name": "redis",
				"url":  "redis://" + miniRedis.Addr(),
			}

			conf.Storage.CacheDriver = cacheDriverParams

			metaDB, err := meta.New(conf.Storage.StorageConfig, log)
			So(err, ShouldBeNil)
			So(metaDB, ShouldNotBeNil)
		})

		Convey("Succeeds with specific key prefix", func() {
			miniRedis := miniredis.RunT(t)

			cacheDriverParams := map[string]interface{}{
				"name": "redis",
				"url":  "redis://" + miniRedis.Addr(),
				"key":  "keyPrefix",
			}

			conf.Storage.CacheDriver = cacheDriverParams

			metaDB, err := meta.New(conf.Storage.StorageConfig, log)
			So(err, ShouldBeNil)
			So(metaDB, ShouldNotBeNil)
		})

		Convey("Fails on Ping()", func() {
			// Redis client will not be responding
			cacheDriverParams := map[string]interface{}{
				"name": "redis",
				"url":  "redis://127.0.0.1:" + tCommon.GetFreePort(),
			}

			conf.Storage.CacheDriver = cacheDriverParams

			_, err := meta.New(conf.Storage.StorageConfig, log)
			So(err, ShouldNotBeNil)
		})

		Convey("Fail on invalid parameters", func() {
			// Bad key types
			cacheDriverParams := map[string]interface{}{
				"name":      "redis",
				"url":       "redis://127.0.0.1:" + tCommon.GetFreePort(),
				"keyprefix": true,
			}

			conf.Storage.CacheDriver = cacheDriverParams

			testFunc := func() { _, _ = meta.New(conf.Storage.StorageConfig, log) }
			So(testFunc, ShouldPanic)

			cacheDriverParams = map[string]interface{}{
				"name":      "redis",
				"url":       "redis://127.0.0.1:" + tCommon.GetFreePort(),
				"keyprefix": "",
			}

			conf.Storage.CacheDriver = cacheDriverParams

			testFunc = func() { _, _ = meta.New(conf.Storage.StorageConfig, log) }
			So(testFunc, ShouldPanic)

			cacheDriverParams = map[string]interface{}{
				"name":      "redis",
				"url":       false,
				"keyprefix": "zot",
			}

			conf.Storage.CacheDriver = cacheDriverParams

			_, err := meta.New(conf.Storage.StorageConfig, log)
			So(err, ShouldNotBeNil)
		})
	})
}
