diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj
index c07571964..97e4b7d4f 100644
--- a/src/chocolatey.tests/chocolatey.tests.csproj
+++ b/src/chocolatey.tests/chocolatey.tests.csproj
@@ -186,6 +186,7 @@
+
diff --git a/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs
new file mode 100644
index 000000000..efab38954
--- /dev/null
+++ b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs
@@ -0,0 +1,320 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using chocolatey.infrastructure.app.configuration;
+using chocolatey.infrastructure.app.nuget;
+using FluentAssertions;
+using Moq;
+using NuGet.Configuration;
+using NUnit.Framework;
+
+namespace chocolatey.tests.infrastructure.app.nuget
+{
+ public class ChocolateyNugetCredentialProviderSpecs
+ {
+ public abstract class ChocolateyNugetCredentialProviderSpecsBase : TinySpec
+ {
+ protected ChocolateyConfiguration Configuration;
+ protected ChocolateyNugetCredentialProvider Provider;
+
+ protected const string Username = "user";
+ protected const string Password = "totally_secure_password!!!";
+ protected const string TargetSourceName = "testsource";
+ protected const string TargetSourceUrl = "https://testserver.org/repository/test-repository";
+ protected Uri TargetSourceUri = new Uri(TargetSourceUrl);
+
+ protected NetworkCredential Result;
+
+ private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
+ protected CancellationToken CancellationToken
+ {
+ get
+ {
+ return _tokenSource.Token;
+ }
+ }
+
+ public override void Context()
+ {
+ Configuration = new ChocolateyConfiguration();
+ Provider = new ChocolateyNugetCredentialProvider(Configuration);
+
+ Configuration.Information.IsInteractive = false;
+ Configuration.MachineSources = new List
+ {
+ new MachineSourceConfiguration
+ {
+ AllowSelfService = true,
+ VisibleToAdminsOnly = false,
+ EncryptedPassword = NugetEncryptionUtility.EncryptString("otherPassword"),
+ Username = "otherUserName",
+ Key = "https://someotherplace.com/repository/things/",
+ Name = "not-this-one",
+ Priority = 1,
+ },
+ new MachineSourceConfiguration
+ {
+ AllowSelfService = true,
+ VisibleToAdminsOnly = false,
+ EncryptedPassword = NugetEncryptionUtility.EncryptString(Password),
+ Username = Username,
+ Key = TargetSourceUrl,
+ Name = TargetSourceName,
+ Priority = 1,
+ },
+ };
+ }
+
+ [OneTimeSetUp]
+ public async Task OneTimeSetup()
+ {
+ await With();
+ }
+
+ public virtual async Task With()
+ {
+ var result = await Provider.GetAsync(TargetSourceUri, proxy: null, CredentialRequestType.Unauthorized, message: string.Empty, isRetry: false, nonInteractive: true, CancellationToken);
+ Result = result.Credentials as NetworkCredential;
+ }
+ }
+
+ public class When_using_explicit_credentials_and_source_param : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl;
+ Configuration.SourceCommand.Username = "user";
+ Configuration.SourceCommand.Password = "totally_secure_password!!!";
+ }
+
+ [Fact]
+ public void Creates_a_valid_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be("user");
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be("totally_secure_password!!!");
+ }
+ }
+
+ public class When_a_source_name_is_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = TargetSourceUrl;
+ Configuration.ExplicitSources = TargetSourceName;
+ }
+
+ [Fact]
+ public void Finds_the_saved_source_and_returns_the_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be(Username);
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be(Password);
+ }
+ }
+
+ public class When_a_source_url_matching_a_saved_source_is_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl;
+ }
+
+ [Fact]
+ public void Finds_the_saved_source_and_returns_the_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be(Username);
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be(Password);
+ }
+ }
+
+ public class Looks_up_source_url_when_name_and_credentials_is_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = TargetSourceUrl;
+ Configuration.ExplicitSources = TargetSourceName;
+
+ Configuration.SourceCommand.Username = "user";
+ Configuration.SourceCommand.Password = "totally_secure_password!!!";
+ }
+
+ [Fact]
+ public void Creates_and_returns_the_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be(Username);
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be(Password);
+ }
+ }
+
+ public class When_no_matching_source_is_found_by_url : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = Configuration.ExplicitSources = "https://unknownurl.com/api/v2/";
+ }
+
+ public override async Task With()
+ {
+ var result = await Provider.GetAsync(new Uri("https://unknownurl.com/api/v2/"), proxy: null, CredentialRequestType.Unauthorized, message: string.Empty, isRetry: false, nonInteractive: true, CancellationToken);
+ Result = result.Credentials as NetworkCredential;
+ }
+
+ [Fact]
+ public void Returns_the_default_network_credential()
+ {
+ Result.Should().Be(CredentialCache.DefaultNetworkCredentials);
+ }
+ }
+
+ public class When_multiple_named_sources_are_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = Configuration.MachineSources.Select(s => s.Key).Join(";");
+ Configuration.ExplicitSources = Configuration.MachineSources.Select(s => s.Name).Join(";");
+ }
+
+ [Fact]
+ public void Finds_the_correct_saved_source_for_the_target_uri_and_returns_the_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be(Username);
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be(Password);
+ }
+ }
+
+ public class When_multiple_source_urls_are_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = Configuration.ExplicitSources = $"https://unknownurl.com/api/v2/;{TargetSourceUrl}";
+ }
+
+ [Fact]
+ public void Finds_the_saved_source_and_returns_the_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be(Username);
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be(Password);
+ }
+ }
+
+ public class When_a_mix_of_named_and_url_sources_are_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ public override void Because()
+ {
+ Configuration.Sources = $"https://unknownurl.com/api/v2/;{TargetSourceUrl}";
+ Configuration.ExplicitSources = $"https://unknownurl.com/api/v2/;{TargetSourceName}";
+ }
+
+ [Fact]
+ public void Finds_the_saved_source_and_returns_the_credential()
+ {
+ Result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Provides_the_correct_username()
+ {
+ Result.UserName.Should().Be(Username);
+ }
+
+ [Fact]
+ public void Provides_the_correct_password()
+ {
+ Result.Password.Should().Be(Password);
+ }
+ }
+
+ // This is a regression test for issue #3565
+ public class When_a_url_matching_the_hostname_only_of_a_saved_source_is_provided : ChocolateyNugetCredentialProviderSpecsBase
+ {
+ private Uri _otherRepoUri;
+ public override void Because()
+ {
+ _otherRepoUri = new Uri(TargetSourceUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped) + "/other_path/repository/");
+ Configuration.Sources = Configuration.ExplicitSources = _otherRepoUri.AbsoluteUri;
+ }
+
+ public override async Task With()
+ {
+ var result = await Provider.GetAsync(new Uri("https://unknownurl.com/api/v2/"), proxy: null, CredentialRequestType.Unauthorized, message: string.Empty, isRetry: false, nonInteractive: true, CancellationToken);
+ Result = result.Credentials as NetworkCredential;
+ }
+
+ [Fact]
+ public void Returns_the_default_network_credential()
+ {
+ Result.Should().Be(CredentialCache.DefaultNetworkCredentials);
+ }
+ }
+ }
+}