feat(releases): add SelfUpdater.DecideUpdateAsync
This commit is contained in:
@@ -1,3 +1,15 @@
|
|||||||
namespace ClaudeDo.Releases;
|
namespace ClaudeDo.Releases;
|
||||||
|
|
||||||
public sealed record InstallerAssetMatch(ReleaseAsset Asset, string Version);
|
public sealed record InstallerAssetMatch(ReleaseAsset Asset, string Version);
|
||||||
|
|
||||||
|
public enum SelfUpdateDecisionKind
|
||||||
|
{
|
||||||
|
NoUpdate,
|
||||||
|
UpdateAvailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record SelfUpdateDecision(
|
||||||
|
SelfUpdateDecisionKind Kind,
|
||||||
|
string? LatestVersion = null,
|
||||||
|
ReleaseAsset? InstallerAsset = null,
|
||||||
|
ReleaseAsset? ChecksumsAsset = null);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Net.Http;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ClaudeDo.Releases;
|
namespace ClaudeDo.Releases;
|
||||||
@@ -19,4 +20,40 @@ public static partial class SelfUpdater
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<SelfUpdateDecision> DecideUpdateAsync(
|
||||||
|
IReleaseClient releases,
|
||||||
|
string currentVersion,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
GiteaRelease? release;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
release = await releases.GetLatestReleaseAsync(ct);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
|
||||||
|
{
|
||||||
|
return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (release is null)
|
||||||
|
return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);
|
||||||
|
|
||||||
|
var match = FindInstallerAsset(release.Assets);
|
||||||
|
if (match is null)
|
||||||
|
return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);
|
||||||
|
|
||||||
|
var cmp = VersionComparer.Compare(match.Version, currentVersion);
|
||||||
|
if (!cmp.IsNewer)
|
||||||
|
return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);
|
||||||
|
|
||||||
|
var checksums = release.Assets.FirstOrDefault(
|
||||||
|
a => string.Equals(a.Name, "checksums.txt", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return new SelfUpdateDecision(
|
||||||
|
SelfUpdateDecisionKind.UpdateAvailable,
|
||||||
|
LatestVersion: match.Version,
|
||||||
|
InstallerAsset: match.Asset,
|
||||||
|
ChecksumsAsset: checksums);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace ClaudeDo.Releases.Tests;
|
namespace ClaudeDo.Releases.Tests;
|
||||||
|
|
||||||
public class SelfUpdaterAssetMatchingTests
|
public class SelfUpdaterAssetMatchingTests
|
||||||
@@ -42,3 +44,83 @@ public class SelfUpdaterAssetMatchingTests
|
|||||||
Assert.Null(SelfUpdater.FindInstallerAsset(assets));
|
Assert.Null(SelfUpdater.FindInstallerAsset(assets));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SelfUpdaterDecisionTests
|
||||||
|
{
|
||||||
|
private sealed class FakeReleaseClient : IReleaseClient
|
||||||
|
{
|
||||||
|
public GiteaRelease? Release { get; set; }
|
||||||
|
public bool Throw { get; set; }
|
||||||
|
|
||||||
|
public Task<GiteaRelease?> GetLatestReleaseAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (Throw) throw new HttpRequestException("boom");
|
||||||
|
return Task.FromResult(Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DownloadAsync(string url, string destPath, IProgress<long> progress, CancellationToken ct)
|
||||||
|
=> throw new NotSupportedException("not used in decision tests");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Decide_NoRelease_NoUpdate()
|
||||||
|
{
|
||||||
|
var client = new FakeReleaseClient { Release = null };
|
||||||
|
var d = await SelfUpdater.DecideUpdateAsync(client, currentVersion: "0.1.0", CancellationToken.None);
|
||||||
|
Assert.Equal(SelfUpdateDecisionKind.NoUpdate, d.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Decide_NetworkError_NoUpdate()
|
||||||
|
{
|
||||||
|
var client = new FakeReleaseClient { Throw = true };
|
||||||
|
var d = await SelfUpdater.DecideUpdateAsync(client, "0.1.0", CancellationToken.None);
|
||||||
|
Assert.Equal(SelfUpdateDecisionKind.NoUpdate, d.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Decide_OlderLatest_NoUpdate()
|
||||||
|
{
|
||||||
|
var client = new FakeReleaseClient
|
||||||
|
{
|
||||||
|
Release = new GiteaRelease("v0.1.0", "rel", new[]
|
||||||
|
{
|
||||||
|
new ReleaseAsset("ClaudeDo.Installer-0.1.0.exe", "u", 1),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
var d = await SelfUpdater.DecideUpdateAsync(client, "0.2.0", CancellationToken.None);
|
||||||
|
Assert.Equal(SelfUpdateDecisionKind.NoUpdate, d.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Decide_NewerLatestWithAsset_UpdateAvailable()
|
||||||
|
{
|
||||||
|
var client = new FakeReleaseClient
|
||||||
|
{
|
||||||
|
Release = new GiteaRelease("v0.3.0", "rel", new[]
|
||||||
|
{
|
||||||
|
new ReleaseAsset("ClaudeDo.Installer-0.3.0.exe", "https://x", 20),
|
||||||
|
new ReleaseAsset("checksums.txt", "https://checks", 1),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
var d = await SelfUpdater.DecideUpdateAsync(client, "0.2.0", CancellationToken.None);
|
||||||
|
Assert.Equal(SelfUpdateDecisionKind.UpdateAvailable, d.Kind);
|
||||||
|
Assert.Equal("0.3.0", d.LatestVersion);
|
||||||
|
Assert.NotNull(d.InstallerAsset);
|
||||||
|
Assert.NotNull(d.ChecksumsAsset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Decide_NewerLatestButNoInstallerAsset_NoUpdate()
|
||||||
|
{
|
||||||
|
var client = new FakeReleaseClient
|
||||||
|
{
|
||||||
|
Release = new GiteaRelease("v0.3.0", "rel", new[]
|
||||||
|
{
|
||||||
|
new ReleaseAsset("ClaudeDo-0.3.0-win-x64.zip", "u", 20),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
var d = await SelfUpdater.DecideUpdateAsync(client, "0.2.0", CancellationToken.None);
|
||||||
|
Assert.Equal(SelfUpdateDecisionKind.NoUpdate, d.Kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user