Esempio n. 1
0
            public Facts()
            {
                Options = new Mock <IOptionsSnapshot <Catalog2RegistrationConfiguration> >();
                Config  = new Catalog2RegistrationConfiguration
                {
                    LegacyBaseUrl        = "https://example/reg/",
                    GzippedBaseUrl       = "https://example/reg-gz/",
                    SemVer2BaseUrl       = "https://example/reg-gz-semver2/",
                    GalleryBaseUrl       = "https://example-gallery/",
                    FlatContainerBaseUrl = "https://example/fc/",
                };
                Options.Setup(x => x.Value).Returns(() => Config);

                LeafItem       = new RegistrationLeafItem();
                Page           = new RegistrationPage();
                Index          = new RegistrationIndex();
                Hive           = HiveType.Legacy;
                Id             = V3Data.PackageId;
                PackageDetails = new PackageDetailsCatalogLeaf
                {
                    PackageVersion = V3Data.NormalizedVersion,
                };
                Commit = new CatalogCommit(V3Data.CommitId, V3Data.CommitTimestamp);

                SerializerSettings            = NuGetJsonSerialization.Settings;
                SerializerSettings.Formatting = Formatting.Indented;
            }
Esempio n. 2
0
        public void UpdateIndexUrls(RegistrationIndex index, HiveType fromHive, HiveType toHive)
        {
            index.Url = _urlBuilder.ConvertHive(fromHive, toHive, index.Url);

            foreach (var pageItem in index.Items)
            {
                UpdatePageUrls(pageItem, fromHive, toHive);
            }
        }
Esempio n. 3
0
 public void UpdateIndex(RegistrationIndex index, HiveType hive, string id, int count)
 {
     Guard.Assert(count > 0, "Indexes must have at least one page item.");
     Guard.Assert(index.Items.Count == count, "The provided count must equal the number of page items.");
     index.Url     = _urlBuilder.GetIndexUrl(hive, id);
     index.Types   = JsonLdConstants.RegistrationIndexTypes;
     index.Count   = count;
     index.Context = JsonLdConstants.RegistrationContainerContext;
 }
Esempio n. 4
0
        public static IndexInfo New()
        {
            var index = new RegistrationIndex
            {
                Items = new List <RegistrationPage>(),
            };

            return(new IndexInfo(index, new List <PageInfo>()));
        }
Esempio n. 5
0
        public async Task WriteIndexAsync(
            HiveType hive,
            IReadOnlyList <HiveType> replicaHives,
            string id,
            RegistrationIndex index)
        {
            var path = _urlBuilder.GetIndexPath(id);

            await WriteAsync(hive, replicaHives, path, index, "index", _entityBuilder.UpdateIndexUrls);
        }
Esempio n. 6
0
        private static IndexInfo MakeIndexInfo(
            List <NuGetVersion> sortedVersions,
            int maxLeavesPerPage,
            Dictionary <NuGetVersion, string> versionToNormalized)
        {
            var index = new RegistrationIndex
            {
                Items = new List <RegistrationPage>(),
            };

            // Populate the pages.
            RegistrationPage currentPage = null;

            for (var i = 0; i < sortedVersions.Count; i++)
            {
                if (i % maxLeavesPerPage == 0)
                {
                    currentPage = new RegistrationPage
                    {
                        Items = new List <RegistrationLeafItem>(),
                    };
                    index.Items.Add(currentPage);
                }

                currentPage.Items.Add(new RegistrationLeafItem
                {
                    CatalogEntry = new RegistrationCatalogEntry
                    {
                        Version = versionToNormalized[sortedVersions[i]],
                    },
                });
            }

            // Update the bounds.
            foreach (var page in index.Items)
            {
                page.Count = page.Items.Count;
                page.Lower = page.Items.First().CatalogEntry.Version;
                page.Upper = page.Items.Last().CatalogEntry.Version;
            }

            return(IndexInfo.Existing(storage: null, hive: HiveType.SemVer2, index: index));
        }
