From 5afa56b0964b50c29fd7e28176a0b42809479262 Mon Sep 17 00:00:00 2001 From: Lessley Dennington Date: Mon, 17 Jul 2023 10:09:34 -0600 Subject: [PATCH] diagnose: add http fallback uri Currently, failure to access http://example.com causes failure of the Networking Diagnostic portion of the diagnose command. To improve the experience for users who are unable to access http://example.com, this change: 1. Adds a fallback URI - if accessing http://example.com throws an exception, we now try http://httpforever.com. 2. Prints a warning when either the primary or both the primary and fallback uris throw an exception (instead of failing the Networking Diagnostic). --- .../Commands/DiagnoseCommandTests.cs | 82 +++++++++++++++++++ .../Core/Diagnostics/NetworkingDiagnostic.cs | 23 +++++- .../Objects/TestHttpMessageHandler.cs | 8 ++ 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs diff --git a/src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs b/src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs new file mode 100644 index 000000000..0118e9d85 --- /dev/null +++ b/src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Net.Http; +using System.Security.AccessControl; +using System.Text; +using GitCredentialManager.Diagnostics; +using GitCredentialManager.Tests.Objects; +using Xunit; + +namespace Core.Tests.Commands; + +public class DiagnoseCommandTests +{ + [Fact] + public void NetworkingDiagnostic_SendHttpRequest_Primary_OK() + { + var primaryUriString = "http://example.com"; + var sb = new StringBuilder(); + var context = new TestCommandContext(); + var networkingDiagnostic = new NetworkingDiagnostic(context); + var primaryUri = new Uri(primaryUriString); + var httpHandler = new TestHttpMessageHandler(); + var httpResponse = new HttpResponseMessage(); + var expected = $"Sending HEAD request to {primaryUriString}... OK{Environment.NewLine}"; + + httpHandler.Setup(HttpMethod.Head, primaryUri, httpResponse); + + networkingDiagnostic.SendHttpRequest(sb, new HttpClient(httpHandler)); + + httpHandler.AssertRequest(HttpMethod.Head, primaryUri, expectedNumberOfCalls: 1); + Assert.Contains(expected, sb.ToString()); + } + + [Fact] + public void NetworkingDiagnostic_SendHttpRequest_Backup_OK() + { + var primaryUriString = "http://example.com"; + var backupUriString = "http://httpforever.com"; + var sb = new StringBuilder(); + var context = new TestCommandContext(); + var networkingDiagnostic = new NetworkingDiagnostic(context); + var primaryUri = new Uri(primaryUriString); + var backupUri = new Uri(backupUriString); + var httpHandler = new TestHttpMessageHandler { SimulatePrimaryUriFailure = true }; + var httpResponse = new HttpResponseMessage(); + var expected = $"Sending HEAD request to {primaryUriString}... warning: HEAD request failed{Environment.NewLine}" + + $"Sending HEAD request to {backupUriString}... OK{Environment.NewLine}"; + + httpHandler.Setup(HttpMethod.Head, primaryUri, httpResponse); + httpHandler.Setup(HttpMethod.Head, backupUri, httpResponse); + + networkingDiagnostic.SendHttpRequest(sb, new HttpClient(httpHandler)); + + httpHandler.AssertRequest(HttpMethod.Head, primaryUri, expectedNumberOfCalls: 1); + httpHandler.AssertRequest(HttpMethod.Head, backupUri, expectedNumberOfCalls: 1); + Assert.Contains(expected, sb.ToString()); + } + + [Fact] + public void NetworkingDiagnostic_SendHttpRequest_No_Network() + { + var primaryUriString = "http://example.com"; + var backupUriString = "http://httpforever.com"; + var sb = new StringBuilder(); + var context = new TestCommandContext(); + var networkingDiagnostic = new NetworkingDiagnostic(context); + var primaryUri = new Uri(primaryUriString); + var backupUri = new Uri(backupUriString); + var httpHandler = new TestHttpMessageHandler { SimulateNoNetwork = true }; + var httpResponse = new HttpResponseMessage(); + var expected = $"Sending HEAD request to {primaryUriString}... warning: HEAD request failed{Environment.NewLine}" + + $"Sending HEAD request to {backupUriString}... warning: HEAD request failed{Environment.NewLine}"; + + httpHandler.Setup(HttpMethod.Head, primaryUri, httpResponse); + httpHandler.Setup(HttpMethod.Head, backupUri, httpResponse); + + networkingDiagnostic.SendHttpRequest(sb, new HttpClient(httpHandler)); + + httpHandler.AssertRequest(HttpMethod.Head, primaryUri, expectedNumberOfCalls: 1); + httpHandler.AssertRequest(HttpMethod.Head, backupUri, expectedNumberOfCalls: 1); + Assert.Contains(expected, sb.ToString()); + } +} diff --git a/src/shared/Core/Diagnostics/NetworkingDiagnostic.cs b/src/shared/Core/Diagnostics/NetworkingDiagnostic.cs index 310b26361..50ab5b4da 100644 --- a/src/shared/Core/Diagnostics/NetworkingDiagnostic.cs +++ b/src/shared/Core/Diagnostics/NetworkingDiagnostic.cs @@ -12,6 +12,7 @@ namespace GitCredentialManager.Diagnostics public class NetworkingDiagnostic : Diagnostic { private const string TestHttpUri = "http://example.com"; + private const string TestHttpUriFallback = "http://httpforever.com"; private const string TestHttpsUri = "https://example.com"; public NetworkingDiagnostic(ICommandContext commandContext) @@ -28,9 +29,7 @@ protected override async Task RunInternalAsync(StringBuilder log, IList RunInternalAsync(StringBuilder log, IList { TestHttpUri, TestHttpUriFallback }) + { + try + { + log.Append($"Sending HEAD request to {uri}..."); + using var httpResponse = await httpClient.HeadAsync(uri); + log.AppendLine(" OK"); + break; + } + catch (HttpRequestException) + { + log.AppendLine(" warning: HEAD request failed"); + } + } + } } } diff --git a/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs b/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs index 9dce7898f..0a2c46e18 100644 --- a/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs +++ b/src/shared/TestInfrastructure/Objects/TestHttpMessageHandler.cs @@ -23,6 +23,8 @@ public class TestHttpMessageHandler : HttpMessageHandler public bool ThrowOnUnexpectedRequest { get; set; } public bool SimulateNoNetwork { get; set; } + public bool SimulatePrimaryUriFailure { get; set; } + public IDictionary<(HttpMethod method, Uri uri), int> RequestCounts => _requestCounts; public void Setup(HttpMethod method, Uri uri, AsyncRequestHandler handler) @@ -80,6 +82,12 @@ protected override async Task SendAsync(HttpRequestMessage throw new HttpRequestException("Simulated no network"); } + if (SimulatePrimaryUriFailure && request.RequestUri != null && + request.RequestUri.ToString().Equals("http://example.com/")) + { + throw new HttpRequestException("Simulated http failure."); + } + foreach (var kvp in _handlers) { if (kvp.Key == requestKey)