Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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.

#nullable enable

using System;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Protocol.Core.Types;
using NuGet.Protocol.Resources;

namespace NuGet.Protocol.Providers
{
/// <summary>NuGet.Protocol resource provider for <see cref="OwnerDetailsUriTemplateResourceV3"/> in V3 HTTP feeds.</summary>
/// <remarks>When successful, returns an instance of <see cref="OwnerDetailsUriTemplateResourceV3"/>.</remarks>
public class OwnerDetailsUriResourceV3Provider : ResourceProvider
{
public OwnerDetailsUriResourceV3Provider()
: base(typeof(OwnerDetailsUriTemplateResourceV3),
nameof(OwnerDetailsUriTemplateResourceV3),
NuGetResourceProviderPositions.Last)
{
}

/// <inheritdoc cref="ResourceProvider.TryCreate(SourceRepository, CancellationToken)"/>
public override async Task<Tuple<bool, INuGetResource?>> TryCreate(SourceRepository source, CancellationToken token)
{
OwnerDetailsUriTemplateResourceV3? resource = null;
ServiceIndexResourceV3? serviceIndex = await source.GetResourceAsync<ServiceIndexResourceV3>(token);
if (serviceIndex != null)
{
Uri? uriTemplate = serviceIndex.GetServiceEntryUri(ServiceTypes.OwnerDetailsUriTemplate);

if (uriTemplate != null)
{
resource = OwnerDetailsUriTemplateResourceV3.CreateOrNull(uriTemplate);
Comment thread
donnie-msft marked this conversation as resolved.
}
}

return new Tuple<bool, INuGetResource?>(resource != null, resource);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#nullable enable
NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider
NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider.OwnerDetailsUriResourceV3Provider() -> void
NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3
NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3.GetUri(string! owner) -> System.Uri!
override NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider.TryCreate(NuGet.Protocol.Core.Types.SourceRepository! source, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Tuple<bool, NuGet.Protocol.Core.Types.INuGetResource?>!>!
static NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3.CreateOrNull(System.Uri! uriTemplate) -> NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3?
~NuGet.Protocol.Core.Types.IPackageSearchMetadata.OwnersList.get -> System.Collections.Generic.IReadOnlyList<string>
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.OwnersList.get -> System.Collections.Generic.IReadOnlyList<string>
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.OwnersList.set -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#nullable enable
NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider
NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider.OwnerDetailsUriResourceV3Provider() -> void
NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3
NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3.GetUri(string! owner) -> System.Uri!
override NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider.TryCreate(NuGet.Protocol.Core.Types.SourceRepository! source, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Tuple<bool, NuGet.Protocol.Core.Types.INuGetResource?>!>!
static NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3.CreateOrNull(System.Uri! uriTemplate) -> NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3?
~NuGet.Protocol.Core.Types.IPackageSearchMetadata.OwnersList.get -> System.Collections.Generic.IReadOnlyList<string>
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.OwnersList.get -> System.Collections.Generic.IReadOnlyList<string>
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.OwnersList.set -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#nullable enable
NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider
NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider.OwnerDetailsUriResourceV3Provider() -> void
NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3
NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3.GetUri(string! owner) -> System.Uri!
override NuGet.Protocol.Providers.OwnerDetailsUriResourceV3Provider.TryCreate(NuGet.Protocol.Core.Types.SourceRepository! source, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task<System.Tuple<bool, NuGet.Protocol.Core.Types.INuGetResource?>!>!
static NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3.CreateOrNull(System.Uri! uriTemplate) -> NuGet.Protocol.Resources.OwnerDetailsUriTemplateResourceV3?
~NuGet.Protocol.Core.Types.IPackageSearchMetadata.OwnersList.get -> System.Collections.Generic.IReadOnlyList<string>
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.OwnersList.get -> System.Collections.Generic.IReadOnlyList<string>
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.OwnersList.set -> void
Expand Down
1 change: 1 addition & 0 deletions src/NuGet.Core/NuGet.Protocol/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public virtual IEnumerable<Lazy<INuGetResourceProvider>> GetCoreV3()
yield return new Lazy<INuGetResourceProvider>(() => new PluginResourceProvider());
yield return new Lazy<INuGetResourceProvider>(() => new RepositorySignatureResourceProvider());
yield return new Lazy<INuGetResourceProvider>(() => new VulnerabilityInfoResourceV3Provider());
yield return new Lazy<INuGetResourceProvider>(() => new OwnerDetailsUriResourceV3Provider());

// Local repository providers
yield return new Lazy<INuGetResourceProvider>(() => new FindLocalPackagesResourceUnzippedProvider());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.

#nullable enable

using System;
using NuGet.Protocol.Core.Types;

namespace NuGet.Protocol.Resources
{
/// <summary>Owner Details Uri Template for NuGet V3 HTTP feeds.</summary>
/// <remarks>Not intended to be created directly. Use <see cref="SourceRepository.GetResourceAsync{T}(CancellationToken)"/>
/// with <see cref="OwnerDetailsUriTemplateResourceV3"/> for T, and typecast to this class.
public class OwnerDetailsUriTemplateResourceV3 : INuGetResource
{
private readonly string _template;

private OwnerDetailsUriTemplateResourceV3(string template)
{
_template = template ?? throw new ArgumentNullException(nameof(template));
}

/// <summary>
/// Creates the specified Owner Details Uri template provided by the server if it exists and is valid.
/// </summary>
/// <param name="uriTemplate">The Absolute Uri template provided by the server.</param>
/// <returns>A valid Owner Details Uri template, or null.</returns>
public static OwnerDetailsUriTemplateResourceV3? CreateOrNull(Uri uriTemplate)
{
if (uriTemplate is null)
{
throw new ArgumentNullException(nameof(uriTemplate));
}

if (uriTemplate.OriginalString.Length == 0
|| !uriTemplate.IsAbsoluteUri
|| !uriTemplate.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
return null;
}

return new OwnerDetailsUriTemplateResourceV3(uriTemplate.OriginalString);
}

/// <summary>
/// Gets a URL for viewing package Owner URL outside of Visual Studio. The URL will not be verified to exist.
/// </summary>
/// <param name="owner">The owner username.</param>
/// <returns>The first URL from the resource, with the URI template applied.</returns>
public Uri GetUri(string owner)
Comment thread
donnie-msft marked this conversation as resolved.
{
var uriString = _template
#if NETCOREAPP
.Replace("{owner}", owner, StringComparison.OrdinalIgnoreCase);
Comment thread
kartheekp-ms marked this conversation as resolved.
#else
.Replace("{owner}", owner);
#endif

return new Uri(uriString);
}
}
}
2 changes: 2 additions & 0 deletions src/NuGet.Core/NuGet.Protocol/ServiceTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static class ServiceTypes
public static readonly string Version500 = "/5.0.0";
public static readonly string Version510 = "/5.1.0";
internal const string Version670 = "/6.7.0";
internal const string Version6110 = "/6.11.0";

public static readonly string[] SearchQueryService = { "SearchQueryService" + Versioned, "SearchQueryService" + Version340, "SearchQueryService" + Version300beta };
public static readonly string[] RegistrationsBaseUrl = { $"RegistrationsBaseUrl{Versioned}", $"RegistrationsBaseUrl{Version360}", $"RegistrationsBaseUrl{Version340}", $"RegistrationsBaseUrl{Version300rc}", $"RegistrationsBaseUrl{Version300beta}", "RegistrationsBaseUrl" };
Expand All @@ -29,5 +30,6 @@ public static class ServiceTypes
public static readonly string[] RepositorySignatures = { "RepositorySignatures" + Version500, "RepositorySignatures" + Version490, "RepositorySignatures" + Version470 };
public static readonly string[] SymbolPackagePublish = { "SymbolPackagePublish" + Version490 };
internal static readonly string[] VulnerabilityInfo = { "VulnerabilityInfo" + Version670 };
internal static readonly string[] OwnerDetailsUriTemplate = { "OwnerDetailsUriTemplate" + Version6110 };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NuGet.Configuration;
using NuGet.Packaging;
using NuGet.Protocol.Core.Types;
using NuGet.Protocol.Providers;
using Xunit;

namespace NuGet.Protocol.Tests.Providers
{
public class OwnerDetailsUriResourceV3ProviderTests
{
[Fact]
public async Task TryCreate_NoResourceInServiceIndex_ReturnsFalseAsync()
{
// Arrange
var serviceIndexProvider = MockServiceIndexResourceV3Provider.Create();
var target = new OwnerDetailsUriResourceV3Provider();
var providers = new INuGetResourceProvider[] { serviceIndexProvider, target };

PackageSource packageSource = new PackageSource("https://nuget.test/v3/index.json");
SourceRepository sourceRepository = new SourceRepository(packageSource, providers);

// Act
Tuple<bool, INuGetResource?> result = await target.TryCreate(sourceRepository, CancellationToken.None);

// Assert
bool providerHandlesInputSource = result.Item1;
INuGetResource? resource = result.Item2;

providerHandlesInputSource.Should().BeFalse();
resource.Should().BeNull();
}

[Fact]
public async Task TryCreate_ResourceInServiceIndex_ReturnsTrueAsync()
{
// Arrange
var ownerDetailsResourceEntry = new ServiceIndexEntry(new Uri("https://nuget.test/profiles/{owner}?_src=template"), ServiceTypes.OwnerDetailsUriTemplate[0], MinClientVersionUtility.GetNuGetClientVersion());
var serviceIndexProvider = MockServiceIndexResourceV3Provider.Create();
var target = new OwnerDetailsUriResourceV3Provider();
var providers = new INuGetResourceProvider[] { serviceIndexProvider, target };

PackageSource packageSource = new PackageSource("https://nuget.test/v3/index.json");
SourceRepository sourceRepository = new SourceRepository(packageSource, providers);

// Act
Tuple<bool, INuGetResource?> result = await target.TryCreate(sourceRepository, CancellationToken.None);

// Assert
bool providerHandlesInputSource = result.Item1;
INuGetResource? resource = result.Item2;

providerHandlesInputSource.Should().BeFalse();
resource.Should().BeNull();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void Provider_WithDefaultProvider_ReturnsDefaultResourceProviders()

int actualCount = resourceProviders.Count();

Assert.Equal(47, actualCount);
Assert.Equal(48, actualCount);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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.

#nullable enable

using System;
using FluentAssertions;
using NuGet.Protocol.Resources;
using Xunit;

namespace NuGet.Protocol.Tests.Resources
{
public class OwnerDetailsUriTemplateResourceV3Tests
{
private readonly Uri _template = new Uri("https://nuget.test/profiles/{owner}?_src=template");
Comment thread
donnie-msft marked this conversation as resolved.

[Fact]
public void CreateOrNull_WhenNullTemplate_Throws()
{
// Arrange
Uri? template = null;

// Act & Assert
Assert.Throws<ArgumentNullException>(() => OwnerDetailsUriTemplateResourceV3.CreateOrNull(template!));
}

[Fact]
public void CreateOrNull_WhenTemplateNotAbsoluteUri_CreatesNullResource()
{
// Arrange
var template = new Uri("/owner/profile", UriKind.Relative);

// Act
var target = OwnerDetailsUriTemplateResourceV3.CreateOrNull(template);

// Assert
target.Should().BeNull();
}

[Fact]
public void CreateOrNull_WhenTemplateNotHttps_CreatesNullResource()
{
// Arrange
var template = new Uri("http://nuget.test/profiles/{owner}?_src=template");
Comment thread
donnie-msft marked this conversation as resolved.

// Act
var target = OwnerDetailsUriTemplateResourceV3.CreateOrNull(template);

// Assert
target.Should().BeNull();
}

[Fact]
public void CreateOrNull_WhenValidTemplateHttps_CreatesResource()
{
// Arrange & Act
var target = OwnerDetailsUriTemplateResourceV3.CreateOrNull(_template);

// Assert
target.Should().NotBeNull();
}

[Fact]
public void GetUri_WithSpacesInOwnerParameter_CreatesValidOwnerUriWithEncoding()
{
// Arrange
string owner = "Microsoft Microsoft Microsoft";
string formattedOwner = "Microsoft%20Microsoft%20Microsoft";

var target = OwnerDetailsUriTemplateResourceV3.CreateOrNull(_template);

// Act
Uri ownerUri = target!.GetUri(owner);

// Assert
ownerUri.Should().NotBeNull();
ownerUri.IsAbsoluteUri.Should().BeTrue();
ownerUri.AbsoluteUri.Should().Be($"https://nuget.test/profiles/{formattedOwner}?_src=template");
}

[Theory]
[InlineData("microsoft")]
[InlineData("MiCroSoFT")]
public void GetUri_WithValidOwnerParameter_CreatesValidOwnerUriWithSameCasing(string owner)
{
// Arrange
var target = OwnerDetailsUriTemplateResourceV3.CreateOrNull(_template);

// Act
Uri ownerUri = target!.GetUri(owner);

// Assert
ownerUri.Should().NotBeNull();
ownerUri.IsAbsoluteUri.Should().BeTrue();
ownerUri.AbsoluteUri.Should().Be($"https://nuget.test/profiles/{owner}?_src=template");
}

[Theory]
[InlineData(null)]
[InlineData("")]
public void GetUri_WithInvalidOwnerParameter_ReturnsOriginalTemplate(string owner)
{
// Arrange
var target = OwnerDetailsUriTemplateResourceV3.CreateOrNull(_template);
string templateWithoutOwner = "https://nuget.test/profiles/?_src=template";

// Act
Uri ownerUri = target!.GetUri(owner);

// Assert
ownerUri.Should().NotBeNull();
ownerUri.IsAbsoluteUri.Should().BeTrue();
ownerUri.AbsoluteUri.Should().Be(templateWithoutOwner);
}
}
}