Esempio n. 7
0
        public static IndexInfo Existing(IHiveStorage storage, HiveType hive, RegistrationIndex index)
        {
            // Ensure the index is sorted in ascending order by lower version bound.
            var sorted = index.Items
                         .Select(pageItem => new
            {
                PageItem = pageItem,
                PageInfo = PageInfo.Existing(pageItem, url => GetPageAsync(storage, hive, url)),
            })
                         .OrderBy(x => x.PageInfo.Lower)
                         .ToList();

            var items = sorted.Select(x => x.PageInfo).ToList();

            index.Items.Clear();
            index.Items.AddRange(sorted.Select(x => x.PageItem));

            return(new IndexInfo(index, items));
        }
            public async Task ConsidersMissingVersionAsUnavailable()
            {
                var index = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "0.0.0",
                            Upper = "10.0.0",
                            Url   = "http://example/page",
                            Items = new List <RegistrationLeafItem>
                            {
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "10.0.0",
                                    },
                                },
                            },
                        },
                    },
                };

                _registrationClient
                .Setup(x => x.GetIndexOrNullAsync(It.IsAny <string>()))
                .ReturnsAsync(index);

                var latest = await _target.GetLatestLeavesAsync(PackageId, _versions);

                Assert.Equal(_eachVersion, latest.Unavailable.OrderBy(x => x).ToArray());
                Assert.Empty(latest.Available);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync(It.IsAny <string>()), Times.Once);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync("https://example/v3-registration/nuget.versioning/index.json"), Times.Once);
                _registrationClient.Verify(
                    x => x.GetPageAsync(It.IsAny <string>()), Times.Never);
            }
