Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.
/ NuGet.Jobs Public archive

Commit

Permalink
Add deprecation information to the catalog if provided (#522)
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott Bommarito authored May 17, 2019
1 parent 432ca5e commit 0c09757
Show file tree
Hide file tree
Showing 23 changed files with 392 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/Catalog/NuGet.Services.Metadata.Catalog.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<Compile Include="IPackagesContainerHandler.cs" />
<Compile Include="IHttpRetryStrategy.cs" />
<Compile Include="PackageCatalogItemCreator.cs" />
<Compile Include="PackageDeprecationItem.cs" />
<Compile Include="PackageDownloader.cs" />
<Compile Include="Helpers\CatalogProperties.cs" />
<Compile Include="Helpers\DeletionAuditEntry.cs" />
Expand Down
64 changes: 62 additions & 2 deletions src/Catalog/PackageCatalogItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ public class PackageCatalogItem : AppendOnlyCatalogItem
public DateTime? CreatedDate { get; }
public DateTime? LastEditedDate { get; }
public DateTime? PublishedDate { get; }

public PackageCatalogItem(NupkgMetadata nupkgMetadata, DateTime? createdDate = null, DateTime? lastEditedDate = null, DateTime? publishedDate = null, string licenseNames = null, string licenseReportUrl = null)
public PackageDeprecationItem Deprecation { get; }

public PackageCatalogItem(
NupkgMetadata nupkgMetadata,
DateTime? createdDate = null,
DateTime? lastEditedDate = null,
DateTime? publishedDate = null,
string licenseNames = null,
string licenseReportUrl = null,
PackageDeprecationItem deprecation = null)
{
NupkgMetadata = nupkgMetadata;
CreatedDate = createdDate;
LastEditedDate = lastEditedDate;
PublishedDate = publishedDate;
Deprecation = deprecation;
}

public override IGraph CreateContentGraph(CatalogContext context)
Expand Down Expand Up @@ -94,6 +103,52 @@ public override IGraph CreateContentGraph(CatalogContext context)
// identity and version
SetIdVersionFromGraph(graph);

// deprecation
if (Deprecation != null)
{
// assert deprecation root node to subject
var deprecationPredicate = graph.CreateUriNode(Schema.Predicates.Deprecation);
var deprecationRootNode = graph.CreateUriNode(new Uri(resource.Subject.ToString() + "#deprecation"));
graph.Assert(resource.Subject, deprecationPredicate, deprecationRootNode);

// assert reasons to deprecation root node
var deprecationReasonRootNode = graph.CreateUriNode(Schema.Predicates.Reasons);
foreach (var reason in Deprecation.Reasons)
{
var reasonNode = graph.CreateLiteralNode(reason);
graph.Assert(deprecationRootNode, deprecationReasonRootNode, reasonNode);
}

// assert message to deprecation root node
if (Deprecation.Message != null)
{
graph.Assert(
deprecationRootNode,
graph.CreateUriNode(Schema.Predicates.Message),
graph.CreateLiteralNode(Deprecation.Message));
}

if (Deprecation.AlternatePackageId != null)
{
// assert alternate package root node to deprecation root node
var deprecationAlternatePackagePredicate = graph.CreateUriNode(Schema.Predicates.AlternatePackage);
var deprecationAlternatePackageRootNode = graph.CreateUriNode(new Uri(resource.Subject.ToString() + "#deprecation/alternatePackage"));
graph.Assert(deprecationRootNode, deprecationAlternatePackagePredicate, deprecationAlternatePackageRootNode);

// assert id to alternate package root node
graph.Assert(
deprecationAlternatePackageRootNode,
graph.CreateUriNode(Schema.Predicates.Id),
graph.CreateLiteralNode(Deprecation.AlternatePackageId));

// assert version range to alternate package root node
graph.Assert(
deprecationAlternatePackageRootNode,
graph.CreateUriNode(Schema.Predicates.Range),
graph.CreateLiteralNode(Deprecation.AlternatePackageRange));
}
}

return graph;
}

Expand Down Expand Up @@ -143,6 +198,11 @@ public override StorageContent CreateContent(CatalogContext context)
graph.Assert(resource.Subject, timeStampPredicate, graph.CreateLiteralNode(TimeStamp.ToString("O"), Schema.DataTypes.DateTime));
graph.Assert(resource.Subject, commitIdPredicate, graph.CreateLiteralNode(CommitId.ToString()));

if (graph.GetTriples(Schema.Predicates.Deprecation).Count() > 1)
{
throw new ArgumentException("Package catalog items can only have a single deprecation.");
}

// create JSON content
JObject frame = context.GetJsonLdContext("context.PackageDetails.json", GetItemType());

