Skip to content

Commit

Permalink
diagnose: add http fallback uri (#1339)
Browse files Browse the repository at this point in the history
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).

Fixes #1215
  • Loading branch information
ldennington committed Jul 17, 2023
2 parents 2334200 + 5afa56b commit 6904af7
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 3 deletions.
82 changes: 82 additions & 0 deletions src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs
Original file line number Diff line number Diff line change
@@ -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());
}
}
23 changes: 20 additions & 3 deletions src/shared/Core/Diagnostics/NetworkingDiagnostic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -28,9 +29,7 @@ protected override async Task<bool> RunInternalAsync(StringBuilder log, IList<st
bool hasNetwork = NetworkInterface.GetIsNetworkAvailable();
log.AppendLine($"IsNetworkAvailable: {hasNetwork}");

log.Append($"Sending HEAD request to {TestHttpUri}...");
using var httpResponse = await httpClient.HeadAsync(TestHttpUri);
log.AppendLine(" OK");
SendHttpRequest(log, httpClient);

log.Append($"Sending HEAD request to {TestHttpsUri}...");
using var httpsResponse = await httpClient.HeadAsync(TestHttpsUri);
Expand Down Expand Up @@ -98,5 +97,23 @@ protected override async Task<bool> RunInternalAsync(StringBuilder log, IList<st

return true;
}

internal /* For testing purposes */ async void SendHttpRequest(StringBuilder log, HttpClient httpClient)
{
foreach (var uri in new List<string> { 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");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -80,6 +82,12 @@ protected override async Task<HttpResponseMessage> 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)
Expand Down

0 comments on commit 6904af7

Please sign in to comment.