Esempio n. 9
0
            public Facts(ITestOutputHelper output)
            {
                CloudBlobClient = new Mock <ICloudBlobClient>();
                EntityBuilder   = new Mock <IEntityBuilder>();
                Throttle        = new Mock <IThrottle>();
                Options         = new Mock <IOptionsSnapshot <Catalog2RegistrationConfiguration> >();
                Logger          = output.GetLogger <HiveStorage>();

                Config = new Catalog2RegistrationConfiguration
                {
                    LegacyBaseUrl           = "https://example/reg/",
                    LegacyStorageContainer  = "reg",
                    GzippedBaseUrl          = "https://example/reg-gz/",
                    GzippedStorageContainer = "reg-gz",
                    SemVer2BaseUrl          = "https://example/reg-gz-semver2/",
                    SemVer2StorageContainer = "reg-gz-semver2",
                    EnsureSingleSnapshot    = false,
                };
                LegacyContainer  = new Mock <ICloudBlobContainer>();
                GzippedContainer = new Mock <ICloudBlobContainer>();
                SemVer2Container = new Mock <ICloudBlobContainer>();
                LegacyBlob       = new Mock <ISimpleCloudBlob>();
                GzippedBlob      = new Mock <ISimpleCloudBlob>();
                SemVer2Blob      = new Mock <ISimpleCloudBlob>();
                LegacyStream     = new MemoryStream();
                GzippedStream    = new MemoryStream();
                SemVer2Stream    = new MemoryStream();
                LegacySegment    = new Mock <ISimpleBlobResultSegment>();
                Hive             = HiveType.Legacy;
                ReplicaHives     = new List <HiveType>();
                Id    = "NuGet.Versioning";
                Index = new RegistrationIndex();
                Page  = new RegistrationPage();
                Leaf  = new RegistrationLeaf();

                Options.Setup(x => x.Value).Returns(() => Config);
                CloudBlobClient.Setup(x => x.GetContainerReference(Config.LegacyStorageContainer)).Returns(() => LegacyContainer.Object);
                CloudBlobClient.Setup(x => x.GetContainerReference(Config.GzippedStorageContainer)).Returns(() => GzippedContainer.Object);
                CloudBlobClient.Setup(x => x.GetContainerReference(Config.SemVer2StorageContainer)).Returns(() => SemVer2Container.Object);
                LegacyContainer.Setup(x => x.GetBlobReference(It.IsAny <string>())).Returns(() => LegacyBlob.Object);
                GzippedContainer.Setup(x => x.GetBlobReference(It.IsAny <string>())).Returns(() => GzippedBlob.Object);
                SemVer2Container.Setup(x => x.GetBlobReference(It.IsAny <string>())).Returns(() => SemVer2Blob.Object);
                LegacyBlob.Setup(x => x.Properties).Returns(new BlobProperties());
                LegacyBlob.Setup(x => x.OpenReadAsync(It.IsAny <AccessCondition>())).ReturnsAsync(() => LegacyStream);
                LegacyBlob.Setup(x => x.Uri).Returns(new Uri("https://example/reg/something.json"));
                LegacyBlob
                .Setup(x => x.UploadFromStreamAsync(It.IsAny <Stream>(), It.IsAny <AccessCondition>()))
                .Returns(Task.CompletedTask)
                .Callback <Stream, AccessCondition>((s, _) => s.CopyTo(LegacyStream));
                LegacyBlob.Setup(x => x.ExistsAsync()).ReturnsAsync(true);
                LegacyContainer
                .Setup(x => x.ListBlobsSegmentedAsync(
                           It.IsAny <string>(),
                           It.IsAny <bool>(),
                           It.IsAny <BlobListingDetails>(),
                           It.IsAny <int?>(),
                           It.IsAny <BlobContinuationToken>(),
                           It.IsAny <BlobRequestOptions>(),
                           It.IsAny <OperationContext>(),
                           It.IsAny <CancellationToken>()))
                .Returns(() => Task.FromResult(LegacySegment.Object));
                LegacySegment.Setup(x => x.Results).Returns(new List <ISimpleCloudBlob>());
                GzippedBlob.Setup(x => x.Properties).Returns(new BlobProperties());
                GzippedBlob.Setup(x => x.OpenReadAsync(It.IsAny <AccessCondition>())).ReturnsAsync(() => GzippedStream);
                GzippedBlob.Setup(x => x.Uri).Returns(new Uri("https://example/reg-gz/something.json"));
                GzippedBlob
                .Setup(x => x.UploadFromStreamAsync(It.IsAny <Stream>(), It.IsAny <AccessCondition>()))
                .Returns(Task.CompletedTask)
                .Callback <Stream, AccessCondition>((s, _) => s.CopyTo(GzippedStream));
                GzippedBlob.Setup(x => x.ExistsAsync()).ReturnsAsync(true);
                SemVer2Blob.Setup(x => x.Properties).Returns(new BlobProperties());
                SemVer2Blob.Setup(x => x.OpenReadAsync(It.IsAny <AccessCondition>())).ReturnsAsync(() => SemVer2Stream);
                SemVer2Blob.Setup(x => x.Uri).Returns(new Uri("https://example/reg-gz-semver2/something.json"));
                SemVer2Blob
                .Setup(x => x.UploadFromStreamAsync(It.IsAny <Stream>(), It.IsAny <AccessCondition>()))
                .Returns(Task.CompletedTask)
                .Callback <Stream, AccessCondition>((s, _) => s.CopyTo(SemVer2Stream));
                SemVer2Blob.Setup(x => x.ExistsAsync()).ReturnsAsync(true);

                SerializeToStream(LegacyStream, new Dictionary <string, string> {
                    { "@id", LegacyBlob.Object.Uri.AbsoluteUri }
                });
                SerializeToStream(GzippedStream, new Dictionary <string, string> {
                    { "@id", GzippedBlob.Object.Uri.AbsoluteUri }
                });
                SerializeToStream(SemVer2Stream, new Dictionary <string, string> {
                    { "@id", SemVer2Blob.Object.Uri.AbsoluteUri }
                });

                Target = new HiveStorage(
                    CloudBlobClient.Object,
                    new RegistrationUrlBuilder(Options.Object),
                    EntityBuilder.Object,
                    Throttle.Object,
                    Options.Object,
                    Logger);
            }
        private void VerifyRegistrationIndex(
            MemoryStorage registrationStorage,
            RegistrationIndex index,
            Uri indexUri,
            string packageId,
            IReadOnlyList <ExpectedPage> expectedPages,
            int partitionSize,
            int packageCountThreshold)
        {
            Assert.Equal(indexUri.AbsoluteUri, index.IdKeyword);
            Assert.Equal(
                new[] { "catalog:CatalogRoot", "PackageRegistration", CatalogConstants.CatalogPermalink },
                index.TypeKeyword);

            Assert.True(Guid.TryParse(index.CommitId, out var guid));
            Assert.True(DateTime.TryParse(index.CommitTimeStamp, out var datetime));
            Assert.Equal(expectedPages.Count, index.Count);
            Assert.Equal(index.Count, index.Items.Length);

            for (var i = 0; i < index.Count; ++i)
            {
                var page           = index.Items[i];
                var expectedPage   = expectedPages[i];
                var expectFullPage = i < index.Count - 1;

                if (expectFullPage)
                {
                    Assert.Equal(partitionSize, page.Count);
                }
                else
                {
                    Assert.InRange(page.Count, low: 1, high: partitionSize);
                }

                if (page.Count == packageCountThreshold)
                {
                    VerifyRegistrationPageReference(
                        registrationStorage,
                        page,
                        packageId,
                        expectedPage,
                        index.CommitId,
                        index.CommitTimeStamp);
                }
                else
                {
                    var pageUri = GetRegistrationPageUri(packageId, expectedPage);

                    VerifyRegistrationPage(
                        registrationStorage,
                        page,
                        pageUri,
                        packageId,
                        expectedPage,
                        index.CommitId,
                        index.CommitTimeStamp);
                }
            }

            var expectedContext = new JObject(
                new JProperty(CatalogConstants.VocabKeyword, CatalogConstants.NuGetSchemaUri),
                new JProperty(CatalogConstants.Catalog, CatalogConstants.NuGetCatalogSchemaUri),
                new JProperty(CatalogConstants.Xsd, CatalogConstants.XmlSchemaUri),
                new JProperty(CatalogConstants.Items,
                              new JObject(
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.CatalogItem),
                                  new JProperty(CatalogConstants.ContainerKeyword, CatalogConstants.SetKeyword))),
                new JProperty(CatalogConstants.CommitTimeStamp,
                              new JObject(
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.CatalogCommitTimeStamp),
                                  new JProperty(CatalogConstants.TypeKeyword, CatalogConstants.XsdDateTime))),
                new JProperty(CatalogConstants.CommitId,
                              new JObject(
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.CatalogCommitId))),
                new JProperty(CatalogConstants.Count,
                              new JObject(
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.CatalogCount))),
                new JProperty(CatalogConstants.Parent,
                              new JObject(
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.CatalogParent),
                                  new JProperty(CatalogConstants.TypeKeyword, CatalogConstants.IdKeyword))),
                new JProperty(CatalogConstants.Tags,
                              new JObject(
                                  new JProperty(CatalogConstants.ContainerKeyword, CatalogConstants.SetKeyword),
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.Tag))),
                new JProperty(CatalogConstants.PackageTargetFrameworks,
                              new JObject(
                                  new JProperty(CatalogConstants.ContainerKeyword, CatalogConstants.SetKeyword),
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.PackageTargetFramework))),
                new JProperty(CatalogConstants.DependencyGroups,
                              new JObject(
                                  new JProperty(CatalogConstants.ContainerKeyword, CatalogConstants.SetKeyword),
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.DependencyGroup))),
                new JProperty(CatalogConstants.Dependencies,
                              new JObject(
                                  new JProperty(CatalogConstants.ContainerKeyword, CatalogConstants.SetKeyword),
                                  new JProperty(CatalogConstants.IdKeyword, CatalogConstants.Dependency))),
                new JProperty(CatalogConstants.PackageContent,
                              new JObject(
                                  new JProperty(CatalogConstants.TypeKeyword, CatalogConstants.IdKeyword))),
                new JProperty(CatalogConstants.Published,
                              new JObject(
                                  new JProperty(CatalogConstants.TypeKeyword, CatalogConstants.XsdDateTime))),
                new JProperty(CatalogConstants.Registration,
                              new JObject(
                                  new JProperty(CatalogConstants.TypeKeyword, CatalogConstants.IdKeyword))));

            Assert.Equal(expectedContext.ToString(), index.ContextKeyword.ToString());
        }
        protected virtual Task <HttpResponseMessage> GetPackageRegistrationResponse(string uri)
        {
            const string uriPrefix = "https://nuget.test/registration/";
            const string uriSuffix = "/index.json";

            if (!uri.StartsWith(uriPrefix) || !uri.EndsWith(uriSuffix))
            {
                return(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound)));
            }
            var packageId = uri.Substring(0, uri.Length - uriSuffix.Length).Substring(uriPrefix.Length);

            if (!_packages.TryGetValue(packageId, out Dictionary <string, string> versions))
            {
                return(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound)));
            }
            // versions: key = version, value = full path to nupkg

            var registrationPage = new RegistrationPage()
            {
                Items = new List <RegistrationLeafItem>(),
                Lower = versions.Select(v => NuGetVersion.Parse(v.Key)).Min().ToNormalizedString(),
                Upper = versions.Select(v => NuGetVersion.Parse(v.Key)).Max().ToNormalizedString(),
            };

            foreach (var version in versions.OrderBy(v => NuGetVersion.Parse(v.Key)))
            {
                using var packageArchiveReader = new PackageArchiveReader(version.Value);
                var nuspecReader = packageArchiveReader.NuspecReader;

                var packageMetadata = new PackageSearchMetadataRegistration();
                SetPrivateProperty(packageMetadata, nameof(packageMetadata.PackageId), nuspecReader.GetId());
                SetPrivateProperty(packageMetadata, nameof(packageMetadata.Version), nuspecReader.GetVersion());

                var contentUrl = new Uri("https://nuget.test/flatcontainer/" + packageId + "/" + packageMetadata.Version.ToNormalizedString().ToLowerInvariant());

                var leafItem = new RegistrationLeafItem()
                {
                    CatalogEntry   = packageMetadata,
                    PackageContent = contentUrl
                };
                registrationPage.Items.Add(leafItem);
            }

            var registrationIndex = new RegistrationIndex()
            {
                Items = new List <RegistrationPage>()
                {
                    registrationPage
                }
            };

            string json;

            using (var stringWriter = new StringWriter())
            {
                JsonExtensions.JsonObjectSerializer.Serialize(stringWriter, registrationIndex);
                json = stringWriter.ToString();
            }

            return(Task.FromResult(new HttpResponseMessage()
            {
                Content = new StringContent(json)
            }));
        }