Expand Down
61 changes: 61 additions & 0 deletions src/Catalog/PackageDeprecationItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;

namespace NuGet.Services.Metadata.Catalog
{
public class PackageDeprecationItem
{
/// <param name="reasons">The list of reasons a package was deprecated.</param>
/// <param name="message">An additional message associated with a package, if one exists.</param>
/// <param name="alternatePackageId">
/// The ID of a package that can be used alternatively. Must be specified if <paramref name="alternatePackageRange"/> is specified.
/// </param>
/// <param name="alternatePackageRange">
/// A string representing the version range of a package that can be used alternatively. Must be specified if <paramref name="alternatePackageId"/> is specified.
/// </param>
public PackageDeprecationItem(
IReadOnlyList<string> reasons,
string message,
string alternatePackageId,
string alternatePackageRange)
{
if (reasons == null)
{
throw new ArgumentNullException(nameof(reasons));
}

if (!reasons.Any())
{
throw new ArgumentException(nameof(reasons));
}

Reasons = reasons;
Message = message;
AlternatePackageId = alternatePackageId;
AlternatePackageRange = alternatePackageRange;

if (AlternatePackageId == null && AlternatePackageRange != null)
{
throw new ArgumentException(
"Cannot specify an alternate package version range if an alternate package ID is not provided.",
nameof(AlternatePackageRange));
}

if (AlternatePackageId != null && AlternatePackageRange == null)
{
throw new ArgumentException(
"Cannot specify an alternate package ID if an alternate package version range is not provided.",
nameof(AlternatePackageId));
}
}

public IReadOnlyList<string> Reasons { get; }
public string Message { get; }
public string AlternatePackageId { get; }
public string AlternatePackageRange { get; }
}
}
6 changes: 6 additions & 0 deletions src/Catalog/Schema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ public static class Predicates
public static readonly Uri LicenseFile = new Uri(Prefixes.NuGet + "licenseFile");

public static readonly Uri IconFile = new Uri(Prefixes.NuGet + "iconFile");

public static readonly Uri Deprecation = new Uri(Prefixes.NuGet + "deprecation");

public static readonly Uri Reasons = new Uri(Prefixes.NuGet + "reasons");
public static readonly Uri Message = new Uri(Prefixes.NuGet + "message");
public static readonly Uri AlternatePackage = new Uri(Prefixes.NuGet + "alternatePackage");
}
}
}
3 changes: 2 additions & 1 deletion src/Catalog/context/PackageDetails.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"published": { "@type": "xsd:dateTime" },
"created": { "@type": "xsd:dateTime" },
"lastEdited" : { "@type" : "xsd:dateTime" },
"catalog:commitTimeStamp" : { "@type" : "xsd:dateTime" }
"catalog:commitTimeStamp" : { "@type" : "xsd:dateTime" },
"reasons" : { "@container" : "@set" }
}
}
1 change: 1 addition & 0 deletions tests/CatalogTests/CatalogTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
<Compile Include="Helpers\UtilsTests.cs" />
<Compile Include="PackageCatalogItemCreatorTests.cs" />
<Compile Include="PackageCatalogItemTests.cs" />
<Compile Include="PackageDeprecationItemTests.cs" />
<Compile Include="PackageEntryTests.cs" />
<Compile Include="Persistence\AggregateStorageTests.cs" />
<Compile Include="Persistence\AzureCloudBlockBlobTests.cs" />
Expand Down
73 changes: 66 additions & 7 deletions tests/CatalogTests/Helpers/FeedHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,36 @@ public async Task DownloadMetadata2CatalogAsync_WhenCreatedPackagesIsTrue_WithNo
}
}

public enum PackageDeprecationItemState
{
NotDeprecated,
DeprecatedWithSingleReason,
DeprecatedWithMessage,
DeprecatedWithAlternate
}

public static IEnumerable<object[]> DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage_Data
{
get
{
foreach (var createdPackages in new[] { false, true })
{
foreach (var updateCreatedFromEdited in new[] { false, true })
{
foreach (var deprecationState in Enum.GetValues(typeof(PackageDeprecationItemState)).Cast<PackageDeprecationItemState>())
{
yield return new object[] { createdPackages, updateCreatedFromEdited, deprecationState };
}
}
}
}
}

