public async Task DownloadMetadata2CatalogAsync_WhenPackageCatalogItemCreatorIsNull_Throws()
        {
            IPackageCatalogItemCreator creator = null;

            var exception = await Assert.ThrowsAsync <ArgumentNullException>(
                () => FeedHelpers.DownloadMetadata2CatalogAsync(
                    creator,
                    new SortedList <DateTime, IList <FeedPackageDetails> >(),
                    Mock.Of <IStorage>(),
                    DateTime.MinValue,
                    DateTime.MinValue,
                    DateTime.MinValue,
                    maxDegreeOfParallelism: 1,
                    createdPackages: null,
                    updateCreatedFromEdited: false,
                    cancellationToken: CancellationToken.None,
                    telemetryService: Mock.Of <ITelemetryService>(),
                    logger: Mock.Of <ILogger>()));

            Assert.Equal("packageCatalogItemCreator", exception.ParamName);
        }
        /// <summary>
        /// Asynchronously writes package metadata to the catalog.
        /// </summary>
        /// <param name="packageCatalogItemCreator">A package catalog item creator.</param>
        /// <param name="packages">Packages to download metadata for.</param>
        /// <param name="storage">Storage.</param>
        /// <param name="lastCreated">The catalog's last created datetime.</param>
        /// <param name="lastEdited">The catalog's last edited datetime.</param>
        /// <param name="lastDeleted">The catalog's last deleted datetime.</param>
        /// <param name="maxDegreeOfParallelism">The maximum degree of parallelism for package processing.</param>
        /// <param name="createdPackages"><c>true</c> to include created packages; otherwise, <c>false</c>.</param>
        /// <param name="updateCreatedFromEdited"><c>true</c> to update the created cursor from the last edited cursor;
        /// otherwise, <c>false</c>.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <param name="telemetryService">A telemetry service.</param>
        /// <param name="logger">A logger.</param>
        /// <returns>A task that represents the asynchronous operation.
        /// The task result (<see cref="Task{TResult}.Result" />) returns the latest
        /// <see cref="DateTime}" /> that was processed.</returns>
        public static async Task <DateTime> DownloadMetadata2CatalogAsync(
            IPackageCatalogItemCreator packageCatalogItemCreator,
            SortedList <DateTime, IList <FeedPackageDetails> > packages,
            IStorage storage,
            DateTime lastCreated,
            DateTime lastEdited,
            DateTime lastDeleted,
            int maxDegreeOfParallelism,
            bool?createdPackages,
            bool updateCreatedFromEdited,
            CancellationToken cancellationToken,
            ITelemetryService telemetryService,
            ILogger logger)
        {
            if (packageCatalogItemCreator == null)
            {
                throw new ArgumentNullException(nameof(packageCatalogItemCreator));
            }

            if (packages == null)
            {
                throw new ArgumentNullException(nameof(packages));
            }

            if (storage == null)
            {
                throw new ArgumentNullException(nameof(storage));
            }

            if (maxDegreeOfParallelism < 1)
            {
                throw new ArgumentOutOfRangeException(
                          nameof(maxDegreeOfParallelism),
                          string.Format(Strings.ArgumentOutOfRange, 1, int.MaxValue));
            }

            if (telemetryService == null)
            {
                throw new ArgumentNullException(nameof(telemetryService));
            }

            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            cancellationToken.ThrowIfCancellationRequested();

            var writer = new AppendOnlyCatalogWriter(storage, telemetryService, Constants.MaxPageSize);

            var lastDate = DetermineLastDate(lastCreated, lastEdited, createdPackages);

            if (packages.Count == 0)
            {
                return(lastDate);
            }

            // Flatten the sorted list.
            var workItems = packages.SelectMany(
                pair => pair.Value.Select(
                    details => new PackageWorkItem(pair.Key, details)))
                            .ToArray();

            await workItems.ForEachAsync(maxDegreeOfParallelism, async workItem =>
            {
                workItem.PackageCatalogItem = await packageCatalogItemCreator.CreateAsync(
                    workItem.FeedPackageDetails,
                    workItem.Timestamp,
                    cancellationToken);
            });

            lastDate = packages.Last().Key;

            // AppendOnlyCatalogWriter.Add(...) is not thread-safe, so add them all at once on one thread.
            foreach (var workItem in workItems.Where(workItem => workItem.PackageCatalogItem != null))
            {
                writer.Add(workItem.PackageCatalogItem);

                logger?.LogInformation("Add metadata from: {PackageDetailsContentUri}", workItem.FeedPackageDetails.ContentUri);
            }

            if (createdPackages.HasValue)
            {
                lastEdited = !createdPackages.Value ? lastDate : lastEdited;

                if (updateCreatedFromEdited)
                {
                    lastCreated = lastEdited;
                }
                else
                {
                    lastCreated = createdPackages.Value ? lastDate : lastCreated;
                }
            }

            var commitMetadata = PackageCatalog.CreateCommitMetadata(writer.RootUri, new CommitMetadata(lastCreated, lastEdited, lastDeleted));

            await writer.Commit(commitMetadata, cancellationToken);

            logger?.LogInformation("COMMIT metadata to catalog.");

            return(lastDate);
        }