Esempio n. 12
0
 private IndexInfo(RegistrationIndex index, List <PageInfo> items)
 {
     Index  = index ?? throw new ArgumentNullException(nameof(index));
     _items = items ?? throw new ArgumentNullException(nameof(items));
 }
Esempio n. 13
0
            public Facts(ITestOutputHelper output)
            {
                Storage       = new Mock <IHiveStorage>();
                Merger        = new Mock <IHiveMerger>();
                EntityBuilder = new Mock <IEntityBuilder>();
                Options       = new Mock <IOptionsSnapshot <Catalog2RegistrationConfiguration> >();
                Logger        = output.GetLogger <HiveUpdater>();

                Config             = new Catalog2RegistrationConfiguration();
                Hive               = HiveType.SemVer2;
                ReplicaHives       = new List <HiveType>();
                Id                 = "NuGet.Versioning";
                Entries            = new List <CatalogCommitItem>();
                EntryToCatalogLeaf = new Dictionary <CatalogCommitItem, PackageDetailsCatalogLeaf>(
                    ReferenceEqualityComparer <CatalogCommitItem> .Default);
                RegistrationIndex = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "1.0.0",
                            Upper = "3.0.0",
                            Count = 2,
                            Items = new List <RegistrationLeafItem>
                            {
                                new RegistrationLeafItem
                                {
                                    Url          = $"https://example/reg/{Id.ToLowerInvariant()}/1.0.0.json",
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "1.0.0",
                                    }
                                },
                                new RegistrationLeafItem
                                {
                                    Url          = $"https://example/reg/{Id.ToLowerInvariant()}/3.0.0.json",
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Version = "3.0.0",
                                    }
                                },
                            }
                        }
                    }
                };
                MergeResult = new HiveMergeResult(
                    new HashSet <PageInfo>(),
                    new HashSet <LeafInfo>(),
                    new HashSet <LeafInfo>());
                RegistrationLeaf   = new RegistrationLeaf();
                RegistrationCommit = new CatalogCommit(
                    "b580f835-f041-4361-aa46-57e5dc338a63",
                    new DateTimeOffset(2019, 10, 25, 0, 0, 0, TimeSpan.Zero));

                Options.Setup(x => x.Value).Returns(() => Config);
                Storage
                .Setup(x => x.ReadIndexOrNullAsync(It.IsAny <HiveType>(), It.IsAny <string>()))
                .ReturnsAsync(() => RegistrationIndex);
                var concreteHiveMerger = new HiveMerger(Options.Object, output.GetLogger <HiveMerger>());

                Merger
                .Setup(x => x.MergeAsync(It.IsAny <IndexInfo>(), It.IsAny <IReadOnlyList <CatalogCommitItem> >()))
                .Returns <IndexInfo, IReadOnlyList <CatalogCommitItem> >((i, e) => concreteHiveMerger.MergeAsync(i, e));
                EntityBuilder
                .Setup(x => x.NewLeaf(It.IsAny <RegistrationLeafItem>()))
                .Returns(() => RegistrationLeaf);
                EntityBuilder
                .Setup(x => x.UpdateNonInlinedPageItem(
                           It.IsAny <RegistrationPage>(),
                           It.IsAny <HiveType>(),
                           It.IsAny <string>(),
                           It.IsAny <int>(),
                           It.IsAny <NuGetVersion>(),
                           It.IsAny <NuGetVersion>()))
                .Callback <RegistrationPage, HiveType, string, int, NuGetVersion, NuGetVersion>((p, h, id, c, l, u) =>
                {
                    p.Url = $"https://example/reg/" +
                            $"{id.ToLowerInvariant()}/" +
                            $"{l.ToNormalizedString().ToLowerInvariant()}/" +
                            $"{u.ToNormalizedString().ToLowerInvariant()}.json";
                });

                Target = new HiveUpdater(
                    Storage.Object,
                    Merger.Object,
                    EntityBuilder.Object,
                    Options.Object,
                    Logger);
            }
            public async Task FetchesLeavesOnlyOnce()
            {
                var details1 = new PackageDetailsCatalogLeaf {
                    Listed = true
                };
                var details2 = new PackageDetailsCatalogLeaf {
                    Listed = true
                };
                var details3 = new PackageDetailsCatalogLeaf {
                    Listed = true
                };
                var details4 = new PackageDetailsCatalogLeaf {
                    Listed = true
                };
                var index = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "0.0.0",
                            Upper = "10.0.0",
                            Url   = "https://example/page",
                            Items = new List <RegistrationLeafItem>
                            {
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/1",
                                        Version = "1.0.0",
                                        Listed  = true,
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/2",
                                        Version = "2.0.0-alpha",
                                        Listed  = true,
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/3",
                                        Version = "3.0.0+git",
                                        Listed  = true,
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/4",
                                        Version = "4.0.0-beta.1",
                                        Listed  = true,
                                    },
                                },
                            },
                        },
                    },
                };

                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/1"))
                .ReturnsAsync(details1);
                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/2"))
                .ReturnsAsync(details2);
                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/3"))
                .ReturnsAsync(details3);
                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/4"))
                .ReturnsAsync(details4);
                _registrationClient
                .Setup(x => x.GetIndexOrNullAsync(It.IsAny <string>()))
                .ReturnsAsync(index);

                var latest = await _target.GetLatestLeavesAsync(PackageId, _versions);

                Assert.Empty(latest.Unavailable);
                Assert.Equal(
                    _eachVersion,
                    latest.Available.Keys.OrderBy(x => x).ToArray());
                Assert.Same(details1, latest.Available[Parse("1.0.0")]);
                Assert.Same(details2, latest.Available[Parse("2.0.0-alpha")]);
                Assert.Same(details3, latest.Available[Parse("3.0.0")]);
                Assert.Same(details4, latest.Available[Parse("4.0.0-beta.1")]);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync(It.IsAny <string>()), Times.Once);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync("https://example/v3-registration/nuget.versioning/index.json"), Times.Once);
                _registrationClient.Verify(
                    x => x.GetPageAsync(It.IsAny <string>()), Times.Never);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync(It.IsAny <string>()),
                    Times.Exactly(4));
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/1"),
                    Times.Once);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/2"),
                    Times.Once);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/3"),
                    Times.Once);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/4"),
                    Times.Once);
            }
            public async Task FetchesPageIfItemsAreNotInlined()
            {
                var versions = new List <IReadOnlyList <NuGetVersion> >
                {
                    new List <NuGetVersion>
                    {
                        Parse("1.0.0"),
                    },
                };
                var details1 = new PackageDetailsCatalogLeaf {
                    Listed = true
                };
                var page = new RegistrationPage
                {
                    Items = new List <RegistrationLeafItem>
                    {
                        new RegistrationLeafItem
                        {
                            CatalogEntry = new RegistrationCatalogEntry
                            {
                                Url     = "https://example/1.0.0",
                                Version = "1.0.0",
                            },
                        },
                    },
                };
                var index = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "1.0.0",
                            Upper = "1.0.0",
                            Url   = "https://example/page",
                        },
                    },
                };

                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/1.0.0"))
                .ReturnsAsync(details1);
                _registrationClient
                .Setup(x => x.GetPageAsync("https://example/page"))
                .ReturnsAsync(page);
                _registrationClient
                .Setup(x => x.GetIndexOrNullAsync(It.IsAny <string>()))
                .ReturnsAsync(index);

                var latest = await _target.GetLatestLeavesAsync(PackageId, versions);

                Assert.Empty(latest.Unavailable);
                Assert.Equal(
                    new[] { Parse("1.0.0") },
                    latest.Available.Keys.OrderBy(x => x).ToArray());
                Assert.Same(details1, latest.Available[Parse("1.0.0")]);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync(It.IsAny <string>()), Times.Once);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync("https://example/v3-registration/nuget.versioning/index.json"), Times.Once);
                _registrationClient.Verify(
                    x => x.GetPageAsync(It.IsAny <string>()), Times.Once);
                _registrationClient.Verify(
                    x => x.GetPageAsync("https://example/page"), Times.Once);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync(It.IsAny <string>()),
                    Times.Once);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/1.0.0"),
                    Times.Once);
            }
            public async Task ReturnsAllProvidedVersionsIfUnlisted()
            {
                var versions = new List <IReadOnlyList <NuGetVersion> >
                {
                    new List <NuGetVersion>
                    {
                        Parse("1.0.0"),
                        Parse("2.0.0"),
                        Parse("3.0.0"),
                    },
                };
                var details1 = new PackageDetailsCatalogLeaf {
                    Listed = false
                };
                var details2 = new PackageDetailsCatalogLeaf {
                    Listed = false
                };
                var details3 = new PackageDetailsCatalogLeaf {
                    Listed = false
                };
                var index = new RegistrationIndex
                {
                    Items = new List <RegistrationPage>
                    {
                        new RegistrationPage
                        {
                            Lower = "0.0.0",
                            Upper = "4.0.0",
                            Url   = "https://example/page",
                            Items = new List <RegistrationLeafItem>
                            {
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/0.0.0",
                                        Version = "0.0.0",
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/1.0.0",
                                        Version = "1.0.0",
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/2.0.0",
                                        Version = "2.0.0",
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/3.0.0",
                                        Version = "3.0.0",
                                    },
                                },
                                new RegistrationLeafItem
                                {
                                    CatalogEntry = new RegistrationCatalogEntry
                                    {
                                        Url     = "https://example/4.0.0",
                                        Version = "4.0.0",
                                    },
                                },
                            },
                        },
                    },
                };

                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/1.0.0"))
                .ReturnsAsync(details1);
                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/2.0.0"))
                .ReturnsAsync(details2);
                _catalogClient
                .Setup(x => x.GetPackageDetailsLeafAsync("https://example/3.0.0"))
                .ReturnsAsync(details3);
                _registrationClient
                .Setup(x => x.GetIndexOrNullAsync(It.IsAny <string>()))
                .ReturnsAsync(index);

                var latest = await _target.GetLatestLeavesAsync(PackageId, versions);

                Assert.Empty(latest.Unavailable);
                Assert.Equal(
                    new[] { Parse("1.0.0"), Parse("2.0.0"), Parse("3.0.0") },
                    latest.Available.Keys.OrderBy(x => x).ToArray());
                Assert.Same(details1, latest.Available[Parse("1.0.0")]);
                Assert.Same(details2, latest.Available[Parse("2.0.0")]);
                Assert.Same(details3, latest.Available[Parse("3.0.0")]);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync(It.IsAny <string>()), Times.Once);
                _registrationClient.Verify(
                    x => x.GetIndexOrNullAsync("https://example/v3-registration/nuget.versioning/index.json"), Times.Once);
                _registrationClient.Verify(
                    x => x.GetPageAsync(It.IsAny <string>()), Times.Never);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync(It.IsAny <string>()),
                    Times.Exactly(3));
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/0.0.0"),
                    Times.Never);
                _catalogClient.Verify(
                    x => x.GetPackageDetailsLeafAsync("https://example/4.0.0"),
                    Times.Never);
            }