From a767c822fdd0c7061113646e2f1303bb0984f37e Mon Sep 17 00:00:00 2001 From: Damon Tivel Date: Tue, 23 May 2017 13:16:15 -0700 Subject: [PATCH] Fix plugin package install, get credentials in multifeed environment, and add get service index request/response. --- src/NuGet.Core/NuGet.Common/Preprocessor.cs | 61 +++- .../FileModifiers/IPackageFileTransformer.cs | 50 ++- .../FileModifiers/PreProcessor.cs | 34 -- .../FileModifiers/Preprocessor.cs | 108 ++++++ .../FileModifiers/XdtTransformer.cs | 89 ++++- .../FileModifiers/XmlTransformer.cs | 136 ++++++-- .../IInstallationCompatibility.cs | 29 +- .../InstallationCompatibility.cs | 45 ++- .../NuGetPackageManager.cs | 15 +- .../Projects/MSBuildNuGetProject.cs | 47 ++- .../Projects/PackagesConfigNuGetProject.cs | 33 +- .../Utility/FileSystemUtility.cs | 54 ++- .../MSBuildNuGetProjectSystemUtility.cs | 62 +++- .../NuGet.Protocol/Plugins/MessageMethod.cs | 5 + .../Messages/GetServiceIndexRequest.cs | 35 ++ .../Messages/GetServiceIndexResponse.cs | 59 ++++ .../GetCredentialsRequestHandler.cs} | 94 ++++-- .../GetServiceIndexRequestHandler.cs | 160 +++++++++ .../DownloadResourcePluginProvider.cs | 46 ++- .../PluginFindPackageByIdResource.cs | 36 +- .../PluginFindPackageByIdResourceProvider.cs | 46 ++- .../Resources/DownloadResourcePlugin.cs | 34 +- .../NuGet.Common.Test/PreprocessorTests.cs | 180 ++++++++++ .../FileModifiers/PreprocessorTests.cs | 201 +++++++++++ .../FileModifiers/XdtTransformerTests.cs | 237 +++++++++++++ .../FileModifiers/XmlTransformerTests.cs | 251 ++++++++++++++ .../InstallationCompatibilityTests.cs | 128 ++++--- .../NuGetPackageManagerTests.cs | 31 +- .../Utility/FileSystemUtilityTests.cs | 136 ++++++++ .../DownloadResourcePluginProviderTests.cs | 7 + .../Plugins/DownloadResourcePluginTests.cs | 43 +-- .../Messages/GetServiceIndexRequestTests.cs | 61 ++++ .../Messages/GetServiceIndexResponseTests.cs | 100 ++++++ ...ginFindPackageByIdResourceProviderTests.cs | 7 + .../PluginFindPackageByIdResourceTests.cs | 43 +-- .../GetCredentialsRequestHandlerTests.cs} | 136 ++++---- .../GetServiceIndexRequestHandlerTests.cs | 312 ++++++++++++++++++ 37 files changed, 2701 insertions(+), 450 deletions(-) delete mode 100644 src/NuGet.Core/NuGet.PackageManagement/FileModifiers/PreProcessor.cs create mode 100644 src/NuGet.Core/NuGet.PackageManagement/FileModifiers/Preprocessor.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexRequest.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexResponse.cs rename src/NuGet.Core/NuGet.Protocol/Plugins/{PluginCredentialsProvider.cs => RequestHandlers/GetCredentialsRequestHandler.cs} (75%) create mode 100644 src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetServiceIndexRequestHandler.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Common.Test/PreprocessorTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/PreprocessorTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XdtTransformerTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XmlTransformerTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.PackageManagement.Test/Utility/FileSystemUtilityTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexRequestTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexResponseTests.cs rename test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/{PluginCredentialsProviderTests.cs => RequestHandlers/GetCredentialsRequestHandlerTests.cs} (85%) create mode 100644 test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetServiceIndexRequestHandlerTests.cs diff --git a/src/NuGet.Core/NuGet.Common/Preprocessor.cs b/src/NuGet.Core/NuGet.Common/Preprocessor.cs index d4d61efc74b..b93e9450fc3 100644 --- a/src/NuGet.Core/NuGet.Common/Preprocessor.cs +++ b/src/NuGet.Core/NuGet.Common/Preprocessor.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace NuGet.Common { @@ -12,16 +14,65 @@ namespace NuGet.Common /// public class Preprocessor { - public static string Process(Func fileStreamFactory, Func tokenReplacement) + /// + /// Asynchronously performs token replacement on a file stream. + /// + /// A stream task factory. + /// A token replacement function. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// The task result () returns a . + /// Thrown if + /// is either null or empty. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public static async Task ProcessAsync( + Func> streamTaskFactory, + Func tokenReplacement, + CancellationToken cancellationToken) { - using (var stream = fileStreamFactory()) + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (tokenReplacement == null) + { + throw new ArgumentNullException(nameof(tokenReplacement)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + using (var stream = await streamTaskFactory()) { return Process(stream, tokenReplacement); } } + /// + /// Performs token replacement on a stream and returns the result. + /// + /// A stream. + /// A token replacement funciton. + /// The token-replaced stream content. + /// Thrown if + /// is null. + /// Thrown if + /// is null. public static string Process(Stream stream, Func tokenReplacement) { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (tokenReplacement == null) + { + throw new ArgumentNullException(nameof(tokenReplacement)); + } + string text; using (var streamReader = new StreamReader(stream)) { @@ -29,10 +80,10 @@ public static string Process(Stream stream, Func tokenReplacemen } var tokenizer = new Tokenizer(text); - StringBuilder result = new StringBuilder(); + var result = new StringBuilder(); for (; ; ) { - Token token = tokenizer.Read(); + var token = tokenizer.Read(); if (token == null) { break; @@ -52,4 +103,4 @@ public static string Process(Stream stream, Func tokenReplacemen return result.ToString(); } } -} +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/IPackageFileTransformer.cs b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/IPackageFileTransformer.cs index d7c42c16c94..05af7bdd49b 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/IPackageFileTransformer.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/IPackageFileTransformer.cs @@ -1,29 +1,61 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace NuGet.ProjectManagement { + /// + /// Represents a package file transformer. + /// public interface IPackageFileTransformer { /// - /// Transforms the file + /// Asynchronously transforms a file. /// - void TransformFile(Func fileStreamFactory, string targetPath, IMSBuildNuGetProjectSystem projectSystem); + /// A stream task factory. + /// A path to the file to be transformed. + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + Task TransformFileAsync( + Func> streamTaskFactory, + string targetPath, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken); /// - /// Reverses the transform on the targetPath, using all the potential source of change + /// Asynchronously reverses the transform on the targetPath, using all the potential source of change. /// - /// A factory for accessing the file to be reverted from the nupkg being uninstalled. + /// A factory for accessing the file to be reverted from the nupkg being uninstalled. /// A path to the file to be reverted. - /// Other files in other packages that may have changed the . + /// Other files in other packages that may have changed the . /// The project where this change is taking place. - void RevertFile(Func fileStreamFactory, + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + Task RevertFileAsync( + Func> streamTaskFactory, string targetPath, IEnumerable matchingFiles, - IMSBuildNuGetProjectSystem projectSystem); + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken); } -} +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/PreProcessor.cs b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/PreProcessor.cs deleted file mode 100644 index 0c7684ac115..00000000000 --- a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/PreProcessor.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using NuGet.Configuration; - -namespace NuGet.ProjectManagement -{ - /// - /// Simple token replacement system for content files. - /// - public class Preprocessor : IPackageFileTransformer - { - public void TransformFile(Func fileStreamFactory, string targetPath, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) - { - MSBuildNuGetProjectSystemUtility.TryAddFile(msBuildNuGetProjectSystem, targetPath, - () => StreamUtility.StreamFromString(Process(fileStreamFactory, msBuildNuGetProjectSystem))); - } - - public void RevertFile(Func fileStreamFactory, string targetPath, IEnumerable matchingFiles, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) - { - MSBuildNuGetProjectSystemUtility.DeleteFileSafe(targetPath, - () => StreamUtility.StreamFromString(Process(fileStreamFactory, msBuildNuGetProjectSystem)), - msBuildNuGetProjectSystem); - } - - internal static string Process(Func fileStreamFactory, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) - { - return NuGet.Common.Preprocessor.Process(fileStreamFactory, propName => msBuildNuGetProjectSystem.GetPropertyValue(propName)); - } - } -} diff --git a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/Preprocessor.cs b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/Preprocessor.cs new file mode 100644 index 00000000000..a06c31b84de --- /dev/null +++ b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/Preprocessor.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace NuGet.ProjectManagement +{ + /// + /// Simple token replacement system for content files. + /// + public class Preprocessor : IPackageFileTransformer + { + /// + /// Asynchronously transforms a file. + /// + /// A stream task factory. + /// A path to the file to be transformed. + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task TransformFileAsync( + Func> streamTaskFactory, + string targetPath, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) + { + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (projectSystem == null) + { + throw new ArgumentNullException(nameof(projectSystem)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await MSBuildNuGetProjectSystemUtility.TryAddFileAsync( + projectSystem, + targetPath, + async () => StreamUtility.StreamFromString(await ProcessAsync(streamTaskFactory, projectSystem, cancellationToken)), + cancellationToken); + } + + /// + /// Asynchronously reverses the transform on the targetPath, using all the potential source of change. + /// + /// A factory for accessing the file to be reverted from the nupkg being uninstalled. + /// A path to the file to be reverted. + /// Other files in other packages that may have changed the . + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task RevertFileAsync( + Func> streamTaskFactory, + string targetPath, + IEnumerable matchingFiles, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) + { + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (projectSystem == null) + { + throw new ArgumentNullException(nameof(projectSystem)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await MSBuildNuGetProjectSystemUtility.DeleteFileSafeAsync( + targetPath, + async () => StreamUtility.StreamFromString(await ProcessAsync(streamTaskFactory, projectSystem, cancellationToken)), + projectSystem, + cancellationToken); + } + + internal static Task ProcessAsync( + Func> streamTaskFactory, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) + { + return Common.Preprocessor.ProcessAsync( + streamTaskFactory, + propName => projectSystem.GetPropertyValue(propName), + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XdtTransformer.cs b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XdtTransformer.cs index 59cdad506c1..60f4a70ac43 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XdtTransformer.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XdtTransformer.cs @@ -1,32 +1,105 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Web.XmlTransform; using NuGet.PackageManagement; namespace NuGet.ProjectManagement { + /// + /// An XDT project file transformer. + /// public class XdtTransformer : IPackageFileTransformer { - public void TransformFile(Func fileStreamFactory, string targetPath, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + /// + /// Asynchronously transforms a file. + /// + /// A stream task factory. + /// A path to the file to be transformed. + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task TransformFileAsync( + Func> streamTaskFactory, + string targetPath, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) { - PerformXdtTransform(fileStreamFactory, targetPath, msBuildNuGetProjectSystem); + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (projectSystem == null) + { + throw new ArgumentNullException(nameof(projectSystem)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await PerformXdtTransformAsync(streamTaskFactory, targetPath, projectSystem, cancellationToken); } - public void RevertFile(Func fileStreamFactory, string targetPath, IEnumerable matchingFiles, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + /// + /// Asynchronously reverses the transform on the targetPath, using all the potential source of change. + /// + /// A factory for accessing the file to be reverted from the nupkg being uninstalled. + /// A path to the file to be reverted. + /// Other files in other packages that may have changed the . + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task RevertFileAsync( + Func> streamTaskFactory, + string targetPath, + IEnumerable matchingFiles, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) { - PerformXdtTransform(fileStreamFactory, targetPath, msBuildNuGetProjectSystem); + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (projectSystem == null) + { + throw new ArgumentNullException(nameof(projectSystem)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + await PerformXdtTransformAsync(streamTaskFactory, targetPath, projectSystem, cancellationToken); } - private static void PerformXdtTransform(Func fileStreamFactory, string targetPath, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + private static async Task PerformXdtTransformAsync( + Func> streamTaskFactory, + string targetPath, + IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, + CancellationToken cancellationToken) { if (FileSystemUtility.FileExists(msBuildNuGetProjectSystem.ProjectFullPath, targetPath)) { - var content = Preprocessor.Process(fileStreamFactory, msBuildNuGetProjectSystem); + var content = await Preprocessor.ProcessAsync(streamTaskFactory, msBuildNuGetProjectSystem, cancellationToken); try { @@ -66,4 +139,4 @@ private static void PerformXdtTransform(Func fileStreamFactory, string t } } } -} +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XmlTransformer.cs b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XmlTransformer.cs index 42c87f17b6b..be41adf6827 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XmlTransformer.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/FileModifiers/XmlTransformer.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using NuGet.Common; @@ -8,80 +8,178 @@ using System.IO.Compression; using System.Linq; using System.Xml.Linq; +using System.Threading.Tasks; +using System.Threading; namespace NuGet.ProjectManagement { + /// + /// An XML project file transformer. + /// public class XmlTransformer : IPackageFileTransformer { private readonly IDictionary> _nodeActions; + /// + /// Initializes a new class. + /// + /// A dictionary of XML node names to node actions. + /// Thrown if + /// is null. public XmlTransformer(IDictionary> nodeActions) { + if (nodeActions == null) + { + throw new ArgumentNullException(nameof(nodeActions)); + } + _nodeActions = nodeActions; } - public void TransformFile(Func fileStreamFactory, string targetPath, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + /// + /// Asynchronously transforms a file. + /// + /// A stream task factory. + /// A path to the file to be transformed. + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task TransformFileAsync( + Func> streamTaskFactory, + string targetPath, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) { + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (projectSystem == null) + { + throw new ArgumentNullException(nameof(projectSystem)); + } + + cancellationToken.ThrowIfCancellationRequested(); + // Get the xml fragment - var xmlFragment = GetXml(fileStreamFactory, msBuildNuGetProjectSystem); + var xmlFragment = await GetXmlAsync(streamTaskFactory, projectSystem, cancellationToken); - var transformDocument = XmlUtility.GetOrCreateDocument(xmlFragment.Name, targetPath, msBuildNuGetProjectSystem); + var transformDocument = XmlUtility.GetOrCreateDocument(xmlFragment.Name, targetPath, projectSystem); // Do a merge transformDocument.Root.MergeWith(xmlFragment, _nodeActions); - MSBuildNuGetProjectSystemUtility.AddFile(msBuildNuGetProjectSystem, targetPath, transformDocument.Save); + MSBuildNuGetProjectSystemUtility.AddFile(projectSystem, targetPath, transformDocument.Save); } - public void RevertFile(Func fileStreamFactory, + /// + /// Asynchronously reverses the transform on the targetPath, using all the potential source of change. + /// + /// A factory for accessing the file to be reverted from the nupkg being uninstalled. + /// A path to the file to be reverted. + /// Other files in other packages that may have changed the . + /// The project where this change is taking place. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task RevertFileAsync( + Func> streamTaskFactory, string targetPath, IEnumerable matchingFiles, - IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) { + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + if (projectSystem == null) + { + throw new ArgumentNullException(nameof(projectSystem)); + } + + cancellationToken.ThrowIfCancellationRequested(); + // Get the xml snippet - var xmlFragment = GetXml(fileStreamFactory, msBuildNuGetProjectSystem); + var xmlFragment = await GetXmlAsync(streamTaskFactory, projectSystem, cancellationToken); var document = XmlUtility.GetOrCreateDocument(xmlFragment.Name, - msBuildNuGetProjectSystem.ProjectFullPath, + projectSystem.ProjectFullPath, targetPath, - msBuildNuGetProjectSystem.NuGetProjectContext); + projectSystem.NuGetProjectContext); // Merge the other xml elements into one element within this xml hierarchy (matching the config file path) - var mergedFragments = matchingFiles.Select(f => GetXml(f, msBuildNuGetProjectSystem)) - .Aggregate(new XElement(xmlFragment.Name), (left, right) => left.MergeWith(right, _nodeActions)); + var elements = new List(); + + foreach (var matchingFile in matchingFiles) + { + elements.Add(await GetXmlAsync(matchingFile, projectSystem, cancellationToken)); + } + + var mergedFragments = elements.Aggregate( + new XElement(xmlFragment.Name), + (left, right) => left.MergeWith(right, _nodeActions)); // Take the difference of the xml and remove it from the main xml file document.Root.Except(xmlFragment.Except(mergedFragments)); // Save the new content to the file system - using (var fileStream = FileSystemUtility.CreateFile(msBuildNuGetProjectSystem.ProjectFullPath, - targetPath, msBuildNuGetProjectSystem.NuGetProjectContext)) + using (var fileStream = FileSystemUtility.CreateFile( + projectSystem.ProjectFullPath, + targetPath, + projectSystem.NuGetProjectContext)) { document.Save(fileStream); } } - private static XElement GetXml(InternalZipFileInfo packageFileInfo, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + private static async Task GetXmlAsync( + InternalZipFileInfo packageFileInfo, + IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, + CancellationToken cancellationToken) { string content; + using (var packageStream = File.OpenRead(packageFileInfo.ZipArchivePath)) { var zipArchive = new ZipArchive(packageStream); var zipArchivePackageEntry = PathUtility.GetEntry(zipArchive, packageFileInfo.ZipArchiveEntryFullName); + if (zipArchivePackageEntry == null) { throw new ArgumentException("internalZipFileInfo"); } - content = Preprocessor.Process(zipArchivePackageEntry.Open, msBuildNuGetProjectSystem); + content = await Preprocessor.ProcessAsync( + () => Task.FromResult(zipArchivePackageEntry.Open()), + msBuildNuGetProjectSystem, + cancellationToken); } + return XElement.Parse(content, LoadOptions.PreserveWhitespace); } - private static XElement GetXml(Func fileStreamFactory, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + private static async Task GetXmlAsync( + Func> streamTaskFactory, + IMSBuildNuGetProjectSystem projectSystem, + CancellationToken cancellationToken) { - var content = Preprocessor.Process(fileStreamFactory, msBuildNuGetProjectSystem); + var content = await Preprocessor.ProcessAsync(streamTaskFactory, projectSystem, cancellationToken); + return XElement.Parse(content, LoadOptions.PreserveWhitespace); } } -} +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/IInstallationCompatibility.cs b/src/NuGet.Core/NuGet.PackageManagement/IInstallationCompatibility.cs index cb78fe5b8a8..5a861ac5cd0 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/IInstallationCompatibility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/IInstallationCompatibility.cs @@ -1,7 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using NuGet.Commands; using NuGet.Common; using NuGet.Packaging.Core; @@ -35,16 +38,26 @@ void EnsurePackageCompatibility( RestoreResult restoreResult); /// - /// Validates the compatibility of a single downloaded package. + /// Asynchronously validates the compatibility of a single downloaded package. /// - /// - /// The NuGet project. The type of the NuGet project determines the sorts or validations that are done. - /// + /// The NuGet project. The type of the NuGet project determines the sorts or + /// validations that are done. /// The identity of that package contained in the download result. /// The downloaded package. - void EnsurePackageCompatibility( + /// A cancellation token.. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + Task EnsurePackageCompatibilityAsync( NuGetProject nuGetProject, PackageIdentity packageIdentity, - DownloadResourceResult resourceResult); + DownloadResourceResult resourceResult, + CancellationToken cancellationToken); } -} +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/InstallationCompatibility.cs b/src/NuGet.Core/NuGet.PackageManagement/InstallationCompatibility.cs index de480ac037b..f0802055c2b 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/InstallationCompatibility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/InstallationCompatibility.cs @@ -1,10 +1,12 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NuGet.Commands; using NuGet.Common; using NuGet.LibraryModel; @@ -77,15 +79,50 @@ public void EnsurePackageCompatibility( } } - public void EnsurePackageCompatibility( + /// + /// Asynchronously validates the compatibility of a single downloaded package. + /// + /// The NuGet project. The type of the NuGet project determines the sorts or + /// validations that are done. + /// The identity of that package contained in the download result. + /// The downloaded package. + /// A cancellation token.. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task EnsurePackageCompatibilityAsync( NuGetProject nuGetProject, PackageIdentity packageIdentity, - DownloadResourceResult resourceResult) + DownloadResourceResult resourceResult, + CancellationToken cancellationToken) { + if (nuGetProject == null) + { + throw new ArgumentNullException(nameof(nuGetProject)); + } + + if (packageIdentity == null) + { + throw new ArgumentNullException(nameof(packageIdentity)); + } + + if (resourceResult == null) + { + throw new ArgumentNullException(nameof(resourceResult)); + } + + cancellationToken.ThrowIfCancellationRequested(); + NuspecReader nuspecReader; if (resourceResult.PackageReader != null) { - nuspecReader = resourceResult.PackageReader.NuspecReader; + nuspecReader = await resourceResult.PackageReader.GetNuspecReaderAsync(cancellationToken); } else { diff --git a/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs b/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs index dcf1485cff3..d0033b570b9 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/NuGetPackageManager.cs @@ -894,7 +894,7 @@ private async Task> PreviewUpdatePackagesForClas var projectInstalledPackageReferences = await nuGetProject.GetInstalledPackagesAsync(token); var oldListOfInstalledPackages = projectInstalledPackageReferences.Select(p => p.PackageIdentity); - bool isUpdateAll = (packageId == null && packageIdentities.Count == 0); + var isUpdateAll = (packageId == null && packageIdentities.Count == 0); var preferredVersions = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -1052,7 +1052,7 @@ private async Task> PreviewUpdatePackagesForClas { // BUG #1181 VS2015 : Updating from one feed fails for packages from different feed. - DependencyInfoResource packagesFolderResource = await PackagesFolderSourceRepository.GetResourceAsync(token); + var packagesFolderResource = await PackagesFolderSourceRepository.GetResourceAsync(token); var packages = new List(); foreach (var installedPackage in projectInstalledPackageReferences) { @@ -1127,7 +1127,7 @@ private async Task> PreviewUpdatePackagesForClas } // if we have been asked for exact versions of packages then we should also force the uninstall/install of those packages (this corresponds to a -Reinstall) - bool isReinstall = PrunePackageTree.IsExactVersion(resolutionContext.VersionConstraints); + var isReinstall = PrunePackageTree.IsExactVersion(resolutionContext.VersionConstraints); var targetIds = Enumerable.Empty(); if (!isUpdateAll) @@ -2129,7 +2129,7 @@ await ExecuteUninstallAsync(nuGetProject, using (var downloadPackageResult = await preFetchResult.GetResultAsync()) { // use the version exactly as specified in the nuspec file - var packageIdentity = downloadPackageResult.PackageReader.GetIdentity(); + var packageIdentity = await downloadPackageResult.PackageReader.GetIdentityAsync(token); await ExecuteInstallAsync( nuGetProject, @@ -2832,7 +2832,7 @@ public bool PackageExistsInPackagesFolder(PackageIdentity packageIdentity) return PackagesFolderNuGetProject.PackageExists(packageIdentity); } - private Task ExecuteInstallAsync( + private async Task ExecuteInstallAsync( NuGetProject nuGetProject, PackageIdentity packageIdentity, DownloadResourceResult resourceResult, @@ -2841,10 +2841,11 @@ private Task ExecuteInstallAsync( CancellationToken token) { // TODO: EnsurePackageCompatibility check should be performed in preview. Can easily avoid a lot of rollback - InstallationCompatibility.EnsurePackageCompatibility(nuGetProject, packageIdentity, resourceResult); + await InstallationCompatibility.EnsurePackageCompatibilityAsync(nuGetProject, packageIdentity, resourceResult, token); packageWithDirectoriesToBeDeleted.Remove(packageIdentity); - return nuGetProject.InstallPackageAsync(packageIdentity, resourceResult, nuGetProjectContext, token); + + await nuGetProject.InstallPackageAsync(packageIdentity, resourceResult, nuGetProjectContext, token); } private async Task ExecuteUninstallAsync(NuGetProject nuGetProject, PackageIdentity packageIdentity, HashSet packageWithDirectoriesToBeDeleted, diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/MSBuildNuGetProject.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/MSBuildNuGetProject.cs index b8b209861d5..88f416bfa7c 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/MSBuildNuGetProject.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/MSBuildNuGetProject.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -141,7 +141,8 @@ public override async Task InstallPackageAsync( throw new ArgumentNullException(nameof(nuGetProjectContext)); } - if (!downloadResourceResult.PackageStream.CanSeek) + if (downloadResourceResult.Status != DownloadResourceResultStatus.AvailableWithoutStream && + !downloadResourceResult.PackageStream.CanSeek) { throw new ArgumentException(Strings.PackageStreamShouldBeSeekable); } @@ -159,15 +160,23 @@ public override async Task InstallPackageAsync( } // Step-2: Create PackageArchiveReader using the PackageStream and obtain the various item groups - downloadResourceResult.PackageStream.Seek(0, SeekOrigin.Begin); - var packageReader = downloadResourceResult.PackageReader ?? new PackageArchiveReader(downloadResourceResult.PackageStream, leaveStreamOpen: true); + if (downloadResourceResult.Status != DownloadResourceResultStatus.AvailableWithoutStream) + { + downloadResourceResult.PackageStream.Seek(0, SeekOrigin.Begin); + } + + // These casts enforce use of -Async(...) methods. + var packageReader = downloadResourceResult.PackageReader + ?? new PackageArchiveReader(downloadResourceResult.PackageStream, leaveStreamOpen: true); + IAsyncPackageContentReader packageContentReader = packageReader; + IAsyncPackageCoreReader packageCoreReader = packageReader; - var libItemGroups = packageReader.GetLibItems(); - var referenceItemGroups = packageReader.GetReferenceItems(); - var frameworkReferenceGroups = packageReader.GetFrameworkItems(); - var contentFileGroups = packageReader.GetContentItems(); - var buildFileGroups = packageReader.GetBuildItems(); - var toolItemGroups = packageReader.GetToolItems(); + var libItemGroups = await packageContentReader.GetLibItemsAsync(token); + var referenceItemGroups = await packageContentReader.GetReferenceItemsAsync(token); + var frameworkReferenceGroups = await packageContentReader.GetFrameworkItemsAsync(token); + var contentFileGroups = await packageContentReader.GetContentItemsAsync(token); + var buildFileGroups = await packageContentReader.GetBuildItemsAsync(token); + var toolItemGroups = await packageContentReader.GetToolItemsAsync(token); // Step-3: Get the most compatible items groups for all items groups var hasCompatibleProjectLevelContent = false; @@ -220,7 +229,7 @@ public override async Task InstallPackageAsync( if (!onlyHasCompatibleTools) { // If it does not have compatible tool items either, check if it at least has dependencies - onlyHasDependencies = packageReader.GetPackageDependencies().Any(); + onlyHasDependencies = (await packageContentReader.GetPackageDependenciesAsync(token)).Any(); } } else @@ -322,8 +331,12 @@ public override async Task InstallPackageAsync( // Step-8.3: Add Content Files if (MSBuildNuGetProjectSystemUtility.IsValid(compatibleContentFilesGroup)) { - MSBuildNuGetProjectSystemUtility.AddFiles(MSBuildNuGetProjectSystem, - packageReader, compatibleContentFilesGroup, FileTransformers); + await MSBuildNuGetProjectSystemUtility.AddFilesAsync( + MSBuildNuGetProjectSystem, + packageCoreReader, + compatibleContentFilesGroup, + FileTransformers, + token); } // Step-8.4: Add Build imports @@ -499,11 +512,13 @@ public override async Task UninstallPackageAsync(PackageIdentity packageId var packagesPaths = (await GetInstalledPackagesAsync(token)) .Select(pr => FolderNuGetProject.GetInstalledPackageFilePath(pr.PackageIdentity)); - MSBuildNuGetProjectSystemUtility.DeleteFiles(MSBuildNuGetProjectSystem, + await MSBuildNuGetProjectSystemUtility.DeleteFilesAsync( + MSBuildNuGetProjectSystem, zipArchive, packagesPaths, compatibleContentFilesGroup, - FileTransformers); + FileTransformers, + token); } // Step-7.4: Remove build imports @@ -607,7 +622,7 @@ public async Task> GetPackageSpecsAsync(DependencyGra // Some projects like website project don't have project file. // Return empty list for this case. - if (String.IsNullOrEmpty(MSBuildNuGetProjectSystem.ProjectFileFullPath)) + if (string.IsNullOrEmpty(MSBuildNuGetProjectSystem.ProjectFileFullPath)) { return new List(); } diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/PackagesConfigNuGetProject.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/PackagesConfigNuGetProject.cs index a1cf950b78a..f50702334b5 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/PackagesConfigNuGetProject.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/PackagesConfigNuGetProject.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -69,7 +69,7 @@ public PackagesConfigNuGetProject(string folderPath, Dictionary } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - public override Task InstallPackageAsync( + public override async Task InstallPackageAsync( PackageIdentity packageIdentity, DownloadResourceResult downloadResourceResult, INuGetProjectContext nuGetProjectContext, @@ -85,8 +85,13 @@ public override Task InstallPackageAsync( throw new ArgumentNullException("nuGetProjectContext"); } - var isDevelopmentDependency = CheckDevelopmentDependency(downloadResourceResult); - var newPackageReference = new PackageReference(packageIdentity, TargetFramework, userInstalled: true, developmentDependency: isDevelopmentDependency, requireReinstallation: false); + var isDevelopmentDependency = await CheckDevelopmentDependencyAsync(downloadResourceResult, token); + var newPackageReference = new PackageReference( + packageIdentity, + TargetFramework, + userInstalled: true, + developmentDependency: isDevelopmentDependency, + requireReinstallation: false); var installedPackagesList = GetInstalledPackagesList(); try @@ -107,7 +112,7 @@ public override Task InstallPackageAsync( if (packageReferenceWithSameId.PackageIdentity.Equals(packageIdentity)) { nuGetProjectContext.Log(MessageLevel.Warning, Strings.PackageAlreadyExistsInPackagesConfig, packageIdentity, Path.GetFileName(FullPath)); - return Task.FromResult(false); + return false; } // Higher version of an installed package is being installed. Remove old and add new @@ -163,7 +168,7 @@ public override Task InstallPackageAsync( } nuGetProjectContext.Log(MessageLevel.Info, Strings.AddedPackageToPackagesConfig, packageIdentity, Path.GetFileName(FullPath)); - return Task.FromResult(true); + return true; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] @@ -248,7 +253,7 @@ public XDocument GetPackagesConfig() var share = FileShare.ReadWrite | FileShare.Delete; // Try to read the config file up to 3 times - for (int i = 0; i < FileUtility.MaxTries; i++) + for (var i = 0; i < FileUtility.MaxTries; i++) { try { @@ -331,9 +336,11 @@ private List GetInstalledPackagesList() return new List(); } - private static bool CheckDevelopmentDependency(DownloadResourceResult downloadResourceResult) + private static async Task CheckDevelopmentDependencyAsync( + DownloadResourceResult downloadResourceResult, + CancellationToken token) { - bool isDevelopmentDependency = false; + var isDevelopmentDependency = false; // Catch any exceptions while fetching DevelopmentDependency element from nuspec file. // So it can continue to write the packages.config file. @@ -341,11 +348,13 @@ private static bool CheckDevelopmentDependency(DownloadResourceResult downloadRe { if (downloadResourceResult.PackageReader != null) { - isDevelopmentDependency = downloadResourceResult.PackageReader.GetDevelopmentDependency(); + isDevelopmentDependency = await downloadResourceResult.PackageReader.GetDevelopmentDependencyAsync(token); } else { - using (var packageReader = new PackageArchiveReader(downloadResourceResult.PackageStream, leaveStreamOpen: true)) + using (var packageReader = new PackageArchiveReader( + downloadResourceResult.PackageStream, + leaveStreamOpen: true)) { var nuspecReader = new NuspecReader(packageReader.GetNuspec()); isDevelopmentDependency = nuspecReader.GetDevelopmentDependency(); @@ -357,4 +366,4 @@ private static bool CheckDevelopmentDependency(DownloadResourceResult downloadRe return isDevelopmentDependency; } } -} +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/Utility/FileSystemUtility.cs b/src/NuGet.Core/NuGet.PackageManagement/Utility/FileSystemUtility.cs index 5fd463ef7ee..1ad0b346865 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Utility/FileSystemUtility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Utility/FileSystemUtility.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NuGet.Common; using NuGet.PackageManagement; using NuGet.Packaging; @@ -303,8 +304,28 @@ orderby directory.Length descending } } + /// + /// Determines if the contents of a file and stream are equal. + /// + /// The path to a file. + /// A stream task factory. + /// true if contents are equal; otherwise, false. + /// Thrown if is either + /// null or an empty string. + /// Thrown if + /// is null. public static bool ContentEquals(string path, Func streamFactory) { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(Strings.Argument_Cannot_Be_Null_Or_Empty, nameof(path)); + } + + if (streamFactory == null) + { + throw new ArgumentNullException(nameof(streamFactory)); + } + using (Stream stream = streamFactory(), fileStream = File.OpenRead(path)) { @@ -312,6 +333,37 @@ public static bool ContentEquals(string path, Func streamFactory) } } + /// + /// Asynchronously determines if the contents of a file and stream are equal. + /// + /// The path to a file. + /// A stream task factory. + /// A task that represents the asynchronous operation. + /// The task result () returns a + /// which is true if contents are equal; otherwise, false. + /// Thrown if is either + /// null or an empty string. + /// Thrown if + /// is null. + public static async Task ContentEqualsAsync(string path, Func> streamTaskFactory) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(Strings.Argument_Cannot_Be_Null_Or_Empty, nameof(path)); + } + + if (streamTaskFactory == null) + { + throw new ArgumentNullException(nameof(streamTaskFactory)); + } + + using (Stream stream = await streamTaskFactory(), + fileStream = File.OpenRead(path)) + { + return StreamUtility.ContentEquals(stream, fileStream); + } + } + // PendAddFiles method here is called after addition of files during package extraction // whereas, delete files performs necessary pending followed by the deletion of files in a single method public static void PendAddFiles( diff --git a/src/NuGet.Core/NuGet.PackageManagement/Utility/MSBuildNuGetProjectSystemUtility.cs b/src/NuGet.Core/NuGet.PackageManagement/Utility/MSBuildNuGetProjectSystemUtility.cs index 6db7e4751f3..c11b78a59fb 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Utility/MSBuildNuGetProjectSystemUtility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Utility/MSBuildNuGetProjectSystemUtility.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -8,6 +8,7 @@ using System.IO.Compression; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NuGet.Common; using NuGet.Frameworks; using NuGet.PackageManagement; @@ -77,7 +78,11 @@ internal static bool IsValid(FrameworkSpecificGroup frameworkSpecificGroup) return false; } - internal static void TryAddFile(IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, string path, Func content) + internal static async Task TryAddFileAsync( + IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, + string path, + Func> streamTaskFactory, + CancellationToken cancellationToken) { if (msBuildNuGetProjectSystem.FileExistsInProject(path)) { @@ -90,7 +95,7 @@ internal static void TryAddFile(IMSBuildNuGetProjectSystem msBuildNuGetProjectSy { // overwrite msBuildNuGetProjectSystem.NuGetProjectContext.Log(MessageLevel.Info, Strings.Info_OverwritingExistingFile, path); - using (var stream = content()) + using (var stream = await streamTaskFactory()) { msBuildNuGetProjectSystem.AddFile(path, stream); } @@ -103,14 +108,15 @@ internal static void TryAddFile(IMSBuildNuGetProjectSystem msBuildNuGetProjectSy } else { - msBuildNuGetProjectSystem.AddFile(path, content()); + msBuildNuGetProjectSystem.AddFile(path, await streamTaskFactory()); } } - internal static void AddFiles(IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, - IPackageCoreReader packageReader, + internal static async Task AddFilesAsync(IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, + IAsyncPackageCoreReader packageReader, FrameworkSpecificGroup frameworkSpecificGroup, - IDictionary fileTransformers) + IDictionary fileTransformers, + CancellationToken cancellationToken) { var packageTargetFramework = frameworkSpecificGroup.TargetFramework; @@ -151,8 +157,11 @@ internal static void AddFiles(IMSBuildNuGetProjectSystem msBuildNuGetProjectSyst { if (installTransformer != null) { - installTransformer.TransformFile(() => packageReader.GetStream(file), path, - msBuildNuGetProjectSystem); + await installTransformer.TransformFileAsync( + () => packageReader.GetStreamAsync(file, cancellationToken), + path, + msBuildNuGetProjectSystem, + cancellationToken); } else { @@ -165,18 +174,24 @@ internal static void AddFiles(IMSBuildNuGetProjectSystem msBuildNuGetProjectSyst { continue; } - TryAddFile(msBuildNuGetProjectSystem, path, () => packageReader.GetStream(file)); + + await TryAddFileAsync( + msBuildNuGetProjectSystem, + path, + () => packageReader.GetStreamAsync(file, cancellationToken), + cancellationToken); } } } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] - internal static void DeleteFiles(IMSBuildNuGetProjectSystem projectSystem, + internal static async Task DeleteFilesAsync(IMSBuildNuGetProjectSystem projectSystem, ZipArchive zipArchive, IEnumerable otherPackagesPath, FrameworkSpecificGroup frameworkSpecificGroup, - IDictionary fileTransformers) + IDictionary fileTransformers, + CancellationToken cancellationToken) { var packageTargetFramework = frameworkSpecificGroup.TargetFramework; IPackageFileTransformer transformer; @@ -194,7 +209,7 @@ from directory in FileSystemUtility.GetDirectories(grouping.Key, altDirectorySep orderby directory.Length descending select directory; - string projectFullPath = projectSystem.ProjectFullPath; + var projectFullPath = projectSystem.ProjectFullPath; // Remove files from every directory foreach (var directory in directories) @@ -268,8 +283,11 @@ orderby directory.Length descending var zipArchiveFileEntry = PathUtility.GetEntry(zipArchive, file); if (zipArchiveFileEntry != null) { - transformer.RevertFile(zipArchiveFileEntry.Open, path, matchingFiles, - projectSystem); + await transformer.RevertFileAsync( + () => Task.FromResult(zipArchiveFileEntry.Open()), + path, matchingFiles, + projectSystem, + cancellationToken); } } catch (Exception e) @@ -284,7 +302,11 @@ orderby directory.Length descending var zipArchiveFileEntry = PathUtility.GetEntry(zipArchive, file); if (zipArchiveFileEntry != null) { - DeleteFileSafe(path, zipArchiveFileEntry.Open, projectSystem); + await DeleteFileSafeAsync( + path, + () => Task.FromResult(zipArchiveFileEntry.Open()), + projectSystem, + cancellationToken); } } catch (Exception e) @@ -329,13 +351,17 @@ internal static IEnumerable GetFiles(IMSBuildNuGetProjectSystem msBuildN return msBuildNuGetProjectSystem.GetFiles(path, filter, recursive); } - internal static void DeleteFileSafe(string path, Func streamFactory, IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem) + internal static async Task DeleteFileSafeAsync( + string path, + Func> streamFactory, + IMSBuildNuGetProjectSystem msBuildNuGetProjectSystem, + CancellationToken cancellationToken) { // Only delete the file if it exists and the checksum is the same if (msBuildNuGetProjectSystem.FileExistsInProject(path)) { var fullPath = Path.Combine(msBuildNuGetProjectSystem.ProjectFullPath, path); - if (FileSystemUtility.ContentEquals(fullPath, streamFactory)) + if (await FileSystemUtility.ContentEqualsAsync(fullPath, streamFactory)) { PerformSafeAction(() => msBuildNuGetProjectSystem.RemoveFile(path), msBuildNuGetProjectSystem.NuGetProjectContext); } diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/MessageMethod.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/MessageMethod.cs index 1b9e32d024f..c80ab3594c2 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/MessageMethod.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/MessageMethod.cs @@ -53,6 +53,11 @@ public enum MessageMethod /// GetPackageVersions, + /// + /// Get service index + /// + GetServiceIndex, + /// /// Handshake /// diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexRequest.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexRequest.cs new file mode 100644 index 00000000000..4e2c5646aba --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexRequest.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; + +namespace NuGet.Protocol.Plugins +{ + /// + /// A request to get the service index for a package source repository. + /// + public sealed class GetServiceIndexRequest + { + /// + /// Gets the package source repository location. + /// + [JsonRequired] + public string PackageSourceRepository { get; } + + /// + /// Initializes a new class. + /// + /// The package source repository location. + [JsonConstructor] + public GetServiceIndexRequest(string packageSourceRepository) + { + if (string.IsNullOrEmpty(packageSourceRepository)) + { + throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(packageSourceRepository)); + } + + PackageSourceRepository = packageSourceRepository; + } + } +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexResponse.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexResponse.cs new file mode 100644 index 00000000000..a44f76e7db4 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/Messages/GetServiceIndexResponse.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace NuGet.Protocol.Plugins +{ + /// + /// A response to a get service index request. + /// + public sealed class GetServiceIndexResponse + { + /// + /// Gets the response code. + /// + [JsonRequired] + public MessageResponseCode ResponseCode { get; } + + /// + /// Gets the service index (index.json) for the package source repository. + /// + public JObject ServiceIndex { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The response code. + /// The service index (index.json) for the package source repository. + /// Thrown if + /// is an undefined value. + /// Thrown if + /// is and + /// is null. + [JsonConstructor] + public GetServiceIndexResponse(MessageResponseCode responseCode, JObject serviceIndex) + { + if (!Enum.IsDefined(typeof(MessageResponseCode), responseCode)) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + Strings.Plugin_UnrecognizedEnumValue, + responseCode), + nameof(responseCode)); + } + + if (responseCode == MessageResponseCode.Success && serviceIndex == null) + { + throw new ArgumentNullException(nameof(serviceIndex)); + } + + ResponseCode = responseCode; + ServiceIndex = serviceIndex; + } + } +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginCredentialsProvider.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetCredentialsRequestHandler.cs similarity index 75% rename from src/NuGet.Core/NuGet.Protocol/Plugins/PluginCredentialsProvider.cs rename to src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetCredentialsRequestHandler.cs index 715b2cd3c09..74399072161 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginCredentialsProvider.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetCredentialsRequestHandler.cs @@ -2,46 +2,44 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Globalization; using System.Net; using System.Threading; using System.Threading.Tasks; using NuGet.Configuration; +using NuGet.Protocol.Core.Types; namespace NuGet.Protocol.Plugins { /// - /// A credentials provider for plugins. + /// A request handler for get credentials requests. /// - public sealed class PluginCredentialsProvider : IRequestHandler, IDisposable + public sealed class GetCredentialsRequestHandler : IRequestHandler, IDisposable { private const string _basicAuthenticationType = "Basic"; private readonly ICredentialService _credentialService; private bool _isDisposed; - private readonly PackageSource _packageSource; private readonly IPlugin _plugin; private readonly IWebProxy _proxy; + private readonly ConcurrentDictionary _repositories; /// - /// Gets the cancellation token. + /// Gets the for a request. /// public CancellationToken CancellationToken => CancellationToken.None; /// - /// Initializes a new class. + /// Initializes a new class. /// /// A plugin. - /// A package source. /// A web proxy. /// An optional credential service. /// Thrown if /// is null. - /// Thrown if - /// is null. - public PluginCredentialsProvider( + public GetCredentialsRequestHandler( IPlugin plugin, - PackageSource packageSource, IWebProxy proxy, ICredentialService credentialService) { @@ -50,15 +48,10 @@ public PluginCredentialsProvider( throw new ArgumentNullException(nameof(plugin)); } - if (packageSource == null) - { - throw new ArgumentNullException(nameof(packageSource)); - } - _plugin = plugin; - _packageSource = packageSource; _proxy = proxy; _credentialService = credentialService; + _repositories = new ConcurrentDictionary(); } /// @@ -76,6 +69,28 @@ public void Dispose() } } + /// + /// Adds or updates a source repository in a source repository cache. + /// + /// A source repository. + /// Thrown if + /// is null. + public void AddOrUpdateSourceRepository(SourceRepository sourceRepository) + { + if (sourceRepository == null) + { + throw new ArgumentNullException(nameof(sourceRepository)); + } + + if (sourceRepository.PackageSource != null && sourceRepository.PackageSource.IsHttp) + { + _repositories.AddOrUpdate( + sourceRepository.PackageSource.Source, + sourceRepository, + (source, repo) => sourceRepository); + } + } + /// /// Asynchronously handles cancelling a request. /// @@ -133,12 +148,14 @@ public async Task HandleResponseAsync( cancellationToken.ThrowIfCancellationRequested(); var requestPayload = MessageUtilities.DeserializePayload(request); + var packageSource = GetPackageSource(requestPayload.PackageSourceRepository); + GetCredentialsResponse responsePayload; - if (_packageSource.IsHttp && + if (packageSource.IsHttp && string.Equals( requestPayload.PackageSourceRepository, - _packageSource.Source, + packageSource.Source, StringComparison.OrdinalIgnoreCase)) { NetworkCredential credential = null; @@ -149,7 +166,10 @@ public async Task HandleResponseAsync( PluginConstants.ProgressInterval, cancellationToken)) { - credential = await GetCredentialAsync(requestPayload.StatusCode, cancellationToken); + credential = await GetCredentialAsync( + packageSource, + requestPayload.StatusCode, + cancellationToken); } if (credential == null) @@ -179,6 +199,7 @@ public async Task HandleResponseAsync( } private async Task GetCredentialAsync( + PackageSource packageSource, HttpStatusCode statusCode, CancellationToken cancellationToken) { @@ -186,19 +207,20 @@ private async Task GetCredentialAsync( if (requestType == CredentialRequestType.Proxy) { - return await GetProxyCredentialAsync(cancellationToken); + return await GetProxyCredentialAsync(packageSource, cancellationToken); } - return await GetPackageSourceCredential(requestType, cancellationToken); + return await GetPackageSourceCredential(requestType, packageSource, cancellationToken); } private async Task GetPackageSourceCredential( CredentialRequestType requestType, + PackageSource packageSource, CancellationToken cancellationToken) { - if (_packageSource.Credentials != null && _packageSource.Credentials.IsValid()) + if (packageSource.Credentials != null && packageSource.Credentials.IsValid()) { - return new NetworkCredential(_packageSource.Credentials.Username, _packageSource.Credentials.Password); + return new NetworkCredential(packageSource.Credentials.Username, packageSource.Credentials.Password); } if (_credentialService == null) @@ -212,17 +234,17 @@ private async Task GetPackageSourceCredential( message = string.Format( CultureInfo.CurrentCulture, Strings.Http_CredentialsForUnauthorized, - _packageSource.Source); + packageSource.Source); } else { message = string.Format( CultureInfo.CurrentCulture, Strings.Http_CredentialsForForbidden, - _packageSource.Source); + packageSource.Source); } - var sourceUri = _packageSource.SourceUri; + var sourceUri = packageSource.SourceUri; var credentials = await _credentialService.GetCredentialsAsync( sourceUri, _proxy, @@ -230,14 +252,16 @@ private async Task GetPackageSourceCredential( message, cancellationToken); - return credentials.GetCredential(sourceUri, authType: null); + return credentials?.GetCredential(sourceUri, authType: null); } - private async Task GetProxyCredentialAsync(CancellationToken cancellationToken) + private async Task GetProxyCredentialAsync( + PackageSource packageSource, + CancellationToken cancellationToken) { if (_proxy != null && _credentialService != null) { - var sourceUri = _packageSource.SourceUri; + var sourceUri = packageSource.SourceUri; var proxyUri = _proxy.GetProxy(sourceUri); var message = string.Format( CultureInfo.CurrentCulture, @@ -271,5 +295,17 @@ private static CredentialRequestType GetCredentialRequestType(HttpStatusCode sta return CredentialRequestType.Forbidden; } } + + private PackageSource GetPackageSource(string packageSourceRepository) + { + SourceRepository sourceRepository; + + if (_repositories.TryGetValue(packageSourceRepository, out sourceRepository)) + { + return sourceRepository.PackageSource; + } + + return new PackageSource(packageSourceRepository); + } } } \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetServiceIndexRequestHandler.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetServiceIndexRequestHandler.cs new file mode 100644 index 00000000000..4c04088dfc4 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/RequestHandlers/GetServiceIndexRequestHandler.cs @@ -0,0 +1,160 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using NuGet.Protocol.Core.Types; + +namespace NuGet.Protocol.Plugins +{ + /// + /// A request handler for get service index requests. + /// + public sealed class GetServiceIndexRequestHandler : IRequestHandler, IDisposable + { + private bool _isDisposed; + private readonly IPlugin _plugin; + private readonly ConcurrentDictionary _repositories; + + /// + /// Gets the for a request. + /// + public CancellationToken CancellationToken => CancellationToken.None; + + /// + /// Initializes a new class. + /// + /// A plugin. + /// Thrown if is null. + public GetServiceIndexRequestHandler(IPlugin plugin) + { + if (plugin == null) + { + throw new ArgumentNullException(nameof(plugin)); + } + + _plugin = plugin; + _repositories = new ConcurrentDictionary(); + } + + /// + /// Disposes of this instance. + /// + public void Dispose() + { + if (!_isDisposed) + { + _plugin.Dispose(); + + GC.SuppressFinalize(this); + + _isDisposed = true; + } + } + + /// + /// Adds or updates a source repository in a source repository cache. + /// + /// A source repository. + /// Thrown if + /// is null. + public void AddOrUpdateSourceRepository(SourceRepository sourceRepository) + { + if (sourceRepository == null) + { + throw new ArgumentNullException(nameof(sourceRepository)); + } + + if (sourceRepository.PackageSource != null && sourceRepository.PackageSource.IsHttp) + { + _repositories.AddOrUpdate( + sourceRepository.PackageSource.Source, + sourceRepository, + (source, repo) => sourceRepository); + } + } + + /// + /// Asynchronously handles cancelling a request. + /// + /// The connection. + /// A request message. + /// A response handler. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown always. + public Task HandleCancelAsync( + IConnection connection, + Message request, + IResponseHandler responseHandler, + CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + + /// + /// Asynchronously handles responding to a request. + /// + /// The connection. + /// A request message. + /// A response handler. + /// A cancellation token. + /// A task that represents the asynchronous operation. + /// Thrown if + /// is null. + /// Thrown if is null. + /// Thrown if + /// is null. + /// Thrown if + /// is cancelled. + public async Task HandleResponseAsync( + IConnection connection, + Message request, + IResponseHandler responseHandler, + CancellationToken cancellationToken) + { + if (connection == null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (responseHandler == null) + { + throw new ArgumentNullException(nameof(responseHandler)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + var getRequest = MessageUtilities.DeserializePayload(request); + SourceRepository sourceRepository; + ServiceIndexResourceV3 serviceIndex = null; + GetServiceIndexResponse responsePayload; + + if (_repositories.TryGetValue(getRequest.PackageSourceRepository, out sourceRepository)) + { + serviceIndex = await sourceRepository.GetResourceAsync(cancellationToken); + } + + if (serviceIndex == null) + { + responsePayload = new GetServiceIndexResponse(MessageResponseCode.NotFound, serviceIndex: null); + } + else + { + var serviceIndexJson = JObject.Parse(serviceIndex.Json); + + responsePayload = new GetServiceIndexResponse(MessageResponseCode.Success, serviceIndexJson); + } + + await responseHandler.SendResponseAsync(request, responsePayload, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Protocol/Providers/DownloadResourcePluginProvider.cs b/src/NuGet.Core/NuGet.Protocol/Providers/DownloadResourcePluginProvider.cs index 1d2afeddad9..e1d88c59031 100644 --- a/src/NuGet.Core/NuGet.Protocol/Providers/DownloadResourcePluginProvider.cs +++ b/src/NuGet.Core/NuGet.Protocol/Providers/DownloadResourcePluginProvider.cs @@ -60,22 +60,54 @@ public override async Task> TryCreate( if (result != null) { - var credentialsProvider = new PluginCredentialsProvider( - result.Plugin, - source.PackageSource, - httpHandlerResource.ClientHandler?.Proxy, - HttpHandlerResourceV3.CredentialService); + AddOrUpdateGetCredentialsRequestHandler(result.Plugin, source, httpHandlerResource); + AddOrUpdateGetServiceIndexRequestHandler(result.Plugin, source); resource = new DownloadResourcePlugin( result.Plugin, result.PluginMulticlientUtilities, - source.PackageSource, - credentialsProvider); + source.PackageSource); } } } return new Tuple(resource != null, resource); } + + private static void AddOrUpdateGetCredentialsRequestHandler( + IPlugin plugin, + SourceRepository source, + HttpHandlerResource httpHandlerResource) + { + plugin.Connection.MessageDispatcher.RequestHandlers.AddOrUpdate( + MessageMethod.GetCredentials, + () => new GetCredentialsRequestHandler( + plugin, + httpHandlerResource.ClientHandler?.Proxy, + HttpHandlerResourceV3.CredentialService), + existingHandler => + { + var handler = (GetCredentialsRequestHandler)existingHandler; + + handler.AddOrUpdateSourceRepository(source); + + return handler; + }); + } + + private static void AddOrUpdateGetServiceIndexRequestHandler(IPlugin plugin, SourceRepository source) + { + plugin.Connection.MessageDispatcher.RequestHandlers.AddOrUpdate( + MessageMethod.GetServiceIndex, + () => new GetServiceIndexRequestHandler(plugin), + existingHandler => + { + var handler = (GetServiceIndexRequestHandler)existingHandler; + + handler.AddOrUpdateSourceRepository(source); + + return handler; + }); + } } } \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Protocol/RemoteRepositories/PluginFindPackageByIdResource.cs b/src/NuGet.Core/NuGet.Protocol/RemoteRepositories/PluginFindPackageByIdResource.cs index 6300cb7a311..4c4bb693024 100644 --- a/src/NuGet.Core/NuGet.Protocol/RemoteRepositories/PluginFindPackageByIdResource.cs +++ b/src/NuGet.Core/NuGet.Protocol/RemoteRepositories/PluginFindPackageByIdResource.cs @@ -23,7 +23,6 @@ namespace NuGet.Protocol.Core.Types /// public sealed class PluginFindPackageByIdResource : FindPackageByIdResource { - private PluginCredentialsProvider _credentialsProvider; private readonly ConcurrentDictionary>> _packageInfoCache = new ConcurrentDictionary>>(StringComparer.OrdinalIgnoreCase); private readonly PackageSource _packageSource; @@ -36,20 +35,16 @@ public sealed class PluginFindPackageByIdResource : FindPackageByIdResource /// A plugin. /// A plugin multiclient utilities. /// A package source. - /// A plugin credentials provider. /// Thrown if /// is null. /// Thrown if /// is null. /// Thrown if /// is null. - /// Thrown if - /// is null. public PluginFindPackageByIdResource( IPlugin plugin, IPluginMulticlientUtilities utilities, - PackageSource packageSource, - PluginCredentialsProvider credentialsProvider) + PackageSource packageSource) { if (plugin == null) { @@ -66,15 +61,9 @@ public PluginFindPackageByIdResource( throw new ArgumentNullException(nameof(packageSource)); } - if (credentialsProvider == null) - { - throw new ArgumentNullException(nameof(credentialsProvider)); - } - _plugin = plugin; _utilities = utilities; _packageSource = packageSource; - _credentialsProvider = credentialsProvider; } /// @@ -185,8 +174,6 @@ public override async Task> GetAllVersionsAsync( AddOrUpdateLogger(_plugin, logger); - _credentialsProvider = TryUpdateCredentialProvider(_plugin, _credentialsProvider); - await _utilities.DoOncePerPluginLifetimeAsync( MessageMethod.SetLogLevel.ToString(), () => SetLogLevelAsync(logger, cancellationToken), @@ -252,8 +239,6 @@ public override async Task GetDependencyInfoAsync { AddOrUpdateLogger(_plugin, logger); - _credentialsProvider = TryUpdateCredentialProvider(_plugin, _credentialsProvider); - await _utilities.DoOncePerPluginLifetimeAsync( MessageMethod.SetLogLevel.ToString(), () => SetLogLevelAsync(logger, cancellationToken), @@ -419,25 +404,6 @@ await _plugin.Connection.SendRequestAndReceiveResponseAsync> TryCreate( if (result != null) { - var credentialsProvider = new PluginCredentialsProvider( - result.Plugin, - source.PackageSource, - httpHandlerResource.ClientHandler?.Proxy, - HttpHandlerResourceV3.CredentialService); + AddOrUpdateGetCredentialsRequestHandler(result.Plugin, source, httpHandlerResource); + AddOrUpdateGetServiceIndexRequestHandler(result.Plugin, source); resource = new PluginFindPackageByIdResource( result.Plugin, result.PluginMulticlientUtilities, - source.PackageSource, - credentialsProvider); + source.PackageSource); } } } return new Tuple(resource != null, resource); } + + private static void AddOrUpdateGetCredentialsRequestHandler( + IPlugin plugin, + SourceRepository source, + HttpHandlerResource httpHandlerResource) + { + plugin.Connection.MessageDispatcher.RequestHandlers.AddOrUpdate( + MessageMethod.GetCredentials, + () => new GetCredentialsRequestHandler( + plugin, + httpHandlerResource.ClientHandler?.Proxy, + HttpHandlerResourceV3.CredentialService), + existingHandler => + { + var handler = (GetCredentialsRequestHandler)existingHandler; + + handler.AddOrUpdateSourceRepository(source); + + return handler; + }); + } + + private static void AddOrUpdateGetServiceIndexRequestHandler(IPlugin plugin, SourceRepository source) + { + plugin.Connection.MessageDispatcher.RequestHandlers.AddOrUpdate( + MessageMethod.GetServiceIndex, + () => new GetServiceIndexRequestHandler(plugin), + existingHandler => + { + var handler = (GetServiceIndexRequestHandler)existingHandler; + + handler.AddOrUpdateSourceRepository(source); + + return handler; + }); + } } } \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Protocol/Resources/DownloadResourcePlugin.cs b/src/NuGet.Core/NuGet.Protocol/Resources/DownloadResourcePlugin.cs index be6b07d9780..1acdadbae69 100644 --- a/src/NuGet.Core/NuGet.Protocol/Resources/DownloadResourcePlugin.cs +++ b/src/NuGet.Core/NuGet.Protocol/Resources/DownloadResourcePlugin.cs @@ -18,7 +18,6 @@ namespace NuGet.Protocol /// public sealed class DownloadResourcePlugin : DownloadResource { - private PluginCredentialsProvider _credentialsProvider; private readonly IPlugin _plugin; private readonly PackageSource _packageSource; private readonly IPluginMulticlientUtilities _utilities; @@ -29,20 +28,16 @@ public sealed class DownloadResourcePlugin : DownloadResource /// A plugin. /// A plugin multiclient utilities. /// A package source. - /// A plugin credentials provider. /// Thrown if /// is null. /// Thrown if /// is null. /// Thrown if /// is null. - /// Thrown if - /// is null. public DownloadResourcePlugin( IPlugin plugin, IPluginMulticlientUtilities utilities, - PackageSource packageSource, - PluginCredentialsProvider credentialsProvider) + PackageSource packageSource) { if (plugin == null) { @@ -59,15 +54,9 @@ public DownloadResourcePlugin( throw new ArgumentNullException(nameof(packageSource)); } - if (credentialsProvider == null) - { - throw new ArgumentNullException(nameof(credentialsProvider)); - } - _plugin = plugin; _utilities = utilities; _packageSource = packageSource; - _credentialsProvider = credentialsProvider; } /// @@ -113,8 +102,6 @@ public async override Task GetDownloadResourceResultAsyn AddOrUpdateLogger(_plugin, logger); - _credentialsProvider = TryUpdateCredentialProvider(_plugin, _credentialsProvider); - await _utilities.DoOncePerPluginLifetimeAsync( MessageMethod.SetLogLevel.ToString(), () => SetLogLevelAsync(logger, cancellationToken), @@ -169,24 +156,5 @@ await _plugin.Connection.SendRequestAndReceiveResponseAsync( + () => Preprocessor.Process(stream: null, tokenReplacement: t => t)); + + Assert.Equal("stream", exception.ParamName); + } + + [Fact] + public void Process_ThrowsForNullTokenReplacement() + { + var exception = Assert.Throws( + () => Preprocessor.Process(Stream.Null, tokenReplacement: null)); + + Assert.Equal("tokenReplacement", exception.ParamName); + } + + [Fact] + public void Process_ReplacesNoTokens() + { + var rawText = "a b c"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawText))) + { + var actualResult = Preprocessor.Process(stream, token => token); + + Assert.Equal(rawText, actualResult); + } + } + + [Fact] + public void Process_ReplacesOneToken() + { + var rawText = "a $b$ c"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawText))) + { + var actualResult = Preprocessor.Process(stream, token => "d"); + + Assert.Equal("a d c", actualResult); + } + } + + [Fact] + public void Process_ReplacesTwoTokens() + { + var rawText = "$a$ b $c$"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawText))) + { + var actualResult = Preprocessor.Process(stream, token => + { + switch (token) + { + case "a": + return "d"; + + case "c": + return "e"; + + default: + return "f"; + } + }); + + Assert.Equal("d b e", actualResult); + } + } + + [Fact] + public async Task ProcessAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => Preprocessor.ProcessAsync( + streamTaskFactory: null, + tokenReplacement: t => t, + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task ProcessAsync_ThrowsForNullTokenReplacement() + { + var exception = await Assert.ThrowsAsync( + () => Preprocessor.ProcessAsync( + () => Task.FromResult(Stream.Null), + tokenReplacement: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("tokenReplacement", exception.ParamName); + } + + [Fact] + public async Task ProcessAsync_ThrowsIfCanelled() + { + await Assert.ThrowsAsync( + () => Preprocessor.ProcessAsync( + () => Task.FromResult(Stream.Null), + t => t, + new CancellationToken(canceled: true))); + } + + [Fact] + public async Task ProcessAsync_ReplacesNoTokens() + { + var rawText = "a b c"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawText))) + { + var actualResult = await Preprocessor.ProcessAsync( + () => Task.FromResult(stream), + token => token, + CancellationToken.None); + + Assert.Equal(rawText, actualResult); + } + } + + [Fact] + public async Task ProcessAsync_ReplacesOneToken() + { + var rawText = "a $b$ c"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawText))) + { + var actualResult = await Preprocessor.ProcessAsync( + () => Task.FromResult(stream), + token => "d", + CancellationToken.None); + + Assert.Equal("a d c", actualResult); + } + } + + [Fact] + public async Task ProcessAsync_ReplacesTwoTokens() + { + var rawText = "$a$ b $c$"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawText))) + { + var actualResult = await Preprocessor.ProcessAsync( + () => Task.FromResult(stream), + token => + { + switch (token) + { + case "a": + return "d"; + + case "c": + return "e"; + + default: + return "f"; + } + }, + CancellationToken.None); + + Assert.Equal("d b e", actualResult); + } + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/PreprocessorTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/PreprocessorTests.cs new file mode 100644 index 00000000000..da7d17f0c23 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/PreprocessorTests.cs @@ -0,0 +1,201 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using NuGet.ProjectManagement; +using NuGet.Test.Utility; +using Xunit; + +namespace NuGet.PackageManagement.Test +{ + public class PreprocessorTests + { + private readonly Preprocessor _processor; + + public PreprocessorTests() + { + _processor = new Preprocessor(); + } + + [Fact] + public async Task TransformFileAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => _processor.TransformFileAsync( + streamTaskFactory: null, + targetPath: "a", + projectSystem: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsForNullProjectSystem() + { + var exception = await Assert.ThrowsAsync( + () => _processor.TransformFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + projectSystem: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("projectSystem", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => _processor.TransformFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + projectSystem: Mock.Of(), + cancellationToken: new CancellationToken(canceled: true))); + } + + [Fact] + public async Task TransformFileAsync_AddsFileToProject() + { + using (var test = new PreprocessorTest("a")) + { + test.ProjectSystem.Setup(x => x.FileExistsInProject(It.IsNotNull())) + .Returns(false); + + test.ProjectSystem.Setup(x => x.AddFile(It.IsNotNull(), It.IsNotNull())) + .Callback( + (filePath, stream) => + { + Assert.Equal(test.TargetFile.FullName, filePath); + + stream.Seek(offset: 0, origin: SeekOrigin.Begin); + + using (var reader = new StreamReader(stream)) + { + var actualStreamContent = reader.ReadToEnd(); + + Assert.Equal(test.StreamContent, actualStreamContent); + } + }); + + await test.Processor.TransformFileAsync( + test.StreamTaskFactory, + test.TargetFile.FullName, + test.ProjectSystem.Object, + CancellationToken.None); + } + } + + [Fact] + public async Task RevertFileAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => _processor.RevertFileAsync( + streamTaskFactory: null, + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task RevertFileAsync_ThrowsForNullProjectSystem() + { + var exception = await Assert.ThrowsAsync( + () => _processor.RevertFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("projectSystem", exception.ParamName); + } + + [Fact] + public async Task RevertFileAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => _processor.RevertFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: Mock.Of(), + cancellationToken: new CancellationToken(canceled: true))); + } + + [Fact] + public async Task RevertFileAsync_RemovesFileFromProject() + { + using (var test = new PreprocessorTest("a")) + { + File.WriteAllText(test.TargetFile.FullName, test.StreamContent); + + test.ProjectSystem.Setup(x => x.FileExistsInProject(It.IsNotNull())) + .Returns(true); + + test.ProjectSystem.SetupGet(x => x.ProjectFullPath) + .Returns(test.TargetFile.DirectoryName); + + test.ProjectSystem.SetupGet(x => x.NuGetProjectContext) + .Returns((INuGetProjectContext)null); + + test.ProjectSystem.Setup(x => x.RemoveFile(It.IsNotNull())) + .Callback( + (filePath) => + { + Assert.Equal(test.TargetFile.FullName, filePath); + }); + + await test.Processor.RevertFileAsync( + test.StreamTaskFactory, + test.TargetFile.FullName, + Enumerable.Empty(), + test.ProjectSystem.Object, + CancellationToken.None); + } + } + + private sealed class PreprocessorTest : IDisposable + { + private readonly MemoryStream _stream; + + internal Preprocessor Processor { get; } + internal Mock ProjectSystem { get; } + internal string StreamContent { get; } + internal Func> StreamTaskFactory { get; } + internal FileInfo TargetFile { get; } + internal TestDirectory TestDirectory { get; } + + internal PreprocessorTest(string streamContent) + { + _stream = new MemoryStream(Encoding.UTF8.GetBytes(streamContent)); + + StreamContent = streamContent; + StreamTaskFactory = () => Task.FromResult(_stream); + Processor = new Preprocessor(); + ProjectSystem = new Mock(MockBehavior.Strict); + TestDirectory = TestDirectory.Create(); + TargetFile = new FileInfo(Path.Combine(TestDirectory.Path, "target.file")); + } + + public void Dispose() + { + _stream.Dispose(); + TestDirectory.Dispose(); + + GC.SuppressFinalize(this); + + ProjectSystem.Verify(); + } + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XdtTransformerTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XdtTransformerTests.cs new file mode 100644 index 00000000000..b35754dfb2e --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XdtTransformerTests.cs @@ -0,0 +1,237 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using NuGet.ProjectManagement; +using NuGet.Test.Utility; +using Xunit; + +namespace NuGet.PackageManagement.Test +{ + public class XdtTransformerTests + { + private readonly XdtTransformer _transformer; + + public XdtTransformerTests() + { + _transformer = new XdtTransformer(); + } + + [Fact] + public async Task TransformFileAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.TransformFileAsync( + streamTaskFactory: null, + targetPath: "a", + projectSystem: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsForNullProjectSystem() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.TransformFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + projectSystem: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("projectSystem", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => _transformer.TransformFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + projectSystem: Mock.Of(), + cancellationToken: new CancellationToken(canceled: true))); + } + + [Fact] + public async Task TransformFileAsync_TransformsFile() + { + using (var test = new XdtTransformerTest("$c$")) + { + var projectFileOriginalContent = ""; + + File.WriteAllText(test.TargetFile.FullName, projectFileOriginalContent); + + test.ProjectSystem.SetupGet(x => x.ProjectFullPath) + .Returns(test.TargetFile.DirectoryName); + test.ProjectSystem.SetupGet(x => x.ProjectName) + .Returns("ProjectName"); + test.ProjectSystem.Setup(x => x.GetPropertyValue(It.IsNotNull())) + .Returns("d"); + test.ProjectSystem.Setup(x => x.AddFile(It.IsNotNull(), It.IsNotNull())) + .Callback( + (targetFilePath, stream) => + { + Assert.Equal(test.TargetFile.Name, targetFilePath); + + stream.Seek(offset: 0, origin: SeekOrigin.Begin); + + using (var reader = new StreamReader(stream)) + { + var actualResult = reader.ReadToEnd(); + var expectedResult = "\t\t\t\t\td\t\t\t"; + + Assert.Equal(expectedResult, actualResult); + } + }); + + await test.Transformer.TransformFileAsync( + test.StreamTaskFactory, + test.TargetFile.Name, + test.ProjectSystem.Object, + CancellationToken.None); + } + } + + [Fact] + public async Task RevertFileAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.RevertFileAsync( + streamTaskFactory: null, + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task RevertFileAsync_ThrowsForNullProjectSystem() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.RevertFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("projectSystem", exception.ParamName); + } + + [Fact] + public async Task RevertFileAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => _transformer.RevertFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: Mock.Of(), + cancellationToken: new CancellationToken(canceled: true))); + } + + [Fact] + public async Task RevertFileAsync_RevertsFile() + { + var projectFileContent = string.Format( + CultureInfo.InvariantCulture, + "{0} {0} d{0} {0} d{0} {0} {0}", + Environment.NewLine); + + using (var test = new XdtTransformerTest("$c$")) + { + var zipArchiveFilePath = Path.Combine(test.TestDirectory.Path, "archive.zip"); + var zipFileInfo = new InternalZipFileInfo(zipArchiveFilePath, "install.xdt"); + + using (var zipFileStream = File.OpenWrite(zipArchiveFilePath)) + using (var zipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Create)) + { + var content = Encoding.UTF8.GetBytes("$c$"); + + zipArchive.AddEntry(zipFileInfo.ZipArchiveEntryFullName, content); + } + + File.WriteAllText(test.TargetFile.FullName, projectFileContent); + + test.ProjectSystem.SetupGet(x => x.ProjectFullPath) + .Returns(test.TargetFile.DirectoryName); + test.ProjectSystem.SetupGet(x => x.ProjectName) + .Returns("ProjectName"); + test.ProjectSystem.Setup(x => x.GetPropertyValue(It.IsNotNull())) + .Returns("d"); + test.ProjectSystem.Setup(x => x.AddFile(It.IsNotNull(), It.IsNotNull())) + .Callback( + (targetFilePath, stream) => + { + Assert.Equal(test.TargetFile.Name, targetFilePath); + + stream.Seek(offset: 0, origin: SeekOrigin.Begin); + + using (var reader = new StreamReader(stream)) + { + var actualResult = reader.ReadToEnd(); + var expectedResult = string.Format( + CultureInfo.InvariantCulture, + "{0} {0} d{0} {0}", + Environment.NewLine); + + Assert.Equal(expectedResult, actualResult); + } + }); + + await test.Transformer.RevertFileAsync( + test.StreamTaskFactory, + test.TargetFile.Name, + new[] { zipFileInfo }, + test.ProjectSystem.Object, + CancellationToken.None); + } + } + + private sealed class XdtTransformerTest : IDisposable + { + private readonly MemoryStream _stream; + + internal Mock ProjectSystem { get; } + internal Func> StreamTaskFactory { get; } + internal FileInfo TargetFile { get; } + internal TestDirectory TestDirectory { get; } + internal XdtTransformer Transformer { get; } + internal string TransformStreamContent { get; } + + internal XdtTransformerTest(string transformStreamContent) + { + _stream = new MemoryStream(Encoding.UTF8.GetBytes(transformStreamContent)); + + TransformStreamContent = transformStreamContent; + StreamTaskFactory = () => Task.FromResult(_stream); + Transformer = new XdtTransformer(); + ProjectSystem = new Mock(MockBehavior.Strict); + TestDirectory = TestDirectory.Create(); + TargetFile = new FileInfo(Path.Combine(TestDirectory.Path, "target.file")); + } + + public void Dispose() + { + _stream.Dispose(); + TestDirectory.Dispose(); + + GC.SuppressFinalize(this); + + ProjectSystem.Verify(); + } + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XmlTransformerTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XmlTransformerTests.cs new file mode 100644 index 00000000000..d84977bc842 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/FileModifiers/XmlTransformerTests.cs @@ -0,0 +1,251 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Moq; +using NuGet.ProjectManagement; +using NuGet.Test.Utility; +using Xunit; + +namespace NuGet.PackageManagement.Test +{ + public class XmlTransformerTests + { + private readonly XmlTransformer _transformer; + + public XmlTransformerTests() + { + _transformer = new XmlTransformer(new Dictionary> + { + { "x", (parent, element) => parent.AddFirst(element) } + }); + } + + [Fact] + public void Constructor_ThrowsForNullNodeActions() + { + var exception = Assert.Throws( + () => new XmlTransformer(nodeActions: null)); + + Assert.Equal("nodeActions", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.TransformFileAsync( + streamTaskFactory: null, + targetPath: "a", + projectSystem: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsForNullProjectSystem() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.TransformFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + projectSystem: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("projectSystem", exception.ParamName); + } + + [Fact] + public async Task TransformFileAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => _transformer.TransformFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + projectSystem: Mock.Of(), + cancellationToken: new CancellationToken(canceled: true))); + } + + [Fact] + public async Task TransformFileAsync_TransformsFile() + { + using (var test = new XmlTransformerTest("$c$")) + { + var projectFileOriginalContent = ""; + + File.WriteAllText(test.TargetFile.FullName, projectFileOriginalContent); + + test.ProjectSystem.Setup(x => x.FileExistsInProject(It.IsNotNull())) + .Returns(false); + test.ProjectSystem.SetupGet(x => x.ProjectFullPath) + .Returns(test.TargetFile.DirectoryName); + test.ProjectSystem.SetupGet(x => x.NuGetProjectContext) + .Returns(Mock.Of()); + test.ProjectSystem.Setup(x => x.GetPropertyValue(It.IsNotNull())) + .Returns("d"); + test.ProjectSystem.Setup(x => x.AddFile(It.IsNotNull(), It.IsNotNull())) + .Callback( + (targetFilePath, stream) => + { + Assert.Equal(test.TargetFile.Name, targetFilePath); + + stream.Seek(offset: 0, origin: SeekOrigin.Begin); + + using (var reader = new StreamReader(stream)) + { + var actualResult = reader.ReadToEnd(); + var expectedResult = string.Format( + CultureInfo.InvariantCulture, + "{0}{0} {0} {0} {0} d{0}", + Environment.NewLine); + + Assert.Equal(expectedResult, actualResult); + } + }); + + await test.Transformer.TransformFileAsync( + test.StreamTaskFactory, + test.TargetFile.Name, + test.ProjectSystem.Object, + CancellationToken.None); + } + } + + [Fact] + public async Task RevertFileAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.RevertFileAsync( + streamTaskFactory: null, + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task RevertFileAsync_ThrowsForNullProjectSystem() + { + var exception = await Assert.ThrowsAsync( + () => _transformer.RevertFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("projectSystem", exception.ParamName); + } + + [Fact] + public async Task RevertFileAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => _transformer.RevertFileAsync( + () => Task.FromResult(Stream.Null), + targetPath: "a", + matchingFiles: Enumerable.Empty(), + projectSystem: Mock.Of(), + cancellationToken: new CancellationToken(canceled: true))); + } + + [Fact] + public async Task RevertFileAsync_RevertsFile() + { + var projectFileContent = string.Format( + CultureInfo.InvariantCulture, + "{0}{0} {0} {0} d{0} {0} d{0}", + Environment.NewLine); + + using (var test = new XmlTransformerTest(projectFileContent)) + { + var zipArchiveFilePath = Path.Combine(test.TestDirectory.Path, "archive.zip"); + var zipFileInfo = new InternalZipFileInfo(zipArchiveFilePath, "xml.transform"); + + using (var zipFileStream = File.OpenWrite(zipArchiveFilePath)) + using (var zipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Create)) + { + var content = Encoding.UTF8.GetBytes("$c$"); + + zipArchive.AddEntry(zipFileInfo.ZipArchiveEntryFullName, content); + } + + File.WriteAllText(test.TargetFile.FullName, projectFileContent); + + test.ProjectSystem.Setup(x => x.FileExistsInProject(It.IsNotNull())) + .Returns(true); + test.ProjectSystem.SetupGet(x => x.ProjectFullPath) + .Returns(test.TargetFile.DirectoryName); + test.ProjectSystem.SetupGet(x => x.NuGetProjectContext) + .Returns(Mock.Of()); + test.ProjectSystem.Setup(x => x.GetPropertyValue(It.IsNotNull())) + .Returns("d"); + test.ProjectSystem.Setup(x => x.RemoveFile(It.IsNotNull())); + + await test.Transformer.RevertFileAsync( + test.StreamTaskFactory, + test.TargetFile.Name, + new[] { zipFileInfo }, + test.ProjectSystem.Object, + CancellationToken.None); + + var expectedResult = string.Format( + CultureInfo.InvariantCulture, + "{0}{0} {0} {0} d{0} {0} {0}", + Environment.NewLine); + var actualResult = File.ReadAllText(test.TargetFile.FullName); + + Assert.Equal(expectedResult, actualResult); + } + } + + private sealed class XmlTransformerTest : IDisposable + { + private readonly MemoryStream _stream; + + internal Mock ProjectSystem { get; } + internal Func> StreamTaskFactory { get; } + internal FileInfo TargetFile { get; } + internal TestDirectory TestDirectory { get; } + internal XmlTransformer Transformer { get; } + internal string TransformStreamContent { get; } + + internal XmlTransformerTest(string transformStreamContent) + { + _stream = new MemoryStream(Encoding.UTF8.GetBytes(transformStreamContent)); + + TransformStreamContent = transformStreamContent; + StreamTaskFactory = () => Task.FromResult(_stream); + Transformer = new XmlTransformer(new Dictionary> + { + { "x", (parent, element) => parent.AddFirst(element) } + }); + ProjectSystem = new Mock(MockBehavior.Strict); + TestDirectory = TestDirectory.Create(); + TargetFile = new FileInfo(Path.Combine(TestDirectory.Path, "target.file")); + } + + public void Dispose() + { + _stream.Dispose(); + TestDirectory.Dispose(); + + GC.SuppressFinalize(this); + + ProjectSystem.Verify(); + } + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/InstallationCompatibilityTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/InstallationCompatibilityTests.cs index 15d8471d178..088281ff53c 100644 --- a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/InstallationCompatibilityTests.cs +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/InstallationCompatibilityTests.cs @@ -1,10 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Moq; @@ -30,7 +31,57 @@ namespace NuGet.PackageManagement.Test public class InstallationCompatibilityTests { [Fact] - public void InstallationCompatibility_WithLowerMinClientVersion_Fails() + public async Task EnsurePackageCompatibilityAsync_ThrowsForNullNuGetProject() + { + var exception = await Assert.ThrowsAsync( + () => InstallationCompatibility.Instance.EnsurePackageCompatibilityAsync( + nuGetProject: null, + packageIdentity: new PackageIdentity(id: "a", version: NuGetVersion.Parse("1.0.0")), + resourceResult: new DownloadResourceResult(DownloadResourceResultStatus.NotFound), + cancellationToken: CancellationToken.None)); + + Assert.Equal("nuGetProject", exception.ParamName); + } + + [Fact] + public async Task EnsurePackageCompatibilityAsync_ThrowsForNullPackageIdentity() + { + var exception = await Assert.ThrowsAsync( + () => InstallationCompatibility.Instance.EnsurePackageCompatibilityAsync( + Mock.Of(), + packageIdentity: null, + resourceResult: new DownloadResourceResult(DownloadResourceResultStatus.NotFound), + cancellationToken: CancellationToken.None)); + + Assert.Equal("packageIdentity", exception.ParamName); + } + + [Fact] + public async Task EnsurePackageCompatibilityAsync_ThrowsForNullResourceResult() + { + var exception = await Assert.ThrowsAsync( + () => InstallationCompatibility.Instance.EnsurePackageCompatibilityAsync( + Mock.Of(), + new PackageIdentity(id: "a", version: NuGetVersion.Parse("1.0.0")), + resourceResult: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("resourceResult", exception.ParamName); + } + + [Fact] + public async Task EnsurePackageCompatibilityAsync_ThrowsIfCancelled() + { + await Assert.ThrowsAsync( + () => InstallationCompatibility.Instance.EnsurePackageCompatibilityAsync( + Mock.Of(), + new PackageIdentity(id: "a", version: NuGetVersion.Parse("1.0.0")), + new DownloadResourceResult(DownloadResourceResultStatus.NotFound), + new CancellationToken(canceled: true))); + } + + [Fact] + public async Task EnsurePackageCompatibilityAsync_WithLowerMinClientVersion_Fails() { // Arrange var tc = new TestContext(); @@ -38,11 +89,12 @@ public void InstallationCompatibility_WithLowerMinClientVersion_Fails() var result = new DownloadResourceResult(Stream.Null, tc.PackageReader.Object); // Act & Assert - var ex = Assert.Throws(() => - tc.Target.EnsurePackageCompatibility( + var ex = await Assert.ThrowsAsync(() => + tc.Target.EnsurePackageCompatibilityAsync( tc.NuGetProject, tc.PackageIdentityA, - result)); + result, + CancellationToken.None)); Assert.Equal( "The 'PackageA 1.0.0' package requires NuGet client version '10.0.0' or above, " + @@ -57,7 +109,7 @@ public void InstallationCompatibility_WithLowerMinClientVersion_Fails() } [Fact] - public async Task InstallationCompatibility_WithValidProjectActions_Succeeds() + public async Task EnsurePackageCompatibility_WithValidProjectActions_Succeeds() { // Arrange using (var userPackageFolder = TestDirectory.Create()) @@ -104,7 +156,7 @@ await SimpleTestPackageUtility.CreateFolderFeedV3( } [Fact] - public async Task InstallationCompatibility_WithInvalidProjectActions_Fails() + public async Task EnsurePackageCompatibility_WithInvalidProjectActions_Fails() { // Arrange using (var userPackageFolder = TestDirectory.Create()) @@ -140,33 +192,33 @@ await SimpleTestPackageUtility.CreateFolderFeedV3( } [Fact] - public void InstallationCompatibility_WithNuGetProject_WithInvalidType_Fails() + public async Task EnsurePackageCompatibilityAsync_WithNuGetProject_WithInvalidType_Fails() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(tc.InvalidPackageType); // Act & Assert - tc.VerifyFailure( + await tc.VerifyFailureAsync( tc.NuGetProject, "Package 'PackageA 1.0.0' has a package type 'Invalid 1.2' that is not supported by project 'TestNuGetProject'."); } [Fact] - public void InstallationCompatibility_WithNuGetProject_WithInvalidTypeAndEmptyVersion_Fails() + public async Task EnsurePackageCompatibilityAsync_WithNuGetProject_WithInvalidTypeAndEmptyVersion_Fails() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(new PackageType("Invalid", PackageType.EmptyVersion)); // Act & Assert - tc.VerifyFailure( + await tc.VerifyFailureAsync( tc.NuGetProject, "Package 'PackageA 1.0.0' has a package type 'Invalid' that is not supported by project 'TestNuGetProject'."); } [Fact] - public void InstallationCompatibility_WithNuGetProject_WitMultipleTypes_Fails() + public async Task EnsurePackageCompatibilityAsync_WithNuGetProject_WitMultipleTypes_Fails() { // Arrange var tc = new TestContext(); @@ -174,61 +226,61 @@ public void InstallationCompatibility_WithNuGetProject_WitMultipleTypes_Fails() tc.PackageTypes.Add(PackageType.Dependency); // Act & Assert - tc.VerifyFailure( + await tc.VerifyFailureAsync( tc.NuGetProject, "Package 'PackageA 1.0.0' has multiple package types, which is not supported."); } [Fact] - public void InstallationCompatibility_WithNuGetProject_WithDotnetCliToolType_Fails() + public async Task EnsurePackageCompatibilityAsync_WithNuGetProject_WithDotnetCliToolType_Fails() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(PackageType.DotnetCliTool); // Act & Assert - tc.VerifyFailure( + await tc.VerifyFailureAsync( tc.NuGetProject, "Package 'PackageA 1.0.0' has a package type 'DotnetCliTool' that is not supported by project 'TestNuGetProject'."); } [Fact] - public void InstallationCompatibility_WithNuGetProject_WithLegacyType_Succeeds() + public async Task EnsurePackageCompatibilityAsync_WithNuGetProject_WithLegacyType_Succeeds() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(PackageType.Legacy); // Act & Assert - tc.VerifySuccess(tc.NuGetProject); + await tc.VerifySuccessAsync(tc.NuGetProject); } [Fact] - public void InstallationCompatibility_WithNuGetProject_WithDependency_Succeeds() + public async Task EnsurePackageCompatibilityAsync_WithNuGetProject_WithDependency_Succeeds() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(PackageType.Dependency); // Act & Assert - tc.VerifySuccess(tc.NuGetProject); + await tc.VerifySuccessAsync(tc.NuGetProject); } [Fact] - public void InstallationCompatibility_WithProjectKProject_WithInvalidType_Fails() + public async Task EnsurePackageCompatibilityAsync_WithProjectKProject_WithInvalidType_Fails() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(tc.InvalidPackageType); // Act & Assert - tc.VerifyFailure( + await tc.VerifyFailureAsync( tc.ProjectKProject, "Package 'PackageA 1.0.0' has a package type 'Invalid 1.2' that is not supported by project 'TestProjectKNuGetProject'."); } [Fact] - public void InstallationCompatibility_WithProjectKProject_WithMultipleTypes_Fails() + public async Task EnsurePackageCompatibilityAsync_WithProjectKProject_WithMultipleTypes_Fails() { // Arrange var tc = new TestContext(); @@ -236,42 +288,42 @@ public void InstallationCompatibility_WithProjectKProject_WithMultipleTypes_Fail tc.PackageTypes.Add(PackageType.Dependency); // Act & Assert - tc.VerifyFailure( + await tc.VerifyFailureAsync( tc.ProjectKProject, "Package 'PackageA 1.0.0' has multiple package types, which is not supported."); } [Fact] - public void InstallationCompatibility_WithProjectKProject_WithDotnetCliToolType_Succeeds() + public async Task EnsurePackageCompatibilityAsync_WithProjectKProject_WithDotnetCliToolType_Succeeds() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(PackageType.DotnetCliTool); // Act & Assert - tc.VerifySuccess(tc.ProjectKProject); + await tc.VerifySuccessAsync(tc.ProjectKProject); } [Fact] - public void InstallationCompatibility_WithProjectKProject_WithLegacyType_Succeeds() + public async Task EnsurePackageCompatibilityAsync_WithProjectKProject_WithLegacyType_Succeeds() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(PackageType.Legacy); // Act & Assert - tc.VerifySuccess(tc.ProjectKProject); + await tc.VerifySuccessAsync(tc.ProjectKProject); } [Fact] - public void InstallationCompatibility_WithProjectKProject_WithDependency_Succeeds() + public async Task EnsurePackageCompatibilityAsync_WithProjectKProject_WithDependency_Succeeds() { // Arrange var tc = new TestContext(); tc.PackageTypes.Add(PackageType.Dependency); // Act & Assert - tc.VerifySuccess(tc.ProjectKProject); + await tc.VerifySuccessAsync(tc.ProjectKProject); } private class TestContext @@ -352,7 +404,7 @@ public TestContext(string userPackageFolder = null) public InstallationCompatibility Target { get; } public PackageType InvalidPackageType { get; } - public void VerifyFailure( + public async Task VerifyFailureAsync( NuGetProject nugetProject, string expected) { @@ -360,11 +412,12 @@ public void VerifyFailure( var result = new DownloadResourceResult(Stream.Null, PackageReader.Object); // Act & Assert - var ex = Assert.Throws(() => - Target.EnsurePackageCompatibility( + var ex = await Assert.ThrowsAsync(() => + Target.EnsurePackageCompatibilityAsync( nugetProject, PackageIdentityA, - result)); + result, + CancellationToken.None)); Assert.Equal(expected, ex.Message); PackageReader.Verify(x => x.GetMinClientVersion(), Times.Never); @@ -373,16 +426,17 @@ public void VerifyFailure( NuspecReader.Verify(x => x.GetPackageTypes(), Times.Once); } - public void VerifySuccess(NuGetProject nugetProject) + public async Task VerifySuccessAsync(NuGetProject nugetProject) { // Arrange var result = new DownloadResourceResult(Stream.Null, PackageReader.Object); // Act & Assert - Target.EnsurePackageCompatibility( + await Target.EnsurePackageCompatibilityAsync( nugetProject, PackageIdentityA, - result); + result, + CancellationToken.None); PackageReader.Verify(x => x.GetMinClientVersion(), Times.Never); PackageReader.Verify(x => x.GetPackageTypes(), Times.Never); @@ -457,4 +511,4 @@ public RestoreResult GetRestoreResult(IEnumerable identities) } } } -} +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/NuGetPackageManagerTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/NuGetPackageManagerTests.cs index 9668ca6141f..f9e6129f223 100644 --- a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/NuGetPackageManagerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/NuGetPackageManagerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -121,7 +121,7 @@ public async Task TestPacManInstallAndRequestInstalledPackages() // Act // Install and Uninstall 50 times while polling for installed packages - for (int i = 0; i < 50; i++) + for (var i = 0; i < 50; i++) { // Install await nuGetPackageManager.InstallPackageAsync(projectA, "packageA", @@ -198,10 +198,11 @@ await nuGetPackageManager.InstallPackageAsync(msBuildNuGetProject, packageIdenti // Ensure that installation compatibility was checked. installationCompatibility.Verify( - x => x.EnsurePackageCompatibility( + x => x.EnsurePackageCompatibilityAsync( msBuildNuGetProject, packageIdentity, - It.IsAny()), + It.IsAny(), + It.IsAny()), Times.Once); installationCompatibility.Verify( x => x.EnsurePackageCompatibility( @@ -5542,9 +5543,9 @@ public async Task TestPacMan_PreviewUpdatePackage_DeepDependencies() // Set up Package Dependencies var dependencies = new List(); - for (int j = 1; j < 3; j++) + for (var j = 1; j < 3; j++) { - for (int i = 2; i <= 30; i++) + for (var i = 2; i <= 30; i++) { dependencies.Add(new PackageDependency($"Package{i}", new VersionRange(new NuGetVersion(j, 0, 0)))); } @@ -5552,10 +5553,10 @@ public async Task TestPacMan_PreviewUpdatePackage_DeepDependencies() // Set up Package Source var packages = new List(); - int next = 1; - for (int i = 1; i < 3; i++) + var next = 1; + for (var i = 1; i < 3; i++) { - for (int j = 1; j < 30; j++) + for (var j = 1; j < 30; j++) { next = j + 1; packages.Add(new SourcePackageDependencyInfo($"Package{j}", new NuGetVersion(i, 0, 0), @@ -5579,7 +5580,7 @@ public async Task TestPacMan_PreviewUpdatePackage_DeepDependencies() var fwk45 = NuGetFramework.Parse("net45"); var installedPackages = new List(); - for (int i = 1; i <= 30; i++) + for (var i = 1; i <= 30; i++) { installedPackages.Add(new PackageReference( new PackageIdentity($"Package{i}", new NuGetVersion(1, 0, 0)), fwk45, true)); @@ -5622,7 +5623,7 @@ public async Task TestPacMan_PreviewUpdatePackage_DeepDependencies() var resulting = result.Select(a => Tuple.Create(a.PackageIdentity, a.NuGetProjectActionType)).ToArray(); var expected = new List>(); - for (int i = 1; i <= 30; i++) + for (var i = 1; i <= 30; i++) { Expected(expected, $"Package{i}", new NuGetVersion(1, 0, 0), new NuGetVersion(2, 0, 0)); } @@ -6119,7 +6120,7 @@ private static bool Compare( IEnumerable> lhs, IEnumerable> rhs) { - bool ok = true; + var ok = true; ok &= RhsContainsAllLhs(lhs, rhs); ok &= RhsContainsAllLhs(rhs, lhs); return ok; @@ -6143,8 +6144,8 @@ private class ActionComparer : IEqualityComparer x, Tuple y) { - bool f1 = x.Item1.Equals(y.Item1); - bool f2 = x.Item2 == y.Item2; + var f1 = x.Item1.Equals(y.Item1); + var f2 = x.Item2 == y.Item2; return f1 && f2; } @@ -6154,4 +6155,4 @@ public int GetHashCode(Tuple obj) } } } -} +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/Utility/FileSystemUtilityTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/Utility/FileSystemUtilityTests.cs new file mode 100644 index 00000000000..24c2d0a9b22 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/Utility/FileSystemUtilityTests.cs @@ -0,0 +1,136 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using NuGet.ProjectManagement; +using NuGet.Test.Utility; +using Xunit; + +namespace NuGet.PackageManagement.Test +{ + public class FileSystemUtilityTests + { + [Theory] + [InlineData(null)] + [InlineData("")] + public void ContentEquals_ThrowsForNullOrEmptyPath(string path) + { + var exception = Assert.Throws( + () => FileSystemUtility.ContentEquals( + path: null, + streamFactory: () => Stream.Null)); + + Assert.Equal("path", exception.ParamName); + } + + [Fact] + public void ContentEquals_ThrowsForNullStreamTaskFactory() + { + var exception = Assert.Throws( + () => FileSystemUtility.ContentEquals( + path: "a", + streamFactory: null)); + + Assert.Equal("streamFactory", exception.ParamName); + } + + [Fact] + public void ContentEquals_ReturnsFalseIfNotEqual() + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("a"))) + using (var testDirectory = TestDirectory.Create()) + { + var filePath = Path.Combine(testDirectory.Path, "file"); + + File.WriteAllText(filePath, "b"); + + var areEqual = FileSystemUtility.ContentEquals(filePath, () => stream); + + Assert.False(areEqual); + } + } + + [Fact] + public void ContentEquals_ReturnsTrueIfEqual() + { + var content = "a"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + using (var testDirectory = TestDirectory.Create()) + { + var filePath = Path.Combine(testDirectory.Path, "file"); + + File.WriteAllText(filePath, content); + + var areEqual = FileSystemUtility.ContentEquals(filePath, () => stream); + + Assert.True(areEqual); + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public async Task ContentEqualsAsync_ThrowsForNullOrEmptyPath(string path) + { + var exception = await Assert.ThrowsAsync( + () => FileSystemUtility.ContentEqualsAsync( + path: null, + streamTaskFactory: () => Task.FromResult(Stream.Null))); + + Assert.Equal("path", exception.ParamName); + } + + [Fact] + public async Task ContentEqualsAsync_ThrowsForNullStreamTaskFactory() + { + var exception = await Assert.ThrowsAsync( + () => FileSystemUtility.ContentEqualsAsync( + path: "a", + streamTaskFactory: null)); + + Assert.Equal("streamTaskFactory", exception.ParamName); + } + + [Fact] + public async Task ContentEqualsAsync_ReturnsFalseIfNotEqual() + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("a"))) + using (var testDirectory = TestDirectory.Create()) + { + var filePath = Path.Combine(testDirectory.Path, "file"); + + File.WriteAllText(filePath, "b"); + + var areEqual = await FileSystemUtility.ContentEqualsAsync( + filePath, + () => Task.FromResult(stream)); + + Assert.False(areEqual); + } + } + + [Fact] + public async Task ContentEqualsAsync_ReturnsTrueIfEqual() + { + var content = "a"; + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + using (var testDirectory = TestDirectory.Create()) + { + var filePath = Path.Combine(testDirectory.Path, "file"); + + File.WriteAllText(filePath, content); + + var areEqual = await FileSystemUtility.ContentEqualsAsync( + filePath, + () => Task.FromResult(stream)); + + Assert.True(areEqual); + } + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginProviderTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginProviderTests.cs index ad39bd9b820..cbe5d307a3d 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginProviderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginProviderTests.cs @@ -187,6 +187,13 @@ private static PluginResourceProvider CreatePluginResourceProvider(bool createRe var plugin = new Mock(); var utilities = new Mock(); var connection = new Mock(); + var dispatcher = new Mock(); + + dispatcher.SetupGet(x => x.RequestHandlers) + .Returns(new RequestHandlers()); + + connection.SetupGet(x => x.MessageDispatcher) + .Returns(dispatcher.Object); plugin.Setup(x => x.Connection) .Returns(connection.Object); diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginTests.cs index c0db6250712..23e665db672 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/DownloadResourcePluginTests.cs @@ -15,11 +15,10 @@ namespace NuGet.Protocol.Plugins.Tests { - public class DownloadResourcePluginTests : IDisposable + public class DownloadResourcePluginTests { private readonly Mock _connection; private readonly Mock _credentialService; - private readonly PluginCredentialsProvider _credentialsProvider; private readonly Mock _dispatcher; private readonly PackageSource _packageSource; private readonly Mock _plugin; @@ -36,11 +35,6 @@ public DownloadResourcePluginTests() _connection = new Mock(); _plugin = new Mock(); _utilities = new Mock(); - _credentialsProvider = new PluginCredentialsProvider( - _plugin.Object, - _packageSource, - _proxy.Object, - _credentialService.Object); _dispatcher.SetupGet(x => x.RequestHandlers) .Returns(new RequestHandlers()); @@ -54,15 +48,7 @@ public DownloadResourcePluginTests() _resource = new DownloadResourcePlugin( _plugin.Object, _utilities.Object, - _packageSource, - _credentialsProvider); - } - - public void Dispose() - { - _credentialsProvider.Dispose(); - - GC.SuppressFinalize(this); + _packageSource); } [Fact] @@ -72,8 +58,7 @@ public void Constructor_ThrowsForNullPlugin() () => new DownloadResourcePlugin( plugin: null, utilities: _utilities.Object, - packageSource: _packageSource, - credentialsProvider: _credentialsProvider)); + packageSource: _packageSource)); Assert.Equal("plugin", exception.ParamName); } @@ -85,8 +70,7 @@ public void Constructor_ThrowsForNullPluginMulticlientUtilities() () => new DownloadResourcePlugin( _plugin.Object, utilities: null, - packageSource: _packageSource, - credentialsProvider: _credentialsProvider)); + packageSource: _packageSource)); Assert.Equal("utilities", exception.ParamName); } @@ -98,25 +82,11 @@ public void Constructor_ThrowsForNullPackageSource() () => new DownloadResourcePlugin( _plugin.Object, _utilities.Object, - packageSource: null, - credentialsProvider: _credentialsProvider)); + packageSource: null)); Assert.Equal("packageSource", exception.ParamName); } - [Fact] - public void Constructor_ThrowsForNullCredentialProvider() - { - var exception = Assert.Throws( - () => new DownloadResourcePlugin( - _plugin.Object, - _utilities.Object, - _packageSource, - credentialsProvider: null)); - - Assert.Equal("credentialsProvider", exception.ParamName); - } - [Fact] public async Task GetDownloadResourceResultAsync_ThrowsForNullIdentity() { @@ -140,8 +110,7 @@ public async Task GetDownloadResourceResultAsync_ThrowsForNullDownloadContext() var resource = new DownloadResourcePlugin( _plugin.Object, _utilities.Object, - _packageSource, - _credentialsProvider); + _packageSource); using (var sourceCacheContext = new SourceCacheContext()) { diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexRequestTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexRequestTests.cs new file mode 100644 index 00000000000..467b7d682b1 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexRequestTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace NuGet.Protocol.Plugins.Tests +{ + public class GetServiceIndexRequestTests + { + [Theory] + [InlineData(null)] + [InlineData("")] + public void Constructor_ThrowsForNullOrEmptyPackageSourceRepository(string packageSourceRepository) + { + var exception = Assert.Throws( + () => new GetServiceIndexRequest(packageSourceRepository)); + + Assert.Equal("packageSourceRepository", exception.ParamName); + } + + [Fact] + public void Constructor_InitializesPackageSourceRepositoryProperty() + { + var request = new GetServiceIndexRequest(packageSourceRepository: "a"); + + Assert.Equal("a", request.PackageSourceRepository); + } + + [Fact] + public void JsonSerialization_ReturnsCorrectJson() + { + var request = new GetServiceIndexRequest(packageSourceRepository: "a"); + + var json = TestUtilities.Serialize(request); + + Assert.Equal("{\"PackageSourceRepository\":\"a\"}", json); + } + + [Fact] + public void JsonDeserialization_ReturnsCorrectObject() + { + var json = "{\"PackageSourceRepository\":\"a\"}"; + var request = JsonSerializationUtilities.Deserialize(json); + + Assert.Equal("a", request.PackageSourceRepository); + } + + [Theory] + [InlineData("{}")] + [InlineData("{\"PackageSourceRepository\":null}")] + [InlineData("{\"PackageSourceRepository\":\"\"}")] + public void JsonDeserialization_ThrowsForInvalidPackageSourceRepository(string json) + { + var exception = Assert.Throws( + () => JsonSerializationUtilities.Deserialize(json)); + + Assert.Equal("packageSourceRepository", exception.ParamName); + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexResponseTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexResponseTests.cs new file mode 100644 index 00000000000..514920e992c --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/Messages/GetServiceIndexResponseTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace NuGet.Protocol.Plugins.Tests +{ + public class GetServiceIndexResponseTests + { + [Fact] + public void Constructor_ThrowsForUndefinedResponseCode() + { + var exception = Assert.Throws( + () => new GetServiceIndexResponse((MessageResponseCode)int.MaxValue, JObject.Parse("{}"))); + + Assert.Equal("responseCode", exception.ParamName); + } + + [Fact] + public void Constructor_ThrowsForNullServiceIndexWhenResponseCodeIsSuccess() + { + var exception = Assert.Throws( + () => new GetServiceIndexResponse(MessageResponseCode.Success, serviceIndex: null)); + + Assert.Equal("serviceIndex", exception.ParamName); + } + + [Fact] + public void Constructor_InitializesProperties() + { + var serviceIndex = JObject.Parse("{}"); + var response = new GetServiceIndexResponse(MessageResponseCode.Success, serviceIndex); + + Assert.Equal(MessageResponseCode.Success, response.ResponseCode); + Assert.Same(serviceIndex, response.ServiceIndex); + } + + [Fact] + public void JsonSerialization_ReturnsCorrectJson() + { + var serviceIndex = JObject.Parse("{\"a\":\"b\"}"); + var response = new GetServiceIndexResponse(MessageResponseCode.Success, serviceIndex); + + var json = TestUtilities.Serialize(response); + + Assert.Equal("{\"ResponseCode\":\"Success\",\"ServiceIndex\":{\"a\":\"b\"}}", json); + } + + [Fact] + public void JsonDeserialization_ReturnsCorrectObjectForSuccess() + { + var json = "{\"ResponseCode\":\"Success\",\"ServiceIndex\":{\"a\":\"b\"}}"; + var response = JsonSerializationUtilities.Deserialize(json); + + Assert.Equal(MessageResponseCode.Success, response.ResponseCode); + Assert.Equal("{\"a\":\"b\"}", response.ServiceIndex.ToString(Formatting.None)); + } + + [Fact] + public void JsonDeserialization_ReturnsCorrectObjectForNotFound() + { + var json = "{\"ResponseCode\":\"NotFound\"}"; + var response = JsonSerializationUtilities.Deserialize(json); + + Assert.Equal(MessageResponseCode.NotFound, response.ResponseCode); + Assert.Null(response.ServiceIndex); + } + + [Theory] + [InlineData("{\"ResponseCode\":null}")] + [InlineData("{\"ResponseCode\":\"\"}")] + [InlineData("{\"ResponseCode\":\"b\"}")] + public void JsonDeserialization_ThrowsForInvalidResponseCode(string json) + { + Assert.Throws( + () => JsonSerializationUtilities.Deserialize(json)); + } + + [Theory] + [InlineData("{}", typeof(ArgumentNullException))] + [InlineData("{\"ResponseCode\":\"Success\"}", typeof(ArgumentNullException))] + [InlineData("{\"ResponseCode\":\"Success\",\"ServiceIndex\":null}", typeof(ArgumentNullException))] + [InlineData("{\"ResponseCode\":\"Success\",\"ServiceIndex\":\"a\"}", typeof(InvalidCastException))] + [InlineData("{\"ResponseCode\":\"Success\",\"ServiceIndex\":1}", typeof(InvalidCastException))] + public void JsonDeserialization_ThrowsForInvalidServiceIndex(string json, Type exceptionType) + { + var exception = Assert.Throws( + exceptionType, + () => JsonSerializationUtilities.Deserialize(json)); + + if (exception is ArgumentNullException) + { + Assert.Equal("serviceIndex", ((ArgumentNullException)exception).ParamName); + } + } + } +} \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceProviderTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceProviderTests.cs index be0d7946568..87f3647b183 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceProviderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceProviderTests.cs @@ -181,6 +181,13 @@ private static PluginResourceProvider CreatePluginResourceProvider(bool createRe var plugin = new Mock(); var utilities = new Mock(); var connection = new Mock(); + var dispatcher = new Mock(); + + dispatcher.SetupGet(x => x.RequestHandlers) + .Returns(new RequestHandlers()); + + connection.SetupGet(x => x.MessageDispatcher) + .Returns(dispatcher.Object); plugin.Setup(x => x.Connection) .Returns(connection.Object); diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceTests.cs index e4a6fbbd76a..55d4d4e61c3 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFindPackageByIdResourceTests.cs @@ -17,9 +17,8 @@ namespace NuGet.Protocol.Plugins.Tests { - public class PluginFindPackageByIdResourceTests : IDisposable + public class PluginFindPackageByIdResourceTests { - private readonly PluginCredentialsProvider _credentialsProvider; private readonly Mock _credentialService; private readonly PackageSource _packageSource; private readonly Mock _plugin; @@ -33,22 +32,10 @@ public PluginFindPackageByIdResourceTests() _credentialService = new Mock(); _plugin = new Mock(); _utilities = new Mock(); - _credentialsProvider = new PluginCredentialsProvider( - _plugin.Object, - _packageSource, - _proxy.Object, - _credentialService.Object); HttpHandlerResourceV3.CredentialService = Mock.Of(); } - public void Dispose() - { - _credentialsProvider.Dispose(); - - GC.SuppressFinalize(this); - } - [Fact] public void Constructor_ThrowsForNullPlugin() { @@ -56,8 +43,7 @@ public void Constructor_ThrowsForNullPlugin() () => new PluginFindPackageByIdResource( plugin: null, utilities: _utilities.Object, - packageSource: _packageSource, - credentialsProvider: _credentialsProvider)); + packageSource: _packageSource)); Assert.Equal("plugin", exception.ParamName); } @@ -69,8 +55,7 @@ public void Constructor_ThrowsForNullPluginMulticlientUtilities() () => new PluginFindPackageByIdResource( _plugin.Object, utilities: null, - packageSource: _packageSource, - credentialsProvider: _credentialsProvider)); + packageSource: _packageSource)); Assert.Equal("utilities", exception.ParamName); } @@ -82,25 +67,11 @@ public void Constructor_ThrowsForNullPackageSource() () => new PluginFindPackageByIdResource( _plugin.Object, _utilities.Object, - packageSource: null, - credentialsProvider: _credentialsProvider)); + packageSource: null)); Assert.Equal("packageSource", exception.ParamName); } - [Fact] - public void Constructor_ThrowsForNullCredentialProvider() - { - var exception = Assert.Throws( - () => new PluginFindPackageByIdResource( - _plugin.Object, - _utilities.Object, - _packageSource, - credentialsProvider: null)); - - Assert.Equal("credentialsProvider", exception.ParamName); - } - [Theory] [InlineData(null)] [InlineData("")] @@ -552,17 +523,15 @@ internal static PluginFindPackageByIdResourceTest Create( var utilities = new Mock(); var credentialService = new Mock(); - var credentialsProvider = new PluginCredentialsProvider( + var credentialsProvider = new GetCredentialsRequestHandler( plugin.Object, - packageSource, proxy: null, credentialService: credentialService.Object); var resource = new PluginFindPackageByIdResource( plugin.Object, utilities.Object, - packageSource, - credentialsProvider); + packageSource); return new PluginFindPackageByIdResourceTest( resource, diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginCredentialsProviderTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetCredentialsRequestHandlerTests.cs similarity index 85% rename from test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginCredentialsProviderTests.cs rename to test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetCredentialsRequestHandlerTests.cs index b12c6baba3a..e09dba5a8f5 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginCredentialsProviderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetCredentialsRequestHandlerTests.cs @@ -2,16 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using Moq; using NuGet.Configuration; +using NuGet.Protocol.Core.Types; using Xunit; namespace NuGet.Protocol.Plugins.Tests { - public class PluginCredentialsProviderTests + public class GetCredentialsRequestHandlerTests { private readonly PackageSource _packageSource = new PackageSource("https://unit.test"); @@ -19,38 +21,21 @@ public class PluginCredentialsProviderTests public void Constructor_ThrowsForNullPlugin() { var exception = Assert.Throws( - () => new PluginCredentialsProvider( + () => new GetCredentialsRequestHandler( plugin: null, - packageSource: _packageSource, proxy: Mock.Of(), credentialService: Mock.Of())); Assert.Equal("plugin", exception.ParamName); } - [Fact] - public void Constructor_ThrowsForNullPackageSource() - { - var exception = Assert.Throws( - () => new PluginCredentialsProvider( - Mock.Of(), - packageSource: null, - proxy: Mock.Of(), - credentialService: Mock.Of())); - - Assert.Equal("packageSource", exception.ParamName); - } - [Fact] public void CancellationToken_IsNone() { - var provider = new PluginCredentialsProvider( - Mock.Of(), - _packageSource, - Mock.Of(), - Mock.Of()); - - Assert.Equal(CancellationToken.None, provider.CancellationToken); + using (var provider = CreateDefaultRequestHandler()) + { + Assert.Equal(CancellationToken.None, provider.CancellationToken); + } } [Fact] @@ -60,9 +45,8 @@ public void Dispose_DisposesDisposables() plugin.Setup(x => x.Dispose()); - var provider = new PluginCredentialsProvider( + var provider = new GetCredentialsRequestHandler( plugin.Object, - _packageSource, Mock.Of(), Mock.Of()); @@ -78,9 +62,8 @@ public void Dispose_IsIdempotent() plugin.Setup(x => x.Dispose()); - var provider = new PluginCredentialsProvider( + var provider = new GetCredentialsRequestHandler( plugin.Object, - _packageSource, Mock.Of(), Mock.Of()); @@ -90,26 +73,38 @@ public void Dispose_IsIdempotent() plugin.Verify(x => x.Dispose(), Times.Once); } + [Fact] + public void AddOrUpdateSourceRepository_ThrowsForNullSourceRepository() + { + using (var provider = CreateDefaultRequestHandler()) + { + var exception = Assert.Throws( + () => provider.AddOrUpdateSourceRepository(sourceRepository: null)); + + Assert.Equal("sourceRepository", exception.ParamName); + } + } + [Fact] public async Task HandleCancelAsync_Throws() { - using (var provider = CreateDefaultPluginCredentialsProvider()) + using (var provider = CreateDefaultRequestHandler()) { var request = CreateRequest(MessageType.Cancel); await Assert.ThrowsAsync( () => provider.HandleCancelAsync( - connection: Mock.Of(), - request: request, - responseHandler: Mock.Of(), - cancellationToken: CancellationToken.None)); + Mock.Of(), + request, + Mock.Of(), + CancellationToken.None)); } } [Fact] public async Task HandleResponseAsync_ThrowsForNullConnection() { - using (var provider = CreateDefaultPluginCredentialsProvider()) + using (var provider = CreateDefaultRequestHandler()) { var request = CreateRequest(MessageType.Request); @@ -127,11 +122,11 @@ public async Task HandleResponseAsync_ThrowsForNullConnection() [Fact] public async Task HandleResponseAsync_ThrowsForNullRequest() { - using (var provider = CreateDefaultPluginCredentialsProvider()) + using (var provider = CreateDefaultRequestHandler()) { var exception = await Assert.ThrowsAsync( () => provider.HandleResponseAsync( - connection: Mock.Of(), + Mock.Of(), request: null, responseHandler: Mock.Of(), cancellationToken: CancellationToken.None)); @@ -143,14 +138,14 @@ public async Task HandleResponseAsync_ThrowsForNullRequest() [Fact] public async Task HandleResponseAsync_ThrowsForNullResponseHandler() { - using (var provider = CreateDefaultPluginCredentialsProvider()) + using (var provider = CreateDefaultRequestHandler()) { var request = CreateRequest(MessageType.Request); var exception = await Assert.ThrowsAsync( () => provider.HandleResponseAsync( - connection: Mock.Of(), - request: request, + Mock.Of(), + request, responseHandler: null, cancellationToken: CancellationToken.None)); @@ -161,7 +156,7 @@ public async Task HandleResponseAsync_ThrowsForNullResponseHandler() [Fact] public async Task HandleResponseAsync_ThrowsIfCancelled() { - using (var provider = CreateDefaultPluginCredentialsProvider()) + using (var provider = CreateDefaultRequestHandler()) { var request = CreateRequest(MessageType.Request); @@ -175,19 +170,13 @@ await Assert.ThrowsAsync( } [Fact] - public async Task HandleResponseAsync_ReturnsNotFoundForNonHttpSource() + public async Task HandleResponseAsync_ReturnsNotFoundForNonHttpPackageSource() { - var packageSource = new PackageSource("\\unit\test"); - - using (var provider = new PluginCredentialsProvider( - Mock.Of(), - packageSource, - Mock.Of(), - Mock.Of())) + using (var provider = CreateDefaultRequestHandler()) { var request = CreateRequest( MessageType.Request, - new GetCredentialsRequest(packageSource.Source, HttpStatusCode.Unauthorized)); + new GetCredentialsRequest("\\unit\test", HttpStatusCode.Unauthorized)); var responseHandler = new Mock(MockBehavior.Strict); responseHandler.Setup(x => x.SendResponseAsync( @@ -208,19 +197,26 @@ await provider.HandleResponseAsync( } [Fact] - public async Task HandleResponseAsync_ReturnsNotFoundForNonMatchingHttpSource() + public async Task HandleResponseAsync_ReturnsNotFoundIfPackageSourceNotFound() { - var packageSource = new PackageSource("https://unit1.test"); + var credentialService = new Mock(MockBehavior.Strict); + + credentialService.Setup(x => x.GetCredentialsAsync( + It.IsNotNull(), + It.IsNotNull(), + It.Is(c => c == CredentialRequestType.Unauthorized), + It.IsNotNull(), + It.IsAny())) + .ReturnsAsync((ICredentials)null); - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - packageSource, Mock.Of(), - Mock.Of())) + credentialService.Object)) { var request = CreateRequest( MessageType.Request, - new GetCredentialsRequest("https://unit2.test", HttpStatusCode.Unauthorized)); + new GetCredentialsRequest("https://unit.test", HttpStatusCode.Unauthorized)); var responseHandler = new Mock(MockBehavior.Strict); responseHandler.Setup(x => x.SendResponseAsync( @@ -249,8 +245,12 @@ public async Task HandleResponseAsync_ReturnsPackageSourceCredentialsFromPackage passwordText: "b", isPasswordClearText: true); - using (var provider = CreateDefaultPluginCredentialsProvider()) + using (var provider = CreateDefaultRequestHandler()) { + var sourceRepository = new SourceRepository(_packageSource, Enumerable.Empty()); + + provider.AddOrUpdateSourceRepository(sourceRepository); + var request = CreateRequest( MessageType.Request, new GetCredentialsRequest(_packageSource.Source, HttpStatusCode.Unauthorized)); @@ -294,9 +294,8 @@ public async Task HandleResponseAsync_ReturnsPackageSourceCredentialsFromCredent It.IsAny())) .Returns(Task.FromResult(credentials.Object)); - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, proxy, credentialService.Object)) { @@ -325,9 +324,8 @@ await provider.HandleResponseAsync( [Fact] public async Task HandleResponseAsync_ReturnsNullPackageSourceCredentialsIfPackageSourceCredentialsAreInvalidAndCredentialServiceIsNull() { - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, Mock.Of(), credentialService: null)) { @@ -373,9 +371,8 @@ public async Task HandleResponseAsync_ReturnsNullPackageSourceCredentialsIfNoCre It.IsAny())) .Returns(Task.FromResult(credentials.Object)); - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, proxy, credentialService.Object)) { @@ -426,9 +423,8 @@ public async Task HandleResponseAsync_ReturnsProxyCredentialsFromCredentialServi It.IsAny())) .Returns(Task.FromResult(credentials.Object)); - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, proxy.Object, credentialService.Object)) { @@ -457,9 +453,8 @@ await provider.HandleResponseAsync( [Fact] public async Task HandleResponseAsync_ReturnsNullProxyCredentialsIfCredentialServiceIsNull() { - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, Mock.Of(), credentialService: null)) { @@ -505,9 +500,8 @@ public async Task HandleResponseAsync_ReturnsNullProxyCredentialsIfNoCredentials It.IsAny())) .Returns(Task.FromResult(credentials.Object)); - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, proxy, credentialService.Object)) { @@ -536,9 +530,8 @@ await provider.HandleResponseAsync( [Fact] public async Task HandleResponseAsync_ReturnsNullProxyCredentialsIfNoProxy() { - using (var provider = new PluginCredentialsProvider( + using (var provider = new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, proxy: null, credentialService: Mock.Of())) { @@ -564,11 +557,10 @@ await provider.HandleResponseAsync( } } - private PluginCredentialsProvider CreateDefaultPluginCredentialsProvider() + private GetCredentialsRequestHandler CreateDefaultRequestHandler() { - return new PluginCredentialsProvider( + return new GetCredentialsRequestHandler( Mock.Of(), - _packageSource, Mock.Of(), Mock.Of()); } diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetServiceIndexRequestHandlerTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetServiceIndexRequestHandlerTests.cs new file mode 100644 index 00000000000..7b043e68898 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/RequestHandlers/GetServiceIndexRequestHandlerTests.cs @@ -0,0 +1,312 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NuGet.Configuration; +using NuGet.Protocol.Core.Types; +using Xunit; + +namespace NuGet.Protocol.Plugins.Tests +{ + public class GetServiceIndexRequestHandlerTests + { + [Fact] + public void Constructor_ThrowsForNullPlugin() + { + var exception = Assert.Throws( + () => new GetServiceIndexRequestHandler(plugin: null)); + + Assert.Equal("plugin", exception.ParamName); + } + + [Fact] + public void CancellationToken_IsNone() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + Assert.Equal(CancellationToken.None, provider.CancellationToken); + } + } + + [Fact] + public void Dispose_DisposesDisposables() + { + var plugin = new Mock(MockBehavior.Strict); + + plugin.Setup(x => x.Dispose()); + + var provider = new GetServiceIndexRequestHandler(plugin.Object); + + provider.Dispose(); + + plugin.Verify(x => x.Dispose(), Times.Once); + } + + [Fact] + public void Dispose_IsIdempotent() + { + var plugin = new Mock(MockBehavior.Strict); + + plugin.Setup(x => x.Dispose()); + + var provider = new GetServiceIndexRequestHandler(plugin.Object); + + provider.Dispose(); + provider.Dispose(); + + plugin.Verify(x => x.Dispose(), Times.Once); + } + + [Fact] + public void AddOrUpdateSourceRepository_ThrowsForNullSourceRepository() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var exception = Assert.Throws( + () => provider.AddOrUpdateSourceRepository(sourceRepository: null)); + + Assert.Equal("sourceRepository", exception.ParamName); + } + } + + [Fact] + public async Task HandleCancelAsync_Throws() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var request = CreateRequest(MessageType.Cancel); + + await Assert.ThrowsAsync( + () => provider.HandleCancelAsync( + Mock.Of(), + request, + Mock.Of(), + CancellationToken.None)); + } + } + + [Fact] + public async Task HandleResponseAsync_ThrowsForNullConnection() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var request = CreateRequest(MessageType.Request); + + var exception = await Assert.ThrowsAsync( + () => provider.HandleResponseAsync( + connection: null, + request: request, + responseHandler: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("connection", exception.ParamName); + } + } + + [Fact] + public async Task HandleResponseAsync_ThrowsForNullRequest() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var exception = await Assert.ThrowsAsync( + () => provider.HandleResponseAsync( + Mock.Of(), + request: null, + responseHandler: Mock.Of(), + cancellationToken: CancellationToken.None)); + + Assert.Equal("request", exception.ParamName); + } + } + + [Fact] + public async Task HandleResponseAsync_ThrowsForNullResponseHandler() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var request = CreateRequest(MessageType.Request); + + var exception = await Assert.ThrowsAsync( + () => provider.HandleResponseAsync( + Mock.Of(), + request, + responseHandler: null, + cancellationToken: CancellationToken.None)); + + Assert.Equal("responseHandler", exception.ParamName); + } + } + + [Fact] + public async Task HandleResponseAsync_ThrowsIfCancelled() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var request = CreateRequest(MessageType.Request); + + await Assert.ThrowsAsync( + () => provider.HandleResponseAsync( + Mock.Of(), + request, + Mock.Of(), + new CancellationToken(canceled: true))); + } + } + + [Fact] + public async Task HandleResponseAsync_ReturnsNotFoundForNonHttpPackageSource() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var request = CreateRequest( + MessageType.Request, + new GetServiceIndexRequest("\\unit\test")); + var responseHandler = new Mock(MockBehavior.Strict); + + responseHandler.Setup(x => x.SendResponseAsync( + It.Is(r => r == request), + It.Is(r => r.ResponseCode == MessageResponseCode.NotFound), + It.IsAny())) + .Returns(Task.FromResult(0)); + + await provider.HandleResponseAsync( + Mock.Of(), + request, + responseHandler.Object, + CancellationToken.None); + + responseHandler.Verify(); + } + } + + [Fact] + public async Task HandleResponseAsync_ReturnsNotFoundIfPackageSourceNotFound() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var request = CreateRequest( + MessageType.Request, + new GetServiceIndexRequest("https://unit.test")); + var responseHandler = new Mock(MockBehavior.Strict); + + responseHandler.Setup(x => x.SendResponseAsync( + It.Is(r => r == request), + It.Is(r => r.ResponseCode == MessageResponseCode.NotFound), + It.IsAny())) + .Returns(Task.FromResult(0)); + + await provider.HandleResponseAsync( + Mock.Of(), + request, + responseHandler.Object, + CancellationToken.None); + + responseHandler.Verify(); + } + } + + [Fact] + public async Task HandleResponseAsync_ReturnsNotFoundSourceRepositoryReturnsNullServiceIndex() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var packageSource = new PackageSource("https://unit.test"); + var sourceRepository = new SourceRepository(packageSource, Enumerable.Empty()); + + provider.AddOrUpdateSourceRepository(sourceRepository); + + var request = CreateRequest( + MessageType.Request, + new GetServiceIndexRequest(packageSource.Source)); + var responseHandler = new Mock(MockBehavior.Strict); + + responseHandler.Setup(x => x.SendResponseAsync( + It.Is(r => r == request), + It.Is(r => r.ResponseCode == MessageResponseCode.NotFound), + It.IsAny())) + .Returns(Task.FromResult(0)); + + await provider.HandleResponseAsync( + Mock.Of(), + request, + responseHandler.Object, + CancellationToken.None); + + responseHandler.Verify(); + } + } + + [Fact] + public async Task HandleResponseAsync_ReturnsSuccessIfServiceIndexIsFound() + { + using (var provider = new GetServiceIndexRequestHandler(Mock.Of())) + { + var packageSource = new PackageSource("https://unit.test"); + var serviceIndex = JObject.Parse("{}"); + var serviceIndexResource = new ServiceIndexResourceV3(serviceIndex, DateTime.UtcNow); + var serviceIndexResourceProvider = new Mock(); + + serviceIndexResourceProvider.SetupGet(x => x.ResourceType) + .Returns(typeof(ServiceIndexResourceV3)); + + serviceIndexResourceProvider.SetupGet(x => x.Name) + .Returns(nameof(ServiceIndexResourceV3Provider)); + + serviceIndexResourceProvider.Setup(x => x.TryCreate( + It.IsNotNull(), + It.IsAny())) + .ReturnsAsync(new Tuple(true, serviceIndexResource)); + + var sourceRepository = new SourceRepository( + packageSource, + new INuGetResourceProvider[] { serviceIndexResourceProvider.Object }); + + provider.AddOrUpdateSourceRepository(sourceRepository); + + var request = CreateRequest( + MessageType.Request, + new GetServiceIndexRequest(packageSource.Source)); + var responseHandler = new Mock(MockBehavior.Strict); + + responseHandler.Setup(x => x.SendResponseAsync( + It.Is(r => r == request), + It.Is(r => r.ResponseCode == MessageResponseCode.Success + && r.ServiceIndex.ToString(Formatting.None) == serviceIndex.ToString(Formatting.None)), + It.IsAny())) + .Returns(Task.FromResult(0)); + + await provider.HandleResponseAsync( + Mock.Of(), + request, + responseHandler.Object, + CancellationToken.None); + + responseHandler.Verify(); + } + } + + private static Message CreateRequest(MessageType type, GetServiceIndexRequest payload = null) + { + if (payload == null) + { + return new Message( + requestId: "a", + type: type, + method: MessageMethod.GetCredentials, + payload: null); + } + + return MessageUtilities.Create( + requestId: "a", + type: MessageType.Request, + method: MessageMethod.GetCredentials, + payload: payload); + } + } +} \ No newline at end of file