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; + } + } +}