fix(worker): Base-URL ohne Schema laesst die Ticket-Anbindung scheitern
Ein blanker Hostname ist eine relative URI; HttpClient lehnt sie mit InvalidOperationException ab. Die lief an dem zu engen catch-Filter vorbei, flog roh aus dem Hub und wurde von TryInvokeAsync zu null verschluckt - die UI meldete "Worker not reachable", obwohl der Worker lief und die Ursache eine Eingabe war. - TicketSystemConfig ergaenzt fehlendes http:// - BandelTicketClient uebersetzt jede Transport-Exception in eine lesbare TicketApiException - TestTicketConnection faengt alles und antwortet mit dem Grund - GetTicketSettings liefert die effektive URL, damit die Ergaenzung sichtbar ist
This commit is contained in:
@@ -9,6 +9,33 @@ public sealed class BandelTicketClientTests
|
||||
private static BandelTicketClient Make(StubHandler handler)
|
||||
=> new(new HttpClient(handler), "http://api.local", "tsp_x", NullLogger<BandelTicketClient>.Instance);
|
||||
|
||||
// Der Client verspricht der UI eine lesbare Meldung. Vorher wurden nur HttpRequestException
|
||||
// und TaskCanceledException übersetzt — eine InvalidOperationException (die HttpClient bei
|
||||
// einer relativen URI wirft) flog roh durch den Hub und kam als "Worker not reachable" an.
|
||||
[Theory]
|
||||
[ClassData(typeof(UnexpectedTransportFailures))]
|
||||
public async Task Any_transport_failure_becomes_a_readable_TicketApiException(Exception failure)
|
||||
{
|
||||
var client = Make(new StubHandler(failure));
|
||||
|
||||
var ex = await Assert.ThrowsAsync<TicketApiException>(
|
||||
() => client.GetIdentityAsync(CancellationToken.None));
|
||||
|
||||
Assert.Contains("http://api.local", ex.Message);
|
||||
Assert.DoesNotContain("Exception", ex.Message);
|
||||
}
|
||||
|
||||
public sealed class UnexpectedTransportFailures : TheoryData<Exception>
|
||||
{
|
||||
public UnexpectedTransportFailures()
|
||||
{
|
||||
Add(new InvalidOperationException("An invalid request URI was provided."));
|
||||
Add(new HttpRequestException("no such host"));
|
||||
Add(new NotSupportedException("scheme not supported"));
|
||||
Add(new UriFormatException("bad uri"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetIdentity_returns_user_name()
|
||||
{
|
||||
|
||||
@@ -13,12 +13,22 @@ internal sealed class StubHandler : HttpMessageHandler
|
||||
public readonly List<string> Bodies = new();
|
||||
private readonly Func<HttpRequestMessage, (HttpStatusCode, string)> _respond;
|
||||
|
||||
private readonly Exception? _throw;
|
||||
|
||||
public StubHandler(Func<HttpRequestMessage, (HttpStatusCode, string)> respond) => _respond = respond;
|
||||
|
||||
/// <summary>Lets a test reproduce a transport that blows up instead of answering.</summary>
|
||||
public StubHandler(Exception throwOnSend)
|
||||
{
|
||||
_respond = _ => (HttpStatusCode.OK, "");
|
||||
_throw = throwOnSend;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
|
||||
{
|
||||
Requests.Add(request);
|
||||
Bodies.Add(request.Content is null ? "" : await request.Content.ReadAsStringAsync(ct));
|
||||
if (_throw is not null) throw _throw;
|
||||
var (code, body) = _respond(request);
|
||||
return new HttpResponseMessage(code) { Content = new StringContent(body, Encoding.UTF8, "application/json") };
|
||||
}
|
||||
|
||||
@@ -36,6 +36,23 @@ public sealed class TicketSystemConfigTests : IDisposable
|
||||
public void Base_url_trailing_slash_is_trimmed()
|
||||
=> Assert.Equal("http://api.local", Make("http://api.local/", "tsp_x").BaseUrl);
|
||||
|
||||
// Ein blanker Hostname ist das, was man in ein Feld namens "API base URL" tippt. Ohne Schema
|
||||
// ist das eine RELATIVE URI, und HttpClient lehnt sie mit InvalidOperationException ab —
|
||||
// genau daran ist die erste Einrichtung gescheitert.
|
||||
[Theory]
|
||||
[InlineData("bandelapis.fb-tuning.local", "http://bandelapis.fb-tuning.local")]
|
||||
[InlineData("bandelapis.fb-tuning.local/", "http://bandelapis.fb-tuning.local")]
|
||||
[InlineData(" bandelapis.fb-tuning.local ", "http://bandelapis.fb-tuning.local")]
|
||||
[InlineData("api.local:8080", "http://api.local:8080")]
|
||||
public void Missing_scheme_defaults_to_http(string entered, string expected)
|
||||
=> Assert.Equal(expected, Make(entered, "tsp_x").BaseUrl);
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://api.local")]
|
||||
[InlineData("http://api.local")]
|
||||
public void Explicit_scheme_is_left_alone(string entered)
|
||||
=> Assert.Equal(entered, Make(entered, "tsp_x").BaseUrl);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_dir)) Directory.Delete(_dir, recursive: true);
|
||||
|
||||
Reference in New Issue
Block a user