using System.Net.Http; namespace ClaudeDo.Releases.Tests; public class SelfUpdaterAssetMatchingTests { [Fact] public void FindInstallerAsset_PicksInstallerExeByPattern() { var assets = new[] { new ReleaseAsset("ClaudeDo-0.3.0-win-x64.zip", "https://x/app.zip", 10), new ReleaseAsset("ClaudeDo.Installer-0.3.0.exe", "https://x/inst.exe", 20), new ReleaseAsset("checksums.txt", "https://x/checks", 1), }; var result = SelfUpdater.FindInstallerAsset(assets); Assert.NotNull(result); Assert.Equal("ClaudeDo.Installer-0.3.0.exe", result!.Asset.Name); Assert.Equal("0.3.0", result.Version); } [Fact] public void FindInstallerAsset_ReturnsNullWhenAbsent() { var assets = new[] { new ReleaseAsset("ClaudeDo-0.3.0-win-x64.zip", "https://x/app.zip", 10), }; Assert.Null(SelfUpdater.FindInstallerAsset(assets)); } [Fact] public void FindInstallerAsset_IgnoresAppZipThatContainsInstaller() { var assets = new[] { new ReleaseAsset("ClaudeDo.Installer.Portable-0.3.0.zip", "https://x/1", 1), new ReleaseAsset("not-the-installer.exe", "https://x/2", 1), }; 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 GetLatestReleaseAsync(CancellationToken ct) { if (Throw) throw new HttpRequestException("boom"); return Task.FromResult(Release); } public Task DownloadAsync(string url, string destPath, IProgress 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); } }