diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj
index eb22db99f4..3a61990f72 100644
--- a/src/NuGetGallery/NuGetGallery.csproj
+++ b/src/NuGetGallery/NuGetGallery.csproj
@@ -762,6 +762,10 @@
+
+
+
+
diff --git a/src/NuGetGallery/OData/Serializers/FeedPackageAnnotationStrategy.cs b/src/NuGetGallery/OData/Serializers/FeedPackageAnnotationStrategy.cs
new file mode 100644
index 0000000000..2290e9ed60
--- /dev/null
+++ b/src/NuGetGallery/OData/Serializers/FeedPackageAnnotationStrategy.cs
@@ -0,0 +1,57 @@
+// 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 Microsoft.Data.OData;
+using System;
+using System.Net.Http;
+using System.Web.Http.Routing;
+
+namespace NuGetGallery.OData.Serializers
+{
+ internal abstract class FeedPackageAnnotationStrategy
+ : IFeedPackageAnnotationStrategy
+ {
+ private readonly string _contentType;
+
+ protected FeedPackageAnnotationStrategy(string contentType)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ {
+ throw new ArgumentException(nameof(contentType));
+ }
+
+ _contentType = contentType;
+ }
+
+ protected string ContentType => _contentType;
+
+ public bool CanAnnotate(object entityInstance)
+ {
+ return entityInstance != null && entityInstance is TFeedPackage;
+ }
+
+ public abstract void Annotate(HttpRequestMessage request, ODataEntry entry, object entityInstance);
+
+ protected static Uri BuildLinkForStreamProperty(string routePrefix, string id, string version, HttpRequestMessage request)
+ {
+ var url = new UrlHelper(request);
+ var result = url.Route(routePrefix + RouteName.DownloadPackage, new { id, version });
+
+ var builder = new UriBuilder(request.RequestUri);
+ builder.Path = version == null ? EnsureTrailingSlash(result) : result;
+ builder.Query = string.Empty;
+
+ return builder.Uri;
+ }
+
+ private static string EnsureTrailingSlash(string url)
+ {
+ if (url != null && !url.EndsWith("/", StringComparison.OrdinalIgnoreCase))
+ {
+ return url + '/';
+ }
+
+ return url;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/OData/Serializers/IFeedPackageAnnotationStrategy.cs b/src/NuGetGallery/OData/Serializers/IFeedPackageAnnotationStrategy.cs
new file mode 100644
index 0000000000..44500389be
--- /dev/null
+++ b/src/NuGetGallery/OData/Serializers/IFeedPackageAnnotationStrategy.cs
@@ -0,0 +1,14 @@
+// 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 Microsoft.Data.OData;
+using System.Net.Http;
+
+namespace NuGetGallery.OData.Serializers
+{
+ internal interface IFeedPackageAnnotationStrategy
+ {
+ bool CanAnnotate(object entityInstance);
+ void Annotate(HttpRequestMessage request, ODataEntry entry, object entityInstance);
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/OData/Serializers/NuGetEntityTypeSerializer.cs b/src/NuGetGallery/OData/Serializers/NuGetEntityTypeSerializer.cs
index a5a48e67aa..28cdec99e5 100644
--- a/src/NuGetGallery/OData/Serializers/NuGetEntityTypeSerializer.cs
+++ b/src/NuGetGallery/OData/Serializers/NuGetEntityTypeSerializer.cs
@@ -1,13 +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.Net.Http;
+using Microsoft.Data.OData;
+using System.Collections.Generic;
using System.Web.Http.OData;
using System.Web.Http.OData.Formatter.Serialization;
-using System.Web.Http.Routing;
-using Microsoft.Data.OData;
-using Microsoft.Data.OData.Atom;
namespace NuGetGallery.OData.Serializers
{
@@ -15,139 +12,37 @@ public class NuGetEntityTypeSerializer
: ODataEntityTypeSerializer
{
private readonly string _contentType;
+ private readonly IReadOnlyCollection _annotationStrategies;
public NuGetEntityTypeSerializer(ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
_contentType = "application/zip";
+ _annotationStrategies = new List
+ {
+ new V1FeedPackageAnnotationStrategy(_contentType),
+ new V2FeedPackageAnnotationStrategy(_contentType)
+ };
}
public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
{
var entry = base.CreateEntry(selectExpandNode, entityInstanceContext);
- TryAnnotateV1FeedPackage(entry, entityInstanceContext);
- TryAnnotateV2FeedPackage(entry, entityInstanceContext);
-
- return entry;
- }
-
- private void TryAnnotateV1FeedPackage(ODataEntry entry, EntityInstanceContext entityInstanceContext)
- {
- var instance = entityInstanceContext.EntityInstance as V1FeedPackage;
- if (instance != null)
- {
- // Set Atom entry metadata
- var atomEntryMetadata = new AtomEntryMetadata();
- atomEntryMetadata.Title = instance.Title;
- if (!string.IsNullOrEmpty(instance.Authors))
- {
- atomEntryMetadata.Authors = new[] { new AtomPersonMetadata { Name = instance.Authors } };
- }
- if (instance.LastUpdated > DateTime.MinValue)
- {
- atomEntryMetadata.Updated = instance.LastUpdated;
- }
- if (!string.IsNullOrEmpty(instance.Summary))
- {
- atomEntryMetadata.Summary = instance.Summary;
- }
- entry.SetAnnotation(atomEntryMetadata);
-
- // Add package download link
- entry.MediaResource = new ODataStreamReferenceValue
- {
- ContentType = ContentType,
- ReadLink = BuildLinkForStreamProperty("v1", instance.Id, instance.Version, entityInstanceContext.Request)
- };
- }
- }
-
- private void TryAnnotateV2FeedPackage(ODataEntry entry, EntityInstanceContext entityInstanceContext)
- {
- var instance = entityInstanceContext.EntityInstance as V2FeedPackage;
- if (instance != null)
+ foreach (var annotationStrategy in _annotationStrategies)
{
- // Patch links to use normalized versions
- var normalizedVersion = NuGetVersionNormalizer.Normalize(instance.Version);
- NormalizeNavigationLinks(entry, entityInstanceContext.Request, instance, normalizedVersion);
-
- // Set Atom entry metadata
- var atomEntryMetadata = new AtomEntryMetadata();
- atomEntryMetadata.Title = instance.Id;
- if (!string.IsNullOrEmpty(instance.Authors))
- {
- atomEntryMetadata.Authors = new[] { new AtomPersonMetadata { Name = instance.Authors } };
- }
- if (instance.LastUpdated > DateTime.MinValue)
+ if (annotationStrategy.CanAnnotate(entityInstanceContext.EntityInstance))
{
- atomEntryMetadata.Updated = instance.LastUpdated;
+ annotationStrategy.Annotate(entityInstanceContext.Request, entry, entityInstanceContext.EntityInstance);
}
- if (!string.IsNullOrEmpty(instance.Summary))
- {
- atomEntryMetadata.Summary = instance.Summary;
- }
- entry.SetAnnotation(atomEntryMetadata);
-
- // Add package download link
- entry.MediaResource = new ODataStreamReferenceValue
- {
- ContentType = ContentType,
- ReadLink = BuildLinkForStreamProperty("v2", instance.Id, normalizedVersion, entityInstanceContext.Request)
- };
- }
- }
-
- private static void NormalizeNavigationLinks(ODataEntry entry, HttpRequestMessage request, V2FeedPackage instance, string normalizedVersion)
- {
- var idLink = BuildIdLink(instance.Id, normalizedVersion, request);
-
- if (entry.ReadLink != null)
- {
- entry.ReadLink = idLink;
- }
-
- if (entry.EditLink != null)
- {
- entry.EditLink = idLink;
- }
-
- if (entry.Id != null)
- {
- entry.Id = idLink.ToString();
}
+
+ return entry;
}
public string ContentType
{
get { return _contentType; }
}
-
- private static Uri BuildLinkForStreamProperty(string routePrefix, string id, string version, HttpRequestMessage request)
- {
- var url = new UrlHelper(request);
- var result = url.Route(routePrefix + RouteName.DownloadPackage, new { id, version });
-
- var builder = new UriBuilder(request.RequestUri);
- builder.Path = version == null ? EnsureTrailingSlash(result) : result;
- builder.Query = string.Empty;
-
- return builder.Uri;
- }
-
- private static Uri BuildIdLink(string id, string version, HttpRequestMessage request)
- {
- return new Uri($"{request.RequestUri.Scheme}://{request.RequestUri.Host}{request.RequestUri.LocalPath}(Id='{id}',Version='{version}')");
- }
-
- private static string EnsureTrailingSlash(string url)
- {
- if (url != null && !url.EndsWith("/", StringComparison.OrdinalIgnoreCase))
- {
- return url + '/';
- }
-
- return url;
- }
}
}
\ No newline at end of file
diff --git a/src/NuGetGallery/OData/Serializers/V1FeedPackageAnnotationStrategy.cs b/src/NuGetGallery/OData/Serializers/V1FeedPackageAnnotationStrategy.cs
new file mode 100644
index 0000000000..7c07cb8562
--- /dev/null
+++ b/src/NuGetGallery/OData/Serializers/V1FeedPackageAnnotationStrategy.cs
@@ -0,0 +1,56 @@
+// 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 Microsoft.Data.OData;
+using Microsoft.Data.OData.Atom;
+using System;
+using System.Net.Http;
+
+namespace NuGetGallery.OData.Serializers
+{
+ internal class V1FeedPackageAnnotationStrategy
+ : FeedPackageAnnotationStrategy
+ {
+ public V1FeedPackageAnnotationStrategy(string contentType)
+ : base(contentType)
+ {
+ }
+
+ public override void Annotate(HttpRequestMessage request, ODataEntry entry, object entityInstance)
+ {
+ var feedPackage = entityInstance as V1FeedPackage;
+ if (feedPackage == null)
+ {
+ return;
+ }
+
+ // Set Atom entry metadata
+ var atomEntryMetadata = new AtomEntryMetadata();
+ atomEntryMetadata.Title = feedPackage.Title;
+
+ if (!string.IsNullOrEmpty(feedPackage.Authors))
+ {
+ atomEntryMetadata.Authors = new[] { new AtomPersonMetadata { Name = feedPackage.Authors } };
+ }
+
+ if (feedPackage.LastUpdated > DateTime.MinValue)
+ {
+ atomEntryMetadata.Updated = feedPackage.LastUpdated;
+ }
+
+ if (!string.IsNullOrEmpty(feedPackage.Summary))
+ {
+ atomEntryMetadata.Summary = feedPackage.Summary;
+ }
+
+ entry.SetAnnotation(atomEntryMetadata);
+
+ // Add package download link
+ entry.MediaResource = new ODataStreamReferenceValue
+ {
+ ContentType = ContentType,
+ ReadLink = BuildLinkForStreamProperty("v1", feedPackage.Id, feedPackage.Version, request)
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NuGetGallery/OData/Serializers/V2FeedPackageAnnotationStrategy.cs b/src/NuGetGallery/OData/Serializers/V2FeedPackageAnnotationStrategy.cs
new file mode 100644
index 0000000000..8f812f698e
--- /dev/null
+++ b/src/NuGetGallery/OData/Serializers/V2FeedPackageAnnotationStrategy.cs
@@ -0,0 +1,93 @@
+// 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 Microsoft.Data.OData;
+using Microsoft.Data.OData.Atom;
+using System;
+using System.Net.Http;
+
+namespace NuGetGallery.OData.Serializers
+{
+ internal class V2FeedPackageAnnotationStrategy
+ : FeedPackageAnnotationStrategy
+ {
+ public V2FeedPackageAnnotationStrategy(string contentType)
+ : base(contentType)
+ {
+ }
+
+ public override void Annotate(HttpRequestMessage request, ODataEntry entry, object entityInstance)
+ {
+ var feedPackage = entityInstance as V2FeedPackage;
+ if (feedPackage == null)
+ {
+ return;
+ }
+
+ // Patch links to use normalized versions
+ var normalizedVersion = NuGetVersionNormalizer.Normalize(feedPackage.Version);
+ NormalizeNavigationLinks(entry, request, feedPackage, normalizedVersion);
+
+ // Set Atom entry metadata
+ var atomEntryMetadata = new AtomEntryMetadata();
+ atomEntryMetadata.Title = feedPackage.Id;
+
+ if (!string.IsNullOrEmpty(feedPackage.Authors))
+ {
+ atomEntryMetadata.Authors = new[] { new AtomPersonMetadata { Name = feedPackage.Authors } };
+ }
+
+ if (feedPackage.LastUpdated > DateTime.MinValue)
+ {
+ atomEntryMetadata.Updated = feedPackage.LastUpdated;
+ }
+
+ if (!string.IsNullOrEmpty(feedPackage.Summary))
+ {
+ atomEntryMetadata.Summary = feedPackage.Summary;
+ }
+
+ entry.SetAnnotation(atomEntryMetadata);
+
+ // Add package download link
+ entry.MediaResource = new ODataStreamReferenceValue
+ {
+ ContentType = ContentType,
+ ReadLink = BuildLinkForStreamProperty("v2", feedPackage.Id, normalizedVersion, request)
+ };
+ }
+
+ private static void NormalizeNavigationLinks(ODataEntry entry, HttpRequestMessage request, V2FeedPackage instance, string normalizedVersion)
+ {
+ if (entry.Id == null && entry.ReadLink == null && entry.EditLink == null)
+ {
+ return;
+ }
+
+ var idLink = BuildIdLink(instance.Id, normalizedVersion, request);
+
+ if (entry.ReadLink != null)
+ {
+ entry.ReadLink = idLink;
+ }
+
+ if (entry.EditLink != null)
+ {
+ entry.EditLink = idLink;
+ }
+
+ if (entry.Id != null)
+ {
+ entry.Id = idLink.ToString();
+ }
+ }
+
+ private static Uri BuildIdLink(string id, string version, HttpRequestMessage request)
+ {
+ var packageIdentityQuery = $"(Id='{id}',Version='{version}')";
+ var localPath = request.RequestUri.LocalPath.Replace(packageIdentityQuery, string.Empty);
+
+ return new Uri($"{request.RequestUri.Scheme}://{request.RequestUri.Host}{localPath}{packageIdentityQuery}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj
index 8d2e699f0d..38c0c45ede 100644
--- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj
+++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj
@@ -405,6 +405,8 @@
+
+
diff --git a/tests/NuGetGallery.Facts/OData/Serializers/V1FeedPackageAnnotationStrategyFacts.cs b/tests/NuGetGallery.Facts/OData/Serializers/V1FeedPackageAnnotationStrategyFacts.cs
new file mode 100644
index 0000000000..1a92250dc1
--- /dev/null
+++ b/tests/NuGetGallery.Facts/OData/Serializers/V1FeedPackageAnnotationStrategyFacts.cs
@@ -0,0 +1,136 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Data.OData;
+using Microsoft.Data.OData.Atom;
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Web.Http;
+using System.Web.Http.Routing;
+using System.Web.Mvc;
+using Xunit;
+
+namespace NuGetGallery.OData.Serializers
+{
+ public class V1FeedPackageAnnotationStrategyFacts
+ {
+ private readonly string _contentType = "application/zip";
+
+ [Fact]
+ public void CanNotAnnotateNullObject()
+ {
+ // Arrange
+ var annotationStrategy = new V1FeedPackageAnnotationStrategy(_contentType);
+
+ // Act
+ var canAnnotate = annotationStrategy.CanAnnotate(null);
+
+ // Assert
+ Assert.False(canAnnotate);
+ }
+
+ [Fact]
+ public void CanNotAnnotateV2FeedPackage()
+ {
+ // Arrange
+ var v2FeedPackage = new V2FeedPackage();
+ var annotationStrategy = new V1FeedPackageAnnotationStrategy(_contentType);
+
+ // Act
+ var canAnnotate = annotationStrategy.CanAnnotate(v2FeedPackage);
+
+ // Assert
+ Assert.False(canAnnotate);
+ }
+
+ [Fact]
+ public void SetsAtomEntryMetadataAnnotation()
+ {
+ // Arrange
+ var v1FeedPackage = new V1FeedPackage()
+ {
+ Id = "SomePackageId",
+ Version = "1.0.0",
+ Title = "Title",
+ Authors = ".NET Foundation",
+ LastUpdated = DateTime.UtcNow,
+ Summary = "Summary"
+ };
+ var annotationStrategy = new V1FeedPackageAnnotationStrategy(_contentType);
+ var oDataEntry = new ODataEntry();
+ var request = CreateHttpRequestMessage();
+
+ var expectedAtomEntryMetadataAnnotation = new AtomEntryMetadata()
+ {
+ Title = v1FeedPackage.Title,
+ Authors = new[] { new AtomPersonMetadata { Name = v1FeedPackage.Authors } },
+ Updated = v1FeedPackage.LastUpdated,
+ Summary = v1FeedPackage.Summary
+ };
+
+ // Act
+ annotationStrategy.Annotate(request, oDataEntry, v1FeedPackage);
+
+ var actualAtomEntryMetadataAnnotation = oDataEntry.GetAnnotation();
+
+ // Assert
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Title.Text, actualAtomEntryMetadataAnnotation.Title.Text);
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Summary.Text, actualAtomEntryMetadataAnnotation.Summary.Text);
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Authors.Single().Name, actualAtomEntryMetadataAnnotation.Authors.Single().Name);
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Updated, actualAtomEntryMetadataAnnotation.Updated);
+ }
+
+ [Fact]
+ public void SetsMediaResourceAnnotation()
+ {
+ // Arrange
+ var v1FeedPackage = new V1FeedPackage()
+ {
+ Id = "SomePackageId",
+ Version = "1.0.0",
+ Title = "Title",
+ Authors = ".NET Foundation",
+ LastUpdated = DateTime.UtcNow,
+ Summary = "Summary"
+ };
+ var annotationStrategy = new V1FeedPackageAnnotationStrategy(_contentType);
+ var oDataEntry = new ODataEntry();
+ var request = CreateHttpRequestMessage();
+
+ // Act
+ annotationStrategy.Annotate(request, oDataEntry, v1FeedPackage);
+
+ // Assert
+ Assert.Equal(_contentType, oDataEntry.MediaResource.ContentType);
+ Assert.Equal("https://localhost/api/v1/package/SomePackageId/1.0.0", oDataEntry.MediaResource.ReadLink.ToString());
+ }
+
+ private static HttpRequestMessage CreateHttpRequestMessage()
+ {
+ var downloadPackageRoute = new HttpRoute(
+ "api/v1/package/{id}/{version}",
+ defaults: new HttpRouteValueDictionary(
+ new
+ {
+ controller = "Api",
+ action = "GetPackageApi",
+ version = UrlParameter.Optional
+ }),
+ constraints: new HttpRouteValueDictionary(
+ new
+ {
+ httpMethod = new HttpMethodConstraint(HttpMethod.Get)
+ }));
+
+ var routeCollection = new HttpRouteCollection();
+ routeCollection.Add("v1" + RouteName.DownloadPackage, downloadPackageRoute);
+
+ var httpConfiguration = new HttpConfiguration(routeCollection);
+
+ var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/api/v1/Packages");
+ request.SetConfiguration(httpConfiguration);
+ return request;
+ }
+ }
+}
diff --git a/tests/NuGetGallery.Facts/OData/Serializers/V2FeedPackageAnnotationStrategyFacts.cs b/tests/NuGetGallery.Facts/OData/Serializers/V2FeedPackageAnnotationStrategyFacts.cs
new file mode 100644
index 0000000000..843eade501
--- /dev/null
+++ b/tests/NuGetGallery.Facts/OData/Serializers/V2FeedPackageAnnotationStrategyFacts.cs
@@ -0,0 +1,170 @@
+// 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 Microsoft.Data.OData;
+using Microsoft.Data.OData.Atom;
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Web.Http;
+using System.Web.Http.Routing;
+using System.Web.Mvc;
+using Xunit;
+
+namespace NuGetGallery.OData.Serializers
+{
+ public class V2FeedPackageAnnotationStrategyFacts
+ {
+ private readonly string _contentType = "application/zip";
+
+ [Fact]
+ public void CanNotAnnotateNullObject()
+ {
+ // Arrange
+ var annotationStrategy = new V2FeedPackageAnnotationStrategy(_contentType);
+
+ // Act
+ var canAnnotate = annotationStrategy.CanAnnotate(null);
+
+ // Assert
+ Assert.False(canAnnotate);
+ }
+
+ [Fact]
+ public void CanNotAnnotateV1FeedPackage()
+ {
+ // Arrange
+ var v1FeedPackage = new V1FeedPackage();
+ var annotationStrategy = new V2FeedPackageAnnotationStrategy(_contentType);
+
+ // Act
+ var canAnnotate = annotationStrategy.CanAnnotate(v1FeedPackage);
+
+ // Assert
+ Assert.False(canAnnotate);
+ }
+
+ [Fact]
+ public void SetsAtomEntryMetadataAnnotation()
+ {
+ // Arrange
+ var v2FeedPackage = new V2FeedPackage()
+ {
+ Id = "SomePackageId",
+ Version = "1.0.0",
+ Title = "Title",
+ Authors = ".NET Foundation",
+ LastUpdated = DateTime.UtcNow,
+ Summary = "Summary"
+ };
+ var annotationStrategy = new V2FeedPackageAnnotationStrategy(_contentType);
+ var oDataEntry = new ODataEntry();
+ var request = CreateHttpRequestMessage("https://localhost/api/v2/Packages");
+
+ var expectedAtomEntryMetadataAnnotation = new AtomEntryMetadata()
+ {
+ Title = v2FeedPackage.Id,
+ Authors = new[] { new AtomPersonMetadata { Name = v2FeedPackage.Authors } },
+ Updated = v2FeedPackage.LastUpdated,
+ Summary = v2FeedPackage.Summary
+ };
+
+ // Act
+ annotationStrategy.Annotate(request, oDataEntry, v2FeedPackage);
+
+ var actualAtomEntryMetadataAnnotation = oDataEntry.GetAnnotation();
+
+ // Assert
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Title.Text, actualAtomEntryMetadataAnnotation.Title.Text);
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Summary.Text, actualAtomEntryMetadataAnnotation.Summary.Text);
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Authors.Single().Name, actualAtomEntryMetadataAnnotation.Authors.Single().Name);
+ Assert.Equal(expectedAtomEntryMetadataAnnotation.Updated, actualAtomEntryMetadataAnnotation.Updated);
+ }
+
+ [Theory]
+ [InlineData("https://localhost/api/v2/Packages")]
+ [InlineData("https://localhost/api/v2/Packages(Id='SomePackageId',Version='1.0.0')")]
+ public void NormalizesNavigationLinksWhenSet(string requestUri)
+ {
+ // Arrange
+ var v2FeedPackage = new V2FeedPackage()
+ {
+ Id = "SomePackageId",
+ Version = "1.0.0",
+ Title = "Title",
+ Authors = ".NET Foundation",
+ LastUpdated = DateTime.UtcNow,
+ Summary = "Summary"
+ };
+ var annotationStrategy = new V2FeedPackageAnnotationStrategy(_contentType);
+ var oDataEntry = new ODataEntry();
+ var dummyIdLink = new Uri("https://localhost");
+ oDataEntry.Id = dummyIdLink.ToString();
+ oDataEntry.EditLink = dummyIdLink;
+ oDataEntry.ReadLink = dummyIdLink;
+
+ var request = CreateHttpRequestMessage(requestUri);
+ var expectedNormalizedLink = "https://localhost/api/v2/Packages(Id='SomePackageId',Version='1.0.0')";
+
+ // Act
+ annotationStrategy.Annotate(request, oDataEntry, v2FeedPackage);
+
+ // Assert
+ Assert.Equal(expectedNormalizedLink, oDataEntry.ReadLink.ToString());
+ Assert.Equal(expectedNormalizedLink, oDataEntry.EditLink.ToString());
+ Assert.Equal(expectedNormalizedLink, oDataEntry.Id.ToString());
+ }
+
+ [Fact]
+ public void SetsMediaResourceAnnotation()
+ {
+ // Arrange
+ var v2FeedPackage = new V2FeedPackage()
+ {
+ Id = "SomePackageId",
+ Version = "1.0.0",
+ Title = "Title",
+ Authors = ".NET Foundation",
+ LastUpdated = DateTime.UtcNow,
+ Summary = "Summary"
+ };
+ var annotationStrategy = new V2FeedPackageAnnotationStrategy(_contentType);
+ var oDataEntry = new ODataEntry();
+ var request = CreateHttpRequestMessage("https://localhost/api/v2/Packages");
+
+ // Act
+ annotationStrategy.Annotate(request, oDataEntry, v2FeedPackage);
+
+ // Assert
+ Assert.Equal(_contentType, oDataEntry.MediaResource.ContentType);
+ Assert.Equal("https://localhost/api/v2/package/SomePackageId/1.0.0", oDataEntry.MediaResource.ReadLink.ToString());
+ }
+
+ private static HttpRequestMessage CreateHttpRequestMessage(string requestUri)
+ {
+ var downloadPackageRoute = new HttpRoute(
+ "api/v2/package/{id}/{version}",
+ defaults: new HttpRouteValueDictionary(
+ new
+ {
+ controller = "Api",
+ action = "GetPackageApi",
+ version = UrlParameter.Optional
+ }),
+ constraints: new HttpRouteValueDictionary(
+ new
+ {
+ httpMethod = new HttpMethodConstraint(HttpMethod.Get)
+ }));
+
+ var routeCollection = new HttpRouteCollection();
+ routeCollection.Add("v2" + RouteName.DownloadPackage, downloadPackageRoute);
+
+ var httpConfiguration = new HttpConfiguration(routeCollection);
+
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ request.SetConfiguration(httpConfiguration);
+ return request;
+ }
+ }
+}