diff --git a/src/NuGet.Clients/NuGet.CommandLine/Commands/AddCommand.cs b/src/NuGet.Clients/NuGet.CommandLine/Commands/AddCommand.cs new file mode 100644 index 00000000000..427e5fc8814 --- /dev/null +++ b/src/NuGet.Clients/NuGet.CommandLine/Commands/AddCommand.cs @@ -0,0 +1,49 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace NuGet.CommandLine +{ + [Command(typeof(NuGetCommand), "add", "AddCommandDescription", + MinArgs = 1, MaxArgs = 1, UsageDescriptionResourceName = "AddCommandUsageDescription", + UsageSummaryResourceName = "AddCommandUsageSummary", UsageExampleResourceName = "AddCommandUsageExamples")] + public class AddCommand : Command + { + [Option(typeof(NuGetCommand), "AddCommandSourceDescription", AltName = "src")] + public string Source { get; set; } + + [Option(typeof(NuGetCommand), "ExpandDescription")] + public bool Expand { get; set; } + + public override async Task ExecuteCommandAsync() + { + // Arguments[0] will not be null at this point. + // Because, this command has MinArgs set to 1. + var packagePath = Arguments[0]; + + if (string.IsNullOrEmpty(Source)) + { + throw new CommandLineException( + LocalizedResourceManager.GetString(nameof(NuGetResources.AddCommand_SourceNotProvided))); + } + + OfflineFeedUtility.ThrowIfInvalidOrNotFound( + packagePath, + isDirectory: false, + nameOfNotFoundErrorResource: nameof(NuGetResources.NupkgPath_NotFound)); + + // If the Source Feed Folder does not exist, it will be created. + OfflineFeedUtility.ThrowIfInvalid(Source); + + var offlineFeedAddContext = new OfflineFeedAddContext( + packagePath, + Source, + Console, // IConsole is an ILogger + throwIfSourcePackageIsInvalid: true, + throwIfPackageExistsAndInvalid: true, + throwIfPackageExists: false, + expand: Expand); + + await OfflineFeedUtility.AddPackageToSource(offlineFeedAddContext, CancellationToken.None); + } + } +} diff --git a/src/NuGet.Clients/NuGet.CommandLine/Commands/InitCommand.cs b/src/NuGet.Clients/NuGet.CommandLine/Commands/InitCommand.cs new file mode 100644 index 00000000000..72a1c16cfd4 --- /dev/null +++ b/src/NuGet.Clients/NuGet.CommandLine/Commands/InitCommand.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NuGet.CommandLine +{ + [Command(typeof(NuGetCommand), "init", "InitCommandDescription", + MinArgs = 2, MaxArgs = 2, UsageDescriptionResourceName = "InitCommandUsageDescription", + UsageSummaryResourceName = "InitCommandUsageSummary", UsageExampleResourceName = "InitCommandUsageExamples")] + public class InitCommand : Command + { + [Option(typeof(NuGetCommand), "ExpandDescription")] + public bool Expand { get; set; } + + public override async Task ExecuteCommandAsync() + { + // Arguments[0] or Arguments[1] will not be null at this point. + // Because, this command has MinArgs set to 2. + var source = Arguments[0]; + var destination = Arguments[1]; + + OfflineFeedUtility.ThrowIfInvalidOrNotFound( + source, + isDirectory: true, + nameOfNotFoundErrorResource: nameof(NuGetResources.InitCommand_FeedIsNotFound)); + + // If the Destination Feed Folder does not exist, it will be created. + OfflineFeedUtility.ThrowIfInvalid(destination); + + var packagePaths = GetPackageFilePaths(source, "*" + ProjectManagement.Constants.PackageExtension); + + if (packagePaths.Count > 0) + { + foreach (var packagePath in packagePaths) + { + var offlineFeedAddContext = new OfflineFeedAddContext( + packagePath, + destination, + Console, // IConsole is an ILogger + throwIfSourcePackageIsInvalid: false, + throwIfPackageExistsAndInvalid: false, + throwIfPackageExists: false, + expand: Expand); + + await OfflineFeedUtility.AddPackageToSource(offlineFeedAddContext, CancellationToken.None); + } + } + else + { + var message = string.Format( + CultureInfo.CurrentCulture, + LocalizedResourceManager.GetString(nameof(NuGetResources.InitCommand_FeedContainsNoPackages)), + source); + + Console.LogInformation(message); + } + } + + /// + /// Helper method based on LocalPackageRepository and ExpandedPackageRepository + /// to avoid dependency on NuGet.Core. Links to the classes are + /// https://github.com/NuGet/NuGet2/blob/2.9/src/Core/Repositories/LocalPackageRepository.cs + /// AND + /// https://github.com/NuGet/NuGet2/blob/2.9/src/Core/Repositories/ExpandedPackageRepository.cs + /// + private static IReadOnlyList GetPackageFilePaths(string source, string nupkgFilter) + { + var packagePaths = new List(); + var isV2StyleFolderSource = IsV2StyleFolderSource(source, nupkgFilter); + + if (!isV2StyleFolderSource.HasValue) + { + // There are no nupkg files, v2-style or v3-style, under 'source'. + return packagePaths; + } + + if (isV2StyleFolderSource.Value) + { + foreach (var idDirectory in Directory.EnumerateDirectories(source)) + { + // Since we need the .nupkg file for nuget.exe init, PackageSaveMode.Nuspec is not supported. + // And, Default search option for EnumerateFiles is top directory only. + var packagesAtIdDirectory = Directory.EnumerateFiles(idDirectory, nupkgFilter); + + packagePaths.AddRange(packagesAtIdDirectory); + } + + var packagesAtRoot = Directory.EnumerateFiles(source, nupkgFilter); + packagePaths.AddRange(packagesAtRoot); + } + else + { + foreach (var idDirectory in Directory.EnumerateDirectories(source)) + { + var packageId = Path.GetFileName(idDirectory); + + foreach (var versionDirectory in Directory.EnumerateDirectories(idDirectory)) + { + var packagesAtVersionDirectory = Directory.EnumerateFiles(versionDirectory, nupkgFilter); + packagePaths.AddRange(packagesAtVersionDirectory); + } + } + } + + return packagePaths; + } + + /// + /// Helper method based on the LazyLocalPackageRepository.cs to avoid dependency on NuGet.Core + /// https://github.com/NuGet/NuGet2/blob/2.9/src/Core/Repositories/LazyLocalPackageRepository.cs#L74 + /// + /// Return true if source v2 style folder. Otherwise, false. + /// If no nupkgs were found under the source, returns null + private static bool? IsV2StyleFolderSource(string source, string nupkgFilter) + { + var packagesAtRoot = Directory.EnumerateFiles(source, nupkgFilter); + + if (packagesAtRoot.Any()) + { + return true; + } + + foreach (var idDirectory in Directory.EnumerateDirectories(source)) + { + // Since we need the .nupkg file for nuget.exe init, PackageSaveMode.Nuspec is not supported. + // And, Default search option for EnumerateFiles is top directory only. + var packagesAtIdDirectory = Directory.EnumerateFiles(idDirectory, nupkgFilter); + + if (packagesAtIdDirectory.Any()) + { + return true; + } + + foreach (var versionDirectory in Directory.EnumerateDirectories(idDirectory)) + { + var packagesAtVersionDirectory = Directory.EnumerateFiles(versionDirectory, nupkgFilter); + + if (packagesAtVersionDirectory.Any()) + { + return false; + } + } + } + + return null; + } + } +} diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj b/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj index 52a556d6631..c3b6b6e585c 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj +++ b/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj @@ -48,6 +48,7 @@ + @@ -56,6 +57,7 @@ + @@ -107,6 +109,8 @@ True NuGetResources.resx + + diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.Designer.cs b/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.Designer.cs index 823d78da863..e83eed620a8 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.Designer.cs +++ b/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.Designer.cs @@ -60,6 +60,53 @@ internal NuGetCommand() { } } + /// + /// Looks up a localized string similar to Adds the given package to a hierarchical source. http sources are not supported. For more info, goto https://docs.nuget.org/consume/command-line-reference#add-command.. + /// + internal static string AddCommandDescription { + get { + return ResourceManager.GetString("AddCommandDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specifies the fileSourceFolder to which the nupkg will be added. http sources are not supported.. + /// + internal static string AddCommandSourceDescription { + get { + return ResourceManager.GetString("AddCommandSourceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specify the path to the package to be added to the specified file source.. + /// + internal static string AddCommandUsageDescription { + get { + return ResourceManager.GetString("AddCommandUsageDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to nuget add foo.nupkg -Source c:\bar\ + /// + ///nuget add foo.nupkg -Source \\bar\packages\. + /// + internal static string AddCommandUsageExamples { + get { + return ResourceManager.GetString("AddCommandUsageExamples", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <packagePath> -Source <fileSourceFolder> [options]. + /// + internal static string AddCommandUsageSummary { + get { + return ResourceManager.GetString("AddCommandUsageSummary", resourceCulture); + } + } + /// /// Looks up a localized string similar to The API key for the server.. /// @@ -2262,6 +2309,15 @@ internal static string DeleteCommandUsageSummary_trk { } } + /// + /// Looks up a localized string similar to If provided, a package added to offline feed is also expanded.. + /// + internal static string ExpandDescription { + get { + return ResourceManager.GetString("ExpandDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Print detailed help for all available commands.. /// @@ -3102,6 +3158,53 @@ internal static string HelpCommandUsageSummary_trk { } } + /// + /// Looks up a localized string similar to Adds all the packages from the <srcFeed> to the hierarchical <destFeed>. http feeds are not supported. For more info, goto https://docs.nuget.org/consume/command-line-reference#init-command.. + /// + internal static string InitCommandDescription { + get { + return ResourceManager.GetString("InitCommandDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specifies the fileSourceFolder. Cannot be an http source.. + /// + internal static string InitCommandSourceDescription { + get { + return ResourceManager.GetString("InitCommandSourceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specify the path to the feed to be added to the specified destination feed. + /// + internal static string InitCommandUsageDescription { + get { + return ResourceManager.GetString("InitCommandUsageDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to nuget init c:\foo c:\bar + /// + ///nuget add \\foo\packages \\bar\packages. + /// + internal static string InitCommandUsageExamples { + get { + return ResourceManager.GetString("InitCommandUsageExamples", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <srcFeedPath> <destFeedPath> [options]. + /// + internal static string InitCommandUsageSummary { + get { + return ResourceManager.GetString("InitCommandUsageSummary", resourceCulture); + } + } + /// /// Looks up a localized string similar to Installs a package using the specified sources. If no sources are specified, all sources defined in the NuGet configuration file are used. If the configuration file specifies no sources, uses the default NuGet feed.. /// diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.resx b/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.resx index aeece03fe32..660ee16a708 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.resx +++ b/src/NuGet.Clients/NuGet.CommandLine/NuGetCommand.resx @@ -5245,4 +5245,41 @@ nuget update -Self Specifies the version of MSBuild to be used with this command. Supported values are 4, 12, 14. By default the MSBuild in your path is picked, otherwise it defaults to the highest installed version of MSBuild. + + Adds the given package to a hierarchical source. http sources are not supported. For more info, goto https://docs.nuget.org/consume/command-line-reference#add-command. + + + Specify the path to the package to be added to the specified file source. + + + nuget add foo.nupkg -Source c:\bar\ + +nuget add foo.nupkg -Source \\bar\packages\ + + + <packagePath> -Source <fileSourceFolder> [options] + + + Specifies the fileSourceFolder to which the nupkg will be added. http sources are not supported. + + + Adds all the packages from the <srcFeed> to the hierarchical <destFeed>. http feeds are not supported. For more info, goto https://docs.nuget.org/consume/command-line-reference#init-command. + + + Specifies the fileSourceFolder. Cannot be an http source. + + + Specify the path to the feed to be added to the specified destination feed + + + nuget init c:\foo c:\bar + +nuget add \\foo\packages \\bar\packages + + + <srcFeedPath> <destFeedPath> [options] + + + If provided, a package added to offline feed is also expanded. + \ No newline at end of file diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs index 5c153c217df..9badea997b8 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs +++ b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs @@ -60,6 +60,42 @@ internal NuGetResources() { } } + /// + /// Looks up a localized string similar to Package '{0}' already exists at feed '{1}' and is invalid.. + /// + public static string AddCommand_ExistingPackageInvalid { + get { + return ResourceManager.GetString("AddCommand_ExistingPackageInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Package '{0}' already exists at feed '{1}'.. + /// + public static string AddCommand_PackageAlreadyExists { + get { + return ResourceManager.GetString("AddCommand_PackageAlreadyExists", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '-Source' switch is not provided. nupkg gets added to the 'Source' and is mandatory.. + /// + public static string AddCommand_SourceNotProvided { + get { + return ResourceManager.GetString("AddCommand_SourceNotProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully added package '{0}' to feed '{1}'.. + /// + public static string AddCommand_SuccessfullyAdded { + get { + return ResourceManager.GetString("AddCommand_SuccessfullyAdded", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add file '{0}' to package as '{1}'. /// @@ -4191,6 +4227,24 @@ public static string HelpCommandForMoreInfo_trk { } } + /// + /// Looks up a localized string similar to Feed '{0}' contains no packages.. + /// + public static string InitCommand_FeedContainsNoPackages { + get { + return ResourceManager.GetString("InitCommand_FeedContainsNoPackages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Provided feed folder '{0}' is not found.. + /// + public static string InitCommand_FeedIsNotFound { + get { + return ResourceManager.GetString("InitCommand_FeedIsNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to '{0}' contains invalid package references. . /// @@ -6378,6 +6432,24 @@ public static string NoProjectsFound_trk { } } + /// + /// Looks up a localized string similar to '{0}' is not a valid nupkg file.. + /// + public static string NupkgPath_InvalidNupkg { + get { + return ResourceManager.GetString("NupkgPath_InvalidNupkg", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Provided Nupkg file '{0}' is not found.. + /// + public static string NupkgPath_NotFound { + get { + return ResourceManager.GetString("NupkgPath_NotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to NuGet official package source. /// @@ -8772,6 +8844,24 @@ public static string PackagingFilesFromOutputPath_trk { } } + /// + /// Looks up a localized string similar to '{0}' is not a valid path.. + /// + public static string Path_Invalid { + get { + return ResourceManager.GetString("Path_Invalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' should be a local path or a UNC share path.. + /// + public static string Path_Invalid_NotFileNotUnc { + get { + return ResourceManager.GetString("Path_Invalid_NotFileNotUnc", resourceCulture); + } + } + /// /// Looks up a localized string similar to This version of nuget.exe does not support pushing packages to package source '{0}'.. /// @@ -10954,6 +11044,15 @@ public static string SettingsCredentials_UsingSavedCredentials { } } + /// + /// Looks up a localized string similar to Source '{0}' is not found.. + /// + public static string Source_NotFound { + get { + return ResourceManager.GetString("Source_NotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Package source name 'All' is a reserved name.. /// diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx index 65d3b4a7e0b..c9cd5c88130 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx +++ b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx @@ -6072,5 +6072,37 @@ Oluşturma sırasında NuGet'in paketleri indirmesini önlemek için, Visual Stu Failed to load {0}{1} + + '{0}' is not a valid path. + + + '{0}' is not a valid nupkg file. + + + Provided Nupkg file '{0}' is not found. + + + '{0}' should be a local path or a UNC share path. + + + Source '{0}' is not found. + + + Successfully added package '{0}' to feed '{1}'. + + + Package '{0}' already exists at feed '{1}'. + + + Package '{0}' already exists at feed '{1}' and is invalid. + + + Feed '{0}' contains no packages. + + + Provided feed folder '{0}' is not found. + + + '-Source' switch is not provided. nupkg gets added to the 'Source' and is mandatory. \ No newline at end of file diff --git a/src/NuGet.Clients/NuGet.CommandLine/OfflineFeedAddContext.cs b/src/NuGet.Clients/NuGet.CommandLine/OfflineFeedAddContext.cs new file mode 100644 index 00000000000..6ea5c29c4ad --- /dev/null +++ b/src/NuGet.Clients/NuGet.CommandLine/OfflineFeedAddContext.cs @@ -0,0 +1,49 @@ +using System; +using NuGet.ProjectManagement; + +namespace NuGet.CommandLine +{ + public class OfflineFeedAddContext + { + public string PackagePath { get; } + public string Source { get; } + public Logging.ILogger Logger { get; } + public bool ThrowIfSourcePackageIsInvalid { get; } + public bool ThrowIfPackageExistsAndInvalid { get; } + public bool ThrowIfPackageExists { get; } + public bool Expand { get; } + + public OfflineFeedAddContext( + string packagePath, + string source, + Logging.ILogger logger, + bool throwIfSourcePackageIsInvalid, + bool throwIfPackageExistsAndInvalid, + bool throwIfPackageExists, + bool expand) + { + if (string.IsNullOrEmpty(packagePath)) + { + throw new ArgumentException(Strings.Argument_Cannot_Be_Null_Or_Empty, nameof(packagePath)); + } + + if (string.IsNullOrEmpty(source)) + { + throw new ArgumentException(Strings.Argument_Cannot_Be_Null_Or_Empty, nameof(source)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + PackagePath = packagePath; + Source = source; + Logger = logger; + ThrowIfSourcePackageIsInvalid = throwIfSourcePackageIsInvalid; + ThrowIfPackageExists = throwIfPackageExists; + ThrowIfPackageExistsAndInvalid = throwIfPackageExistsAndInvalid; + Expand = expand; + } + } +} diff --git a/src/NuGet.Clients/NuGet.CommandLine/OfflineFeedUtility.cs b/src/NuGet.Clients/NuGet.CommandLine/OfflineFeedUtility.cs new file mode 100644 index 00000000000..aa9d43d391c --- /dev/null +++ b/src/NuGet.Clients/NuGet.CommandLine/OfflineFeedUtility.cs @@ -0,0 +1,235 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Packaging; +using NuGet.Packaging.Core; + +namespace NuGet.CommandLine +{ + public static class OfflineFeedUtility + { + public static bool PackageExists( + PackageIdentity packageIdentity, + string offlineFeed, + out bool isValidPackage) + { + if (packageIdentity == null) + { + throw new ArgumentNullException(nameof(packageIdentity)); + } + + if (string.IsNullOrEmpty(offlineFeed)) + { + throw new ArgumentNullException(nameof(offlineFeed)); + } + + var versionFolderPathResolver = new VersionFolderPathResolver(offlineFeed, normalizePackageId: true); + + var nupkgFilePath + = versionFolderPathResolver.GetPackageFilePath(packageIdentity.Id, packageIdentity.Version); + + var hashFilePath + = versionFolderPathResolver.GetHashPath(packageIdentity.Id, packageIdentity.Version); + + var nuspecFilePath + = versionFolderPathResolver.GetManifestFilePath(packageIdentity.Id, packageIdentity.Version); + + var nupkgFileExists = File.Exists(nupkgFilePath); + + var hashFileExists = File.Exists(hashFilePath); + + var nuspecFileExists = File.Exists(nuspecFilePath); + + if (nupkgFileExists || hashFileExists || nuspecFileExists) + { + if (!nupkgFileExists || !hashFileExists || !nuspecFileExists) + { + // One of the necessary files to represent the package in the feed does not exist + isValidPackage = false; + } + else + { + // All the necessary files to represent the package in the feed are present. + // Check if the existing nupkg matches the hash. Otherwise, it is considered invalid. + var packageHash = GetHash(nupkgFilePath); + var existingHash = File.ReadAllText(hashFilePath); + + isValidPackage = packageHash.Equals(existingHash, StringComparison.Ordinal); + } + + return true; + } + + isValidPackage = false; + return false; + } + + public static void ThrowIfInvalid(string path) + { + Uri pathUri; + if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out pathUri)) + { + throw new CommandLineException( + LocalizedResourceManager.GetString(nameof(NuGetResources.Path_Invalid)), + path); + } + + var invalidPathChars = Path.GetInvalidPathChars(); + if (invalidPathChars.Any(p => path.Contains(p))) + { + throw new CommandLineException( + LocalizedResourceManager.GetString(nameof(NuGetResources.Path_Invalid)), + path); + } + + if (!pathUri.IsAbsoluteUri) + { + path = Path.GetFullPath(path); + pathUri = new Uri(path); + } + + if (!pathUri.IsFile && !pathUri.IsUnc) + { + throw new CommandLineException( + LocalizedResourceManager.GetString(nameof(NuGetResources.Path_Invalid_NotFileNotUnc)), + path); + } + } + + public static void ThrowIfInvalidOrNotFound( + string path, + bool isDirectory, + string nameOfNotFoundErrorResource) + { + if (nameOfNotFoundErrorResource == null) + { + throw new ArgumentNullException(nameof(nameOfNotFoundErrorResource)); + } + + ThrowIfInvalid(path); + + if ((isDirectory && !Directory.Exists(path)) || + (!isDirectory && !File.Exists(path))) + { + throw new CommandLineException( + LocalizedResourceManager.GetString(nameOfNotFoundErrorResource), + path); + } + } + + public static async Task AddPackageToSource( + OfflineFeedAddContext offlineFeedAddContext, + CancellationToken token) + { + var packagePath = offlineFeedAddContext.PackagePath; + var source = offlineFeedAddContext.Source; + var logger = offlineFeedAddContext.Logger; + + using (var packageStream = File.OpenRead(packagePath)) + { + try + { + var packageReader = new PackageReader(packageStream); + var packageIdentity = packageReader.GetIdentity(); + + bool isValidPackage; + if (PackageExists(packageIdentity, source, out isValidPackage)) + { + // Package already exists. Verify if it is valid + if (isValidPackage) + { + var message = string.Format( + CultureInfo.CurrentCulture, + LocalizedResourceManager.GetString( + nameof(NuGetResources.AddCommand_PackageAlreadyExists)), packageIdentity, source); + + if (offlineFeedAddContext.ThrowIfPackageExists) + { + throw new CommandLineException(message); + } + else + { + logger.LogInformation(message); + } + } + else + { + var message = string.Format( + CultureInfo.CurrentCulture, + LocalizedResourceManager.GetString( + nameof(NuGetResources.AddCommand_ExistingPackageInvalid)), packageIdentity, source); + + if (offlineFeedAddContext.ThrowIfPackageExistsAndInvalid) + { + throw new CommandLineException(message); + } + else + { + logger.LogWarning(message); + } + } + } + else + { + packageStream.Seek(0, SeekOrigin.Begin); + var versionFolderPathContext = new VersionFolderPathContext( + packageIdentity, + source, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: !offlineFeedAddContext.Expand, + normalizeFileNames: true); + + await NuGetPackageUtils.InstallFromSourceAsync( + stream => packageStream.CopyToAsync(stream), + versionFolderPathContext, + token); + + var message = string.Format( + CultureInfo.CurrentCulture, + LocalizedResourceManager.GetString(nameof(NuGetResources.AddCommand_SuccessfullyAdded)), + packagePath, + source); + + logger.LogInformation(message); + } + } + catch (InvalidDataException) + { + var message = string.Format( + CultureInfo.CurrentCulture, + LocalizedResourceManager.GetString(nameof(NuGetResources.NupkgPath_InvalidNupkg)), + packagePath); + + if (offlineFeedAddContext.ThrowIfSourcePackageIsInvalid) + { + throw new CommandLineException(message); + } + else + { + logger.LogWarning(message); + } + } + } + } + + private static string GetHash(string nupkgFilePath) + { + string packageHash; + using (var nupkgStream + = File.Open(nupkgFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var sha512 = SHA512.Create()) + { + packageHash = Convert.ToBase64String(sha512.ComputeHash(nupkgStream)); + } + } + + return packageHash; + } + } +} diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand.cs index e31bbf7a037..a408e53de42 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand.cs @@ -858,13 +858,18 @@ private async Task InstallPackagesAsync(IEnumerable graphs, private async Task InstallPackageAsync(RemoteMatch installItem, string packagesDirectory, CancellationToken token) { var packageIdentity = new PackageIdentity(installItem.Library.Name, installItem.Library.Version); - await NuGetPackageUtils.InstallFromSourceAsync( - stream => installItem.Provider.CopyToAsync(installItem.Library, stream, token), + var versionFolderPathContext = new VersionFolderPathContext( packageIdentity, packagesDirectory, _log, fixNuspecIdCasing: true, - token: token); + extractNuspecOnly: false, + normalizeFileNames: false); + + await NuGetPackageUtils.InstallFromSourceAsync( + stream => installItem.Provider.CopyToAsync(installItem.Library, stream, token), + versionFolderPathContext, + token); } private IRemoteDependencyProvider CreateProviderFromSource( diff --git a/src/NuGet.Core/NuGet.Packaging/PackageExtraction/NuGetPackageUtils.cs b/src/NuGet.Core/NuGet.Packaging/PackageExtraction/NuGetPackageUtils.cs index 5a5cf632f79..09fc50e6cfc 100644 --- a/src/NuGet.Core/NuGet.Packaging/PackageExtraction/NuGetPackageUtils.cs +++ b/src/NuGet.Core/NuGet.Packaging/PackageExtraction/NuGetPackageUtils.cs @@ -11,8 +11,6 @@ using System.Threading.Tasks; using System.Xml.Linq; using NuGet.Common; -using NuGet.Logging; -using NuGet.Packaging.Core; namespace NuGet.Packaging { @@ -22,13 +20,24 @@ public static class NuGetPackageUtils public static async Task InstallFromSourceAsync( Func copyToAsync, - PackageIdentity packageIdentity, - string packagesDirectory, - ILogger log, - bool fixNuspecIdCasing, + VersionFolderPathContext versionFolderPathContext, CancellationToken token) { - var packagePathResolver = new VersionFolderPathResolver(packagesDirectory); + if (copyToAsync == null) + { + throw new ArgumentNullException(nameof(copyToAsync)); + } + + if (versionFolderPathContext == null) + { + throw new ArgumentNullException(nameof(versionFolderPathContext)); + } + + var packagePathResolver = new VersionFolderPathResolver( + versionFolderPathContext.PackagesDirectory, versionFolderPathContext.NormalizeFileNames); + + var packageIdentity = versionFolderPathContext.Package; + var logger = versionFolderPathContext.Logger; var targetPath = packagePathResolver.GetInstallPath(packageIdentity.Id, packageIdentity.Version); var targetNuspec = packagePathResolver.GetManifestFilePath(packageIdentity.Id, packageIdentity.Version); @@ -45,7 +54,8 @@ await ConcurrencyUtilities.ExecuteWithFileLocked(targetNupkg, // waiting on this lock don't need to install it again. if (!File.Exists(hashPath)) { - log.LogInformation(string.Format(CultureInfo.CurrentCulture, + logger.LogInformation(string.Format( + CultureInfo.CurrentCulture, Strings.Log_InstallingPackage, packageIdentity.Id, packageIdentity.Version)); @@ -90,13 +100,21 @@ await ConcurrencyUtilities.ExecuteWithFileLocked(targetNupkg, using (var archive = new ZipArchive(nupkgStream, ZipArchiveMode.Read)) { - ExtractFiles(archive, targetPath, NupkgFilter); + if (versionFolderPathContext.ExtractNuspecOnly) + { + ExtractNuspec(archive, targetNuspec); + } + else + { + ExtractFiles(archive, targetPath, NupkgFilter); + } } } - if (fixNuspecIdCasing) + if (versionFolderPathContext.FixNuspecIdCasing) { - // DNU REFACTORING TODO: delete the hacky FixNuSpecIdCasing() and uncomment logic below after we + // DNU REFACTORING TODO: delete the hacky FixNuSpecIdCasing() + // and uncomment logic below after we // have implementation of NuSpecFormatter.Read() // Fixup the casing of the nuspec on disk to match what we expect var nuspecFile = Directory.EnumerateFiles(targetPath, "*" + ManifestExtension).Single(); @@ -130,7 +148,7 @@ await ConcurrencyUtilities.ExecuteWithFileLocked(targetNupkg, File.Move(tempHashPath, hashPath); } - log.LogVerbose($"Completed installation of {packageIdentity.Id} {packageIdentity.Version}"); + logger.LogVerbose($"Completed installation of {packageIdentity.Id} {packageIdentity.Version}"); } return 0; @@ -138,6 +156,30 @@ await ConcurrencyUtilities.ExecuteWithFileLocked(targetNupkg, token: token); } + private static void ExtractNuspec(ZipArchive archive, string targetNuspec) + { + var nuspecEntry = archive.Entries + .FirstOrDefault(p => p.FullName.EndsWith(ManifestExtension, StringComparison.OrdinalIgnoreCase)); + + if (nuspecEntry == null) + { + throw new FileNotFoundException(string.Format( + CultureInfo.CurrentCulture, + Strings.MissingNuspec, + targetNuspec)); + } + + // Nuspec found, extract and leave the rest + using (var entryStream = nuspecEntry.Open()) + { + using (var targetStream + = new FileStream(targetNuspec, FileMode.Create, FileAccess.Write, FileShare.None)) + { + entryStream.CopyTo(targetStream); + } + } + } + // DNU REFACTORING TODO: delete this temporary workaround after we have NuSpecFormatter.Read() private static void FixNuSpecIdCasing(string nuspecFile, string targetNuspec, string correctedId) { @@ -148,8 +190,10 @@ private static void FixNuSpecIdCasing(string nuspecFile, string targetNuspec, st { var xDoc = XDocument.Parse(File.ReadAllText(nuspecFile), LoadOptions.PreserveWhitespace); - var metadataNode = xDoc.Root.Elements().Where(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata")).First(); - var node = metadataNode.Elements(XName.Get("id", metadataNode.GetDefaultNamespace().NamespaceName)).First(); + var metadataNode = xDoc.Root.Elements() + .Where(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata")).First(); + var node = metadataNode.Elements(XName.Get("id", metadataNode.GetDefaultNamespace().NamespaceName)) + .First(); node.Value = correctedId; var tmpNuspecFile = nuspecFile + ".tmp"; @@ -232,7 +276,8 @@ private static void ExtractFiles(ZipArchive archive, string targetPath, Func AddPackageAsync(PackageIdentity var globalPackagesFolder = SettingsUtility.GetGlobalPackagesFolder(settings); - // The following call adds it to the global packages folder. Addition is performed using ConcurrentUtils, such that, + // The following call adds it to the global packages folder. + // Addition is performed using ConcurrentUtils, such that, // multiple processes may add at the same time - await NuGetPackageUtils.InstallFromSourceAsync( - stream => packageStream.CopyToAsync(stream), + + var versionFolderPathContext = new VersionFolderPathContext( packageIdentity, globalPackagesFolder, NullLogger.Instance, fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); + + await NuGetPackageUtils.InstallFromSourceAsync( + stream => packageStream.CopyToAsync(stream), + versionFolderPathContext, token: token); var package = GetPackage(packageIdentity, settings); diff --git a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGet.CommandLine.Test.csproj b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGet.CommandLine.Test.csproj index 4321fbdd61e..98c14beb065 100644 --- a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGet.CommandLine.Test.csproj +++ b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGet.CommandLine.Test.csproj @@ -65,15 +65,18 @@ + + + @@ -106,6 +109,6 @@ - + \ No newline at end of file diff --git a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGetAddCommandTests.cs b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGetAddCommandTests.cs new file mode 100644 index 00000000000..56bdcf9431d --- /dev/null +++ b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGetAddCommandTests.cs @@ -0,0 +1,491 @@ +using System; +using System.Collections.Generic; +using System.IO; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Versioning; +using Test.Utility; +using Xunit; + +namespace NuGet.CommandLine.Test +{ + public class NuGetAddCommandTests + { + private class TestInfo : IDisposable + { + public string NuGetExePath { get; } + public string SourceParamFolder { get; set; } + public string RandomNupkgFolder { get { return Path.GetDirectoryName(RandomNupkgFilePath); } } + public PackageIdentity Package { get; } + public FileInfo TestPackage { get; set; } + public string RandomNupkgFilePath { get { return TestPackage.FullName; } } + public string WorkingPath { get; } + + public TestInfo() + { + NuGetExePath = Util.GetNuGetExePath(); + WorkingPath = TestFilesystemUtility.CreateRandomTestFolder(); + Package = new PackageIdentity("AbCd", new NuGetVersion("1.0.0.0")); + } + + public void Init() + { + Init(TestFilesystemUtility.CreateRandomTestFolder()); + } + + public void Init(string sourceParamFolder) + { + var randomNupkgFolder = TestFilesystemUtility.CreateRandomTestFolder(); + var testPackage = TestPackages.GetLegacyTestPackage( + randomNupkgFolder, + Package.Id, + Package.Version.ToString()); + + Init(sourceParamFolder, testPackage); + } + + public void Init(FileInfo testPackage) + { + Init(TestFilesystemUtility.CreateRandomTestFolder(), testPackage); + } + + public void Init(string sourceParamFolder, FileInfo testPackage) + { + SourceParamFolder = sourceParamFolder; + TestPackage = testPackage; + } + + public void Dispose() + { + TestFilesystemUtility.DeleteRandomTestFolders( + SourceParamFolder, + RandomNupkgFolder, + WorkingPath); + } + } + + [Fact] + public void AddCommand_Fail_NoSourceSpecified() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultFailure(result, NuGetResources.AddCommand_SourceNotProvided); + } + } + + [Fact] + public void AddCommand_Success_SpecifiedSource() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackageExists(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Success_SpecifiedRelativeSource() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var sourceParamFullPath = Path.Combine(testInfo.WorkingPath, "relativePathOfflineFeed"); + testInfo.Init(sourceParamFullPath); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + "relativePathOfflineFeed" + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + + Util.VerifyPackageExists(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Success_SourceDoesNotExist() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var currentlyNonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + testInfo.Init(currentlyNonExistentPath); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + + Util.VerifyPackageExists(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Success_PackageAlreadyExists() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackageExists(testInfo.Package, testInfo.SourceParamFolder); + + // Main Act + result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result, string.Format( + NuGetResources.AddCommand_PackageAlreadyExists, + testInfo.Package.ToString(), + testInfo.SourceParamFolder)); + } + } + + [Fact] + public void AddCommand_Fail_PackageAlreadyExistsAndInvalid() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackageExists(testInfo.Package, testInfo.SourceParamFolder); + + var versionFolderPathResolver = new VersionFolderPathResolver( + testInfo.SourceParamFolder, + normalizePackageId: true); + + File.Delete( + versionFolderPathResolver.GetManifestFilePath(testInfo.Package.Id, testInfo.Package.Version)); + + // Main Act + result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultFailure(result, string.Format( + NuGetResources.AddCommand_ExistingPackageInvalid, + testInfo.Package.ToString(), + testInfo.SourceParamFolder)); + } + } + + [Fact] + public void AddCommand_Fail_HttpSource() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init("https://api.nuget.org/v3/index.json"); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.Path_Invalid_NotFileNotUnc, testInfo.SourceParamFolder); + Util.VerifyResultFailure(result, expectedErrorMessage); + + Util.VerifyPackageDoesNotExist(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Fail_NupkgFileDoesNotExist() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var args = new string[] + { + "add", + nonExistentPath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.NupkgPath_NotFound, nonExistentPath); + + Util.VerifyResultFailure(result, expectedErrorMessage); + + Util.VerifyPackageDoesNotExist(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Fail_NupkgFileIsAnHttpLink() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var invalidNupkgFilePath = "http://www.nuget.org/api/v2/package/EntityFramework/5.0.0"; + var args = new string[] + { + "add", + invalidNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.Path_Invalid_NotFileNotUnc, invalidNupkgFilePath); + + Util.VerifyResultFailure(result, expectedErrorMessage); + + Util.VerifyPackageDoesNotExist(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Fail_NupkgPathIsNotANupkg() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var testPackage = new FileInfo(Path.Combine(Path.GetTempPath(), Path.GetTempFileName())); + testInfo.Init(testPackage); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.NupkgPath_InvalidNupkg, testInfo.RandomNupkgFilePath); + + Util.VerifyResultFailure(result, expectedErrorMessage); + + Util.VerifyPackageDoesNotExist(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Fact] + public void AddCommand_Fail_CorruptNupkgFile() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var testPackage = new FileInfo(Path.Combine(Path.GetTempPath(), Path.GetTempFileName())); + testInfo.Init(testPackage); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.NupkgPath_InvalidNupkg, testInfo.RandomNupkgFilePath); + + Util.VerifyResultFailure(result, expectedErrorMessage); + + Util.VerifyPackageDoesNotExist(testInfo.Package, testInfo.SourceParamFolder); + } + } + + [Theory] + [InlineData("add")] + [InlineData("add -?")] + [InlineData("add nupkgPath -Source srcFolder extraArg")] + public void AddCommand_Success_InvalidArguments_HelpMessage(string args) + { + // Arrange & Act + var result = CommandRunner.Run( + Util.GetNuGetExePath(), + Directory.GetCurrentDirectory(), + args, + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result, "usage: NuGet add -Source [options]"); + } + + [Fact] + public void AddCommand_Success_ExpandSwitch() + { + // Arrange + using (var testInfo = new TestInfo()) + { + testInfo.Init(); + + var args = new string[] + { + "add", + testInfo.RandomNupkgFilePath, + "-Source", + testInfo.SourceParamFolder, + "-Expand" + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + var listOfPackages = new List() { testInfo.Package }; + Util.VerifyExpandedLegacyTestPackagesExist(listOfPackages, testInfo.SourceParamFolder); + } + } + } +} diff --git a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGetInitCommandTests.cs b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGetInitCommandTests.cs new file mode 100644 index 00000000000..df25f0fd6c0 --- /dev/null +++ b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/NuGetInitCommandTests.cs @@ -0,0 +1,669 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Versioning; +using Test.Utility; +using Xunit; + +namespace NuGet.CommandLine.Test +{ + public class NuGetInitCommandTests + { + /// TEST CASES + /// 1. No Destination Feed, or No arguments or '-?' or extra args. SUCCESS with help or usage message. + /// 2. Destination Feed is provided. SUCCESS. + /// 3. Destination Feed is provided and is relative. SUCCESS. + /// 4. Destination Feed does not exist. SUCCESS with messages. + /// 5. Destination Feed already contains packages. SUCCESS with messages. + /// 6. Destination Feed already contains packages but are invalid. SUCCESS with messages. + /// 7. Source Feed is relative. SUCCESS. + /// 8. Source Feed contains no packages. SUCCESS with messages. + /// 9. Source Feed contains invalid packages. SUCCESS with warnings. + /// 10. Source Feed does not exist. FAIL + /// 11. Source Feed is a http source. FAIL + /// 12. Destination Feed is a http source. FAIL + /// 13. Source Feed is an invalid input. FAIL + /// 14. Destination Feed is an invalid input. FAIL + /// 15. Source Feed is v2-based folder (packages are at the package id directory under the root). SUCCESS + /// 16. Source Feed is v3-based folder (packages are at the package version directory under the root). SUCCESS + /// 17. For -Expand switch, Packages are expanded at the destination feed. SUCCESS + + private class TestInfo : IDisposable + { + public string NuGetExePath { get; } + public string WorkingPath { get; } + public string SourceFeed { get; } + public string DestinationFeed { get; } + + public TestInfo(string sourceFeed = null, string destinationFeed = null) + { + NuGetExePath = Util.GetNuGetExePath(); + WorkingPath = TestFilesystemUtility.CreateRandomTestFolder(); + + if (sourceFeed == null) + { + SourceFeed = TestFilesystemUtility.CreateRandomTestFolder(); + } + else + { + SourceFeed = sourceFeed; + } + + if (destinationFeed == null) + { + DestinationFeed = TestFilesystemUtility.CreateRandomTestFolder(); + } + else + { + DestinationFeed = destinationFeed; + } + } + + public static readonly List PackagesSet0 = new List() + { + new PackageIdentity("A", new NuGetVersion("1.0.0")), + new PackageIdentity("A", new NuGetVersion("2.0.0")), + new PackageIdentity("B", new NuGetVersion("1.0.0-BETA")), + }; + + public static readonly List PackagesSet1 = new List() + { + new PackageIdentity("C", new NuGetVersion("1.0.0")), + new PackageIdentity("C", new NuGetVersion("2.0.0")), + new PackageIdentity("D", new NuGetVersion("1.0.0-RC")), + }; + + public static readonly List PackagesSet2 = new List() + { + new PackageIdentity("E", new NuGetVersion("1.0.0")), + new PackageIdentity("E", new NuGetVersion("2.0.0")), + new PackageIdentity("F", new NuGetVersion("1.0.0-ALPHA")), + }; + + public IList AddPackagesToSource() + { + return AddPackagesToSource(PackagesSet0, 0); + } + + /// + /// TEST method: Adds packages to the test source. + /// + /// List of packages to be added + /// 0 if nupkg is at {root}\, 1 if nupkg is at directory {root}\{packageId}\ + /// and 2 if nupkg is at directory {root}\{packageId}\{packageVersion}\. + /// + public IList AddPackagesToSource( + List packages, + int packageLevel) + { + foreach(var package in packages) + { + var packageDirectory = SourceFeed; + if (packageLevel == 2) + { + packageDirectory = Path.Combine(SourceFeed, package.Id, package.Version.ToString()); + Directory.CreateDirectory(packageDirectory); + } + else if (packageLevel == 1) + { + packageDirectory = Path.Combine(SourceFeed, package.Id); + Directory.CreateDirectory(packageDirectory); + } + + TestPackages.GetLegacyTestPackage(packageDirectory, package.Id, package.Version.ToString()); + } + + return packages; + } + + public void Dispose() + { + TestFilesystemUtility.DeleteRandomTestFolders(WorkingPath, SourceFeed, DestinationFeed); + } + } + + + [Theory] + [InlineData("init")] + [InlineData("init -?")] + [InlineData("init srcFolder")] + [InlineData("init srcFolder destFolder extraArg")] + public void InitCommand_Success_InvalidArguments_HelpMessage(string args) + { + // Arrange & Act + var result = CommandRunner.Run( + Util.GetNuGetExePath(), + Directory.GetCurrentDirectory(), + args, + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result, "usage: NuGet init [options]"); + } + + [Fact] + public void InitCommand_Success_DestinationProvided() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackagesExist(packages, testInfo.DestinationFeed); + } + } + + [Fact] + public void InitCommand_Success_DestinationIsRelative() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + ".", + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackagesExist(packages, testInfo.WorkingPath); // Working path is the destination feed + } + } + + [Fact] + public void InitCommand_Success_DestinationDoesNotExist() + { + // Arrange + var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + using (var testInfo = new TestInfo(TestFilesystemUtility.CreateRandomTestFolder(), nonExistentPath)) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackagesExist(packages, testInfo.DestinationFeed); + } + } + + [Fact] + public void InitCommand_Success_DestinationAlreadyContainsPackages() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyPackagesExist(packages, testInfo.DestinationFeed); + + // Main Act + result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Main Assert + Util.VerifyResultSuccess(result); + var output = result.Item2; + foreach(var p in packages) + { + output.Contains(string.Format( + NuGetResources.AddCommand_PackageAlreadyExists, + p.ToString(), + testInfo.DestinationFeed)); + } + } + } + + [Fact] + public void InitCommand_Success_DestinationAlreadyContainsInvalidPackages() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyPackagesExist(packages, testInfo.DestinationFeed); + + var firstPackage = packages.First(); + var packageId = firstPackage.Id.ToLowerInvariant(); + var packageIdDirectory = Path.Combine(testInfo.DestinationFeed, packageId); + Assert.True(Directory.Exists(packageIdDirectory)); + + var packageVersion = firstPackage.Version.ToNormalizedString(); + var packageVersionDirectory = Path.Combine(packageIdDirectory, packageVersion); + Assert.True(Directory.Exists(packageVersionDirectory)); + + var nupkgFileName = Util.GetNupkgFileName(packageId, packageVersion); + var nupkgFilePath = Path.Combine(packageVersionDirectory, nupkgFileName); + File.Delete(nupkgFilePath); + + // Main Act + result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Main Assert + Util.VerifyResultSuccess(result, string.Format( + NuGetResources.AddCommand_ExistingPackageInvalid, + firstPackage.ToString(), + testInfo.DestinationFeed)); + + var output = result.Item2; + foreach (var p in packages.Skip(1)) + { + output.Contains(string.Format( + NuGetResources.AddCommand_PackageAlreadyExists, + p.ToString(), + testInfo.DestinationFeed)); + } + } + } + + [Fact] + public void InitCommand_Success_SourceIsRelative() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + ".", + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.SourceFeed, // Source Feed is the working path + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackagesExist(packages, testInfo.DestinationFeed); + } + } + + [Fact] + public void InitCommand_Success_SourceNoPackages() + { + // Arrange + using (var testInfo = new TestInfo()) + { + // Add no packages to the source. + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedMessage = string.Format( + NuGetResources.InitCommand_FeedContainsNoPackages, + testInfo.SourceFeed); + + Util.VerifyResultSuccess(result, expectedMessage); + } + } + + [Fact] + public void InitCommand_Success_SourceContainsInvalidPackages() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + // Add an invalid package. Following calls add an invalid package to SourceFeed. + var tempFile = Path.GetTempFileName(); + var invalidPackageIdentity = new PackageIdentity("Invalid", new NuGetVersion("1.0.0")); + var invalidPackageFile = Path.Combine( + testInfo.SourceFeed, + invalidPackageIdentity.Id + "." + invalidPackageIdentity.Version.ToString() + ".nupkg"); + File.Move(tempFile, invalidPackageFile); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedWarningMessage = string.Format( + NuGetResources.NupkgPath_InvalidNupkg, + invalidPackageFile); + + Util.VerifyResultSuccess( + result, + expectedOutputMessage: expectedWarningMessage); + + Util.VerifyPackagesExist(packages, testInfo.DestinationFeed); + + // Verify that the invalid package was not copied + Util.VerifyPackageDoesNotExist(invalidPackageIdentity, testInfo.DestinationFeed); + } + } + + [Fact] + public void InitCommand_Fail_SourceDoesNotExist() + { + // Arrange + var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + using (var testInfo = new TestInfo(nonExistentPath, TestFilesystemUtility.CreateRandomTestFolder())) + { + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.InitCommand_FeedIsNotFound, testInfo.SourceFeed); + Util.VerifyResultFailure(result, expectedErrorMessage); + } + } + + [Fact] + public void InitCommand_Fail_SourceIsHttpSource() + { + // Arrange + var invalidPath = "https://api.nuget.org/v3/index.json"; + using (var testInfo = new TestInfo(invalidPath, TestFilesystemUtility.CreateRandomTestFolder())) + { + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.Path_Invalid_NotFileNotUnc, testInfo.SourceFeed); + Util.VerifyResultFailure(result, expectedErrorMessage); + } + } + + [Fact] + public void InitCommand_Fail_DestinationIsHttpSource() + { + // Arrange + var invalidPath = "https://api.nuget.org/v3/index.json"; + using (var testInfo = new TestInfo(TestFilesystemUtility.CreateRandomTestFolder(), invalidPath)) + { + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedErrorMessage + = string.Format(NuGetResources.Path_Invalid_NotFileNotUnc, testInfo.DestinationFeed); + Util.VerifyResultFailure(result, expectedErrorMessage); + } + } + + [Fact] + public void InitCommand_Fail_SourceIsInvalid() + { + // Arrange + var invalidPath = "foo|<>|bar"; + using (var testInfo = new TestInfo(invalidPath, TestFilesystemUtility.CreateRandomTestFolder())) + { + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedMessage = string.Format(NuGetResources.Path_Invalid, invalidPath); + Util.VerifyResultFailure(result, expectedMessage); + } + } + + [Fact] + public void InitCommand_Fail_DestinationIsInvalid() + { + // Arrange + var invalidPath = "foo|<>|bar"; + using (var testInfo = new TestInfo(TestFilesystemUtility.CreateRandomTestFolder(), invalidPath)) + { + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + var expectedMessage = string.Format(NuGetResources.Path_Invalid, invalidPath); + Util.VerifyResultFailure(result, expectedMessage); + } + } + + [Fact] + public void InitCommand_Success_V2Style_DestinationProvided() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packagesAtRoot = testInfo.AddPackagesToSource(TestInfo.PackagesSet0, 0); + var packagesAtIdDirectory = testInfo.AddPackagesToSource(TestInfo.PackagesSet1, 1); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackagesExist(packagesAtRoot, testInfo.DestinationFeed); + Util.VerifyPackagesExist(packagesAtIdDirectory, testInfo.DestinationFeed); + } + } + + [Fact] + public void InitCommand_Success_V3Style_DestinationProvided() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packagesAtVersionDirectory = testInfo.AddPackagesToSource(TestInfo.PackagesSet2, 2); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyPackagesExist(packagesAtVersionDirectory, testInfo.DestinationFeed); + } + } + + [Fact] + public void InitCommand_Success_ExpandSwitch() + { + // Arrange + using (var testInfo = new TestInfo()) + { + var packages = testInfo.AddPackagesToSource(); + + var args = new string[] + { + "init", + testInfo.SourceFeed, + testInfo.DestinationFeed, + "-Expand" + }; + + // Act + var result = CommandRunner.Run( + testInfo.NuGetExePath, + testInfo.WorkingPath, + string.Join(" ", args), + waitForExit: true); + + // Assert + Util.VerifyResultSuccess(result); + Util.VerifyExpandedLegacyTestPackagesExist(packages, testInfo.DestinationFeed); + } + } + } +} diff --git a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/OffineFeedUtilityTests.cs b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/OffineFeedUtilityTests.cs new file mode 100644 index 00000000000..a911b827a32 --- /dev/null +++ b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/OffineFeedUtilityTests.cs @@ -0,0 +1,66 @@ +using System.IO; +using Xunit; + +namespace NuGet.CommandLine.Test +{ + public class OffineFeedUtilityTests + { + [Theory] + [InlineData("c:\\foo|<>|bar")] + [InlineData("c:\\foo|<>|bar.nupkg")] + public void OfflineFeedUtility_ThrowIfInvalid_Throws_PathInvalid(string path) + { + // Act & Assert + var expectedMessage = string.Format(NuGetResources.Path_Invalid, path); + + var exception + = Assert.Throws(() => OfflineFeedUtility.ThrowIfInvalid(path)); + + Assert.Equal(expectedMessage, exception.Message); + } + + [Theory] + [InlineData("http://foonugetbar.org")] + [InlineData("http://foonugetbar.org/A.nupkg")] + public void OfflineFeedUtility_ThrowIfInvalid_Throws_Path_Invalid_NotFileNotUnc(string path) + { + // Act & Assert + var expectedMessage = string.Format(NuGetResources.Path_Invalid_NotFileNotUnc, path); + + var exception + = Assert.Throws(() => OfflineFeedUtility.ThrowIfInvalid(path)); + + Assert.Equal(expectedMessage, exception.Message); + } + + [Theory] + [InlineData("foo\\bar")] + [InlineData("c:\\foo\\bar")] + [InlineData("\\foouncshare\\bar")] + public void OfflineFeedUtility_ThrowIfInvalid_DoesNotThrow(string path) + { + // Act & Assert that the following call does not throw + OfflineFeedUtility.ThrowIfInvalid(path); + } + + [Theory] + [InlineData("c:\\foobardoesnotexist", true)] + [InlineData("foobardoesnotexist\\A.nupkg", false)] + public void OfflineFeedUtility_ThrowIfInvalidOrNotFound_Throws(string path, bool isDirectory) + { + // Arrange + var nameOfNotFoundErrorResource + = isDirectory + ? nameof(NuGetResources.InitCommand_FeedIsNotFound) : nameof(NuGetResources.NupkgPath_NotFound); + + // Act & Assert + var expectedMessage = string.Format(NuGetResources.Path_Invalid_NotFileNotUnc, path); + var exception + = Assert.Throws(() + => OfflineFeedUtility.ThrowIfInvalidOrNotFound( + path, + isDirectory, + nameOfNotFoundErrorResource)); + } + } +} diff --git a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/Util.cs b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/Util.cs index 180f9564f3d..6fe3352d2bf 100644 --- a/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/Util.cs +++ b/test/NuGet.Clients.Tests/NuGet.CommandLine.Test/Util.cs @@ -5,19 +5,22 @@ using System.IO; using System.Linq; using System.Net; -using System.Runtime.Versioning; using System.Text; using System.Xml.Linq; using Moq; using Newtonsoft.Json.Linq; using NuGet.Frameworks; using NuGet.Packaging; +using NuGet.Packaging.Core; using NuGet.Versioning; +using Xunit; namespace NuGet.CommandLine.Test { public static class Util { + private static readonly string NupkgFileFormat = "{0}.{1}.nupkg"; + public static string CreateTestPackage( string packageId, string version, @@ -548,5 +551,119 @@ public static string CreateSolutionFileContent() ""proj1.csproj"", ""{A04C59CC-7622-4223-B16B-CDF2ECAD438D}"" EndProject"; } + + public static void VerifyResultSuccess(Tuple result, string expectedOutputMessage = null) + { + Assert.True( + result.Item1 == 0, + "nuget.exe DID NOT SUCCEED: Ouput is " + result.Item2 + ". Error is " + result.Item3); + + if (!string.IsNullOrEmpty(expectedOutputMessage)) + { + Assert.True( + result.Item2.Contains(expectedOutputMessage), + "Expected output is " + expectedOutputMessage + ". Actual output is " + result.Item2); + } + } + + public static void VerifyResultFailure(Tuple result, string expectedErrorMessage) + { + Assert.True( + result.Item1 != 0, + "nuget.exe DID NOT FAIL: Ouput is " + result.Item2 + ". Error is " + result.Item3); + + Assert.True( + result.Item3.Contains(expectedErrorMessage), + "Expected error is " + expectedErrorMessage + ". Actual error is " + result.Item3); + } + + public static void VerifyPackageExists( + PackageIdentity packageIdentity, + string packagesDirectory) + { + string normalizedId = packageIdentity.Id.ToLowerInvariant(); + string normalizedVersion = packageIdentity.Version.ToNormalizedString(); + + var packageIdDirectory = Path.Combine(packagesDirectory, normalizedId); + Assert.True(Directory.Exists(packageIdDirectory)); + + var packageVersionDirectory = Path.Combine(packageIdDirectory, normalizedVersion); + Assert.True(Directory.Exists(packageVersionDirectory)); + + var nupkgFileName = GetNupkgFileName(normalizedId, normalizedVersion); + + var nupkgFilePath = Path.Combine(packageVersionDirectory, nupkgFileName); + Assert.True(File.Exists(nupkgFilePath)); + + var nupkgSHAFilePath = Path.Combine(packageVersionDirectory, nupkgFileName + ".sha512"); + Assert.True(File.Exists(nupkgSHAFilePath)); + + var nuspecFilePath = Path.Combine(packageVersionDirectory, normalizedId + ".nuspec"); + Assert.True(File.Exists(nuspecFilePath)); + } + + public static void VerifyPackageDoesNotExist( + PackageIdentity packageIdentity, + string packagesDirectory) + { + string normalizedId = packageIdentity.Id.ToLowerInvariant(); + var packageIdDirectory = Path.Combine(packagesDirectory, normalizedId); + Assert.False(Directory.Exists(packageIdDirectory)); + } + + public static void VerifyPackagesExist( + IList packages, + string packagesDirectory) + { + foreach(var package in packages) + { + VerifyPackageExists(package, packagesDirectory); + } + } + + public static void VerifyPackagesDoNotExist( + IList packages, + string packagesDirectory) + { + foreach (var package in packages) + { + VerifyPackageDoesNotExist(package, packagesDirectory); + } + } + + /// + /// To verify packages created using TestPackages.GetLegacyTestPackage + /// + public static void VerifyExpandedLegacyTestPackagesExist( + IList packages, + string packagesDirectory) + { + var versionFolderPathResolver + = new VersionFolderPathResolver(packagesDirectory, normalizePackageId: true); + + var packageFiles = new[] + { + "lib/test.dll", + "lib/net40/test40.dll", + "lib/net40/test40b.dll", + "lib/net45/test45.dll", + }; + + foreach (var package in packages) + { + Util.VerifyPackageExists(package, packagesDirectory); + var packageRoot = versionFolderPathResolver.GetInstallPath(package.Id, package.Version); + foreach (var packageFile in packageFiles) + { + var filePath = Path.Combine(packageRoot, packageFile); + Assert.True(File.Exists(filePath), $"For {package}, {filePath} does not exist."); + } + } + } + + public static string GetNupkgFileName(string normalizedId, string normalizedVersion) + { + return string.Format(NupkgFileFormat, normalizedId, normalizedVersion); + } } } \ No newline at end of file diff --git a/test/NuGet.Core.Tests/NuGet.Packaging.Test/NugetPackageUtilTests.cs b/test/NuGet.Core.Tests/NuGet.Packaging.Test/NugetPackageUtilTests.cs index 52d5b997267..9a4a4f0d48b 100644 --- a/test/NuGet.Core.Tests/NuGet.Packaging.Test/NugetPackageUtilTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Packaging.Test/NugetPackageUtilTests.cs @@ -27,16 +27,20 @@ public async Task PackageExpander_ExpandsPackage() var token = CancellationToken.None; var logger = NullLogger.Instance; + var versionFolderPathContext = new VersionFolderPathContext( + identity, + packagesDir, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); // Act using (var stream = package.File.OpenRead()) { await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token); + versionFolderPathContext, + token); } // Assert @@ -64,16 +68,20 @@ public async Task PackageExpander_ExpandsPackage_WithNupkgCopy() var token = CancellationToken.None; var logger = NullLogger.Instance; + var versionFolderPathContext = new VersionFolderPathContext( + identity, + packagesDir, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); // Act using (var stream = package.File.OpenRead()) { await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token); + versionFolderPathContext, + token); } // Assert @@ -103,6 +111,13 @@ public async Task PackageExpander_ExpandsPackage_SkipsIfShaIsThere() var token = CancellationToken.None; var logger = NullLogger.Instance; + var versionFolderPathContext = new VersionFolderPathContext( + identity, + packagesDir, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); var packageDir = Path.Combine(packagesDir, package.Id, package.Version); @@ -119,11 +134,8 @@ public async Task PackageExpander_ExpandsPackage_SkipsIfShaIsThere() using (var stream = package.File.OpenRead()) { await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token); + versionFolderPathContext, + token); } // Assert @@ -150,6 +162,13 @@ public async Task PackageExpander_CleansExtraFiles() var token = CancellationToken.None; var logger = NullLogger.Instance; + var versionFolderPathContext = new VersionFolderPathContext( + identity, + packagesDir, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); var packageDir = Path.Combine(packagesDir, package.Id, package.Version); @@ -168,11 +187,8 @@ public async Task PackageExpander_CleansExtraFiles() using (var stream = package.File.OpenRead()) { await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token); + versionFolderPathContext, + token); } // Assert @@ -201,6 +217,13 @@ public async Task PackageExpander_Recovers_WhenStreamIsCorrupt() var token = CancellationToken.None; var logger = NullLogger.Instance; + var versionFolderPathContext = new VersionFolderPathContext( + identity, + packagesDir, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); var packageDir = Path.Combine(packagesDir, package.Id, package.Version); Assert.False(Directory.Exists(packageDir), packageDir + " exist"); @@ -211,11 +234,8 @@ public async Task PackageExpander_Recovers_WhenStreamIsCorrupt() await Assert.ThrowsAnyAsync(async () => await NuGetPackageUtils.InstallFromSourceAsync( async (d) => await new CorruptStreamWrapper(stream).CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token)); + versionFolderPathContext, + token)); } Assert.True(Directory.Exists(packageDir), packageDir + " does not exist"); @@ -225,11 +245,8 @@ await NuGetPackageUtils.InstallFromSourceAsync( using (var stream = package.File.OpenRead()) { await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token); + versionFolderPathContext, + token); } // Assert @@ -256,6 +273,13 @@ public async Task PackageExpander_Recovers_WhenFileIsLocked() var token = CancellationToken.None; var logger = NullLogger.Instance; + var versionFolderPathContext = new VersionFolderPathContext( + identity, + packagesDir, + logger, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); var packageDir = Path.Combine(packagesDir, package.Id, package.Version); Assert.False(Directory.Exists(packageDir), packageDir + " exist"); @@ -270,11 +294,8 @@ public async Task PackageExpander_Recovers_WhenFileIsLocked() await Assert.ThrowsAnyAsync(async () => await NuGetPackageUtils.InstallFromSourceAsync( async (d) => await fileLocker.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token)); + versionFolderPathContext, + token)); fileLocker.Release(); } @@ -289,11 +310,8 @@ await NuGetPackageUtils.InstallFromSourceAsync( using (var stream = package.File.OpenRead()) { await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToAsync(d), - identity, - packagesDir, - logger, - fixNuspecIdCasing: false, - token: token); + versionFolderPathContext, + token); } // Assert @@ -311,6 +329,139 @@ await NuGetPackageUtils.InstallFromSourceAsync(async (d) => await stream.CopyToA Assert.Equal(new byte[] { 0 }, File.ReadAllBytes(filePathToLock)); } + [Fact] + public async Task Test_ExtractPackage() + { + // Arrange + var package = new PackageIdentity("packageA", new NuGetVersion("2.0.3")); + var packageFileInfo = TestPackages.GetLegacyTestPackage(); + var packagesDirectory = TestFileSystemUtility.CreateRandomTestFolder(); + var versionFolderPathContext = new VersionFolderPathContext( + package, + packagesDirectory, + NullLogger.Instance, + fixNuspecIdCasing: false, + extractNuspecOnly: false, + normalizeFileNames: false); + + try + { + // Act + using (var packageFileStream = packageFileInfo.OpenRead()) + { + await NuGetPackageUtils.InstallFromSourceAsync( + stream => packageFileStream.CopyToAsync(stream), + versionFolderPathContext, + CancellationToken.None); + } + + // Assert + var packageIdDirectory = Path.Combine(packagesDirectory, package.Id); + var packageVersionDirectory = Path.Combine(packageIdDirectory, package.Version.ToNormalizedString()); + Assert.True(Directory.Exists(packageIdDirectory)); + Assert.True(Directory.Exists(packageVersionDirectory)); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.2.0.3.nupkg"))); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.nuspec"))); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.2.0.3.nupkg.sha512"))); + + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, @"lib", "test.dll"))); + } + finally + { + TestFileSystemUtility.DeleteRandomTestFolders(packagesDirectory); + } + } + + [Fact] + public async Task Test_ExtractNuspecOnly() + { + // Arrange + var package = new PackageIdentity("packageA", new NuGetVersion("2.0.3")); + var packageFileInfo = TestPackages.GetLegacyTestPackage(); + var packagesDirectory = TestFileSystemUtility.CreateRandomTestFolder(); + var versionFolderPathContext = new VersionFolderPathContext( + package, + packagesDirectory, + NullLogger.Instance, + fixNuspecIdCasing: false, + extractNuspecOnly: true, + normalizeFileNames: false); + + try + { + // Act + using (var packageFileStream = packageFileInfo.OpenRead()) + { + await NuGetPackageUtils.InstallFromSourceAsync( + stream => packageFileStream.CopyToAsync(stream), + versionFolderPathContext, + CancellationToken.None); + } + + // Assert + var packageIdDirectory = Path.Combine(packagesDirectory, package.Id); + var packageVersionDirectory = Path.Combine(packageIdDirectory, package.Version.ToNormalizedString()); + Assert.True(Directory.Exists(packageIdDirectory)); + Assert.True(Directory.Exists(packageVersionDirectory)); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.2.0.3.nupkg"))); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.nuspec"))); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.2.0.3.nupkg.sha512"))); + + Assert.False(File.Exists(Path.Combine(packageVersionDirectory, @"lib", "test.dll"))); + } + finally + { + TestFileSystemUtility.DeleteRandomTestFolders(packagesDirectory); + } + } + + [Fact] + public async Task Test_ExtractNuspecOnly_NormalizeFileNames() + { + // Arrange + var package = new PackageIdentity("packageA", new NuGetVersion("2.0.3")); + var packageFileInfo = TestPackages.GetLegacyTestPackage(); + var packagesDirectory = TestFileSystemUtility.CreateRandomTestFolder(); + var versionFolderPathContext = new VersionFolderPathContext( + package, + packagesDirectory, + NullLogger.Instance, + fixNuspecIdCasing: false, + extractNuspecOnly: true, + normalizeFileNames: true); + + try + { + // Act + using (var packageFileStream = packageFileInfo.OpenRead()) + { + await NuGetPackageUtils.InstallFromSourceAsync( + stream => packageFileStream.CopyToAsync(stream), + versionFolderPathContext, + CancellationToken.None); + } + + // Assert + var packageIdDirectory = Path.Combine(packagesDirectory, package.Id.ToLowerInvariant()); + var packageVersionDirectory = Path.Combine(packageIdDirectory, package.Version.ToNormalizedString()); + Assert.True(Directory.Exists(packageIdDirectory)); + Assert.True(Directory.Exists(packageVersionDirectory)); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.2.0.3.nupkg"))); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.nuspec"))); + Assert.True(File.Exists(Path.Combine(packageVersionDirectory, "packageA.2.0.3.nupkg.sha512"))); + + Assert.False(File.Exists(Path.Combine(packageVersionDirectory, @"lib", "test.dll"))); + + // The following check ensures that the file name is normalized + var nuspecFile = Directory.EnumerateFiles(packageVersionDirectory, "*.nuspec").FirstOrDefault(); + Assert.True(nuspecFile.EndsWith("packagea.nuspec", StringComparison.Ordinal)); + } + finally + { + TestFileSystemUtility.DeleteRandomTestFolders(packagesDirectory); + } + } + private class StreamWrapperBase : Stream { protected readonly Stream _stream;