diff --git a/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs b/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs index ea99fce84d..645edbe64e 100644 --- a/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs +++ b/src/NuGetGallery.Core/Frameworks/FrameworkCompatibilityService.cs @@ -7,13 +7,13 @@ namespace NuGetGallery.Frameworks { - public class FrameworkCompatibilityService : IFrameworkCompatibilityService + public static class FrameworkCompatibilityService { private static readonly IFrameworkCompatibilityProvider CompatibilityProvider = DefaultCompatibilityProvider.Instance; private static readonly IReadOnlyList AllSupportedFrameworks = SupportedFrameworks.AllSupportedNuGetFrameworks; private static readonly IReadOnlyDictionary> CompatibilityMatrix = GetCompatibilityMatrix(); - public ISet GetCompatibleFrameworks(IEnumerable packageFrameworks) + public static ISet GetCompatibleFrameworks(IEnumerable packageFrameworks) { if (packageFrameworks == null) { diff --git a/src/NuGetGallery.Core/Frameworks/IFrameworkCompatibilityService.cs b/src/NuGetGallery.Core/Frameworks/IFrameworkCompatibilityService.cs deleted file mode 100644 index e3a61409ba..0000000000 --- a/src/NuGetGallery.Core/Frameworks/IFrameworkCompatibilityService.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using NuGet.Frameworks; - -namespace NuGetGallery.Frameworks -{ - public interface IFrameworkCompatibilityService - { - /// - /// Computes a set of compatible target frameworks from a list of target frameworks. - /// - /// List of frameworks. - /// A set of computed compatible target frameworks. - /// - /// Every element on the returned set is compatible with at least one of the target frameworks from the input. - /// - ISet GetCompatibleFrameworks(IEnumerable frameworks); - } -} \ No newline at end of file diff --git a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs index 12a90a32ae..7eaae5a658 100644 --- a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs +++ b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityFactory.cs @@ -22,13 +22,6 @@ public class PackageFrameworkCompatibilityFactory : IPackageFrameworkCompatibili private readonly NuGetFrameworkSorter Sorter = new NuGetFrameworkSorter(); private readonly int NetStartingMajorVersion = 5; - private readonly IFrameworkCompatibilityService _compatibilityService; - - public PackageFrameworkCompatibilityFactory(IFrameworkCompatibilityService compatibilityService) - { - _compatibilityService = compatibilityService ?? throw new ArgumentNullException(); - } - public PackageFrameworkCompatibility Create(ICollection packageFrameworks) { if (packageFrameworks == null) @@ -53,7 +46,7 @@ public PackageFrameworkCompatibility Create(ICollection packag private IReadOnlyDictionary> CreateFrameworkCompatibilityTable(ICollection filteredPackageFrameworks) { - var compatibleFrameworks = _compatibilityService.GetCompatibleFrameworks(filteredPackageFrameworks); + var compatibleFrameworks = FrameworkCompatibilityService.GetCompatibleFrameworks(filteredPackageFrameworks); var table = new Dictionary>(); diff --git a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs index dc2d9c2604..5a5d01fac3 100644 --- a/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs +++ b/src/NuGetGallery.Core/Frameworks/PackageFrameworkCompatibilityTableData.cs @@ -13,7 +13,7 @@ public class PackageFrameworkCompatibilityTableData public NuGetFramework Framework { get; set; } /// - /// if the was computed from .

+ /// if the was computed from .

/// if the was retrieved from the package asset frameworks. ///
public bool IsComputed { get; set; } diff --git a/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs b/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs index e852c86a23..f001a03f86 100644 --- a/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs +++ b/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs @@ -471,11 +471,6 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); - builder.RegisterType() - .AsSelf() - .As() - .SingleInstance(); - builder.RegisterType() .AsSelf() .As() diff --git a/src/NuGetGallery/Areas/Admin/Controllers/CorrectIsLatestController.cs b/src/NuGetGallery/Areas/Admin/Controllers/CorrectIsLatestController.cs new file mode 100644 index 0000000000..a96318afa9 --- /dev/null +++ b/src/NuGetGallery/Areas/Admin/Controllers/CorrectIsLatestController.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Web.Mvc; +using NuGetGallery.Areas.Admin.Models; +using NuGetGallery.Areas.Admin.ViewModels; + +namespace NuGetGallery.Areas.Admin.Controllers +{ + public class CorrectIsLatestController : AdminControllerBase + { + private readonly IPackageService _packageService; + private readonly IEntitiesContext _entitiesContext; + private readonly IPackageFileService _packageFileService; + private readonly ITelemetryService _telemetryService; + + public CorrectIsLatestController(IPackageService packageService, IEntitiesContext entitiesContext, IPackageFileService packageFileService, ITelemetryService telemetryService) + { + _packageService = packageService ?? throw new ArgumentNullException(nameof(packageService)); + _entitiesContext = entitiesContext ?? throw new ArgumentNullException(nameof(entitiesContext)); + _packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService)); + _telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService)); + } + + [HttpGet] + public ActionResult Index() + { + return View(); + } + + [HttpGet] + public ActionResult CorrectIsLatestPackages() + { + var result = _entitiesContext + .PackageRegistrations + .Where(pr => pr.Packages.Any(p => p.IsLatest || p.IsLatestStable || p.IsLatestSemVer2 || p.IsLatestStableSemVer2)) + .Select(pr => new CorrectIsLatestPackage() + { + Id = pr.Id, + Version = pr.Packages + .Where(p => p.IsLatest || p.IsLatestStable || p.IsLatestSemVer2 || p.IsLatestStableSemVer2) + .FirstOrDefault() + .Version, + IsLatestCount = pr.Packages.Where(p => p.IsLatest).Count(), + IsLatestStableCount = pr.Packages.Where(p => p.IsLatestStable).Count(), + IsLatestSemVer2Count = pr.Packages.Where(p => p.IsLatestSemVer2).Count(), + IsLatestStableSemVer2Count = pr.Packages.Where(p => p.IsLatestStableSemVer2).Count(), + HasIsLatestUnlisted = pr.Packages.Any(p => + !p.Listed + && (p.IsLatest + || p.IsLatestStable + || p.IsLatestSemVer2 + || p.IsLatestStableSemVer2)) + }) + .Where(pr => pr.IsLatestCount > 1 + || pr.IsLatestStableCount > 1 + || pr.IsLatestSemVer2Count > 1 + || pr.IsLatestStableSemVer2Count > 1 + || pr.HasIsLatestUnlisted) + .OrderBy(pr => pr.Id) + .ToList(); + + return Json(result, JsonRequestBehavior.AllowGet); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ReflowPackages(CorrectIsLatestRequest request) + { + if (request == null || request.Packages == null || request.Packages.Count == 0) + { + return Json(HttpStatusCode.BadRequest, "Packages cannot be null or empty.", JsonRequestBehavior.AllowGet); + } + + var reflowPackageService = new ReflowPackageService( + _entitiesContext, + (PackageService)_packageService, + _packageFileService, + _telemetryService); + + var totalPackagesReflowed = 0; + var totalPackagesFailReflowed = 0; + + foreach (var package in request.Packages) + { + try + { + await reflowPackageService.ReflowAsync(package.Id, package.Version); + totalPackagesReflowed++; + } + catch (Exception ex) + { + ex.Log(); + totalPackagesFailReflowed++; + } + } + + var reflowedPackagesMessage = totalPackagesReflowed == 1 ? $"{totalPackagesReflowed} package reflowed" : $"{totalPackagesReflowed} packages reflowed"; + var failedPackagesMessage = totalPackagesFailReflowed == 1 ? $"{totalPackagesFailReflowed} package fail reflow" : $"{totalPackagesFailReflowed} packages fail reflow"; + + return Json(HttpStatusCode.OK, $"{reflowedPackagesMessage}, {failedPackagesMessage}.", JsonRequestBehavior.AllowGet); + } + } +} diff --git a/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs b/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs index d8d77fbf59..fbe6ae9b15 100644 --- a/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs +++ b/src/NuGetGallery/Areas/Admin/Controllers/PopularityTransferController.cs @@ -98,37 +98,55 @@ public ActionResult ValidateInputs(string packagesFromInput, string packagesToIn } // create validated input result - var input = new PopularityTransferItem(CreatePackageSearchResult(packageFrom.Packages.First()), - CreatePackageSearchResult(packageTo.Packages.First()), - packageFrom.Key, - packageTo.Key); + result.ValidatedInputs.Add(new PopularityTransferItem(packageFrom, packageTo)); - result.ValidatedInputs.Add(input); + // checking for existing entries in the PackageRenames table + // 1. 'From' input that already has a 'From' entry in the PackageRenames table -- Conflict + var existingRenames = _packageRenameService.GetPackageRenames(packageFrom); - // check for existing entries in the PackageRename table for the 'From' packages - var existingRenamesFrom = _packageRenameService.GetPackageRenames(packageFrom); - - if (existingRenamesFrom.Any()) + if (existingRenames.Any()) { - if (existingRenamesFrom.Count == 1) + if (existingRenames.Count == 1) { - var existingRenamesMessage = $"{packageFrom.Id} already has 1 entry in the PackageRenames table. This will be removed with this operation."; - result.ExistingPackageRenames.Add(existingRenamesMessage); + result.ExistingPackageRenamesMessagesConflict.Add($"{packageFrom.Id} already has 1 entry in the PackageRenames table. This will be removed with this operation."); } else { - var existingRenamesMessage = $"{packageFrom.Id} already has {existingRenamesFrom.Count} entries in the PackageRenames table. These will be removed with this operation."; - result.ExistingPackageRenames.Add(existingRenamesMessage); + result.ExistingPackageRenamesMessagesConflict.Add($"{packageFrom.Id} already has {existingRenames.Count} entries in the PackageRenames table. These will be removed with this operation."); + } + + foreach (var existingRename in existingRenames) + { + result.ExistingPackageRenamesConflict.Add(new PopularityTransferItem(existingRename.FromPackageRegistration, existingRename.ToPackageRegistration)); + } + } + + // 2. 'From' input that already has a 'To' entry in the PackageRenames table -- Transitive + existingRenames = _packageRenameService.GetPackageRenamesTo(packageFrom); + + if (existingRenames.Any()) + { + var existingRenamesMessage = $"{packageFrom.Id} already has entries in the PackageRenames table. This popularity transfer will result in a new transitive relationship. Please look at the PackageRenames table and verify your input before proceeding."; + result.ExistingPackageRenamesMessagesTransitive.Add(existingRenamesMessage); + + foreach (var existingRename in existingRenames) + { + result.ExistingPackageRenamesTransitive.Add(new PopularityTransferItem(existingRename.FromPackageRegistration, existingRename.ToPackageRegistration)); } } - // check for existing entries in the PackageRename table for the 'To' packages - var existingRenamesTo = _packageRenameService.GetPackageRenames(packageTo); + // 3. 'To' input that already has a 'From' entry in the PackageRenames table -- Transitive + existingRenames = _packageRenameService.GetPackageRenames(packageTo); - if (existingRenamesTo.Any()) + if (existingRenames.Any()) { var existingRenamesMessage = $"{packageTo.Id} already has entries in the PackageRenames table. This popularity transfer will result in a new transitive relationship. Please look at the PackageRenames table and verify your input before proceeding."; - result.ExistingPackageRenames.Insert(0, existingRenamesMessage); + result.ExistingPackageRenamesMessagesTransitive.Add(existingRenamesMessage); + + foreach (var existingRename in existingRenames) + { + result.ExistingPackageRenamesTransitive.Add(new PopularityTransferItem(existingRename.FromPackageRegistration, existingRename.ToPackageRegistration)); + } } } diff --git a/src/NuGetGallery/Areas/Admin/Models/CorrectIsLatestPackage.cs b/src/NuGetGallery/Areas/Admin/Models/CorrectIsLatestPackage.cs new file mode 100644 index 0000000000..fa0e96ff08 --- /dev/null +++ b/src/NuGetGallery/Areas/Admin/Models/CorrectIsLatestPackage.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. + +namespace NuGetGallery.Areas.Admin.Models +{ + public class CorrectIsLatestPackage + { + public string Id { get; set; } + public string Version { get; set; } + public int IsLatestCount { get; set; } + public int IsLatestStableCount { get; set; } + public int IsLatestSemVer2Count { get; set; } + public int IsLatestStableSemVer2Count { get; set; } + public bool HasIsLatestUnlisted { get; set; } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Areas/Admin/ViewModels/CorrectIsLatestRequest.cs b/src/NuGetGallery/Areas/Admin/ViewModels/CorrectIsLatestRequest.cs new file mode 100644 index 0000000000..d5e80b69d1 --- /dev/null +++ b/src/NuGetGallery/Areas/Admin/ViewModels/CorrectIsLatestRequest.cs @@ -0,0 +1,19 @@ +// 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.Generic; + +namespace NuGetGallery.Areas.Admin.ViewModels +{ + public class CorrectIsLatestRequest + { + public ICollection Packages { get; set; } + } + + public class CorrectIsLatestPackageRequest + { + public string Id { get; set; } + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs b/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs index d68dfffed3..1a1acc2413 100644 --- a/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs +++ b/src/NuGetGallery/Areas/Admin/ViewModels/PopularityTransferViewModel.cs @@ -1,9 +1,12 @@ // 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.Linq; using System.Web; using System.Web.Mvc; +using NuGet.Services.Entities; namespace NuGetGallery.Areas.Admin.ViewModels { @@ -12,12 +15,18 @@ public class PopularityTransferViewModel public PopularityTransferViewModel() { ValidatedInputs = new List(); - ExistingPackageRenames = new List(); + ExistingPackageRenamesConflict = new List(); + ExistingPackageRenamesTransitive = new List(); + ExistingPackageRenamesMessagesConflict = new List(); + ExistingPackageRenamesMessagesTransitive = new List(); SuccessMessage = string.Empty; } public List ValidatedInputs { get; set; } - public List ExistingPackageRenames { get; set; } + public List ExistingPackageRenamesConflict { get; set; } + public List ExistingPackageRenamesTransitive { get; set; } + public List ExistingPackageRenamesMessagesConflict { get; set; } + public List ExistingPackageRenamesMessagesTransitive { get; set; } public string SuccessMessage { get; set; } = string.Empty; } @@ -28,35 +37,49 @@ public PopularityTransferItem() FromOwners = new List(); ToOwners = new List(); } - - public PopularityTransferItem( - PackageSearchResult packageFrom, - PackageSearchResult packageTo, - int fromKey, - int toKey) + + public PopularityTransferItem(PackageRegistration packageFrom, PackageRegistration packageTo) { - FromId = packageFrom.PackageId; - FromUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageFrom.PackageId); - FromDownloads = packageFrom.DownloadCount; - FromOwners = packageFrom.Owners; - FromKey = fromKey; - - ToId = packageTo.PackageId; - ToUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageTo.PackageId); - ToDownloads = packageTo.DownloadCount; - ToOwners = packageTo.Owners; - ToKey = toKey; + FromId = packageFrom.Id; + FromUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageFrom.Id); + FromDownloads = packageFrom.DownloadCount.ToNuGetNumberString(); + FromOwners = packageFrom + .Owners + .Select(u => u.Username) + .OrderBy(u => u, StringComparer.OrdinalIgnoreCase) + .Select(u => new UserViewModel + { + Username = u, + ProfileUrl = UrlHelperExtensions.User(new UrlHelper(HttpContext.Current.Request.RequestContext), u), + }) + .ToList(); + FromKey = packageFrom.Key; + + ToId = packageTo.Id; + ToUrl = UrlHelperExtensions.Package(new UrlHelper(HttpContext.Current.Request.RequestContext), packageTo.Id); + ToDownloads = packageTo.DownloadCount.ToNuGetNumberString(); + ToOwners = packageTo + .Owners + .Select(u => u.Username) + .OrderBy(u => u, StringComparer.OrdinalIgnoreCase) + .Select(u => new UserViewModel + { + Username = u, + ProfileUrl = UrlHelperExtensions.User(new UrlHelper(HttpContext.Current.Request.RequestContext), u), + }) + .ToList(); + ToKey = packageTo.Key; } public string FromId { get; set; } public string FromUrl { get; set; } - public long FromDownloads { get; set; } + public string FromDownloads { get; set; } public IReadOnlyList FromOwners { get; set; } = new List(); public int FromKey { get; set; } public string ToId { get; set; } public string ToUrl { get; set; } - public long ToDownloads { get; set; } + public string ToDownloads { get; set; } public IReadOnlyList ToOwners { get; set; } = new List(); public int ToKey { get; set; } } diff --git a/src/NuGetGallery/Areas/Admin/Views/CorrectIsLatest/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/CorrectIsLatest/Index.cshtml new file mode 100644 index 0000000000..328c7b09a6 --- /dev/null +++ b/src/NuGetGallery/Areas/Admin/Views/CorrectIsLatest/Index.cshtml @@ -0,0 +1,193 @@ +@model IReadOnlyList +@{ + ViewBag.Title = "Correct IsLatest packages"; +} +@ViewHelpers.AjaxAntiForgeryToken(Html) + +
+

Correct IsLatest packages

+ + + +
+ @ViewHelpers.AlertInfo(@Loading packages...) +
+
+

No packages with incorrect IsLatest found.

+
+ + @using (Html.BeginForm()) + { + @Html.AntiForgeryToken() + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ @ViewHelpers.Alert(@, "info", "Info") +
+
+ @ViewHelpers.AlertInfo(@The packages are being reflowed. It may take a while for this change to propagate through our system.) +
+
+ @ViewHelpers.AlertDanger(@) +
+
+ +
+ } +
+ +@section BottomScripts { + +} \ No newline at end of file diff --git a/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml index ca73cc8509..200abcd523 100644 --- a/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml +++ b/src/NuGetGallery/Areas/Admin/Views/Home/Index.cshtml @@ -255,6 +255,17 @@ Transfer popularity from one package to another.

+
  • +

    + + + Correct IsLatest + +

    +

    + Correct IsLatest state from packages. +

    +
  • diff --git a/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml index 2513d665fe..3e4bc89d27 100644 --- a/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml +++ b/src/NuGetGallery/Areas/Admin/Views/PopularityTransfer/Index.cshtml @@ -3,6 +3,36 @@ } @ViewHelpers.AjaxAntiForgeryToken(Html) +@helper AddPopularityTransferItemTable(string inputName, string tableId, string tableLabel) +{ + + + + + + + + + + + + + + + + + + + + + + +} +
    -
    +
    @@ -91,7 +106,10 @@ this.errorInputs = ko.observable(''); this.validatedInputs = ko.observableArray([]); - this.existingPackageRenames = ko.observableArray([]); + this.existingPackageRenamesConflict = ko.observableArray([]); + this.existingPackageRenamesMessagesConflict = ko.observableArray([]); + this.existingPackageRenamesTransitive = ko.observableArray([]); + this.existingPackageRenamesMessagesTransitive = ko.observableArray([]); this.successMessage = ko.observable(''); this.errorSubmitChanges = ko.observable(''); @@ -100,7 +118,10 @@ $self.doneValidateInputs(false); $self.errorInputs(''); $self.validatedInputs([]); - $self.existingPackageRenames([]); + $self.existingPackageRenamesConflict([]); + $self.existingPackageRenamesMessagesConflict([]); + $self.existingPackageRenamesTransitive([]); + $self.existingPackageRenamesMessagesTransitive([]); $self.successMessage(''); $self.errorSubmitChanges(''); @@ -117,7 +138,10 @@ dataType: 'json', success: function (result) { $self.validatedInputs(result.ValidatedInputs); - $self.existingPackageRenames(result.ExistingPackageRenames); + $self.existingPackageRenamesConflict(result.ExistingPackageRenamesConflict); + $self.existingPackageRenamesMessagesConflict(result.ExistingPackageRenamesMessagesConflict); + $self.existingPackageRenamesTransitive(result.ExistingPackageRenamesTransitive); + $self.existingPackageRenamesMessagesTransitive(result.ExistingPackageRenamesMessagesTransitive); } }) .fail(function (xhr, textStatus, errorMessage) { diff --git a/src/NuGetGallery/Areas/Admin/Views/UpdateListed/Index.cshtml b/src/NuGetGallery/Areas/Admin/Views/UpdateListed/Index.cshtml index 91642dd223..139b6fc5d8 100644 --- a/src/NuGetGallery/Areas/Admin/Views/UpdateListed/Index.cshtml +++ b/src/NuGetGallery/Areas/Admin/Views/UpdateListed/Index.cshtml @@ -106,22 +106,37 @@ return true; }; - this.selectListed = function (e) { + let selectFilteredResults = function (isListedFilter) { ko.utils.arrayForEach($self.searchResults(), function (result) { - if (result.Listed) { + result.Selected(false); + }); + + var selection = parseInt(window.prompt("How many? (Default: All)", "")); + selection = isNaN(selection) ? $self.searchResults().length : selection; + + if (selection <= 0) { + return true; + } + + let i = 0; + ko.utils.arrayFirst($self.searchResults(), function (result) { + if (result.Listed == isListedFilter) { result.Selected(true); + i++; } + + // Stop looping when we selected enough items + return i >= selection; }); return true; + } + + this.selectListed = function (e) { + selectFilteredResults(true); }; this.selectUnlisted = function (e) { - ko.utils.arrayForEach($self.searchResults(), function (result) { - if (!result.Listed) { - result.Selected(true); - } - }); - return true; + selectFilteredResults(false); }; this.generateValue = function (package) { diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 71a32ea9d0..acdb141ad1 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -137,6 +137,7 @@ + @@ -184,7 +185,9 @@ + + @@ -2334,6 +2337,9 @@ + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/NuGetGallery/Services/IPackageRenameService.cs b/src/NuGetGallery/Services/IPackageRenameService.cs index 73405a42fa..abaa40ad7f 100644 --- a/src/NuGetGallery/Services/IPackageRenameService.cs +++ b/src/NuGetGallery/Services/IPackageRenameService.cs @@ -9,5 +9,6 @@ namespace NuGetGallery public interface IPackageRenameService { IReadOnlyList GetPackageRenames(PackageRegistration package); + IReadOnlyList GetPackageRenamesTo(PackageRegistration package); } } \ No newline at end of file diff --git a/src/NuGetGallery/Services/PackageRenameService.cs b/src/NuGetGallery/Services/PackageRenameService.cs index 25040df0a6..aeb8f37286 100644 --- a/src/NuGetGallery/Services/PackageRenameService.cs +++ b/src/NuGetGallery/Services/PackageRenameService.cs @@ -23,6 +23,17 @@ public IReadOnlyList GetPackageRenames(PackageRegistration packag { return _packageRenameRepository.GetAll() .Where(pr => pr.FromPackageRegistrationKey == packageRegistration.Key) + .Include(pr => pr.FromPackageRegistration) + .Include(pr => pr.ToPackageRegistration) + .ToList(); + } + + + public IReadOnlyList GetPackageRenamesTo(PackageRegistration packageRegistration) + { + return _packageRenameRepository.GetAll() + .Where(pr => pr.ToPackageRegistrationKey == packageRegistration.Key) + .Include(pr => pr.FromPackageRegistration) .Include(pr => pr.ToPackageRegistration) .ToList(); } diff --git a/src/VerifyGitHubVulnerabilities/README.md b/src/VerifyGitHubVulnerabilities/README.md index e07bcbf4c8..8b9d32efc8 100644 --- a/src/VerifyGitHubVulnerabilities/README.md +++ b/src/VerifyGitHubVulnerabilities/README.md @@ -10,14 +10,25 @@ A typical command line will look like this: VerifyGitHubVulnerabilities.exe -Configuration appsettings.json -InstrumentationKey -HeartbeatIntervalSeconds 60 ``` -### Using DEV resources - -The easiest way to run the tool if you are on the nuget.org team is to use the DEV environment resources: +Setup for this command: 1. Install the certificate used to authenticate as our client Microsoft Entra ID app registration into your `CurrentUser` certificate store. -1. Clone our internal [`NuGetDeployment`](https://nuget.visualstudio.com/DefaultCollection/NuGetMicrosoft/_git/NuGetDeploymentp) repository. -1. Take a copy of the [DEV VerifyGitHubVulnerabilities appsettings.json](https://nuget.visualstudio.com/NuGetMicrosoft/_git/NuGetDeployment?path=%2Fsrc%2FJobs%2FNuGet.Jobs.Cloud%2FJobs%VerifyGitHubVulnerabilities%2FDEV%2Fnorthcentralus%2Fappsettings.json) file and place it in the same directory as the `VerifyGitHubVulnerabilities.exe`. This will use our secrets to authenticate to the SQL server (this file also contains a reference to the secret used for the access token to GitHub). -1. Run as per above. +1. Create a file called `appsettings.json` in the same driectory as the `VerifyGitHubVulnerabilities.exe`. The contents of this JSON file should look like the following: + + ``` + { + "GalleryDb": { + "ConnectionString": + }, + "Initialization": { + "GitHubPersonalAccessToken": "", + "NuGetV3Index": "" + }, + "KeyVault_VaultName": "", + "KeyVault_UseManagedIdentity": true + } + ``` + ## Algorithm diff --git a/tests/NuGetGallery.Core.Facts/Frameworks/FrameworkCompatibilityServiceFacts.cs b/tests/NuGetGallery.Core.Facts/Frameworks/FrameworkCompatibilityServiceFacts.cs index b6699d9757..fa19e926c7 100644 --- a/tests/NuGetGallery.Core.Facts/Frameworks/FrameworkCompatibilityServiceFacts.cs +++ b/tests/NuGetGallery.Core.Facts/Frameworks/FrameworkCompatibilityServiceFacts.cs @@ -11,24 +11,18 @@ namespace NuGetGallery.Frameworks { public class FrameworkCompatibilityServiceFacts { - private readonly IFrameworkCompatibilityService _service; private readonly IFrameworkCompatibilityProvider CompatibilityProvider = DefaultCompatibilityProvider.Instance; - public FrameworkCompatibilityServiceFacts() - { - _service = new FrameworkCompatibilityService(); - } - [Fact] public void NullPackageFrameworksThrowsArgumentNullException() { - Assert.Throws(() => _service.GetCompatibleFrameworks(null)); + Assert.Throws(() => FrameworkCompatibilityService.GetCompatibleFrameworks(null)); } [Fact] public void EmptyPackageFrameworksReturnsEmptySet() { - var result = _service.GetCompatibleFrameworks(new List()); + var result = FrameworkCompatibilityService.GetCompatibleFrameworks(new List()); Assert.Empty(result); } @@ -38,7 +32,7 @@ public void UnknownSupportedPackageReturnsSetWithSameFramework() { var framework = NuGetFramework.Parse("net45-client"); var frameworks = new List() { framework }; - var compatible = _service.GetCompatibleFrameworks(frameworks); + var compatible = FrameworkCompatibilityService.GetCompatibleFrameworks(frameworks); Assert.False(framework.IsUnsupported); Assert.Equal(expected: 1, compatible.Count); @@ -53,7 +47,7 @@ public void UnsupportedPackageFrameworksReturnsEmptySet(string unsupportedFramew { var unsupportedFramework = NuGetFramework.Parse(unsupportedFrameworkName); - var result = _service.GetCompatibleFrameworks(new List() { unsupportedFramework }); + var result = FrameworkCompatibilityService.GetCompatibleFrameworks(new List() { unsupportedFramework }); Assert.True(unsupportedFramework.IsUnsupported); Assert.Equal(expected: 0, actual: result.Count); @@ -67,7 +61,7 @@ public void PCLPackageFrameworksReturnsEmptySet(string pclFrameworkName) { var portableFramework = NuGetFramework.Parse(pclFrameworkName); - var result = _service.GetCompatibleFrameworks(new List() { portableFramework }); + var result = FrameworkCompatibilityService.GetCompatibleFrameworks(new List() { portableFramework }); Assert.True(portableFramework.IsPCL); Assert.Equal(expected: 0, actual: result.Count); @@ -85,7 +79,7 @@ public void ValidPackageFrameworksReturnsFrameworksCompatibleForAtLeastOne(param frameworks.Add(NuGetFramework.Parse(frameworkName)); } - var compatibleFrameworks = _service.GetCompatibleFrameworks(frameworks); + var compatibleFrameworks = FrameworkCompatibilityService.GetCompatibleFrameworks(frameworks); Assert.True(compatibleFrameworks.Count > 0); @@ -108,7 +102,7 @@ public void WindowsPlatformVersionsShouldContainAllSpecifiedFrameworks(string wi projectFrameworks.Add(NuGetFramework.Parse(frameworkName)); } - var compatibleFrameworks = _service.GetCompatibleFrameworks(new HashSet() { packageFramework }); + var compatibleFrameworks = FrameworkCompatibilityService.GetCompatibleFrameworks(new HashSet() { packageFramework }); Assert.Equal(windowsProjectFrameworks.Length, compatibleFrameworks.Count); var containsAllCompatibleFrameworks = compatibleFrameworks.All(cf => projectFrameworks.Contains(cf)); diff --git a/tests/NuGetGallery.Core.Facts/Frameworks/PackageFrameworkCompatibilityFactoryFacts.cs b/tests/NuGetGallery.Core.Facts/Frameworks/PackageFrameworkCompatibilityFactoryFacts.cs index 15e3d26dd9..b5377a2190 100644 --- a/tests/NuGetGallery.Core.Facts/Frameworks/PackageFrameworkCompatibilityFactoryFacts.cs +++ b/tests/NuGetGallery.Core.Facts/Frameworks/PackageFrameworkCompatibilityFactoryFacts.cs @@ -13,18 +13,10 @@ namespace NuGetGallery.Frameworks public class PackageFrameworkCompatibilityFactoryFacts { private readonly PackageFrameworkCompatibilityFactory _factory; - private readonly IFrameworkCompatibilityService _service; public PackageFrameworkCompatibilityFactoryFacts() { - _service = new FrameworkCompatibilityService(); - _factory = new PackageFrameworkCompatibilityFactory(_service); - } - - [Fact] - public void NullFrameworkCompatibilityServiceThrowsArgumentNullException() - { - Assert.Throws(() => new PackageFrameworkCompatibilityFactory(null)); + _factory = new PackageFrameworkCompatibilityFactory(); } [Fact] diff --git a/tests/NuGetGallery.Facts/Areas/Admin/Controllers/CorrectIsLatestControllerFacts.cs b/tests/NuGetGallery.Facts/Areas/Admin/Controllers/CorrectIsLatestControllerFacts.cs new file mode 100644 index 0000000000..0124c382e8 --- /dev/null +++ b/tests/NuGetGallery.Facts/Areas/Admin/Controllers/CorrectIsLatestControllerFacts.cs @@ -0,0 +1,354 @@ +// 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.Generic; +using System.Net; +using System.Threading.Tasks; +using System.Web; +using System.Web.Mvc; +using Moq; +using NuGet.Services.Entities; +using NuGetGallery.Areas.Admin.Models; +using NuGetGallery.Areas.Admin.ViewModels; +using NuGetGallery.Framework; +using NuGetGallery.Helpers; +using NuGetGallery.TestUtils; +using Xunit; + +namespace NuGetGallery.Areas.Admin.Controllers +{ + public class CorrectIsLatestControllerFacts + { + public class CorrectIsLatestPackagesMethod : FactsBase + { + [Fact] + public void WhenPackagesHaveCorrectIsLatestReturnEmptyArray() + { + var result = CorrectIsLatestController.CorrectIsLatestPackages() as JsonResult; + var packages = result.Data as List; + + Assert.Empty(packages); + } + + [Theory] + [MemberData(nameof(IsLatestTestData))] + public void WhenPackageIsLatestAndIsUnlistedReturnsHasIsLatestUnlistedAsTrue(bool isLatest, bool isLatestStable, bool isLatestSemVer2, bool isLatestStableSemVer2) + { + var validPackages = new HashSet(); + var packageId = "IsLatestAndUnlisted"; + validPackages.Add(new Package() + { + Id = packageId, + Version = "1.0.0", + Listed = false, + IsLatest = isLatest, + IsLatestStable = isLatestStable, + IsLatestSemVer2 = isLatestSemVer2, + IsLatestStableSemVer2 = isLatestStableSemVer2, + }); + validPackages.Add(new Package() + { + Id = packageId, + Version = "1.0.1", + Listed = true, + IsLatest = false, + IsLatestStable = false, + IsLatestSemVer2 = false, + IsLatestStableSemVer2 = false, + }); + + var packageRegistration = new PackageRegistration() + { + Id = packageId, + Packages = validPackages + }; + + var fakeContext = GetFakeContext(); + fakeContext.PackageRegistrations.Add(packageRegistration); + + var result = CorrectIsLatestController.CorrectIsLatestPackages() as JsonResult; + var correctIsLatestPackages = result.Data as List; + + Assert.NotEmpty(correctIsLatestPackages); + + var correctIsLatestPackage = correctIsLatestPackages[0]; + Assert.True(correctIsLatestPackage.HasIsLatestUnlisted); + Assert.True(correctIsLatestPackage.IsLatestCount > 0 || + correctIsLatestPackage.IsLatestStableCount > 0 || + correctIsLatestPackage.IsLatestSemVer2Count > 0 || + correctIsLatestPackage.IsLatestStableSemVer2Count > 0); + } + + [Theory] + [MemberData(nameof(IsLatestTestData))] + public void WhenPackageHasMultipleIsLatestReturnSum(bool isLatest, bool isLatestStable, bool isLatestSemVer2, bool isLatestStableSemVer2) + { + var packages = new HashSet(); + var packageId = "MultipleIsLatest"; + packages.Add(new Package() + { + Id = packageId, + Version = "1.0.0", + Listed = true, + IsLatest = isLatest, + IsLatestStable = isLatestStable, + IsLatestSemVer2 = isLatestSemVer2, + IsLatestStableSemVer2 = isLatestStableSemVer2, + }); + packages.Add(new Package() + { + Id = packageId, + Version = "1.0.1", + Listed = true, + IsLatest = isLatest, + IsLatestStable = isLatestStable, + IsLatestSemVer2 = isLatestSemVer2, + IsLatestStableSemVer2 = isLatestStableSemVer2, + }); + + var packageRegistration = new PackageRegistration() + { + Id = packageId, + Packages = packages + }; + + var fakeContext = GetFakeContext(); + fakeContext.PackageRegistrations.Add(packageRegistration); + + var result = CorrectIsLatestController.CorrectIsLatestPackages() as JsonResult; + var correctIsLatestPackages = result.Data as List; + + Assert.NotEmpty(packages); + + var correctIsLatestPackage = correctIsLatestPackages[0]; + Assert.False(correctIsLatestPackage.HasIsLatestUnlisted); + + if (isLatest) + { + Assert.True(correctIsLatestPackage.IsLatestCount == 2); + } + if (isLatestStable) + { + Assert.True(correctIsLatestPackage.IsLatestStableCount == 2); + } + if (isLatestSemVer2) + { + Assert.True(correctIsLatestPackage.IsLatestSemVer2Count == 2); + } + if (isLatestStableSemVer2) + { + Assert.True(correctIsLatestPackage.IsLatestStableSemVer2Count == 2); + } + } + } + + public class ReflowPackagesMethod : FactsBase + { + [Theory] + [MemberData(nameof(BadReflowPackagesTestData))] + public async Task WhenInvalidReturnsBadRequest(CorrectIsLatestRequest request) + { + // Act + var result = await CorrectIsLatestController.ReflowPackages(request) as JsonResult; + + // Assert + Assert.Equal(((int)HttpStatusCode.BadRequest), CorrectIsLatestController.Response.StatusCode); + Assert.Equal("Packages cannot be null or empty.", result.Data); + } + + [Fact] + public async Task WhenPackageValidReturnsReflowCount() + { + // Arrange + var request = new CorrectIsLatestRequest() + { + Packages = new List() + { + new CorrectIsLatestPackageRequest() + { + Id = ReflowPackage.Id, + Version = ReflowPackage.Version + }, + new CorrectIsLatestPackageRequest() + { + Id = ReflowPackage2.Id, + Version = ReflowPackage2.Version + } + } + }; + + // Act + var result = await CorrectIsLatestController.ReflowPackages(request) as JsonResult; + + // Assert + Assert.Equal(((int)HttpStatusCode.OK), CorrectIsLatestController.Response.StatusCode); + Assert.Equal("2 packages reflowed, 0 packages fail reflow.", result.Data); + } + + [Fact] + public async Task WhenPackageInvalidReturnsReflowCount() + { + // Arrange + var request = new CorrectIsLatestRequest() + { + Packages = new List() + { + new CorrectIsLatestPackageRequest() + { + Id = FailReflowPackage.Id, + Version = FailReflowPackage.Version + }, + new CorrectIsLatestPackageRequest() + { + Id = FailReflowPackage2.Id, + Version = FailReflowPackage2.Version + } + } + }; + + // Act + var result = await CorrectIsLatestController.ReflowPackages(request) as JsonResult; + + // Assert + Assert.Equal(((int)HttpStatusCode.OK), CorrectIsLatestController.Response.StatusCode); + Assert.Equal("0 packages reflowed, 2 packages fail reflow.", result.Data); + } + + [Fact] + public async Task WhenPackageValidAndInvalidReturnsReflowCount() + { + // Arrange + var request = new CorrectIsLatestRequest() + { + Packages = new List() + { + new CorrectIsLatestPackageRequest() + { + Id = ReflowPackage.Id, + Version = ReflowPackage.Version + }, + new CorrectIsLatestPackageRequest() + { + Id = FailReflowPackage.Id, + Version = FailReflowPackage.Version + } + } + }; + + // Act + var result = await CorrectIsLatestController.ReflowPackages(request) as JsonResult; + + // Assert + Assert.Equal(((int)HttpStatusCode.OK), CorrectIsLatestController.Response.StatusCode); + Assert.Equal("1 package reflowed, 1 package fail reflow.", result.Data); + } + } + + public class FactsBase : TestContainer + { + protected CorrectIsLatestController CorrectIsLatestController; + protected Mock PackageServiceMock; + protected Mock PackageFileServiceMock; + + protected Package ReflowPackage; + protected Package ReflowPackage2; + protected Package FailReflowPackage; + protected Package FailReflowPackage2; + + public FactsBase() + { + var entitiesContextMock = ReflowServiceSetupHelper.SetupEntitiesContext(); + var database = new Mock(); + database.Setup(x => x.BeginTransaction()).Returns(() => new Mock().Object); + entitiesContextMock.Setup(m => m.GetDatabase()).Returns(database.Object); + + var correctPackages = new HashSet(); + var correctPackageId = "TheCorrectIsLatestPackage"; + correctPackages.Add(new Package() + { + Id = correctPackageId, + Version = "1.0.0", + Listed = true, + IsLatest = false, + IsLatestStable = false, + IsLatestSemVer2 = false, + IsLatestStableSemVer2 = false, + }); + correctPackages.Add(new Package() + { + Id = correctPackageId, + Version = "1.0.1", + Listed = true, + IsLatest = true, + IsLatestStable = true, + IsLatestSemVer2 = true, + IsLatestStableSemVer2 = true, + }); + + var packageRegistration = new PackageRegistration() + { + Id = correctPackageId, + Packages = correctPackages + }; + + var fakeContext = GetFakeContext(); + fakeContext.PackageRegistrations.Add(packageRegistration); + + entitiesContextMock + .SetupGet(ec => ec.PackageRegistrations) + .Returns(fakeContext.PackageRegistrations); + + PackageServiceMock = ReflowServiceSetupHelper.SetupPackageService(); + PackageFileServiceMock = new Mock(); + + ReflowPackage = PackageServiceUtility.CreateTestPackage("ReflowPackage"); + ReflowPackage2 = PackageServiceUtility.CreateTestPackage("ReflowPackage2"); + FailReflowPackage = PackageServiceUtility.CreateTestPackage("FailReflowPackage"); + FailReflowPackage2 = PackageServiceUtility.CreateTestPackage("FailReflowPackage2"); + + ReflowServiceSetupHelper.SetupPackages(PackageServiceMock, PackageFileServiceMock, new List() { ReflowPackage, ReflowPackage2 }); + PackageServiceMock.ThrowFindPackageByIdAndVersionStrict(new List() { FailReflowPackage, FailReflowPackage2 }); + + CorrectIsLatestController = new CorrectIsLatestController( + PackageServiceMock.Object, + entitiesContextMock.Object, + PackageFileServiceMock.Object, + GetMock().Object); + + TestUtility.SetupHttpContextMockForUrlGeneration(new Mock(), CorrectIsLatestController); + } + + public static IEnumerable IsLatestTestData + { + get + { + yield return new object[] { true, false, false, false }; + yield return new object[] { false, true, false, false }; + yield return new object[] { false, false, true, false }; + yield return new object[] { false, false, false, true }; + } + } + + public static IEnumerable BadReflowPackagesTestData + { + get + { + yield return new object[] { null }; + yield return new object[] { new CorrectIsLatestRequest() }; + yield return new object[] { new CorrectIsLatestRequest() { Packages = new List() } }; + } + } + + protected override void Dispose(bool disposing) + { + if (CorrectIsLatestController != null) + { + CorrectIsLatestController.Dispose(); + } + + base.Dispose(disposing); + } + } + + } +} diff --git a/tests/NuGetGallery.Facts/Helpers/ReflowServiceSetupHelper.cs b/tests/NuGetGallery.Facts/Helpers/ReflowServiceSetupHelper.cs new file mode 100644 index 0000000000..46624bec39 --- /dev/null +++ b/tests/NuGetGallery.Facts/Helpers/ReflowServiceSetupHelper.cs @@ -0,0 +1,192 @@ +// 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.IO.Compression; +using System.Threading.Tasks; +using Moq; +using NuGet.Packaging; +using NuGet.Services.Entities; +using NuGetGallery.Framework; +using NuGetGallery.Packaging; +using NuGetGallery.Security; + +namespace NuGetGallery.Helpers +{ + public static class ReflowServiceSetupHelper + { + public static Mock SetupPackageService(Package package) + { + var packageService = SetupPackageService(); + + packageService + .Setup(s => s.FindPackageByIdAndVersionStrict(package.Id, package.Version)) + .Returns(package) + .Verifiable(); + + return packageService; + } + + public static void SetupPackages(Mock packageServiceMock, Mock packageFileServiceMock, List packages) + { + foreach (var package in packages) + { + packageServiceMock + .Setup(s => s.FindPackageByIdAndVersionStrict(package.Id, package.Version)) + .Returns(package) + .Verifiable(); + + packageFileServiceMock + .Setup(s => s.DownloadPackageFileAsync(package)) + .Returns(Task.FromResult(ReflowServiceSetupHelper.CreateTestPackageStream(CreateTestNuspec(package.Id), $"{package.Id}.nuspec"))); + } + } + + public static void ThrowFindPackageByIdAndVersionStrict(this Mock packageServiceMock, List packages) + { + foreach (var package in packages) + { + packageServiceMock + .Setup(s => s.FindPackageByIdAndVersionStrict(package.Id, package.Version)) + .Throws(new NotSupportedException($"Unknown deprecation fields 'test'")); + } + } + + public static Mock SetupPackageService() + { + var packageRegistrationRepository = new Mock>(); + var packageRepository = new Mock>(); + var certificateRepository = new Mock>(); + var auditingService = new TestAuditingService(); + var telemetryService = new Mock(); + var securityPolicyService = new Mock(); + var entitiesContext = new Mock(); + var contentObjectService = new Mock(); + var featureFlagService = new Mock(); + featureFlagService.Setup(x => x.ArePatternSetTfmHeuristicsEnabled()).Returns(true); + + + var packageService = new Mock( + packageRegistrationRepository.Object, + packageRepository.Object, + certificateRepository.Object, + auditingService, + telemetryService.Object, + securityPolicyService.Object, + entitiesContext.Object, + contentObjectService.Object, + featureFlagService.Object); + + packageService.CallBase = true; + + packageService + .Setup(s => s.EnrichPackageFromNuGetPackage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .CallBase() + .Verifiable(); + + packageService + .Setup(s => s.UpdateIsLatestAsync( + It.IsAny(), + It.IsAny())) + .Returns(Task.CompletedTask) + .Verifiable(); + + return packageService; + } + + public static Mock SetupEntitiesContext() + { + var entitiesContext = new Mock(); + + entitiesContext + .Setup(s => s.Set().Remove(It.IsAny())) + .Verifiable(); + + entitiesContext + .Setup(s => s.Set().Remove(It.IsAny())) + .Verifiable(); + + entitiesContext + .Setup(s => s.Set().Remove(It.IsAny())) + .Verifiable(); + + entitiesContext + .Setup(s => s.Set().Remove(It.IsAny())) + .Verifiable(); + + return entitiesContext; + } + + public static Mock SetupPackageFileService(Package package, Stream packageStream = null) + { + var packageFileService = new Mock(); + + packageFileService + .Setup(s => s.DownloadPackageFileAsync(package)) + .Returns(Task.FromResult(packageStream ?? CreateTestPackageStream())) + .Verifiable(); + + return packageFileService; + } + + public static string CreateTestNuspec(string id = "test") + { + return $@" + + + {id} + 1.0.0 + Test package + authora, authorb + ownera + false + package A description. + en-US + http://www.nuget.org/ + http://www.nuget.org/ + http://www.nuget.org/ + + + + + + + + + + + "; + } + + public static Stream CreateTestPackageStream(string nuspec = null, string nuspecFilename = "TestPackage.nuspec") + { + if (string.IsNullOrEmpty(nuspec)) + { + nuspec = CreateTestNuspec(); + } + + var packageStream = new MemoryStream(); + using (var packageArchive = new ZipArchive(packageStream, ZipArchiveMode.Create, true)) + { + var nuspecEntry = packageArchive.CreateEntry(nuspecFilename, CompressionLevel.Fastest); + using (var streamWriter = new StreamWriter(nuspecEntry.Open())) + { + streamWriter.WriteLine(nuspec); + } + + packageArchive.CreateEntry("content\\HelloWorld.cs", CompressionLevel.Fastest); + } + + packageStream.Position = 0; + + return packageStream; + } + } +} diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index 649d032362..7e01263796 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -1,4 +1,4 @@ - + Debug @@ -66,6 +66,7 @@ + @@ -86,6 +87,7 @@ + diff --git a/tests/NuGetGallery.Facts/Services/ReflowPackageServiceFacts.cs b/tests/NuGetGallery.Facts/Services/ReflowPackageServiceFacts.cs index 615b35a9c5..b3a812ddd9 100644 --- a/tests/NuGetGallery.Facts/Services/ReflowPackageServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Services/ReflowPackageServiceFacts.cs @@ -4,14 +4,10 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Compression; using System.Threading.Tasks; using Moq; -using NuGet.Packaging; using NuGet.Services.Entities; -using NuGetGallery.Framework; -using NuGetGallery.Packaging; -using NuGetGallery.Security; +using NuGetGallery.Helpers; using NuGetGallery.TestUtils; using Xunit; @@ -59,9 +55,9 @@ public async Task ReturnsNullWhenPackageNotFound() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -81,9 +77,9 @@ public async Task RetrievesOriginalPackageBinary() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -103,9 +99,9 @@ public async Task RetrievesOriginalPackageMetadata() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -126,9 +122,9 @@ public async Task RemovesOriginalChildEntities() var package = PackageServiceUtility.CreateTestPackage(); package.PackageTypes = new List { new PackageType { Name = "Dependency", Version = "0.0" } }; - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -148,9 +144,9 @@ public async Task UpdatesPackageMetadata() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -205,9 +201,9 @@ public async Task UpdatesPackageLastEdited() var package = PackageServiceUtility.CreateTestPackage(); var lastEdited = package.LastEdited; - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -230,9 +226,9 @@ public async Task DoesNotUpdatePackageListed(bool listed) var package = PackageServiceUtility.CreateTestPackage(); package.Listed = listed; - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -252,9 +248,9 @@ public async Task CallsUpdateIsLatestAsync() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var service = CreateService( packageService: packageService, @@ -274,9 +270,9 @@ public async Task EmitsTelemetry() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService(package); + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService(package); var telemetryService = new Mock(); var service = CreateService( @@ -300,9 +296,9 @@ public async Task AllowsInvalidPackageDependencyVersion() // Arrange var package = PackageServiceUtility.CreateTestPackage(); - var packageService = SetupPackageService(package); - var entitiesContext = SetupEntitiesContext(); - var packageFileService = SetupPackageFileService( + var packageService = ReflowServiceSetupHelper.SetupPackageService(package); + var entitiesContext = ReflowServiceSetupHelper.SetupEntitiesContext(); + var packageFileService = ReflowServiceSetupHelper.SetupPackageFileService( package, CreateInvalidDependencyVersionTestPackageStream()); @@ -335,96 +331,9 @@ public async Task AllowsInvalidPackageDependencyVersion() } } - private static Mock SetupPackageService(Package package) - { - var packageRegistrationRepository = new Mock>(); - var packageRepository = new Mock>(); - var certificateRepository = new Mock>(); - var auditingService = new TestAuditingService(); - var telemetryService = new Mock(); - var securityPolicyService = new Mock(); - var entitiesContext = new Mock(); - var contentObjectService = new Mock(); - var featureFlagService = new Mock(); - featureFlagService.Setup(x => x.ArePatternSetTfmHeuristicsEnabled()).Returns(true); - - - var packageService = new Mock( - packageRegistrationRepository.Object, - packageRepository.Object, - certificateRepository.Object, - auditingService, - telemetryService.Object, - securityPolicyService.Object, - entitiesContext.Object, - contentObjectService.Object, - featureFlagService.Object); - - packageService.CallBase = true; - - packageService - .Setup(s => s.FindPackageByIdAndVersionStrict("test", "1.0.0")) - .Returns(package) - .Verifiable(); - - packageService - .Setup(s => s.EnrichPackageFromNuGetPackage( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .CallBase() - .Verifiable(); - - packageService - .Setup(s => s.UpdateIsLatestAsync( - It.IsAny(), - It.IsAny())) - .Returns(Task.CompletedTask) - .Verifiable(); - - return packageService; - } - - private static Mock SetupEntitiesContext() - { - var entitiesContext = new Mock(); - - entitiesContext - .Setup(s => s.Set().Remove(It.IsAny())) - .Verifiable(); - - entitiesContext - .Setup(s => s.Set().Remove(It.IsAny())) - .Verifiable(); - - entitiesContext - .Setup(s => s.Set().Remove(It.IsAny())) - .Verifiable(); - - entitiesContext - .Setup(s => s.Set().Remove(It.IsAny())) - .Verifiable(); - - return entitiesContext; - } - - private static Mock SetupPackageFileService(Package package, Stream packageStream = null) - { - var packageFileService = new Mock(); - - packageFileService - .Setup(s => s.DownloadPackageFileAsync(package)) - .Returns(Task.FromResult(packageStream ?? CreateTestPackageStream())) - .Verifiable(); - - return packageFileService; - } - private static Stream CreateInvalidDependencyVersionTestPackageStream() { - return CreateTestPackageStream(@" + var nuspec = @" test @@ -448,55 +357,9 @@ private static Stream CreateInvalidDependencyVersionTestPackageStream() - "); - } - - private static Stream CreateTestPackageStream() - { - return CreateTestPackageStream(@" - - - test - 1.0.0 - Test package - authora, authorb - ownera - false - package A description. - en-US - http://www.nuget.org/ - http://www.nuget.org/ - http://www.nuget.org/ - - - - - - - - - - - "); - } - - private static Stream CreateTestPackageStream(string nuspec) - { - var packageStream = new MemoryStream(); - using (var packageArchive = new ZipArchive(packageStream, ZipArchiveMode.Create, true)) - { - var nuspecEntry = packageArchive.CreateEntry("TestPackage.nuspec", CompressionLevel.Fastest); - using (var streamWriter = new StreamWriter(nuspecEntry.Open())) - { - streamWriter.WriteLine(nuspec); - } - - packageArchive.CreateEntry("content\\HelloWorld.cs", CompressionLevel.Fastest); - } - - packageStream.Position = 0; + "; - return packageStream; + return ReflowServiceSetupHelper.CreateTestPackageStream(nuspec); } } } \ No newline at end of file