test(net): exercise UpdateChecker.CheckAsync end-to-end via a stub transport
CheckAsync built its own HttpClient inline, so nothing beyond tag parsing was testable without hitting the real GitHub API. Added an internal CheckAsync(currentVersion, HttpMessageHandler, ct) overload (the public method now delegates to it) as a seam for a stub handler, and tests for the full best-effort matrix: newer/equal/older/unparseable tag, missing html_url, HTTP error status, malformed JSON, a thrown HttpRequestException, and an unparseable local version — all of which must resolve to null or the correct LatestRelease, never throw.
This commit is contained in:
@@ -28,11 +28,19 @@ public static class UpdateChecker
|
|||||||
public string? HtmlUrl { get; set; }
|
public string? HtmlUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<LatestRelease?> CheckAsync(string currentVersion, CancellationToken ct = default)
|
public static Task<LatestRelease?> CheckAsync(string currentVersion, CancellationToken ct = default) =>
|
||||||
|
CheckAsync(currentVersion, handler: null, ct);
|
||||||
|
|
||||||
|
/// <summary>Test seam: <paramref name="handler"/> replaces the real HTTP transport.</summary>
|
||||||
|
internal static async Task<LatestRelease?> CheckAsync(string currentVersion,
|
||||||
|
HttpMessageHandler? handler, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
using var http = handler is null
|
||||||
|
? new HttpClient()
|
||||||
|
: new HttpClient(handler, disposeHandler: false);
|
||||||
|
http.Timeout = TimeSpan.FromSeconds(10);
|
||||||
http.DefaultRequestHeaders.UserAgent.ParseAdd("PalladiumWallet");
|
http.DefaultRequestHeaders.UserAgent.ParseAdd("PalladiumWallet");
|
||||||
using var response = await http.GetAsync(ReleasesApiUrl, ct).ConfigureAwait(false);
|
using var response = await http.GetAsync(ReleasesApiUrl, ct).ConfigureAwait(false);
|
||||||
if (!response.IsSuccessStatusCode) return null;
|
if (!response.IsSuccessStatusCode) return null;
|
||||||
|
|||||||
@@ -1,14 +1,86 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using PalladiumWallet.Core.Net;
|
using PalladiumWallet.Core.Net;
|
||||||
|
|
||||||
namespace PalladiumWallet.Tests.Net;
|
namespace PalladiumWallet.Tests.Net;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests for the release-tag parsing used by the update check. The network
|
/// Tests for the update check: tag parsing plus the full CheckAsync flow via
|
||||||
/// call itself is best-effort by design (any failure → null) and is not
|
/// a stub HttpMessageHandler (the same seam the production overload wraps),
|
||||||
/// exercised here: only the version-comparison logic is deterministic.
|
/// so no real GitHub call is ever made.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UpdateCheckerTests
|
public class UpdateCheckerTests
|
||||||
{
|
{
|
||||||
|
/// <summary>Handler stub: replies with a fixed status/body, or throws.</summary>
|
||||||
|
private sealed class StubHandler(HttpStatusCode status, string? body = null,
|
||||||
|
Exception? throws = null) : HttpMessageHandler
|
||||||
|
{
|
||||||
|
protected override Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request, CancellationToken ct) =>
|
||||||
|
throws is not null
|
||||||
|
? Task.FromException<HttpResponseMessage>(throws)
|
||||||
|
: Task.FromResult(new HttpResponseMessage(status)
|
||||||
|
{
|
||||||
|
Content = new StringContent(body ?? "", Encoding.UTF8, "application/json"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<LatestRelease?> Check(string current, HttpStatusCode status,
|
||||||
|
string? body = null, Exception? throws = null) =>
|
||||||
|
UpdateChecker.CheckAsync(current, new StubHandler(status, body, throws));
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Una_release_piu_nuova_viene_segnalata_con_tag_e_url()
|
||||||
|
{
|
||||||
|
var release = await Check("0.9.1", HttpStatusCode.OK,
|
||||||
|
"""{"tag_name":"v1.0.0","html_url":"https://example.test/rel/v1.0.0"}""");
|
||||||
|
|
||||||
|
Assert.NotNull(release);
|
||||||
|
Assert.Equal("v1.0.0", release!.Tag);
|
||||||
|
Assert.Equal("https://example.test/rel/v1.0.0", release.HtmlUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Senza_html_url_viene_costruito_il_link_alla_pagina_della_release()
|
||||||
|
{
|
||||||
|
var release = await Check("0.9.1", HttpStatusCode.OK, """{"tag_name":"v1.0.0"}""");
|
||||||
|
|
||||||
|
Assert.NotNull(release);
|
||||||
|
Assert.Contains("/releases/tag/v1.0.0", release!.HtmlUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("""{"tag_name":"v0.9.1"}""")] // same version
|
||||||
|
[InlineData("""{"tag_name":"v0.9.0"}""")] // older
|
||||||
|
[InlineData("""{"tag_name":"main"}""")] // unparseable tag
|
||||||
|
[InlineData("""{"tag_name":""}""")] // empty tag
|
||||||
|
[InlineData("""{}""")] // missing tag
|
||||||
|
public async Task Nessun_aggiornamento_quando_il_tag_non_e_piu_nuovo(string body)
|
||||||
|
{
|
||||||
|
Assert.Null(await Check("0.9.1", HttpStatusCode.OK, body));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Una_risposta_http_di_errore_risolve_a_null()
|
||||||
|
{
|
||||||
|
Assert.Null(await Check("0.9.1", HttpStatusCode.NotFound));
|
||||||
|
Assert.Null(await Check("0.9.1", HttpStatusCode.InternalServerError));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Json_malformato_o_errore_di_rete_risolvono_a_null()
|
||||||
|
{
|
||||||
|
Assert.Null(await Check("0.9.1", HttpStatusCode.OK, "not json at all"));
|
||||||
|
Assert.Null(await Check("0.9.1", HttpStatusCode.OK,
|
||||||
|
"""{"tag_name":"v9.9.9"}""", throws: new HttpRequestException("offline")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Una_versione_locale_non_parsabile_risolve_a_null()
|
||||||
|
{
|
||||||
|
Assert.Null(await Check("dev-build", HttpStatusCode.OK, """{"tag_name":"v9.9.9"}"""));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("v1.2.3", "1.2.3")]
|
[InlineData("v1.2.3", "1.2.3")]
|
||||||
[InlineData("V0.9.1", "0.9.1")]
|
[InlineData("V0.9.1", "0.9.1")]
|
||||||
|
|||||||
Reference in New Issue
Block a user