Merge branch 'feat/self-update'
Self-update for app and installer. Integrates cleanly with the worker-log-footer feature that landed on main in parallel — the shell VM now carries both worker-log state and update-check state, and MainWindow hosts both the update banner and the footer log line. Conflict resolved in IslandsShellViewModel.cs: kept nullable property types from main's test-only parameterless constructor work, and added the UpdateCheck property exposing the injected service.
This commit is contained in:
58
tests/ClaudeDo.Ui.Tests/Services/InstallerLocatorTests.cs
Normal file
58
tests/ClaudeDo.Ui.Tests/Services/InstallerLocatorTests.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using ClaudeDo.Ui.Services;
|
||||
|
||||
namespace ClaudeDo.Ui.Tests.Services;
|
||||
|
||||
public class InstallerLocatorTests : IDisposable
|
||||
{
|
||||
private readonly string _root;
|
||||
|
||||
public InstallerLocatorTests()
|
||||
{
|
||||
_root = Path.Combine(Path.GetTempPath(), "ClaudeDo.Ui.Tests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(_root);
|
||||
}
|
||||
|
||||
public void Dispose() { try { Directory.Delete(_root, true); } catch { } }
|
||||
|
||||
[Fact]
|
||||
public void Find_WalkUpFromAppDir_ToInstallJsonSibling()
|
||||
{
|
||||
var installDir = Path.Combine(_root, "ClaudeDo");
|
||||
var appDir = Path.Combine(installDir, "app");
|
||||
var uninstallerDir = Path.Combine(installDir, "uninstaller");
|
||||
Directory.CreateDirectory(appDir);
|
||||
Directory.CreateDirectory(uninstallerDir);
|
||||
|
||||
File.WriteAllText(Path.Combine(installDir, "install.json"), "{}");
|
||||
var installerPath = Path.Combine(uninstallerDir, "ClaudeDo.Installer.exe");
|
||||
File.WriteAllText(installerPath, "x");
|
||||
|
||||
var locator = new InstallerLocator();
|
||||
var found = locator.FindByWalkingUp(appDir);
|
||||
|
||||
Assert.Equal(installerPath, found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_ReturnsNullWhenNoInstallJson()
|
||||
{
|
||||
var appDir = Path.Combine(_root, "somewhere", "app");
|
||||
Directory.CreateDirectory(appDir);
|
||||
|
||||
var locator = new InstallerLocator();
|
||||
Assert.Null(locator.FindByWalkingUp(appDir));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_ReturnsNullWhenInstallerMissingFromUninstallerDir()
|
||||
{
|
||||
var installDir = Path.Combine(_root, "ClaudeDo");
|
||||
var appDir = Path.Combine(installDir, "app");
|
||||
Directory.CreateDirectory(appDir);
|
||||
Directory.CreateDirectory(Path.Combine(installDir, "uninstaller"));
|
||||
File.WriteAllText(Path.Combine(installDir, "install.json"), "{}");
|
||||
|
||||
var locator = new InstallerLocator();
|
||||
Assert.Null(locator.FindByWalkingUp(appDir));
|
||||
}
|
||||
}
|
||||
62
tests/ClaudeDo.Ui.Tests/Services/UpdateCheckServiceTests.cs
Normal file
62
tests/ClaudeDo.Ui.Tests/Services/UpdateCheckServiceTests.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Net.Http;
|
||||
using ClaudeDo.Releases;
|
||||
using ClaudeDo.Ui.Services;
|
||||
|
||||
namespace ClaudeDo.Ui.Tests.Services;
|
||||
|
||||
public class UpdateCheckServiceTests
|
||||
{
|
||||
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();
|
||||
return Task.FromResult(Release);
|
||||
}
|
||||
|
||||
public Task DownloadAsync(string url, string destPath, IProgress<long> progress, CancellationToken ct)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_NewerRelease_SetsUpdateAvailable()
|
||||
{
|
||||
var svc = new UpdateCheckService(new FakeReleaseClient
|
||||
{
|
||||
Release = new GiteaRelease("v0.3.0", "r", new[] { new ReleaseAsset("ClaudeDo-0.3.0-win-x64.zip", "u", 1) }),
|
||||
},
|
||||
currentVersion: "0.1.0");
|
||||
|
||||
await svc.CheckNowAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(UpdateCheckStatus.UpdateAvailable, svc.LastCheckStatus);
|
||||
Assert.True(svc.IsUpdateAvailable);
|
||||
Assert.Equal("0.3.0", svc.LatestVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_SameRelease_SetsUpToDate()
|
||||
{
|
||||
var svc = new UpdateCheckService(new FakeReleaseClient
|
||||
{
|
||||
Release = new GiteaRelease("v0.1.0", "r", new[] { new ReleaseAsset("ClaudeDo-0.1.0-win-x64.zip", "u", 1) }),
|
||||
},
|
||||
currentVersion: "0.1.0");
|
||||
|
||||
await svc.CheckNowAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(UpdateCheckStatus.UpToDate, svc.LastCheckStatus);
|
||||
Assert.False(svc.IsUpdateAvailable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Check_NetworkError_SetsCheckFailedButDoesNotThrow()
|
||||
{
|
||||
var svc = new UpdateCheckService(new FakeReleaseClient { Throw = true }, "0.1.0");
|
||||
await svc.CheckNowAsync(CancellationToken.None);
|
||||
Assert.Equal(UpdateCheckStatus.CheckFailed, svc.LastCheckStatus);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user