[Theory]
[InlineData(true, true)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(false, false)]
public async Task DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage(bool createdPackages, bool updateCreatedFromEdited)
[MemberData(nameof(DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage_Data))]
public async Task DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage(bool createdPackages, bool updateCreatedFromEdited, PackageDeprecationItemState deprecationState)
{
// Arrange
using (var test = new DownloadMetadata2CatalogAsyncTest())
{
test.CreatedPackages = createdPackages;
Expand All @@ -321,11 +344,13 @@ public async Task DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage(bo

NupkgMetadata nupkgMetadata = GetNupkgMetadata("Newtonsoft.Json.9.0.2-beta1.nupkg");

var deprecationItem = GetPackageDeprecationItemFromState(deprecationState);
var packageCatalogItem = new PackageCatalogItem(
nupkgMetadata,
test.FeedPackageDetails.CreatedDate,
test.FeedPackageDetails.LastEditedDate,
test.FeedPackageDetails.PublishedDate);
test.FeedPackageDetails.PublishedDate,
deprecation: deprecationItem);

test.PackageCatalogItemCreator.Setup(x => x.CreateAsync(
It.Is<FeedPackageDetails>(details => details == test.FeedPackageDetails),
Expand Down Expand Up @@ -357,8 +382,10 @@ public async Task DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage(bo
It.Is<TimeSpan>(duration => duration > TimeSpan.Zero),
It.Is<Uri>(uri => uri == test.CatalogIndexUri)));

// Act
var result = await test.DownloadMetadata2CatalogAsync();

// Assert
Assert.Equal(test.UtcNow, result);

Assert.Equal(3, blobs.Count);
Expand All @@ -381,7 +408,9 @@ public async Task DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage(bo
packageCatalogItem.TimeStamp,
packageCatalogItem.CreatedDate.Value,
packageCatalogItem.LastEditedDate.Value,
packageCatalogItem.PublishedDate.Value);
packageCatalogItem.PublishedDate.Value,
deprecationItem);

var actualContent = JObject.Parse(stringContent.Content);

Assert.Equal(expectedContent.ToString(), actualContent.ToString());
Expand Down Expand Up @@ -427,6 +456,36 @@ public async Task DownloadMetadata2CatalogAsync_WithOnePackage_UpdatesStorage(bo
}
}

private static PackageDeprecationItem GetPackageDeprecationItemFromState(PackageDeprecationItemState deprecationState)
{
if (deprecationState == PackageDeprecationItemState.NotDeprecated)
{
return null;
}

var reasons = new[] { "first", "second" };
string message = null;
string altId = null;
string altVersion = null;
if (deprecationState == PackageDeprecationItemState.DeprecatedWithSingleReason)
{
reasons = reasons.Take(1).ToArray();
}

if (deprecationState == PackageDeprecationItemState.DeprecatedWithMessage)
{
message = "this is the message";
}

if (deprecationState == PackageDeprecationItemState.DeprecatedWithAlternate)
{
altId = "theId";
altVersion = "[2.4.5, 2.4.5]";
}

return new PackageDeprecationItem(reasons, message, altId, altVersion);
}

private Task<IList<FeedPackageDetails>> TestGetPackagesAsync(IEnumerable<ODataPackage> oDataPackages)
{
return FeedHelpers.GetPackages(
Expand Down
41 changes: 41 additions & 0 deletions tests/CatalogTests/PackageCatalogItemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
using Moq;
using NuGet.Services.Metadata.Catalog;
using VDS.RDF;
using Xunit;
Expand Down Expand Up @@ -109,6 +110,46 @@ public void CreateContent_HasExpectedPageContent(string packageName, string id,
Assert.Equal(version, triples[1].Object.ToString());
}

[Fact]
public void CreateContent_ThrowsIfMultipleDeprecationTriples()
{
var packageDetails = Schema.DataTypes.PackageDetails;
var catalogItemMock = new Mock<PackageCatalogItem>(null, null, null, null, null, null, null)
{
CallBase = true
};

var context = new CatalogContext();

catalogItemMock
.Setup(x => x.GetItemType())
.Returns(packageDetails);

var graph = new Graph();
var subject = graph.CreateBlankNode();
graph.Assert(
subject,
graph.CreateUriNode(Schema.Predicates.Type),
graph.CreateUriNode(packageDetails));

graph.Assert(
subject,
graph.CreateUriNode(Schema.Predicates.Deprecation),
graph.CreateLiteralNode("deprecation1"));

graph.Assert(
subject,
graph.CreateUriNode(Schema.Predicates.Deprecation),
graph.CreateLiteralNode("deprecation2"));

catalogItemMock
.Setup(x => x.CreateContentGraph(context))
.Returns(graph);

Assert.Throws<ArgumentException>(
() => catalogItemMock.Object.CreateContent(context));
}

private static CatalogItem CreateCatalogItem(string packageName)
{
var path = Path.GetFullPath(Path.Combine("TestData", $"{packageName}.nupkg"));
Expand Down
Loading

0 comments on commit 0c09757

Please sign in to comment.