diff --git a/.nuget/packages.config b/.nuget/packages.config index 4bbca7b..47d6c8e 100644 --- a/.nuget/packages.config +++ b/.nuget/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/NuGet.Settings.targets b/NuGet.Settings.targets index 92d33eb..cb8b5b8 100644 --- a/NuGet.Settings.targets +++ b/NuGet.Settings.targets @@ -4,7 +4,7 @@ true $(DefineConstants);MONO; 11.0 - + @@ -18,18 +18,17 @@ Properties 512 - prompt 4 true $(DefineConstants);TRACE $(NuGetRoot)\Extended.Settings.targets - + Microsoft.VisualStudio.Shell.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL ..\..\lib\VS10\Microsoft.VisualStudio.Shell.10.0.dll - + Microsoft.Build Microsoft.VisualStudio.VCProjectEngine, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\..\lib\Microsoft.VisualStudio.VCProjectEngine.dll @@ -41,7 +40,7 @@ Microsoft.VisualStudio.VCProjectEngine, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - + Microsoft.VisualStudio.Shell.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL ..\..\lib\VS12\Microsoft.VisualStudio.Shell.12.0.dll @@ -108,7 +107,7 @@ bin\Coverage\ true - $(DefineConstants);CODE_COVERAGE + $(DefineConstants);CODE_COVERAGE diff --git a/NuGet.config b/NuGet.config index 6fd27e4..5f7414d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,7 +9,7 @@ - + diff --git a/build.ps1 b/build.ps1 index 400c31e..eae1de6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -9,9 +9,11 @@ param ( [string]$SemanticVersion = '1.0.0-zlocal', [string]$Branch, [string]$CommitSHA, - [string]$BuildBranch = '1c479a7381ebbc0fe1fded765de70d513b8bd68e' + [string]$BuildBranch = 'cb2b9e41b18cb77ee644a51951d8c8f24cde9adf' ) +$msBuildVersion = 15; + # For TeamCity - If any issue occurs, this script fail the build. - By default, TeamCity returns an exit code of 0 for all powershell scripts, even if they fail trap { Write-Host "BUILD FAILED: $_" -ForegroundColor Red @@ -24,7 +26,11 @@ trap { if (-not (Test-Path "$PSScriptRoot/build")) { New-Item -Path "$PSScriptRoot/build" -ItemType "directory" } -wget -UseBasicParsing -Uri "https://raw.githubusercontent.com/NuGet/ServerCommon/$BuildBranch/build/init.ps1" -OutFile "$PSScriptRoot/build/init.ps1" + +# Enable TLS 1.2 since GitHub requires it. +[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + +Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/NuGet/ServerCommon/$BuildBranch/build/init.ps1" -OutFile "$PSScriptRoot/build/init.ps1" . "$PSScriptRoot/build/init.ps1" -BuildBranch "$BuildBranch" Write-Host ("`r`n" * 3) @@ -69,14 +75,24 @@ Invoke-BuildStep 'Set version metadata in AssemblyInfo.cs' { Invoke-BuildStep 'Building solution' { $SolutionPath = Join-Path $PSScriptRoot "NuGet.Server.sln" - Build-Solution $Configuration $BuildNumber -MSBuildVersion "14" $SolutionPath -SkipRestore:$SkipRestore ` + Build-Solution $Configuration $BuildNumber -MSBuildVersion "$msBuildVersion" $SolutionPath -SkipRestore:$SkipRestore ` } ` -ev +BuildErrors Invoke-BuildStep 'Creating artifacts' { - New-Package (Join-Path $PSScriptRoot "src\NuGet.Server.Core\NuGet.Server.Core.csproj") -Configuration $Configuration -Symbols -BuildNumber $BuildNumber -Version $SemanticVersion -Branch $Branch - New-Package (Join-Path $PSScriptRoot "src\NuGet.Server.V2\NuGet.Server.V2.csproj") -Configuration $Configuration -Symbols -BuildNumber $BuildNumber -Version $SemanticVersion -Branch $Branch - New-Package (Join-Path $PSScriptRoot "src\NuGet.Server\NuGet.Server.nuspec") -Configuration $Configuration -Symbols -BuildNumber $BuildNumber -Version $SemanticVersion -Branch $Branch + $projects = ` + "src\NuGet.Server.Core\NuGet.Server.Core.csproj", ` + "src\NuGet.Server.V2\NuGet.Server.V2.csproj", ` + "src\NuGet.Server\NuGet.Server.nuspec" + + Foreach ($project in $projects) { + New-Package (Join-Path $PSScriptRoot $project) -Configuration $Configuration -Symbols -BuildNumber $BuildNumber -MSBuildVersion "$msBuildVersion" -Version $SemanticVersion -Branch $Branch + } + } ` + -ev +BuildErrors + +Invoke-BuildStep 'Signing the packages' { + Sign-Packages -Configuration $Configuration -BuildNumber $BuildNumber -MSBuildVersion $msBuildVersion ` } ` -ev +BuildErrors diff --git a/lib/NuGet-Chocolatey/NuGet.Core.dll b/lib/NuGet-Chocolatey/NuGet.Core.dll new file mode 100644 index 0000000..9a3800e Binary files /dev/null and b/lib/NuGet-Chocolatey/NuGet.Core.dll differ diff --git a/samples/NuGet.Server.V2.Samples.OwinHost/DictionarySettingsProvider.cs b/samples/NuGet.Server.V2.Samples.OwinHost/DictionarySettingsProvider.cs index 8958501..001bf47 100644 --- a/samples/NuGet.Server.V2.Samples.OwinHost/DictionarySettingsProvider.cs +++ b/samples/NuGet.Server.V2.Samples.OwinHost/DictionarySettingsProvider.cs @@ -1,5 +1,6 @@ // 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 NuGet.Server.Core.Infrastructure; @@ -7,19 +8,26 @@ namespace NuGet.Server.V2.Samples.OwinHost { public class DictionarySettingsProvider : ISettingsProvider { - readonly Dictionary _settings; + private readonly Dictionary _settings; - public DictionarySettingsProvider(Dictionary settings) + public DictionarySettingsProvider(Dictionary settings) { _settings = settings; } - public bool GetBoolSetting(string key, bool defaultValue) { - System.Diagnostics.Debug.WriteLine("getSetting: " + key); - return _settings.ContainsKey(key) ? _settings[key] : defaultValue; + return _settings.ContainsKey(key) ? Convert.ToBoolean(_settings[key]) : defaultValue; + } + + public int GetIntSetting(string key, int defaultValue) + { + return _settings.ContainsKey(key) ? Convert.ToInt32(_settings[key]) : defaultValue; + } + public string GetStringSetting(string key, string defaultValue) + { + return _settings.ContainsKey(key) ? Convert.ToString(_settings[key]) : defaultValue; } } } diff --git a/samples/NuGet.Server.V2.Samples.OwinHost/NuGet.Server.V2.Samples.OwinHost.csproj b/samples/NuGet.Server.V2.Samples.OwinHost/NuGet.Server.V2.Samples.OwinHost.csproj index f50c9b2..47cf0e7 100644 --- a/samples/NuGet.Server.V2.Samples.OwinHost/NuGet.Server.V2.Samples.OwinHost.csproj +++ b/samples/NuGet.Server.V2.Samples.OwinHost/NuGet.Server.V2.Samples.OwinHost.csproj @@ -1,5 +1,6 @@  + Debug @@ -9,9 +10,11 @@ Properties NuGet.Server.V2.Samples.OwinHost NuGet.Server.V2.Samples.OwinHost - v4.6 + v4.8 512 + + AnyCPU @@ -38,14 +41,14 @@ NuGet.Server.V2.Samples.OwinHost.Program - + False - ..\..\packages\Microsoft.Data.Edm.5.7.0\lib\net40\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll True - + False - ..\..\packages\Microsoft.Data.OData.5.7.0\lib\net40\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll True @@ -71,10 +74,6 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll - True - ..\..\packages\Owin.1.0\lib\net40\Owin.dll True @@ -87,9 +86,9 @@ False ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - + False - ..\..\packages\System.Spatial.5.7.0\lib\net40\System.Spatial.dll + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll True @@ -136,5 +135,20 @@ - + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/samples/NuGet.Server.V2.Samples.OwinHost/Program.cs b/samples/NuGet.Server.V2.Samples.OwinHost/Program.cs index 6f952bd..46e775f 100644 --- a/samples/NuGet.Server.V2.Samples.OwinHost/Program.cs +++ b/samples/NuGet.Server.V2.Samples.OwinHost/Program.cs @@ -24,7 +24,7 @@ static void Main(string[] args) // Set up a common settingsProvider to be used by all repositories. // If a setting is not present in dictionary default value will be used. - var settings = new Dictionary(); + var settings = new Dictionary(); settings.Add("enableDelisting", false); //default=false settings.Add("enableFrameworkFiltering", false); //default=false settings.Add("ignoreSymbolsPackages", true); //default=false diff --git a/samples/NuGet.Server.V2.Samples.OwinHost/app.config b/samples/NuGet.Server.V2.Samples.OwinHost/app.config index 8496c6a..f7c05d7 100644 --- a/samples/NuGet.Server.V2.Samples.OwinHost/app.config +++ b/samples/NuGet.Server.V2.Samples.OwinHost/app.config @@ -1,35 +1,35 @@ - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/samples/NuGet.Server.V2.Samples.OwinHost/packages.config b/samples/NuGet.Server.V2.Samples.OwinHost/packages.config index 7a404b8..bd784dc 100644 --- a/samples/NuGet.Server.V2.Samples.OwinHost/packages.config +++ b/samples/NuGet.Server.V2.Samples.OwinHost/packages.config @@ -1,12 +1,13 @@  + - - + + @@ -14,5 +15,5 @@ - + \ No newline at end of file diff --git a/src/NuGet.Server.Core/Core/DuplicatePackageException.cs b/src/NuGet.Server.Core/Core/DuplicatePackageException.cs new file mode 100644 index 0000000..b37958e --- /dev/null +++ b/src/NuGet.Server.Core/Core/DuplicatePackageException.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace NuGet.Server.Core +{ + /// + /// This exception is thrown when trying to add a package in a version that already exists on the server + /// and is set to false. + /// + public class DuplicatePackageException : Exception + { + public DuplicatePackageException() + { + } + + public DuplicatePackageException(string message) : base(message) + { + } + + public DuplicatePackageException(string message, Exception innerException) : base(message, innerException) + { + } + + protected DuplicatePackageException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/NuGet.Server.Core/Core/NullFileSystem.cs b/src/NuGet.Server.Core/Core/NullFileSystem.cs new file mode 100644 index 0000000..cda19b1 --- /dev/null +++ b/src/NuGet.Server.Core/Core/NullFileSystem.cs @@ -0,0 +1,40 @@ +// 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; + +namespace NuGet.Server.Core +{ + /// + /// A file system implementation that persists nothing. This is intended to be used with + /// so that package files are never actually extracted anywhere on disk. + /// + public class NullFileSystem : IFileSystem + { + public static NullFileSystem Instance { get; } = new NullFileSystem(); + + public Stream CreateFile(string path) => Stream.Null; + public bool DirectoryExists(string path) => true; + public bool FileExists(string path) => false; + public string GetFullPath(string path) => null; + public Stream OpenFile(string path) => Stream.Null; + + public ILogger Logger { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public string Root => throw new NotSupportedException(); + public void AddFile(string path, Stream stream) => throw new NotSupportedException(); + public void AddFile(string path, Action writeToStream) => throw new NotSupportedException(); + public void AddFiles(IEnumerable files, string rootDir) => throw new NotSupportedException(); + public void DeleteDirectory(string path, bool recursive) => throw new NotSupportedException(); + public void DeleteFile(string path) => throw new NotSupportedException(); + public void DeleteFiles(IEnumerable files, string rootDir) => throw new NotSupportedException(); + public DateTimeOffset GetCreated(string path) => throw new NotSupportedException(); + public IEnumerable GetDirectories(string path) => throw new NotSupportedException(); + public IEnumerable GetFiles(string path, string filter, bool recursive) => throw new NotSupportedException(); + public DateTimeOffset GetLastAccessed(string path) => throw new NotSupportedException(); + public DateTimeOffset GetLastModified(string path) => throw new NotSupportedException(); + public void MakeFileWritable(string path) => throw new NotSupportedException(); + public void MoveFile(string source, string destination) => throw new NotSupportedException(); + } +} diff --git a/src/NuGet.Server.Core/Core/PackageFactory.cs b/src/NuGet.Server.Core/Core/PackageFactory.cs new file mode 100644 index 0000000..59a3eb0 --- /dev/null +++ b/src/NuGet.Server.Core/Core/PackageFactory.cs @@ -0,0 +1,29 @@ +// 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; + +namespace NuGet.Server.Core +{ + public static class PackageFactory + { + public static IPackage Open(string fullPackagePath) + { + if (string.IsNullOrEmpty(fullPackagePath)) + { + throw new ArgumentNullException(nameof(fullPackagePath)); + } + + var directoryName = Path.GetDirectoryName(fullPackagePath); + var fileName = Path.GetFileName(fullPackagePath); + + var fileSystem = new PhysicalFileSystem(directoryName); + + return new OptimizedZipPackage( + fileSystem, + fileName, + NullFileSystem.Instance); + } + } +} diff --git a/src/NuGet.Server.Core/DataServices/ODataPackage.cs b/src/NuGet.Server.Core/DataServices/ODataPackage.cs index 9f2a356..fd2a0ca 100644 --- a/src/NuGet.Server.Core/DataServices/ODataPackage.cs +++ b/src/NuGet.Server.Core/DataServices/ODataPackage.cs @@ -66,5 +66,40 @@ public class ODataPackage public string MinClientVersion { get; set; } public string Language { get; set; } + + #region NuSpec Enhancements + public string ProjectSourceUrl { get; set; } + public string PackageSourceUrl { get; set; } + public string DocsUrl { get; set; } + public string WikiUrl { get; set; } + public string MailingListUrl { get; set; } + public string BugTrackerUrl { get; set; } + public string Replaces { get; set; } + public string Provides { get; set; } + public string Conflicts { get; set; } + // round 2 + public string SoftwareDisplayName { get; set; } + public string SoftwareDisplayVersion { get; set; } + #endregion + + #region Server Metadata Only + + public bool IsApproved { get; set; } + public string PackageStatus { get; set; } + public string PackageSubmittedStatus { get; set; } + public string PackageTestResultStatus { get; set; } + public DateTime? PackageTestResultStatusDate { get; set; } + public string PackageValidationResultStatus { get; set; } + public DateTime? PackageValidationResultDate { get; set; } + public DateTime? PackageCleanupResultDate { get; set; } + public DateTime? PackageReviewedDate { get; set; } + public DateTime? PackageApprovedDate { get; set; } + public string PackageReviewer { get; set; } + public bool IsDownloadCacheAvailable { get; set; } + public DateTime? DownloadCacheDate { get; set; } + public string DownloadCache { get; set; } + + #endregion + } } \ No newline at end of file diff --git a/src/NuGet.Server.Core/DataServices/PackageExtensions.cs b/src/NuGet.Server.Core/DataServices/PackageExtensions.cs index 9f686b1..4447ccf 100644 --- a/src/NuGet.Server.Core/DataServices/PackageExtensions.cs +++ b/src/NuGet.Server.Core/DataServices/PackageExtensions.cs @@ -11,6 +11,8 @@ namespace NuGet.Server.Core.DataServices { public static class PackageExtensions { + private static readonly DateTime PublishedForUnlisted = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public static ODataPackage AsODataPackage(this IServerPackage package, ClientCompatibility compatibility) { return new ODataPackage @@ -31,7 +33,7 @@ public static ODataPackage AsODataPackage(this IServerPackage package, ClientCom Description = package.Description, Summary = package.Summary, ReleaseNotes = package.ReleaseNotes, - Published = package.Created.UtcDateTime, + Published = package.Listed ? package.Created.UtcDateTime : PublishedForUnlisted, LastUpdated = package.LastUpdated.UtcDateTime, Dependencies = string.Join("|", package.DependencySets.SelectMany(ConvertDependencySetToStrings)), PackageHash = package.PackageHash, @@ -44,8 +46,36 @@ public static ODataPackage AsODataPackage(this IServerPackage package, ClientCom Listed = package.Listed, VersionDownloadCount = package.DownloadCount, MinClientVersion = package.MinClientVersion == null ? null : package.MinClientVersion.ToString(), - Language = package.Language - }; + Language = package.Language, + // enhancements + ProjectSourceUrl = package.ProjectSourceUrl == null ? null : package.ProjectSourceUrl.GetComponents(UriComponents.HttpRequestUrl, UriFormat.Unescaped), + PackageSourceUrl = package.PackageSourceUrl == null ? null : package.PackageSourceUrl.GetComponents(UriComponents.HttpRequestUrl, UriFormat.Unescaped), + DocsUrl = package.DocsUrl == null ? null : package.DocsUrl.GetComponents(UriComponents.HttpRequestUrl, UriFormat.Unescaped), + WikiUrl = package.WikiUrl == null ? null : package.WikiUrl.GetComponents(UriComponents.HttpRequestUrl, UriFormat.Unescaped), + MailingListUrl = package.MailingListUrl == null ? null : package.MailingListUrl.GetComponents(UriComponents.HttpRequestUrl, UriFormat.Unescaped), + BugTrackerUrl = package.BugTrackerUrl == null ? null : package.BugTrackerUrl.GetComponents(UriComponents.HttpRequestUrl, UriFormat.Unescaped), + Replaces = package.Replaces == null ? null : string.Join(",", package.Replaces), + Provides = package.Provides == null ? null : string.Join(",", package.Provides), + Conflicts = package.Conflicts == null ? null : string.Join(",", package.Conflicts), + // server metadata + IsApproved = package.IsApproved, + PackageStatus = package.PackageStatus, + PackageSubmittedStatus = package.PackageSubmittedStatus, + PackageTestResultStatus = package.PackageTestResultStatus, + PackageTestResultStatusDate = package.PackageTestResultStatusDate, + PackageValidationResultStatus = package.PackageValidationResultStatus, + PackageValidationResultDate = package.PackageValidationResultDate, + PackageCleanupResultDate = package.PackageCleanupResultDate, + PackageReviewedDate = package.PackageReviewedDate, + PackageApprovedDate = package.PackageApprovedDate, + PackageReviewer = package.PackageReviewer, + IsDownloadCacheAvailable = package.IsDownloadCacheAvailable, + DownloadCacheDate = package.DownloadCacheDate, + DownloadCache = package.DownloadCache == null ? null : string.Join("|", package.DownloadCache.Select(ConvertDownloadCacheToStrings)), + // enhancements round 2 + SoftwareDisplayName = package.SoftwareDisplayName, + SoftwareDisplayVersion = package.SoftwareDisplayVersion, + }; } private static IEnumerable ConvertDependencySetToStrings(PackageDependencySet dependencySet) @@ -85,5 +115,10 @@ private static string ConvertDependency(PackageDependency packageDependency, Fra return string.Format("{0}:{1}:{2}", packageDependency.Id, packageDependency.VersionSpec, VersionUtility.GetShortFrameworkName(targetFramework)); } } + + private static string ConvertDownloadCacheToStrings(DownloadCache cache) + { + return string.Format("{0}^{1}^{2}", cache.OriginalUrl, cache.FileName, cache.Checksum); + } } } \ No newline at end of file diff --git a/src/NuGet.Server.Core/Infrastructure/DefaultSettingsProvider.cs b/src/NuGet.Server.Core/Infrastructure/DefaultSettingsProvider.cs index 65f31e1..b8056ba 100644 --- a/src/NuGet.Server.Core/Infrastructure/DefaultSettingsProvider.cs +++ b/src/NuGet.Server.Core/Infrastructure/DefaultSettingsProvider.cs @@ -8,5 +8,15 @@ public bool GetBoolSetting(string key, bool defaultValue) { return defaultValue; } + + public int GetIntSetting(string key, int defaultValue) + { + return defaultValue; + } + + public string GetStringSetting(string key, string defaultValue) + { + return defaultValue; + } } } diff --git a/src/NuGet.Server.Core/Infrastructure/IChocolateyServerPackage.cs b/src/NuGet.Server.Core/Infrastructure/IChocolateyServerPackage.cs new file mode 100644 index 0000000..bf83ad3 --- /dev/null +++ b/src/NuGet.Server.Core/Infrastructure/IChocolateyServerPackage.cs @@ -0,0 +1,40 @@ +// 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. +namespace NuGet.Server.Core.Infrastructure +{ + using System; + using System.Collections.Generic; + + public partial interface IServerPackage + { + Uri ProjectSourceUrl { get; } + Uri PackageSourceUrl { get; } + Uri DocsUrl { get; } + Uri WikiUrl { get; } + Uri MailingListUrl { get; } + Uri BugTrackerUrl { get; } + IEnumerable Replaces { get; } + IEnumerable Provides { get; } + IEnumerable Conflicts { get; } + + string SoftwareDisplayName { get; } + string SoftwareDisplayVersion { get; } + + bool IsApproved { get; } + string PackageStatus { get; } + string PackageSubmittedStatus { get; } + string PackageTestResultStatus { get; } + DateTime? PackageTestResultStatusDate { get; } + string PackageValidationResultStatus { get; } + DateTime? PackageValidationResultDate { get; } + DateTime? PackageCleanupResultDate { get; } + DateTime? PackageReviewedDate { get; } + DateTime? PackageApprovedDate { get; } + string PackageReviewer { get; } + + bool IsDownloadCacheAvailable { get; } + DateTime? DownloadCacheDate { get; } + IEnumerable DownloadCache { get; } + + } +} diff --git a/src/NuGet.Server.Core/Infrastructure/IServerPackage.cs b/src/NuGet.Server.Core/Infrastructure/IServerPackage.cs index bfe6460..e04584e 100644 --- a/src/NuGet.Server.Core/Infrastructure/IServerPackage.cs +++ b/src/NuGet.Server.Core/Infrastructure/IServerPackage.cs @@ -7,7 +7,7 @@ namespace NuGet.Server.Core.Infrastructure { - public interface IServerPackage + public partial interface IServerPackage { string Id { get; } SemanticVersion Version { get; } diff --git a/src/NuGet.Server.Core/Infrastructure/IServerPackageCache.cs b/src/NuGet.Server.Core/Infrastructure/IServerPackageCache.cs index 6af757b..50c8d53 100644 --- a/src/NuGet.Server.Core/Infrastructure/IServerPackageCache.cs +++ b/src/NuGet.Server.Core/Infrastructure/IServerPackageCache.cs @@ -18,9 +18,9 @@ public interface IServerPackageCache IEnumerable GetAll(); - void Add(ServerPackage entity); + void Add(ServerPackage entity, bool enableDelisting); - void AddRange(IEnumerable entities); + void AddRange(IEnumerable entities, bool enableDelisting); void Remove(string id, SemanticVersion version, bool enableDelisting); diff --git a/src/NuGet.Server.Core/Infrastructure/IServerPackageRepository.cs b/src/NuGet.Server.Core/Infrastructure/IServerPackageRepository.cs index b584a04..9c0c4ce 100644 --- a/src/NuGet.Server.Core/Infrastructure/IServerPackageRepository.cs +++ b/src/NuGet.Server.Core/Infrastructure/IServerPackageRepository.cs @@ -22,6 +22,14 @@ Task> SearchAsync( ClientCompatibility compatibility, CancellationToken token); + Task> SearchAsync( + string searchTerm, + IEnumerable targetFrameworks, + bool allowPrereleaseVersions, + bool allowUnlistedVersions, + ClientCompatibility compatibility, + CancellationToken token); + Task ClearCacheAsync(CancellationToken token); Task RemovePackageAsync(string packageId, SemanticVersion version, CancellationToken token); diff --git a/src/NuGet.Server.Core/Infrastructure/ISettingsProvider.cs b/src/NuGet.Server.Core/Infrastructure/ISettingsProvider.cs index bd20aa5..49e56c5 100644 --- a/src/NuGet.Server.Core/Infrastructure/ISettingsProvider.cs +++ b/src/NuGet.Server.Core/Infrastructure/ISettingsProvider.cs @@ -5,5 +5,7 @@ namespace NuGet.Server.Core.Infrastructure public interface ISettingsProvider { bool GetBoolSetting(string key, bool defaultValue); + string GetStringSetting(string key, string defaultValue); + int GetIntSetting(string key, int defaultValue); } } diff --git a/src/NuGet.Server.Core/Infrastructure/JsonNetPackagesSerializer.cs b/src/NuGet.Server.Core/Infrastructure/JsonNetPackagesSerializer.cs index cf82826..487b3ac 100644 --- a/src/NuGet.Server.Core/Infrastructure/JsonNetPackagesSerializer.cs +++ b/src/NuGet.Server.Core/Infrastructure/JsonNetPackagesSerializer.cs @@ -1,6 +1,7 @@ // 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; @@ -18,7 +19,8 @@ public class JsonNetPackagesSerializer private readonly JsonSerializer _serializer = new JsonSerializer { Formatting = Formatting.None, - NullValueHandling = NullValueHandling.Ignore + NullValueHandling = NullValueHandling.Ignore, + Converters = { new AbsoluteUriConverter() }, }; public void Serialize(IEnumerable packages, Stream stream) @@ -51,5 +53,54 @@ public IEnumerable Deserialize(Stream stream) return packages.Packages; } } + + /// + /// This is necessary because Newtonsoft.Json creates instances with + /// which treats UNC paths as relative. NuGet.Core uses + /// which treats UNC paths as absolute. For more details, see: + /// https://github.com/JamesNK/Newtonsoft.Json/issues/2128 + /// + private class AbsoluteUriConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Uri); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + else if (reader.TokenType == JsonToken.String) + { + return new Uri((string)reader.Value, UriKind.Absolute); + } + + throw new JsonSerializationException("The JSON value must be a string."); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + if (!(value is Uri uriValue)) + { + throw new JsonSerializationException("The value must be a URI."); + } + + if (!uriValue.IsAbsoluteUri) + { + throw new JsonSerializationException("The URI value must be an absolute Uri. Relative URI instances are not allowed."); + } + + writer.WriteValue(uriValue.OriginalString); + } + } } } \ No newline at end of file diff --git a/src/NuGet.Server.Core/Infrastructure/KnownPathUtility.cs b/src/NuGet.Server.Core/Infrastructure/KnownPathUtility.cs new file mode 100644 index 0000000..eabd5c6 --- /dev/null +++ b/src/NuGet.Server.Core/Infrastructure/KnownPathUtility.cs @@ -0,0 +1,65 @@ +// 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.IO; + +namespace NuGet.Server.Core.Infrastructure +{ + public static class KnownPathUtility + { + /// + /// Determines if a relative file path could have been generated by . + /// The path is assumed to be relative to the base of the package directory. + /// + /// The file path to parse. + /// The package ID found. + /// The package version found. + /// True if the file name is known. + public static bool TryParseFileName(string path, out string id, out SemanticVersion version) + { + id = null; + version = null; + + if (path == null || Path.IsPathRooted(path)) + { + return false; + } + + var pathPieces = path.Split(Path.DirectorySeparatorChar); + if (pathPieces.Length != 3) // {id}\{version}\{file name} + { + return false; + } + + id = pathPieces[pathPieces.Length - 3]; + var unparsedVersion = pathPieces[pathPieces.Length - 2]; + var fileName = pathPieces[pathPieces.Length - 1]; + + if (!SemanticVersion.TryParse(unparsedVersion, out version) + || version.ToNormalizedString() != unparsedVersion) + { + return false; + } + + string expectedFileName; + if (fileName.EndsWith(NuGet.Constants.PackageExtension)) + { + expectedFileName = $"{id}.{version}{NuGet.Constants.PackageExtension}"; + } + else if (fileName.EndsWith(NuGet.Constants.HashFileExtension)) + { + expectedFileName = $"{id}.{version}{NuGet.Constants.HashFileExtension}"; + } + else if (fileName.EndsWith(NuGet.Constants.ManifestExtension)) + { + expectedFileName = $"{id}{NuGet.Constants.ManifestExtension}"; + } + else + { + return false; + } + + return expectedFileName == fileName; + } + } +} diff --git a/src/NuGet.Server.Core/Infrastructure/ServerPackage.cs b/src/NuGet.Server.Core/Infrastructure/ServerPackage.cs index e5d25c3..9d1f30d 100644 --- a/src/NuGet.Server.Core/Infrastructure/ServerPackage.cs +++ b/src/NuGet.Server.Core/Infrastructure/ServerPackage.cs @@ -60,7 +60,41 @@ public ServerPackage( SemVer1IsAbsoluteLatest = false; SemVer1IsLatest = false; SemVer2IsAbsoluteLatest = false; - SemVer2IsLatest = false; + SemVer2IsLatest = false; + + //enhancements + ProjectSourceUrl = package.ProjectSourceUrl; + PackageSourceUrl = package.PackageSourceUrl; + DocsUrl = package.DocsUrl; + WikiUrl = package.WikiUrl; + MailingListUrl = package.MailingListUrl; + BugTrackerUrl = package.BugTrackerUrl; + + + Replaces = package.Replaces; + Provides = package.Provides; + Conflicts = package.Conflicts; + + // server metadata + //todo: need a place to save this local to the package itself + IsApproved = package.IsApproved; + PackageStatus = package.PackageStatus; + PackageSubmittedStatus = package.PackageSubmittedStatus; + PackageTestResultStatus = package.PackageTestResultStatus; + PackageTestResultStatusDate = package.PackageTestResultStatusDate; + PackageValidationResultStatus = package.PackageValidationResultStatus; + PackageValidationResultDate = package.PackageValidationResultDate; + PackageCleanupResultDate = package.PackageCleanupResultDate; + PackageReviewedDate = package.PackageReviewedDate; + PackageApprovedDate = package.PackageApprovedDate; + PackageReviewer = package.PackageReviewer; + IsDownloadCacheAvailable = package.IsDownloadCacheAvailable; + DownloadCacheDate = package.DownloadCacheDate; + DownloadCache = package.DownloadCache; + + SoftwareDisplayName = package.SoftwareDisplayName; + SoftwareDisplayVersion = package.SoftwareDisplayVersion; + } [JsonRequired] @@ -97,8 +131,43 @@ public ServerPackage( public string Copyright { get; set; } - public string Dependencies { get; set; } + #region NuSpec Enhancements + public Uri ProjectSourceUrl { get; set; } + public Uri PackageSourceUrl { get; set; } + public Uri DocsUrl { get; set; } + public Uri WikiUrl { get; set; } + public Uri MailingListUrl { get; set; } + public Uri BugTrackerUrl { get; set; } + public IEnumerable Replaces { get; set; } + public IEnumerable Provides { get; set; } + public IEnumerable Conflicts { get; set; } + // round 2 + public string SoftwareDisplayName { get; set; } + public string SoftwareDisplayVersion { get; set; } + #endregion + + #region Server Metadata Only + + public bool IsApproved { get; set; } + public string PackageStatus { get; set; } + public string PackageSubmittedStatus { get; set; } + public string PackageTestResultStatus { get; set; } + public DateTime? PackageTestResultStatusDate { get; set; } + public string PackageValidationResultStatus { get; set; } + public DateTime? PackageValidationResultDate { get; set; } + public DateTime? PackageCleanupResultDate { get; set; } + public DateTime? PackageReviewedDate { get; set; } + public DateTime? PackageApprovedDate { get; set; } + public string PackageReviewer { get; set; } + public bool IsDownloadCacheAvailable { get; set; } + public DateTime? DownloadCacheDate { get; set; } + public IEnumerable DownloadCache { get; set; } + + #endregion + + public string Dependencies { get; set; } + private List _dependencySets; [JsonIgnore] diff --git a/src/NuGet.Server.Core/Infrastructure/ServerPackageCache.cs b/src/NuGet.Server.Core/Infrastructure/ServerPackageCache.cs index 5e9f414..7092c09 100644 --- a/src/NuGet.Server.Core/Infrastructure/ServerPackageCache.cs +++ b/src/NuGet.Server.Core/Infrastructure/ServerPackageCache.cs @@ -130,7 +130,7 @@ public void Remove(string id, SemanticVersion version, bool enableDelisting) _packages.RemoveWhere(p => IsMatch(p, id, version)); } - UpdateLatestVersions(_packages.Where(p => IsMatch(p, id))); + UpdateLatestVersions(_packages.Where(p => IsMatch(p, id)), enableDelisting); _isDirty = true; } @@ -140,7 +140,7 @@ public void Remove(string id, SemanticVersion version, bool enableDelisting) } } - public void Add(ServerPackage entity) + public void Add(ServerPackage entity, bool enableDelisting) { _syncLock.EnterWriteLock(); try @@ -148,7 +148,7 @@ public void Add(ServerPackage entity) _packages.Remove(entity); _packages.Add(entity); - UpdateLatestVersions(_packages.Where(p => IsMatch(p, entity.Id))); + UpdateLatestVersions(_packages.Where(p => IsMatch(p, entity.Id)), enableDelisting); _isDirty = true; } @@ -158,7 +158,7 @@ public void Add(ServerPackage entity) } } - public void AddRange(IEnumerable entities) + public void AddRange(IEnumerable entities, bool enableDelisting) { _syncLock.EnterWriteLock(); try @@ -169,7 +169,7 @@ public void AddRange(IEnumerable entities) _packages.Add(entity); } - UpdateLatestVersions(_packages); + UpdateLatestVersions(_packages, enableDelisting); _isDirty = true; } @@ -179,7 +179,7 @@ public void AddRange(IEnumerable entities) } } - private static void UpdateLatestVersions(IEnumerable packages) + private static void UpdateLatestVersions(IEnumerable packages, bool enableDelisting) { var semVer1AbsoluteLatest = InitializePackageDictionary(); var semVer1Latest = InitializePackageDictionary(); @@ -195,6 +195,12 @@ private static void UpdateLatestVersions(IEnumerable packages) package.SemVer2IsAbsoluteLatest = false; package.SemVer2IsLatest = false; + // Unlisted packages are never considered "latest". + if (enableDelisting && !package.Listed) + { + return; + } + // Update the SemVer1 views. if (!package.IsSemVer2) { diff --git a/src/NuGet.Server.Core/Infrastructure/ServerPackageRepository.cs b/src/NuGet.Server.Core/Infrastructure/ServerPackageRepository.cs index 04f936d..4b8a886 100644 --- a/src/NuGet.Server.Core/Infrastructure/ServerPackageRepository.cs +++ b/src/NuGet.Server.Core/Infrastructure/ServerPackageRepository.cs @@ -32,6 +32,7 @@ public class ServerPackageRepository private readonly bool _runBackgroundTasks; private FileSystemWatcher _fileSystemWatcher; + private string _watchDirectory; private bool _isFileSystemWatcherSuppressed; private bool _needsRebuild; @@ -58,7 +59,7 @@ public ServerPackageRepository( _runBackgroundTasks = true; _settingsProvider = settingsProvider ?? new DefaultSettingsProvider(); _logger = logger ?? new TraceLogger(); - _serverPackageCache = InitializeServerPackageStore(); + _serverPackageCache = InitializeServerPackageCache(); _serverPackageStore = new ServerPackageStore( _fileSystem, new ExpandedPackageRepository(_fileSystem, hashProvider), @@ -72,21 +73,16 @@ internal ServerPackageRepository( ISettingsProvider settingsProvider = null, Logging.ILogger logger = null) { - if (fileSystem == null) - { - throw new ArgumentNullException(nameof(fileSystem)); - } - if (innerRepository == null) { throw new ArgumentNullException(nameof(innerRepository)); } - _fileSystem = fileSystem; + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _runBackgroundTasks = runBackgroundTasks; _settingsProvider = settingsProvider ?? new DefaultSettingsProvider(); _logger = logger ?? new TraceLogger(); - _serverPackageCache = InitializeServerPackageStore(); + _serverPackageCache = InitializeServerPackageCache(); _serverPackageStore = new ServerPackageStore( _fileSystem, innerRepository, @@ -110,9 +106,68 @@ internal ServerPackageRepository( private bool EnableFileSystemMonitoring => _settingsProvider.GetBoolSetting("enableFileSystemMonitoring", true); - private ServerPackageCache InitializeServerPackageStore() + private string CacheFileName => _settingsProvider.GetStringSetting("cacheFileName", null); + + private TimeSpan InitialCacheRebuildAfter + { + get + { + var value = GetPositiveIntSetting("initialCacheRebuildAfterSeconds", 15); + return TimeSpan.FromSeconds(value); + } + } + + private TimeSpan CacheRebuildFrequency { - return new ServerPackageCache(_fileSystem, Environment.MachineName.ToLowerInvariant() + ".cache.bin"); + get + { + int value = GetPositiveIntSetting("cacheRebuildFrequencyInMinutes", 60); + return TimeSpan.FromMinutes(value); + } + } + + private int GetPositiveIntSetting(string name, int defaultValue) + { + var value = _settingsProvider.GetIntSetting(name, defaultValue); + if (value <= 0) + { + value = defaultValue; + } + + return value; + } + + private ServerPackageCache InitializeServerPackageCache() + { + return new ServerPackageCache(_fileSystem, ResolveCacheFileName()); + } + + private string ResolveCacheFileName() + { + var fileName = CacheFileName; + const string suffix = ".cache.bin"; + + if (String.IsNullOrWhiteSpace(fileName)) + { + // Default file name + return Environment.MachineName.ToLowerInvariant() + suffix; + } + + if (fileName.LastIndexOfAny(Path.GetInvalidFileNameChars()) > 0) + { + var message = string.Format(Strings.Error_InvalidCacheFileName, fileName); + + _logger.Log(LogLevel.Error, message); + + throw new InvalidOperationException(message); + } + + if (fileName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + return fileName; + } + + return fileName + suffix; } /// @@ -188,6 +243,17 @@ public async Task> SearchAsync( bool allowPrereleaseVersions, ClientCompatibility compatibility, CancellationToken token) + { + return await SearchAsync(searchTerm, targetFrameworks, allowPrereleaseVersions, false, compatibility, token); + } + + public async Task> SearchAsync( + string searchTerm, + IEnumerable targetFrameworks, + bool allowPrereleaseVersions, + bool allowUnlistedVersions, + ClientCompatibility compatibility, + CancellationToken token) { var cache = await GetPackagesAsync(compatibility, token); @@ -195,7 +261,7 @@ public async Task> SearchAsync( .Find(searchTerm) .FilterByPrerelease(allowPrereleaseVersions); - if (EnableDelisting) + if (EnableDelisting && !allowUnlistedVersions) { packages = packages.Where(p => p.Listed); } @@ -241,7 +307,7 @@ private void AddPackagesFromDropFolderWithoutLocking() try { // Create package - var package = new OptimizedZipPackage(_fileSystem, packageFile); + var package = PackageFactory.Open(_fileSystem.GetFullPath(packageFile)); if (!CanPackageBeAddedWithoutLocking(package, shouldThrow: false)) { @@ -272,7 +338,7 @@ private void AddPackagesFromDropFolderWithoutLocking() } // Add packages to metadata store in bulk - _serverPackageCache.AddRange(serverPackages); + _serverPackageCache.AddRange(serverPackages, EnableDelisting); _serverPackageCache.PersistIfDirty(); _logger.Log(LogLevel.Info, "Finished adding packages from drop folder."); @@ -302,7 +368,7 @@ public async Task AddPackageAsync(IPackage package, CancellationToken token) EnableDelisting); // Add the package to the metadata store. - _serverPackageCache.Add(serverPackage); + _serverPackageCache.Add(serverPackage, EnableDelisting); _logger.Log(LogLevel.Info, "Finished adding package {0} {1}.", package.Id, package.Version); } @@ -334,7 +400,7 @@ private bool CanPackageBeAddedWithoutLocking(IPackage package, bool shouldThrow) if (shouldThrow) { - throw new InvalidOperationException(message); + throw new DuplicatePackageException(message); } return false; @@ -428,7 +494,7 @@ private async Task RebuildPackageStoreWithoutLockingAsync(CancellationToken toke // Build cache var packages = await ReadPackagesFromDiskWithoutLockingAsync(token); _serverPackageCache.Clear(); - _serverPackageCache.AddRange(packages); + _serverPackageCache.AddRange(packages, EnableDelisting); // Add packages from drop folder AddPackagesFromDropFolderWithoutLocking(); @@ -470,7 +536,7 @@ private async Task> ReadPackagesFromDiskWithoutLockingAsy throw; } } - + /// /// Sets the current cache to null so it will be regenerated next time. /// @@ -492,18 +558,21 @@ private void SetupBackgroundJobs() _logger.Log(LogLevel.Info, "Registering background jobs..."); // Persist to package store at given interval (when dirty) + _logger.Log(LogLevel.Info, "Persisting the cache file every 1 minute."); _persistenceTimer = new Timer( callback: state => _serverPackageCache.PersistIfDirty(), state: null, dueTime: TimeSpan.FromMinutes(1), period: TimeSpan.FromMinutes(1)); - // Rebuild the package store in the background (every hour) + // Rebuild the package store in the background + _logger.Log(LogLevel.Info, "Rebuilding the cache file for the first time after {0} second(s).", InitialCacheRebuildAfter.TotalSeconds); + _logger.Log(LogLevel.Info, "Rebuilding the cache file every {0} hour(s).", CacheRebuildFrequency.TotalHours); _rebuildTimer = new Timer( callback: state => RebuildPackageStoreAsync(CancellationToken.None), state: null, - dueTime: TimeSpan.FromSeconds(15), - period: TimeSpan.FromHours(1)); + dueTime: InitialCacheRebuildAfter, + period: CacheRebuildFrequency); _logger.Log(LogLevel.Info, "Finished registering background jobs."); } @@ -517,9 +586,14 @@ private void RegisterFileSystemWatcher() if (EnableFileSystemMonitoring && _runBackgroundTasks && _fileSystemWatcher == null && !string.IsNullOrEmpty(Source) && Directory.Exists(Source)) { // ReSharper disable once UseObjectOrCollectionInitializer - _fileSystemWatcher = new FileSystemWatcher(Source); - _fileSystemWatcher.Filter = "*"; - _fileSystemWatcher.IncludeSubdirectories = true; + _fileSystemWatcher = new FileSystemWatcher(Source) + { + Filter = "*", + IncludeSubdirectories = true, + }; + + //Keep the normalized watch path. + _watchDirectory = Path.GetFullPath(_fileSystemWatcher.Path); _fileSystemWatcher.Changed += FileSystemChangedAsync; _fileSystemWatcher.Created += FileSystemChangedAsync; @@ -549,8 +623,9 @@ private void UnregisterFileSystemWatcher() _logger.Log(LogLevel.Verbose, "Destroyed FileSystemWatcher - no longer monitoring {0}.", Source); } - } + _watchDirectory = null; + } /// /// This is an event handler for background work. Therefore, it should never throw exceptions. @@ -564,10 +639,24 @@ private async void FileSystemChangedAsync(object sender, FileSystemEventArgs e) return; } + if (ShouldIgnoreFileSystemEvent(e)) + { + _logger.Log(LogLevel.Verbose, "File system event ignored. File: {0} - Change: {1}", e.Name, e.ChangeType); + return; + } + _logger.Log(LogLevel.Verbose, "File system changed. File: {0} - Change: {1}", e.Name, e.ChangeType); + var changedDirectory = Path.GetDirectoryName(e.FullPath); + if (changedDirectory == null || _watchDirectory == null) + { + return; + } + + changedDirectory = Path.GetFullPath(changedDirectory); + // 1) If a .nupkg is dropped in the root, add it as a package - if (string.Equals(Path.GetDirectoryName(e.FullPath), _fileSystemWatcher.Path, StringComparison.OrdinalIgnoreCase) + if (string.Equals(changedDirectory, _watchDirectory, StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(e.Name), ".nupkg", StringComparison.OrdinalIgnoreCase)) { // When a package is dropped into the server packages root folder, add it to the repository. @@ -575,7 +664,7 @@ private async void FileSystemChangedAsync(object sender, FileSystemEventArgs e) } // 2) If a file is updated in a subdirectory, *or* a folder is deleted, invalidate the cache - if ((!string.Equals(Path.GetDirectoryName(e.FullPath), _fileSystemWatcher.Path, StringComparison.OrdinalIgnoreCase) && File.Exists(e.FullPath)) + if ((!string.Equals(changedDirectory, _watchDirectory, StringComparison.OrdinalIgnoreCase) && File.Exists(e.FullPath)) || e.ChangeType == WatcherChangeTypes.Deleted) { // TODO: invalidating *all* packages for every nupkg change under this folder seems more expensive than it should. @@ -590,6 +679,59 @@ private async void FileSystemChangedAsync(object sender, FileSystemEventArgs e) } } + private bool ShouldIgnoreFileSystemEvent(FileSystemEventArgs e) + { + // We can only ignore Created or Changed events. All other types are always processed. Eventually we could + // try to ignore some Deleted events in the case of API package delete, but this is harder. + if (e.ChangeType != WatcherChangeTypes.Created + && e.ChangeType != WatcherChangeTypes.Changed) + { + _logger.Log(LogLevel.Verbose, "The file system event change type is not ignorable."); + return false; + } + + /// We can only ignore events related to file paths changed by the + /// . If the file system event is representing a known file path + /// extracted during package push, we can ignore the event. File system events are supressed during package + /// push but this is still necessary since file system events can come some time after the suppression + /// window has ended. + if (!KnownPathUtility.TryParseFileName(e.Name, out var id, out var version)) + { + _logger.Log(LogLevel.Verbose, "The file system event is not related to a known package path."); + return false; + } + + /// The file path could have been generated by . Now + /// determine if the package is in the cache. + var matchingPackage = _serverPackageCache + .GetAll() + .Where(p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, id)) + .Where(p => version.Equals(p.Version)) + .FirstOrDefault(); + + if (matchingPackage == null) + { + _logger.Log(LogLevel.Verbose, "The file system event is not related to a known package."); + return false; + } + + var fileInfo = new FileInfo(e.FullPath); + if (!fileInfo.Exists) + { + _logger.Log(LogLevel.Verbose, "The package file is missing."); + return false; + } + + var minimumCreationTime = DateTimeOffset.UtcNow.AddMinutes(-1); + if (fileInfo.CreationTimeUtc < minimumCreationTime) + { + _logger.Log(LogLevel.Verbose, "The package file was not created recently."); + return false; + } + + return true; + } + private async Task LockAsync(CancellationToken token) { var handle = new Lock(_syncLock); @@ -643,12 +785,7 @@ private sealed class SuppressedFileSystemWatcher : IDisposable public SuppressedFileSystemWatcher(ServerPackageRepository repository) { - if (repository == null) - { - throw new ArgumentNullException(nameof(repository)); - } - - _repository = repository; + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); } public bool LockTaken => _lockHandle.LockTaken; @@ -663,8 +800,8 @@ public void Dispose() { if (_lockHandle != null && _lockHandle.LockTaken) { - _lockHandle.Dispose(); _repository._isFileSystemWatcherSuppressed = false; + _lockHandle.Dispose(); } } } diff --git a/src/NuGet.Server.Core/Infrastructure/ServerPackageRepositoryExtensions.cs b/src/NuGet.Server.Core/Infrastructure/ServerPackageRepositoryExtensions.cs index 2d9acf2..e5d782f 100644 --- a/src/NuGet.Server.Core/Infrastructure/ServerPackageRepositoryExtensions.cs +++ b/src/NuGet.Server.Core/Infrastructure/ServerPackageRepositoryExtensions.cs @@ -38,6 +38,23 @@ public static async Task> SearchAsync( token); } + public static async Task> SearchAsync( + this IServerPackageRepository repository, + string searchTerm, + bool allowPrereleaseVersions, + bool allowUnlistedVersions, + ClientCompatibility compatibility, + CancellationToken token) + { + return await repository.SearchAsync( + searchTerm, + Enumerable.Empty(), + allowPrereleaseVersions, + allowUnlistedVersions, + compatibility, + token); + } + public static async Task FindPackageAsync( this IServerPackageRepository repository, string id, diff --git a/src/NuGet.Server.Core/Infrastructure/ServerPackageStore.cs b/src/NuGet.Server.Core/Infrastructure/ServerPackageStore.cs index 94eb648..533c850 100644 --- a/src/NuGet.Server.Core/Infrastructure/ServerPackageStore.cs +++ b/src/NuGet.Server.Core/Infrastructure/ServerPackageStore.cs @@ -45,9 +45,7 @@ public void Remove(string id, SemanticVersion version, bool enableDelisting) { if (enableDelisting) { - var physicalFileSystem = _fileSystem as PhysicalFileSystem; - - if (physicalFileSystem != null) + if (_fileSystem is PhysicalFileSystem physicalFileSystem) { var fileName = physicalFileSystem.GetFullPath( GetPackageFileName(id, version.ToNormalizedString())); @@ -150,10 +148,7 @@ private PackageDerivedData GetPackageDerivedData(IPackage package, bool enableDe var normalizedVersion = package.Version.ToNormalizedString(); var packageFileName = GetPackageFileName(package.Id, normalizedVersion); var hashFileName = GetHashFileName(package.Id, normalizedVersion); - - // File system - var physicalFileSystem = _fileSystem as PhysicalFileSystem; - + // Build package info var packageDerivedData = new PackageDerivedData(); @@ -165,7 +160,7 @@ private PackageDerivedData GetPackageDerivedData(IPackage package, bool enableDe // Read package info var localPackage = package as LocalPackage; - if (physicalFileSystem != null) + if (_fileSystem is PhysicalFileSystem physicalFileSystem) { // Read package info from file system var fullPath = _fileSystem.GetFullPath(packageFileName); diff --git a/src/NuGet.Server.Core/NuGet.Server.Core.csproj b/src/NuGet.Server.Core/NuGet.Server.Core.csproj index 07c3a26..6b51387 100644 --- a/src/NuGet.Server.Core/NuGet.Server.Core.csproj +++ b/src/NuGet.Server.Core/NuGet.Server.Core.csproj @@ -1,5 +1,6 @@ - + + Debug @@ -9,9 +10,11 @@ Properties NuGet.Server.Core NuGet.Server.Core - v4.6 + v4.8 512 + + true @@ -39,9 +42,9 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll - True + + False + ..\..\lib\NuGet-Chocolatey\NuGet.Core.dll @@ -57,8 +60,11 @@ HashCodeCombiner.cs + + + @@ -70,6 +76,8 @@ + + @@ -111,5 +119,20 @@ - - \ No newline at end of file + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/NuGet.Server.Core/NuGet.Server.Core.nuspec b/src/NuGet.Server.Core/NuGet.Server.Core.nuspec index e1a8ca4..4e4bf92 100644 --- a/src/NuGet.Server.Core/NuGet.Server.Core.nuspec +++ b/src/NuGet.Server.Core/NuGet.Server.Core.nuspec @@ -4,11 +4,12 @@ $id$ $version$ $description$ - .NET Foundation - $author$ - https://raw.githubusercontent.com/NuGet/NuGet.Server/dev/LICENSE.txt + Microsoft + Microsoft + Apache-2.0 https://github.com/NuGet/NuGet.Server - $copyright$ + © Microsoft Corporation. All rights reserved. + diff --git a/src/NuGet.Server.Core/Strings.Designer.cs b/src/NuGet.Server.Core/Strings.Designer.cs index 687e05d..7aaf25b 100644 --- a/src/NuGet.Server.Core/Strings.Designer.cs +++ b/src/NuGet.Server.Core/Strings.Designer.cs @@ -60,6 +60,15 @@ internal Strings() { } } + /// + /// Looks up a localized string similar to Configured cache file name '{0}' is invalid. Keep it simple; No paths allowed.. + /// + internal static string Error_InvalidCacheFileName { + get { + return ResourceManager.GetString("Error_InvalidCacheFileName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Package {0} already exists. The server is configured to not allow overwriting packages that already exist.. /// diff --git a/src/NuGet.Server.Core/Strings.resx b/src/NuGet.Server.Core/Strings.resx index f3e2b0b..9b10159 100644 --- a/src/NuGet.Server.Core/Strings.resx +++ b/src/NuGet.Server.Core/Strings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Configured cache file name '{0}' is invalid. Keep it simple; No paths allowed. + Package {0} already exists. The server is configured to not allow overwriting packages that already exist. diff --git a/src/NuGet.Server.Core/packages.config b/src/NuGet.Server.Core/packages.config index 6397f52..ca36acf 100644 --- a/src/NuGet.Server.Core/packages.config +++ b/src/NuGet.Server.Core/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/NuGet.Server.V2/Controllers/NuGetODataController.cs b/src/NuGet.Server.V2/Controllers/NuGetODataController.cs index 250b581..77b63ad 100644 --- a/src/NuGet.Server.V2/Controllers/NuGetODataController.cs +++ b/src/NuGet.Server.V2/Controllers/NuGetODataController.cs @@ -14,6 +14,7 @@ using System.Web.Http; using System.Web.Http.OData; using System.Web.Http.OData.Query; +using NuGet.Server.Core; using NuGet.Server.Core.DataServices; using NuGet.Server.Core.Infrastructure; using NuGet.Server.V2.Model; @@ -40,20 +41,12 @@ protected NuGetODataController( IServerPackageRepository repository, IPackageAuthenticationService authenticationService = null) { - if (repository == null) - { - throw new ArgumentNullException(nameof(repository)); - } - - _serverRepository = repository; + _serverRepository = repository ?? throw new ArgumentNullException(nameof(repository)); _authenticationService = authenticationService; } - + // GET /Packages - // Never seen this invoked. NuGet.Exe and Visual Studio seems to use 'Search' for all package listing. - // Probably required to be OData compliant? [HttpGet] - [EnableQuery(PageSize = 100, HandleNullPropagation = HandleNullPropagationOption.False)] public virtual async Task Get( ODataQueryOptions options, [FromUri] string semVerLevel = "", @@ -136,8 +129,8 @@ public virtual IHttpActionResult GetPropertyFromPackages(string propertyName, st [HttpPost] public virtual async Task Search( ODataQueryOptions options, - [FromODataUri] string searchTerm = "", - [FromODataUri] string targetFramework = "", + [FromODataUri] string searchTerm = "", + [FromODataUri] string targetFramework = "", [FromODataUri] bool includePrerelease = false, [FromODataUri] bool includeDelisted = false, [FromUri] string semVerLevel = "", @@ -151,6 +144,7 @@ public virtual async Task Search( searchTerm, targetFrameworks, includePrerelease, + includeDelisted, clientCompatibility, token); @@ -210,8 +204,8 @@ public virtual async Task GetUpdates( var idValues = packageIds.Trim().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); var versionValues = versions.Trim().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - var targetFrameworkValues = String.IsNullOrEmpty(targetFrameworks) - ? null + var targetFrameworkValues = String.IsNullOrEmpty(targetFrameworks) + ? null : targetFrameworks.Split('|').Select(VersionUtility.ParseFrameworkName).ToList(); var versionConstraintValues = (String.IsNullOrEmpty(versionConstraints) ? new string[idValues.Length] @@ -226,8 +220,7 @@ public virtual async Task GetUpdates( var packagesToUpdate = new List(); for (var i = 0; i < idValues.Length; i++) { - SemanticVersion semVersion; - if(SemanticVersion.TryParse(versionValues[i],out semVersion)) + if (SemanticVersion.TryParse(versionValues[i], out var semVersion)) { packagesToUpdate.Add(new PackageBuilder { Id = idValues[i], Version = semVersion }); } @@ -373,7 +366,7 @@ public virtual async Task DeletePackage( } else { - return CreateStringResponse(HttpStatusCode.Forbidden, string.Format("Access denied for package '{0}', version '{1}'.", requestedPackage.Id,version)); + return CreateStringResponse(HttpStatusCode.Forbidden, string.Format("Access denied for package '{0}', version '{1}'.", requestedPackage.Id, version)); } } @@ -405,14 +398,20 @@ public virtual async Task UploadPackage(CancellationToken t } } - var package = new OptimizedZipPackage(temporaryFile); - + var package = PackageFactory.Open(temporaryFile); HttpResponseMessage retValue; if (_authenticationService.IsAuthenticated(User, apiKey, package.Id)) { - await _serverRepository.AddPackageAsync(package, token); - retValue = Request.CreateResponse(HttpStatusCode.Created); + try + { + await _serverRepository.AddPackageAsync(package, token); + retValue = Request.CreateResponse(HttpStatusCode.Created); + } + catch (DuplicatePackageException ex) + { + retValue = CreateStringResponse(HttpStatusCode.Conflict, ex.Message); + } } else { @@ -425,7 +424,7 @@ public virtual async Task UploadPackage(CancellationToken t File.Delete(temporaryFile); } catch (Exception) - { + { retValue = CreateStringResponse(HttpStatusCode.InternalServerError, "Could not remove temporary upload file."); } @@ -441,12 +440,11 @@ protected HttpResponseMessage CreateStringResponse(HttpStatusCode statusCode, st private string GetApiKeyFromHeader() { string apiKey = null; - IEnumerable values; - if (Request.Headers.TryGetValues(ApiKeyHeader, out values)) + if (Request.Headers.TryGetValues(ApiKeyHeader, out var values)) { apiKey = values.FirstOrDefault(); } - + return apiKey; } diff --git a/src/NuGet.Server.V2/NuGet.Server.V2.csproj b/src/NuGet.Server.V2/NuGet.Server.V2.csproj index 2d1f801..a051830 100644 --- a/src/NuGet.Server.V2/NuGet.Server.V2.csproj +++ b/src/NuGet.Server.V2/NuGet.Server.V2.csproj @@ -1,5 +1,6 @@ - + + Debug @@ -9,9 +10,11 @@ Properties NuGet.Server.V2 NuGet.Server.V2 - v4.6 + v4.8 512 + + true @@ -33,14 +36,14 @@ false - + False - ..\..\packages\Microsoft.Data.Edm.5.7.0\lib\net40\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll True - + False - ..\..\packages\Microsoft.Data.OData.5.7.0\lib\net40\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll True @@ -51,9 +54,9 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll - True + + False + ..\..\lib\NuGet-Chocolatey\NuGet.Core.dll @@ -64,9 +67,9 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True - + False - ..\..\packages\System.Spatial.5.7.0\lib\net40\System.Spatial.dll + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll True @@ -117,5 +120,20 @@ - - \ No newline at end of file + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/NuGet.Server.V2/NuGet.Server.V2.nuspec b/src/NuGet.Server.V2/NuGet.Server.V2.nuspec index 82a7e1d..decea47 100644 --- a/src/NuGet.Server.V2/NuGet.Server.V2.nuspec +++ b/src/NuGet.Server.V2/NuGet.Server.V2.nuspec @@ -4,11 +4,12 @@ $id$ $version$ $description$ - .NET Foundation - $author$ - https://raw.githubusercontent.com/NuGet/NuGet.Server/dev/LICENSE.txt + Microsoft + Microsoft + Apache-2.0 https://github.com/NuGet/NuGet.Server - $copyright$ + © Microsoft Corporation. All rights reserved. + @@ -22,9 +23,9 @@ - - - + + + diff --git a/src/NuGet.Server.V2/NuGetV2WebApiEnabler.cs b/src/NuGet.Server.V2/NuGetV2WebApiEnabler.cs index 98830c9..f24a5af 100644 --- a/src/NuGet.Server.V2/NuGetV2WebApiEnabler.cs +++ b/src/NuGet.Server.V2/NuGetV2WebApiEnabler.cs @@ -1,5 +1,6 @@ // 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.Net.Http; @@ -19,10 +20,46 @@ namespace NuGet.Server.V2 { public static class NuGetV2WebApiEnabler { + /// + /// Enables the NuGet V2 protocol routes on this . Note that this method does + /// not activate the legacy push URL, api/v2/package. To activate the legacy push route, use the + /// method overload. + /// + /// The HTTP configuration associated with your web app. + /// The route name prefix, to allow multiple feeds per web app. + /// The base URL for the routes, to allow multiple feeds per web app. + /// The name of the OData controller containing the actions. + /// The provided, for chaining purposes. public static HttpConfiguration UseNuGetV2WebApiFeed(this HttpConfiguration config, string routeName, - string routeUrlRoot, + string routeUrlRoot, string oDatacontrollerName) + { + return config.UseNuGetV2WebApiFeed( + routeName, + routeUrlRoot, + oDatacontrollerName, + enableLegacyPushRoute: false); + } + + /// + /// Enables the NuGet V2 protocol routes on this . + /// + /// The HTTP configuration associated with your web app. + /// The route name prefix, to allow multiple feeds per web app. + /// The base URL for the routes, to allow multiple feeds per web app. + /// The name of the OData controller containing the actions. + /// + /// Whether or not to enable the legacy push URL, api/v2/package. Note that this route does not + /// use the prefix or and therefore should only + /// be enabled once (i.e. on a single controller). + /// + /// The provided, for chaining purposes. + public static HttpConfiguration UseNuGetV2WebApiFeed(this HttpConfiguration config, + string routeName, + string routeUrlRoot, + string oDatacontrollerName, + bool enableLegacyPushRoute) { // Insert conventions to make NuGet-compatible OData feed possible var conventions = ODataRoutingConventions.CreateDefault(); @@ -53,6 +90,16 @@ public static HttpConfiguration UseNuGetV2WebApiFeed(this HttpConfiguration conf constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Delete) } ); + if (enableLegacyPushRoute) + { + config.Routes.MapHttpRoute( + name: "apiv2package_upload", + routeTemplate: "api/v2/package", + defaults: new { controller = oDatacontrollerName, action = "UploadPackage" }, + constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Put) } + ); + } + config.Routes.MapODataServiceRoute(routeName, routeUrlRoot, oDataModel, new CountODataPathHandler(), conventions); return config; } diff --git a/src/NuGet.Server.V2/app.config b/src/NuGet.Server.V2/app.config index 26bb328..42f1cdd 100644 --- a/src/NuGet.Server.V2/app.config +++ b/src/NuGet.Server.V2/app.config @@ -16,15 +16,15 @@ - + - + - + diff --git a/src/NuGet.Server.V2/packages.config b/src/NuGet.Server.V2/packages.config index 3b1ee39..30d4e7d 100644 --- a/src/NuGet.Server.V2/packages.config +++ b/src/NuGet.Server.V2/packages.config @@ -1,12 +1,13 @@  + - - + + - + \ No newline at end of file diff --git a/src/NuGet.Server/App_Start/NuGetODataConfig.cs b/src/NuGet.Server/App_Start/NuGetODataConfig.cs index 56bec91..2d1a056 100644 --- a/src/NuGet.Server/App_Start/NuGetODataConfig.cs +++ b/src/NuGet.Server/App_Start/NuGetODataConfig.cs @@ -3,8 +3,10 @@ using System.Net.Http; using System.Web.Http; +using System.Web.Http.ExceptionHandling; using System.Web.Http.Routing; using NuGet.Server.DataServices; +using NuGet.Server.Infrastructure; using NuGet.Server.V2; // The consuming project executes this logic with its own copy of this class. This is done with a .pp file that is @@ -26,7 +28,17 @@ public static void Start() public static void Initialize(HttpConfiguration config, string controllerName) { - NuGetV2WebApiEnabler.UseNuGetV2WebApiFeed(config, "NuGetDefault", "nuget", controllerName); + NuGetV2WebApiEnabler.UseNuGetV2WebApiFeed( + config, + "NuGetDefault", + "nuget", + controllerName, + enableLegacyPushRoute: true); + + config.Services.Replace(typeof(IExceptionLogger), new TraceExceptionLogger()); + + // Trace.Listeners.Add(new TextWriterTraceListener(HostingEnvironment.MapPath("~/NuGet.Server.log"))); + // Trace.AutoFlush = true; config.Routes.MapHttpRoute( name: "NuGetDefault_ClearCache", diff --git a/src/NuGet.Server/App_Start/NuGetODataConfig.cs.pp b/src/NuGet.Server/App_Start/NuGetODataConfig.cs.pp index bba5618..ac6dfec 100644 --- a/src/NuGet.Server/App_Start/NuGetODataConfig.cs.pp +++ b/src/NuGet.Server/App_Start/NuGetODataConfig.cs.pp @@ -1,7 +1,9 @@ using System.Net.Http; using System.Web.Http; +using System.Web.Http.ExceptionHandling; using System.Web.Http.Routing; using NuGet.Server; +using NuGet.Server.Infrastructure; using NuGet.Server.V2; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.NuGetODataConfig), "Start")] @@ -9,14 +11,24 @@ namespace $rootnamespace$.App_Start { public static class NuGetODataConfig - { + { public static void Start() - { + { ServiceResolver.SetServiceResolver(new DefaultServiceResolver()); var config = GlobalConfiguration.Configuration; - NuGetV2WebApiEnabler.UseNuGetV2WebApiFeed(config, "NuGetDefault", "nuget", "PackagesOData"); + NuGetV2WebApiEnabler.UseNuGetV2WebApiFeed( + config, + "NuGetDefault", + "nuget", + "PackagesOData", + enableLegacyPushRoute: true); + + config.Services.Replace(typeof(IExceptionLogger), new TraceExceptionLogger()); + + // Trace.Listeners.Add(new TextWriterTraceListener(HostingEnvironment.MapPath("~/NuGet.Server.log"))); + // Trace.AutoFlush = true; config.Routes.MapHttpRoute( name: "NuGetDefault_ClearCache", diff --git a/src/NuGet.Server/Controllers/PackagesODataController.cs b/src/NuGet.Server/Controllers/PackagesODataController.cs index f68cea5..0862d9f 100644 --- a/src/NuGet.Server/Controllers/PackagesODataController.cs +++ b/src/NuGet.Server/Controllers/PackagesODataController.cs @@ -29,7 +29,7 @@ protected PackagesODataController(IServiceResolver serviceResolver) // Exposed through ordinary Web API route. Bypasses OData pipeline. public async Task ClearCache(CancellationToken token) { - if (RequestContext.IsLocal) + if (RequestContext.IsLocal || ServiceResolver.Current.Resolve().GetBoolSetting("allowRemoteCacheManagement", false)) { await _serverRepository.ClearCacheAsync(token); return CreateStringResponse(HttpStatusCode.OK, "Server cache has been cleared."); diff --git a/src/NuGet.Server/Core/DefaultServiceResolver.cs b/src/NuGet.Server/Core/DefaultServiceResolver.cs index dae8fab..af627b8 100644 --- a/src/NuGet.Server/Core/DefaultServiceResolver.cs +++ b/src/NuGet.Server/Core/DefaultServiceResolver.cs @@ -13,6 +13,7 @@ namespace NuGet.Server public sealed class DefaultServiceResolver : IServiceResolver, IDisposable { + private readonly Core.Logging.ILogger _logger; private readonly CryptoHashProvider _hashProvider; private readonly ServerPackageRepository _packageRepository; private readonly PackageAuthenticationService _packageAuthenticationService; @@ -24,20 +25,33 @@ public DefaultServiceResolver() : this( { } - public DefaultServiceResolver(string packagePath, NameValueCollection settings) + public DefaultServiceResolver(string packagePath, NameValueCollection settings) : this( + packagePath, + settings, + new TraceLogger()) { + } + + public DefaultServiceResolver(string packagePath, NameValueCollection settings, Core.Logging.ILogger logger) + { + _logger = logger; + _hashProvider = new CryptoHashProvider(Core.Constants.HashAlgorithm); _settingsProvider = new WebConfigSettingsProvider(settings); - _packageRepository = new ServerPackageRepository(packagePath, _hashProvider, _settingsProvider, new TraceLogger()); + _packageRepository = new ServerPackageRepository(packagePath, _hashProvider, _settingsProvider, _logger); _packageAuthenticationService = new PackageAuthenticationService(settings); - } public object Resolve(Type type) { + if (type == typeof(Core.Logging.ILogger)) + { + return _logger; + } + if (type == typeof(IHashProvider)) { return _hashProvider; @@ -53,6 +67,11 @@ public object Resolve(Type type) return _packageAuthenticationService; } + if (type == typeof(ISettingsProvider)) + { + return _settingsProvider; + } + return null; } diff --git a/src/NuGet.Server/Core/Helpers.cs b/src/NuGet.Server/Core/Helpers.cs index e4bb6d9..424a872 100644 --- a/src/NuGet.Server/Core/Helpers.cs +++ b/src/NuGet.Server/Core/Helpers.cs @@ -8,12 +8,12 @@ public static class Helpers { public static string GetRepositoryUrl(Uri currentUrl, string applicationPath) { - return GetBaseUrl(currentUrl, applicationPath) + "nuget"; + return GetBaseUrl(currentUrl, applicationPath) + "chocolatey"; } public static string GetPushUrl(Uri currentUrl, string applicationPath) { - return GetBaseUrl(currentUrl, applicationPath) + "nuget"; + return GetBaseUrl(currentUrl, applicationPath) + "chocolatey"; } public static string GetBaseUrl(Uri currentUrl, string applicationPath) diff --git a/src/NuGet.Server/Default.aspx b/src/NuGet.Server/Default.aspx index ff32180..feeff08 100644 --- a/src/NuGet.Server/Default.aspx +++ b/src/NuGet.Server/Default.aspx @@ -34,7 +34,7 @@ <% } %> - <% if (Request.IsLocal) { %> + <% if (Request.IsLocal || ServiceResolver.Current.Resolve().GetBoolSetting("allowRemoteCacheManagement", false)) { %>
Adding packages diff --git a/src/NuGet.Server/Infrastructure/TraceExceptionLogger.cs b/src/NuGet.Server/Infrastructure/TraceExceptionLogger.cs new file mode 100644 index 0000000..899e8b4 --- /dev/null +++ b/src/NuGet.Server/Infrastructure/TraceExceptionLogger.cs @@ -0,0 +1,16 @@ +// 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.Diagnostics; +using System.Web.Http.ExceptionHandling; + +namespace NuGet.Server.Infrastructure +{ + public class TraceExceptionLogger : ExceptionLogger + { + public override void Log(ExceptionLoggerContext context) + { + Trace.TraceError(context.ExceptionContext.Exception.ToString()); + } + } +} \ No newline at end of file diff --git a/src/NuGet.Server/Infrastructure/WebConfigSettingsProvider.cs b/src/NuGet.Server/Infrastructure/WebConfigSettingsProvider.cs index 658cf08..4113948 100644 --- a/src/NuGet.Server/Infrastructure/WebConfigSettingsProvider.cs +++ b/src/NuGet.Server/Infrastructure/WebConfigSettingsProvider.cs @@ -28,5 +28,18 @@ public bool GetBoolSetting(string key, bool defaultValue) bool value; return !bool.TryParse(settings[key], out value) ? defaultValue : value; } + + public int GetIntSetting(string key, int defaultValue) + { + var settings = _getSettings(); + int value; + return !int.TryParse(settings[key], out value) ? defaultValue : value; + } + + public string GetStringSetting(string key, string defaultValue) + { + var settings = _getSettings(); + return settings[key] ?? defaultValue; + } } } \ No newline at end of file diff --git a/src/NuGet.Server/NuGet.Server.csproj b/src/NuGet.Server/NuGet.Server.csproj index bce6a5d..c59cf1c 100644 --- a/src/NuGet.Server/NuGet.Server.csproj +++ b/src/NuGet.Server/NuGet.Server.csproj @@ -1,5 +1,6 @@ - + + @@ -15,8 +16,9 @@ - v4.6 + v4.8 + {793B20A9-E263-4B54-BB31-305B602087CE} @@ -30,14 +32,14 @@ - + False - ..\..\packages\Microsoft.Data.Edm.5.7.0\lib\net40\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll True - + False - ..\..\packages\Microsoft.Data.OData.5.7.0\lib\net40\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll True @@ -54,9 +56,8 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll - True + + ..\..\lib\NuGet-Chocolatey\NuGet.Core.dll @@ -67,9 +68,9 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True - + False - ..\..\packages\System.Spatial.5.7.0\lib\net40\System.Spatial.dll + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll True @@ -109,6 +110,7 @@ + @@ -158,11 +160,18 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + + - @@ -191,4 +200,12 @@ REM Rename the Web.config file for packing. copy $(ProjectDir)Web.config $(TargetDir)Web.config.transform >NUL - \ No newline at end of file + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/NuGet.Server/NuGet.Server.nuspec b/src/NuGet.Server/NuGet.Server.nuspec index 8549cd9..0f29aed 100644 --- a/src/NuGet.Server/NuGet.Server.nuspec +++ b/src/NuGet.Server/NuGet.Server.nuspec @@ -4,11 +4,12 @@ NuGet.Server $version$ Web Application used to host a simple NuGet feed - .NET Foundation - .NET Foundation - https://raw.githubusercontent.com/NuGet/NuGet.Server/dev/LICENSE.txt + Microsoft + Microsoft + Apache-2.0 https://github.com/NuGet/NuGet.Server - © .NET Foundation. All rights reserved. + © Microsoft Corporation. All rights reserved. + diff --git a/src/NuGet.Server/Web.config b/src/NuGet.Server/Web.config index b084c2c..eda918f 100644 --- a/src/NuGet.Server/Web.config +++ b/src/NuGet.Server/Web.config @@ -22,6 +22,12 @@ --> + + + @@ -56,6 +62,7 @@ Uncomment the following configuration entry to enable NAT support. --> + + + + + + + + + + + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - \ No newline at end of file + diff --git a/test/NuGet.Server.Core.Tests/PackageExtensionsTest.cs b/test/NuGet.Server.Core.Tests/PackageExtensionsTest.cs index 0782ab7..d55f975 100644 --- a/test/NuGet.Server.Core.Tests/PackageExtensionsTest.cs +++ b/test/NuGet.Server.Core.Tests/PackageExtensionsTest.cs @@ -1,6 +1,7 @@ // 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 NuGet.Server.Core.DataServices; using NuGet.Server.Core.Infrastructure; @@ -10,6 +11,52 @@ namespace NuGet.Server.Core.Tests { public class PackageExtensionsTest { + [Fact] + public void AsODataPackage_Uses1900ForUnlistedPublished() + { + // Arrange + var package = new ServerPackage + { + Version = new SemanticVersion("0.1.0"), + Authors = Enumerable.Empty(), + Owners = Enumerable.Empty(), + + Listed = false, + Created = new DateTimeOffset(2017, 11, 29, 21, 21, 32, TimeSpan.FromHours(-8)), + }; + + // Act + var actual = package.AsODataPackage(ClientCompatibility.Max); + + // Assert + Assert.Equal( + new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc), + actual.Published); + } + + [Fact] + public void AsODataPackage_UsesCreatedForListedPublished() + { + // Arrange + var package = new ServerPackage + { + Version = new SemanticVersion("0.1.0"), + Authors = Enumerable.Empty(), + Owners = Enumerable.Empty(), + + Listed = true, + Created = new DateTimeOffset(2017, 11, 29, 21, 21, 32, TimeSpan.FromHours(-8)), + }; + + // Act + var actual = package.AsODataPackage(ClientCompatibility.Max); + + // Assert + Assert.Equal( + new DateTime(2017, 11, 30, 5, 21, 32, DateTimeKind.Utc), + actual.Published); + } + [Theory] [InlineData(true, true, false, false, 1, true, true)] [InlineData(false, false, true, true, 1, false, false)] @@ -38,7 +85,7 @@ public void AsODataPackage_PicksCorrectLatestProperties( SemVer2IsAbsoluteLatest = v2AbsLatest, SemVer2IsLatest = v2Latest, }; - var semVerLevel = new SemanticVersion(level, minor: 0, build: 0, specialVersion: null); + var semVerLevel = new SemanticVersion(level, minor: 0, build: 0, specialVersion: null, packageReleaseVersion:0); // Act var actual = package.AsODataPackage(new ClientCompatibility(semVerLevel)); diff --git a/test/NuGet.Server.Core.Tests/ServerPackageCacheTest.cs b/test/NuGet.Server.Core.Tests/ServerPackageCacheTest.cs index 21bf498..02b2a70 100644 --- a/test/NuGet.Server.Core.Tests/ServerPackageCacheTest.cs +++ b/test/NuGet.Server.Core.Tests/ServerPackageCacheTest.cs @@ -146,7 +146,7 @@ public void Persist_RetainsSemVer2Version() { Id = PackageId, Version = SemVer2Version - }); + }, enableDelisting: false); // Act actual.Persist(); @@ -232,7 +232,11 @@ public void Exists_IsCaseInsensitive() .Setup(x => x.FileExists(CacheFileName)) .Returns(false); var target = new ServerPackageCache(fileSystem.Object, CacheFileName); - target.Add(new ServerPackage { Id = "NuGet.Versioning", Version = new SemanticVersion("3.5.0-beta2") }); + target.Add(new ServerPackage + { + Id = "NuGet.Versioning", + Version = new SemanticVersion("3.5.0-beta2"), + }, enableDelisting: false); // Act var actual = target.Exists("nuget.versioning", new SemanticVersion("3.5.0-BETA2")); @@ -250,7 +254,11 @@ public void Exists_ReturnsFalseWhenPackageDoesNotExist() .Setup(x => x.FileExists(CacheFileName)) .Returns(false); var target = new ServerPackageCache(fileSystem.Object, CacheFileName); - target.Add(new ServerPackage { Id = "NuGet.Versioning", Version = new SemanticVersion("3.5.0-beta2") }); + target.Add(new ServerPackage + { + Id = "NuGet.Versioning", + Version = new SemanticVersion("3.5.0-beta2"), + }, enableDelisting: false); // Act var actual = target.Exists("NuGet.Frameworks", new SemanticVersion("3.5.0-beta2")); @@ -268,7 +276,11 @@ public void Exists_ReturnsTrueWhenPackageExists() .Setup(x => x.FileExists(CacheFileName)) .Returns(false); var target = new ServerPackageCache(fileSystem.Object, CacheFileName); - target.Add(new ServerPackage { Id = "NuGet.Versioning", Version = new SemanticVersion("3.5.0-beta2") }); + target.Add(new ServerPackage + { + Id = "NuGet.Versioning", + Version = new SemanticVersion("3.5.0-beta2"), + }, enableDelisting: false); // Act var actual = target.Exists("NuGet.Versioning", new SemanticVersion("3.5.0-beta2")); diff --git a/test/NuGet.Server.Core.Tests/ServerPackageRepositoryTest.cs b/test/NuGet.Server.Core.Tests/ServerPackageRepositoryTest.cs index ded407a..65e2fee 100644 --- a/test/NuGet.Server.Core.Tests/ServerPackageRepositoryTest.cs +++ b/test/NuGet.Server.Core.Tests/ServerPackageRepositoryTest.cs @@ -23,7 +23,7 @@ public class ServerPackageRepositoryTest public static async Task CreateServerPackageRepositoryAsync( string path, Action setupRepository = null, - Func getSetting = null) + Func getSetting = null) { var fileSystem = new PhysicalFileSystem(path); var expandedPackageRepository = new ExpandedPackageRepository(fileSystem); @@ -106,11 +106,11 @@ public async Task ServerPackageRepositoryAddsPackagesFromDropFolderOnStart(bool foreach (var packageToAddToDropFolder in packagesToAddToDropFolder) { var package = packages.FirstOrDefault( - p => p.Id == packageToAddToDropFolder.Value.Id + p => p.Id == packageToAddToDropFolder.Value.Id && p.Version == packageToAddToDropFolder.Value.Version); // check the package from drop folder has been added - Assert.NotNull(package); + Assert.NotNull(package); // check the package in the drop folder has been removed Assert.False(File.Exists(Path.Combine(temporaryDirectory.Path, packageToAddToDropFolder.Key))); @@ -203,7 +203,7 @@ public async Task ServerPackageRepository_DuplicateAddAfterClearObservesOverride } else { - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => await serverRepository.AddPackageAsync(CreatePackage("test", "1.2"), Token)); } } @@ -319,19 +319,10 @@ public async Task ServerPackageRepositorySearchUnlisted() using (var temporaryDirectory = new TemporaryDirectory()) { // Arrange - Func getSetting = (key, defaultValue) => - { - if (key == "enableDelisting") - { - return true; - } - return defaultValue; - }; - var serverRepository = await CreateServerPackageRepositoryAsync(temporaryDirectory.Path, repository => { repository.AddPackage(CreatePackage("test1", "1.0")); - }, getSetting); + }, EnableDelisting); // Assert base setup var packages = (await serverRepository.SearchAsync( @@ -365,6 +356,98 @@ public async Task ServerPackageRepositorySearchUnlisted() } } + [Fact] + public async Task ServerPackageRepositorySearchUnlistingDisabledAndExclude() + { + await ServerPackageRepositorySearchUnlistedWithOptions( + enableUnlisting: false, + allowUnlistedVersions: false, + searchable: false, + gettable: false); + } + + [Fact] + public async Task ServerPackageRepositorySearchUnlistingDisabledAndInclude() + { + await ServerPackageRepositorySearchUnlistedWithOptions( + enableUnlisting: false, + allowUnlistedVersions: true, + searchable: false, + gettable: false); + } + + [Fact] + public async Task ServerPackageRepositorySearchUnlistingEnabledAndExclude() + { + await ServerPackageRepositorySearchUnlistedWithOptions( + enableUnlisting: true, + allowUnlistedVersions: false, + searchable: false, + gettable: true); + } + + [Fact] + public async Task ServerPackageRepositorySearchUnlistingEnabledAndInclude() + { + await ServerPackageRepositorySearchUnlistedWithOptions( + enableUnlisting: true, + allowUnlistedVersions: true, + searchable: true, + gettable: true); + } + + private async Task ServerPackageRepositorySearchUnlistedWithOptions( + bool enableUnlisting, bool allowUnlistedVersions, bool searchable, bool gettable) + { + using (var temporaryDirectory = new TemporaryDirectory()) + { + // Arrange + var getSetting = enableUnlisting ? EnableDelisting : (Func)null; + var serverRepository = await CreateServerPackageRepositoryAsync(temporaryDirectory.Path, repository => + { + repository.AddPackage(CreatePackage("test1", "1.0")); + }, getSetting); + + // Remove the package + await serverRepository.RemovePackageAsync("test1", new SemanticVersion("1.0"), Token); + + // Verify that the package is not returned by search + var packages = (await serverRepository.SearchAsync( + "test1", + allowPrereleaseVersions: true, + allowUnlistedVersions: allowUnlistedVersions, + compatibility: ClientCompatibility.Max, + token: Token)).ToList(); + if (searchable) + { + Assert.Equal(1, packages.Count); + Assert.Equal("test1", packages[0].Id); + Assert.Equal("1.0", packages[0].Version.ToString()); + Assert.False(packages[0].Listed); + } + else + { + Assert.Equal(0, packages.Count); + } + + // Act: search with includeDelisted=true + packages = (await serverRepository.GetPackagesAsync(ClientCompatibility.Max, Token)).ToList(); + + // Assert + if (gettable) + { + Assert.Equal(1, packages.Count); + Assert.Equal("test1", packages[0].Id); + Assert.Equal("1.0", packages[0].Version.ToString()); + Assert.False(packages[0].Listed); + } + else + { + Assert.Equal(0, packages.Count); + } + } + } + [Fact] public async Task ServerPackageRepositoryFindPackageById() { @@ -472,7 +555,7 @@ public async Task ServerPackageRepositoryFindPackage() new SemanticVersion("1.0.0-alpha"), Token); var invalidPreRel = await serverRepository.FindPackageAsync( - "test3", + "test3", new SemanticVersion("1.0.0"), Token); var invalid = await serverRepository.FindPackageAsync("bad", new SemanticVersion("1.0"), Token); @@ -515,12 +598,15 @@ public async Task ServerPackageRepositoryMultipleIds() } } - [Fact] - public async Task ServerPackageRepositorySemVer1IsAbsoluteLatest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ServerPackageRepositorySemVer1IsAbsoluteLatest(bool enableDelisting) { using (var temporaryDirectory = new TemporaryDirectory()) { // Arrange + var getSetting = enableDelisting ? EnableDelisting : (Func)null; var serverRepository = await CreateServerPackageRepositoryAsync(temporaryDirectory.Path, repository => { repository.AddPackage(CreatePackage("test", "2.0-alpha")); @@ -528,8 +614,14 @@ public async Task ServerPackageRepositorySemVer1IsAbsoluteLatest() repository.AddPackage(CreatePackage("test", "2.2-beta")); repository.AddPackage(CreatePackage("test", "2.3")); repository.AddPackage(CreatePackage("test", "2.4.0-prerel")); + repository.AddPackage(CreatePackage("test", "2.5.0-prerel")); repository.AddPackage(CreatePackage("test", "3.2.0+taggedOnly")); - }); + }, getSetting); + + await serverRepository.RemovePackageAsync( + "test", + new SemanticVersion("2.5.0-prerel"), + CancellationToken.None); // Act var packages = await serverRepository.GetPackagesAsync(ClientCompatibility.Default, Token); @@ -540,12 +632,15 @@ public async Task ServerPackageRepositorySemVer1IsAbsoluteLatest() } } - [Fact] - public async Task ServerPackageRepositorySemVer2IsAbsoluteLatest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ServerPackageRepositorySemVer2IsAbsoluteLatest(bool enableDelisting) { using (var temporaryDirectory = new TemporaryDirectory()) { // Arrange + var getSetting = enableDelisting ? EnableDelisting : (Func)null; var serverRepository = await CreateServerPackageRepositoryAsync(temporaryDirectory.Path, repository => { repository.AddPackage(CreatePackage("test", "2.0-alpha")); @@ -554,7 +649,13 @@ public async Task ServerPackageRepositorySemVer2IsAbsoluteLatest() repository.AddPackage(CreatePackage("test", "2.3")); repository.AddPackage(CreatePackage("test", "2.4.0-prerel")); repository.AddPackage(CreatePackage("test", "3.2.0+taggedOnly")); - }); + repository.AddPackage(CreatePackage("test", "3.3.0+unlisted")); + }, getSetting); + + await serverRepository.RemovePackageAsync( + "test", + new SemanticVersion("3.3.0+unlisted"), + CancellationToken.None); // Act var packages = await serverRepository.GetPackagesAsync(ClientCompatibility.Max, Token); @@ -577,7 +678,7 @@ public async Task ServerPackageRepositoryIsLatestOnlyPreRel() repository.AddPackage(CreatePackage("test", "2.1-alpha")); repository.AddPackage(CreatePackage("test", "2.2-beta+tagged")); }); - + // Act var packages = await serverRepository.GetPackagesAsync(ClientCompatibility.Max, Token); @@ -586,18 +687,27 @@ public async Task ServerPackageRepositoryIsLatestOnlyPreRel() } } - [Fact] - public async Task ServerPackageRepositorySemVer1IsLatest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ServerPackageRepositorySemVer1IsLatest(bool enableDelisting) { using (var temporaryDirectory = new TemporaryDirectory()) { // Arrange + var getSetting = enableDelisting ? EnableDelisting : (Func)null; var serverRepository = await CreateServerPackageRepositoryAsync(temporaryDirectory.Path, repository => { repository.AddPackage(CreatePackage("test1", "1.0.0")); + repository.AddPackage(CreatePackage("test1", "1.1.0")); repository.AddPackage(CreatePackage("test1", "1.2.0+taggedOnly")); repository.AddPackage(CreatePackage("test1", "2.0.0-alpha")); - }); + }, getSetting); + + await serverRepository.RemovePackageAsync( + "test1", + new SemanticVersion("1.1.0"), + CancellationToken.None); // Act var packages = await serverRepository.GetPackagesAsync(ClientCompatibility.Default, Token); @@ -608,21 +718,30 @@ public async Task ServerPackageRepositorySemVer1IsLatest() } } - [Fact] - public async Task ServerPackageRepositorySemVer2IsLatest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ServerPackageRepositorySemVer2IsLatest(bool enableDelisting) { using (var temporaryDirectory = new TemporaryDirectory()) { // Arrange + var getSetting = enableDelisting ? EnableDelisting : (Func)null; var serverRepository = await CreateServerPackageRepositoryAsync(temporaryDirectory.Path, repository => { repository.AddPackage(CreatePackage("test", "1.11")); + repository.AddPackage(CreatePackage("test", "2.0")); repository.AddPackage(CreatePackage("test", "1.9")); repository.AddPackage(CreatePackage("test", "2.0-alpha")); repository.AddPackage(CreatePackage("test1", "1.0.0")); repository.AddPackage(CreatePackage("test1", "1.2.0+taggedOnly")); repository.AddPackage(CreatePackage("test1", "2.0.0-alpha")); - }); + }, getSetting); + + await serverRepository.RemovePackageAsync( + "test", + new SemanticVersion("2.0"), + CancellationToken.None); // Act var packages = await serverRepository.GetPackagesAsync(ClientCompatibility.Max, Token); @@ -776,7 +895,7 @@ public async Task ServerPackageRepositoryAddPackageRejectsDuplicatesWithSemVer2( await serverRepository.AddPackageAsync(CreatePackage("Foo", "1.0.0-beta.1+foo"), Token); // Act & Assert - var actual = await Assert.ThrowsAsync(async () => + var actual = await Assert.ThrowsAsync(async () => await serverRepository.AddPackageAsync(CreatePackage("Foo", "1.0.0-beta.1+bar"), Token)); Assert.Equal( "Package Foo 1.0.0-beta.1 already exists. The server is configured to not allow overwriting packages that already exist.", @@ -784,6 +903,83 @@ public async Task ServerPackageRepositoryAddPackageRejectsDuplicatesWithSemVer2( } } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task ServerPackageRepository_CustomCacheFileNameNotConfigured_UseMachineNameAsFileName(string fileNameFromConfig) + { + using (var temporaryDirectory = new TemporaryDirectory()) + { + ServerPackageRepository serverRepository = await CreateServerPackageRepositoryAsync( + temporaryDirectory.Path, + getSetting: (key, defaultValue) => key == "cacheFileName" ? fileNameFromConfig : defaultValue); + + string expectedCacheFileName = Path.Combine(serverRepository.Source, Environment.MachineName.ToLowerInvariant() + ".cache.bin"); + + Assert.True(File.Exists(expectedCacheFileName)); + } + } + + [Fact] + public async Task ServerPackageRepository_CustomCacheFileNameIsConfigured_CustomCacheFileIsCreated() + { + using (var temporaryDirectory = new TemporaryDirectory()) + { + ServerPackageRepository serverRepository = await CreateServerPackageRepositoryAsync( + temporaryDirectory.Path, + getSetting: (key, defaultValue) => key == "cacheFileName" ? "CustomFileName.cache.bin" : defaultValue); + + string expectedCacheFileName = Path.Combine(serverRepository.Source, "CustomFileName.cache.bin"); + + Assert.True(File.Exists(expectedCacheFileName)); + } + } + + [Fact] + public async Task ServerPackageRepository_CustomCacheFileNameWithoutExtensionIsConfigured_CustomCacheFileWithExtensionIsCreated() + { + using (var temporaryDirectory = new TemporaryDirectory()) + { + ServerPackageRepository serverRepository = await CreateServerPackageRepositoryAsync( + temporaryDirectory.Path, + getSetting: (key, defaultValue) => key == "cacheFileName" ? "CustomFileName" : defaultValue); + + string expectedCacheFileName = Path.Combine(serverRepository.Source, "CustomFileName.cache.bin"); + + Assert.True(File.Exists(expectedCacheFileName)); + } + } + + [Theory] + [InlineData("c:\\file\\is\\a\\path\\to\\Awesome.cache.bin")] + [InlineData("random:invalidFileName.cache.bin")] + public async Task ServerPackageRepository_CustomCacheFileNameIsInvalid_ThrowUp(string invlaidCacheFileName) + { + using (var temporaryDirectory = new TemporaryDirectory()) + { + Task Code() => CreateServerPackageRepositoryAsync( + temporaryDirectory.Path, + getSetting: (key, defaultValue) => key == "cacheFileName" ? invlaidCacheFileName : defaultValue); + + await Assert.ThrowsAsync(Code); + } + } + + [Fact] + public async Task ServerPackageRepository_CustomCacheFileNameIsInvalid_ThrowUpWithCorrectErrorMessage() + { + using (var temporaryDirectory = new TemporaryDirectory()) + { + Task Code() => CreateServerPackageRepositoryAsync( + temporaryDirectory.Path, + getSetting: (key, defaultValue) => key == "cacheFileName" ? "foo:bar/baz" : defaultValue); + + var expectedMessage = "Configured cache file name 'foo:bar/baz' is invalid. Keep it simple; No paths allowed."; + Assert.Equal(expectedMessage, (await Assert.ThrowsAsync(Code)).Message); + } + } + private static IPackage CreateMockPackage(string id, string version) { var package = new Mock(); @@ -795,7 +991,10 @@ private static IPackage CreateMockPackage(string id, string version) return package.Object; } - private IPackage CreatePackage(string id, string version, PackageDependency packageDependency = null) + private IPackage CreatePackage( + string id, + string version, + PackageDependency packageDependency = null) { var parsedVersion = new SemanticVersion(version); var packageBuilder = new PackageBuilder @@ -848,5 +1047,15 @@ private IPackage CreatePackage(string id, string version, PackageDependency pack return outputPackage; } + + private static object EnableDelisting(string key, object defaultValue) + { + if (key == "enableDelisting") + { + return true; + } + + return defaultValue; + } } } diff --git a/test/NuGet.Server.Core.Tests/TestData.cs b/test/NuGet.Server.Core.Tests/TestData.cs index ee20532..0b4030e 100644 --- a/test/NuGet.Server.Core.Tests/TestData.cs +++ b/test/NuGet.Server.Core.Tests/TestData.cs @@ -1,8 +1,10 @@ // 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.Reflection; +using Moq; namespace NuGet.Server.Core.Tests { @@ -36,5 +38,25 @@ public static void CopyResourceToPath(string name, string path) resourceStream.CopyTo(outputStream); } } + + public static Stream GenerateSimplePackage(string id, SemanticVersion version) + { + var simpleFile = new Mock(); + simpleFile.Setup(x => x.Path).Returns("file.txt"); + simpleFile.Setup(x => x.GetStream()).Returns(() => new MemoryStream(new byte[0])); + + var packageBuilder = new PackageBuilder(); + packageBuilder.Id = id; + packageBuilder.Version = version; + packageBuilder.Authors.Add("Integration test"); + packageBuilder.Description = "Simple test package."; + packageBuilder.Files.Add(simpleFile.Object); + + var memoryStream = new MemoryStream(); + packageBuilder.Save(memoryStream); + memoryStream.Position = 0; + + return memoryStream; + } } } diff --git a/test/NuGet.Server.Core.Tests/TestOutputLogger.cs b/test/NuGet.Server.Core.Tests/TestOutputLogger.cs new file mode 100644 index 0000000..aad6273 --- /dev/null +++ b/test/NuGet.Server.Core.Tests/TestOutputLogger.cs @@ -0,0 +1,36 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; +using NuGet.Server.Core.Logging; +using Xunit.Abstractions; + +namespace NuGet.Server.Core.Tests +{ + public class TestOutputLogger : Logging.ILogger + { + private readonly ITestOutputHelper _output; + private ConcurrentQueue _messages; + + public TestOutputLogger(ITestOutputHelper output) + { + _output = output; + _messages = new ConcurrentQueue(); + } + + public IEnumerable Messages => _messages; + + public void Clear() + { + _messages = new ConcurrentQueue(); + } + + public void Log(LogLevel level, string message, params object[] args) + { + var formattedMessage = $"[{level.ToString().Substring(0, 4).ToUpperInvariant()}] {string.Format(message, args)}"; + _messages.Enqueue(formattedMessage); + _output.WriteLine(formattedMessage); + } + } +} diff --git a/test/NuGet.Server.Tests/IntegrationTests.cs b/test/NuGet.Server.Tests/IntegrationTests.cs index 477ce90..c86b73e 100644 --- a/test/NuGet.Server.Tests/IntegrationTests.cs +++ b/test/NuGet.Server.Tests/IntegrationTests.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; @@ -6,16 +6,22 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Dependencies; using NuGet.Server.App_Start; +using NuGet.Server.Core.Infrastructure; +using NuGet.Server.Core.Logging; using NuGet.Server.Core.Tests; using NuGet.Server.Core.Tests.Infrastructure; +using NuGet.Server.V2; using Xunit; +using Xunit.Abstractions; using ISystemDependencyResolver = System.Web.Http.Dependencies.IDependencyResolver; using SystemHttpClient = System.Net.Http.HttpClient; @@ -23,11 +29,18 @@ namespace NuGet.Server.Tests { public class IntegrationTests { + private readonly ITestOutputHelper _output; + + public IntegrationTests(ITestOutputHelper output) + { + _output = output; + } + [Fact] public async Task DropPackageThenReadPackages() { // Arrange - using (var tc = new TestContext()) + using (var tc = new TestContext(_output)) { // Act & Assert // 1. Get the initial list of packages. This should be empty. @@ -56,11 +69,98 @@ public async Task DropPackageThenReadPackages() } } + [Fact] + public async Task DownloadPackage() + { + // Arrange + using (var tc = new TestContext(_output)) + { + // Act & Assert + // 1. Write a package to the drop folder. + var packagePath = Path.Combine(tc.PackagesDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, packagePath); + var expectedBytes = File.ReadAllBytes(packagePath); + + // 2. Download the package. + using (var request = new HttpRequestMessage( + HttpMethod.Get, + $"/nuget/Packages(Id='{TestData.PackageId}',Version='{TestData.PackageVersionString}')/Download")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var actualBytes = await response.Content.ReadAsByteArrayAsync(); + + Assert.Equal("binary/octet-stream", response.Content.Headers.ContentType.ToString()); + Assert.Equal(expectedBytes, actualBytes); + } + } + } + + [Fact] + public async Task FilterOnFramework() + { + // Arrange + using (var tc = new TestContext(_output)) + { + tc.Settings["enableFrameworkFiltering"] = "true"; + + // Act & Assert + // 1. Write a package to the drop folder. + var packagePath = Path.Combine(tc.PackagesDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, packagePath); + + // 2. Search for all packages supporting .NET Framework 4.6 (this should match the test package) + using (var request = new HttpRequestMessage( + HttpMethod.Get, + $"/nuget/Search?targetFramework='net46'")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.Contains(TestData.PackageId, content); + } + + // 3. Search for all packages supporting .NET Framework 2.0 (this should match nothing) + using (var request = new HttpRequestMessage( + HttpMethod.Get, + $"/nuget/Search?targetFramework='net20'")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.DoesNotContain(TestData.PackageId, content); + } + } + } + + [Fact] + public async Task PushDuplicatePackage() + { + // Arrange + using (var tc = new TestContext(_output)) + { + string apiKey = "foobar"; + tc.SetApiKey(apiKey); + + var packagePath = Path.Combine(tc.TemporaryDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, packagePath); + + // Act & Assert + // 1. Push the package. + await tc.PushPackageAsync(apiKey, packagePath); + + // 2. Push the package again expecting a 409 as the Package already exists. + await tc.PushPackageAsync(apiKey, packagePath, excepectedStatusCode: HttpStatusCode.Conflict); + } + } + [Fact] public async Task PushPackageThenReadPackages() { // Arrange - using (var tc = new TestContext()) + using (var tc = new TestContext(_output)) { string apiKey = "foobar"; tc.SetApiKey(apiKey); @@ -70,13 +170,217 @@ public async Task PushPackageThenReadPackages() // Act & Assert // 1. Push the package. + await tc.PushPackageAsync(apiKey, packagePath); + + // 2. Get the list of packages. This should mention the package. + using (var request = new HttpRequestMessage(HttpMethod.Get, "/nuget/Packages()")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.Contains(TestData.PackageId, content); + } + } + } + + [Fact] + public async Task CanSupportMultipleSetsOfRoutes() + { + // Arrange + using (var tc = new TestContext(_output)) + { + // Enable another set of routes. + NuGetV2WebApiEnabler.UseNuGetV2WebApiFeed( + tc.Config, + "NuGetDefault2", + "nuget2", + TestablePackagesODataController.Name); + + string apiKey = "foobar"; + tc.SetApiKey(apiKey); + + var packagePath = Path.Combine(tc.TemporaryDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, packagePath); + + // Act & Assert + // 1. Push to the legacy route. + await tc.PushPackageAsync(apiKey, packagePath, "/api/v2/package"); + + // 2. Make a request to the first set of routes. + using (var request = new HttpRequestMessage(HttpMethod.Get, "/nuget/Packages()")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.Contains(TestData.PackageId, content); + } + + // 3. Make a request to the second set of routes. + using (var request = new HttpRequestMessage(HttpMethod.Get, "/nuget2/Packages()")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.Contains(TestData.PackageId, content); + } + } + } + + /// + /// Added due to https://github.com/NuGet/NuGetGallery/issues/6960. There was a concurrency issue when pushing + /// packages that could lead to unnecessary cache rebuilds. + /// + [Fact] + public async Task DoesNotRebuildTheCacheWhenPackagesArePushed() + { + // Arrange + using (var tc = new TestContext(_output)) + { + // Essentially disable the automatic cache rebuild by setting it really far in the future. + const int initialCacheRebuildAfterSeconds = 60 * 60 * 24; + tc.Settings["initialCacheRebuildAfterSeconds"] = initialCacheRebuildAfterSeconds.ToString(); + + const int workerCount = 8; + const int totalPackages = 320; + + var packagePaths = new ConcurrentBag(); + for (var i = 0; i < totalPackages; i++) + { + var packageId = Guid.NewGuid().ToString(); + var packagePath = Path.Combine(tc.TemporaryDirectory, $"{packageId}.1.0.0.nupkg"); + using (var package = TestData.GenerateSimplePackage(packageId, SemanticVersion.Parse("1.0.0"))) + using (var fileStream = File.OpenWrite(packagePath)) + { + await package.CopyToAsync(fileStream); + } + + packagePaths.Add(packagePath); + } + + string apiKey = "foobar"; + tc.SetApiKey(apiKey); + + // Act & Assert + // 1. Push a single package to build the cache for the first time. + packagePaths.TryTake(out var firstPackagePath); + await tc.PushPackageAsync(apiKey, firstPackagePath); + + Assert.Single(tc.Logger.Messages, "[INFO] Start rebuilding package store..."); + tc.Logger.Clear(); + tc.TestOutputHelper.WriteLine("The first package has been pushed."); + + // 2. Execute a query to register the file system watcher. + using (var request = new HttpRequestMessage(HttpMethod.Get, "/nuget/Packages/$count")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.Equal(1, int.Parse(content)); + } + + Assert.DoesNotContain("[INFO] Start rebuilding package store...", tc.Logger.Messages); + tc.TestOutputHelper.WriteLine("The first count query has completed."); + + // 3. Push the rest of the packages. + var workerTasks = Enumerable + .Range(0, workerCount) + .Select(async i => + { + while (packagePaths.TryTake(out var packagePath)) + { + await tc.PushPackageAsync(apiKey, packagePath); + } + }) + .ToList(); + await Task.WhenAll(workerTasks); + + tc.TestOutputHelper.WriteLine("The rest of the packages have been pushed."); + + // 4. Get the total count of packages. This should match the number of packages pushed. + using (var request = new HttpRequestMessage(HttpMethod.Get, "/nuget/Packages/$count")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + Assert.Equal(totalPackages, int.Parse(content)); + } + + Assert.DoesNotContain("[INFO] Start rebuilding package store...", tc.Logger.Messages); + tc.TestOutputHelper.WriteLine("The second count query has completed."); + } + } + + [Theory] + [MemberData(nameof(EndpointsSupportingProjection))] + public async Task CanQueryUsingProjection(string endpoint) + { + // Arrange + using (var tc = new TestContext(_output)) + { + var packagePath = Path.Combine(tc.PackagesDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, packagePath); + + // Act + using (var request = new HttpRequestMessage(HttpMethod.Get, $"/nuget/{endpoint}$select=Id,Version")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Contains(TestData.PackageId, content); + Assert.Contains(TestData.PackageVersionString, content); + } + } + } + + [Fact] + public async Task DoesNotWriteToNuGetScratch() + { + // Arrange + OptimizedZipPackage.PurgeCache(); + var expectedTempEntries = Directory + .GetFileSystemEntries(Path.Combine(Path.GetTempPath(), "NuGetScratch")) + .OrderBy(x => x) + .ToList(); + + using (var tc = new TestContext(_output)) + { + tc.Settings["enableFrameworkFiltering"] = "true"; + tc.Settings["allowOverrideExistingPackageOnPush"] = "true"; + + string apiKey = "foobar"; + tc.SetApiKey(apiKey); + + // Act & Assert + // 1. Write a package to the drop folder. + var packagePath = Path.Combine(tc.PackagesDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, packagePath); + + // 2. Search for packages. + using (var request = new HttpRequestMessage( + HttpMethod.Get, + $"/nuget/Search?targetFramework='net46'")) + using (var response = await tc.Client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + // 3. Push the package. + var pushPath = Path.Combine(tc.TemporaryDirectory, "package.nupkg"); + TestData.CopyResourceToPath(TestData.PackageResource, pushPath); using (var request = new HttpRequestMessage(HttpMethod.Put, "/nuget") { Headers = { { "X-NUGET-APIKEY", apiKey } }, - Content = tc.GetFileUploadContent(packagePath) + Content = tc.GetFileUploadContent(pushPath), }) { using (request) @@ -86,26 +390,42 @@ public async Task PushPackageThenReadPackages() } } - // 2. Get the list of packages. This should mention the package. - using (var request = new HttpRequestMessage(HttpMethod.Get, "/nuget/Packages()")) + // 4. Search for packages again. + using (var request = new HttpRequestMessage( + HttpMethod.Get, + $"/nuget/Search?targetFramework='net46'")) using (var response = await tc.Client.SendAsync(request)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var content = await response.Content.ReadAsStringAsync(); - - Assert.Contains(TestData.PackageId, content); } + + // 6. Make sure we have not added more temp files. + var actualTempEntries = Directory + .GetFileSystemEntries(Path.Combine(Path.GetTempPath(), "NuGetScratch")) + .OrderBy(x => x) + .ToList(); + Assert.Equal(expectedTempEntries, actualTempEntries); + } + } + + public static IEnumerable EndpointsSupportingProjection + { + get + { + yield return new object[] { "Packages()?" }; + yield return new object[] { "Search()?searchTerm=''&targetFramework=''&includePrerelease=true&includeDelisted=true&" }; + yield return new object[] { $"FindPackagesById()?id='{TestData.PackageId}'&" }; } } private sealed class TestContext : IDisposable { private readonly HttpServer _server; - private readonly DefaultServiceResolver _serviceResolver; - private readonly HttpConfiguration _config; - public TestContext() + public TestContext(ITestOutputHelper output) { + TestOutputHelper = output; + Logger = new TestOutputLogger(output); TemporaryDirectory = new TemporaryDirectory(); PackagesDirectory = new TemporaryDirectory(); @@ -115,22 +435,26 @@ public TestContext() { "apiKey", string.Empty } }; - _serviceResolver = new DefaultServiceResolver(PackagesDirectory, Settings); + ServiceResolver = new DefaultServiceResolver(PackagesDirectory, Settings, Logger); - _config = new HttpConfiguration(); - _config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; - _config.DependencyResolver = new DependencyResolverAdapter(_serviceResolver); + Config = new HttpConfiguration(); + Config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; + Config.DependencyResolver = new DependencyResolverAdapter(ServiceResolver); - NuGetODataConfig.Initialize(_config, "TestablePackagesOData"); + NuGetODataConfig.Initialize(Config, TestablePackagesODataController.Name); - _server = new HttpServer(_config); + _server = new HttpServer(Config); Client = new SystemHttpClient(_server); Client.BaseAddress = new Uri("http://localhost/"); } + public ITestOutputHelper TestOutputHelper { get; } + public TestOutputLogger Logger { get; } + public DefaultServiceResolver ServiceResolver { get; } public TemporaryDirectory TemporaryDirectory { get; } public TemporaryDirectory PackagesDirectory { get; } public NameValueCollection Settings { get; } + public HttpConfiguration Config { get; } public SystemHttpClient Client { get; } public void SetApiKey(string apiKey) @@ -159,12 +483,28 @@ public MultipartContent GetFileUploadContent(params string[] paths) return content; } + public async Task PushPackageAsync(string apiKey, string packagePath, string pushUrl = "/nuget", HttpStatusCode excepectedStatusCode = HttpStatusCode.Created) + { + using (var request = new HttpRequestMessage(HttpMethod.Put, pushUrl) + { + Headers = + { + { "X-NUGET-APIKEY", apiKey } + }, + Content = GetFileUploadContent(packagePath) + }) + using (var response = await Client.SendAsync(request)) + { + Assert.Equal(excepectedStatusCode, response.StatusCode); + } + } + public void Dispose() { Client.Dispose(); _server.Dispose(); - _config.Dispose(); - _serviceResolver.Dispose(); + Config.Dispose(); + ServiceResolver.Dispose(); PackagesDirectory.Dispose(); TemporaryDirectory.Dispose(); } diff --git a/test/NuGet.Server.Tests/NuGet.Server.Tests.csproj b/test/NuGet.Server.Tests/NuGet.Server.Tests.csproj index 3537f51..6bdd537 100644 --- a/test/NuGet.Server.Tests/NuGet.Server.Tests.csproj +++ b/test/NuGet.Server.Tests/NuGet.Server.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -9,7 +9,7 @@ NuGet.Server.Tests NuGet.Server.Tests {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - v4.6 + v4.8 @@ -27,12 +27,12 @@ ..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll True - - ..\..\packages\Microsoft.Data.Edm.5.7.0\lib\net40\Microsoft.Data.Edm.dll + + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll True - - ..\..\packages\Microsoft.Data.OData.5.7.0\lib\net40\Microsoft.Data.OData.dll + + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll True @@ -48,9 +48,9 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll - True + + False + ..\..\lib\NuGet-Chocolatey\NuGet.Core.dll @@ -64,8 +64,8 @@ - - ..\..\packages\System.Spatial.5.7.0\lib\net40\System.Spatial.dll + + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll True @@ -139,11 +139,21 @@ - + + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - \ No newline at end of file + diff --git a/test/NuGet.Server.Tests/TestablePackagesODataController.cs b/test/NuGet.Server.Tests/TestablePackagesODataController.cs index a4b6a10..c2bd8b3 100644 --- a/test/NuGet.Server.Tests/TestablePackagesODataController.cs +++ b/test/NuGet.Server.Tests/TestablePackagesODataController.cs @@ -7,6 +7,9 @@ namespace NuGet.Server.Tests { public class TestablePackagesODataController : PackagesODataController { + public static readonly string Name = nameof(TestablePackagesODataController) + .Substring(0, nameof(TestablePackagesODataController).Length - "Controller".Length); + public TestablePackagesODataController(IServiceResolver serviceResolver) : base(serviceResolver) { diff --git a/test/NuGet.Server.Tests/app.config b/test/NuGet.Server.Tests/app.config index 26bb328..42f1cdd 100644 --- a/test/NuGet.Server.Tests/app.config +++ b/test/NuGet.Server.Tests/app.config @@ -16,15 +16,15 @@ - + - + - + diff --git a/test/NuGet.Server.Tests/packages.config b/test/NuGet.Server.Tests/packages.config index d533011..1c943e2 100644 --- a/test/NuGet.Server.Tests/packages.config +++ b/test/NuGet.Server.Tests/packages.config @@ -6,13 +6,13 @@ - - + + - + diff --git a/test/NuGet.Server.V2.Tests/NuGet.Server.V2.Tests.csproj b/test/NuGet.Server.V2.Tests/NuGet.Server.V2.Tests.csproj index dd2f1a3..2c91d89 100644 --- a/test/NuGet.Server.V2.Tests/NuGet.Server.V2.Tests.csproj +++ b/test/NuGet.Server.V2.Tests/NuGet.Server.V2.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -10,7 +10,7 @@ Properties NuGet.Server.V2.Tests NuGet.Server.V2.Tests - v4.6 + v4.8 512 @@ -38,14 +38,14 @@ ..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll True - + False - ..\..\packages\Microsoft.Data.Edm.5.7.0\lib\net40\Microsoft.Data.Edm.dll + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll True - + False - ..\..\packages\Microsoft.Data.OData.5.7.0\lib\net40\Microsoft.Data.OData.dll + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll True @@ -60,9 +60,9 @@ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True - - ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll - True + + False + ..\..\lib\NuGet-Chocolatey\NuGet.Core.dll @@ -71,9 +71,9 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True - + False - ..\..\packages\System.Spatial.5.7.0\lib\net40\System.Spatial.dll + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll True @@ -137,11 +137,21 @@ - + + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + none + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - \ No newline at end of file + diff --git a/test/NuGet.Server.V2.Tests/app.config b/test/NuGet.Server.V2.Tests/app.config index 8496c6a..10d3a53 100644 --- a/test/NuGet.Server.V2.Tests/app.config +++ b/test/NuGet.Server.V2.Tests/app.config @@ -13,15 +13,15 @@ - + - + - + diff --git a/test/NuGet.Server.V2.Tests/packages.config b/test/NuGet.Server.V2.Tests/packages.config index d657975..70ae5a2 100644 --- a/test/NuGet.Server.V2.Tests/packages.config +++ b/test/NuGet.Server.V2.Tests/packages.config @@ -4,13 +4,13 @@ - - + + - +