Files
ClaudeDo/tests/ClaudeDo.Installer.Tests/DownloadAndExtractStepTests.cs

149 lines
5.8 KiB
C#

using System.IO;
using System.IO.Compression;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Steps;
namespace ClaudeDo.Installer.Tests;
public sealed class DownloadAndExtractStepTests : IDisposable
{
private readonly string _tempDir;
private readonly string _installDir;
public DownloadAndExtractStepTests()
{
_tempDir = Path.Combine(Path.GetTempPath(), "ClaudeDoDownloadStep-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_tempDir);
_installDir = Path.Combine(_tempDir, "install");
Directory.CreateDirectory(_installDir);
}
public void Dispose()
{
try { Directory.Delete(_tempDir, recursive: true); } catch { }
}
private sealed class FileCopyReleaseClient : IReleaseClient
{
private readonly Dictionary<string, string> _urlToSourceFile;
public GiteaRelease? Release { get; set; }
public FileCopyReleaseClient(Dictionary<string, string> urlToSourceFile)
=> _urlToSourceFile = urlToSourceFile;
public Task<GiteaRelease?> GetLatestReleaseAsync(CancellationToken ct) => Task.FromResult(Release);
public Task DownloadAsync(string url, string destPath, IProgress<long> progress, CancellationToken ct)
{
File.Copy(_urlToSourceFile[url], destPath, overwrite: true);
progress.Report(new FileInfo(destPath).Length);
return Task.CompletedTask;
}
}
[Fact]
public async Task Extracts_Zip_Into_InstallDir_App_And_Worker()
{
var zipPath = Path.Combine(_tempDir, "release.zip");
using (var fs = File.Create(zipPath))
using (var zip = new ZipArchive(fs, ZipArchiveMode.Create))
{
var a = zip.CreateEntry("app/a.txt");
using (var w = new StreamWriter(a.Open())) w.Write("hello-app");
var b = zip.CreateEntry("worker/b.txt");
using (var w = new StreamWriter(b.Open())) w.Write("hello-worker");
}
var zipHash = ChecksumVerifier.ComputeSha256(zipPath);
var checksumsPath = Path.Combine(_tempDir, "checksums.txt");
File.WriteAllText(checksumsPath, $"{zipHash} ClaudeDo-0.1.0-win-x64.zip\n");
var release = new GiteaRelease("v0.1.0", "v0.1.0", new[]
{
new ReleaseAsset("ClaudeDo-0.1.0-win-x64.zip", "fake://zip", new FileInfo(zipPath).Length),
new ReleaseAsset("checksums.txt", "fake://checksums", new FileInfo(checksumsPath).Length),
});
var client = new FileCopyReleaseClient(new()
{
["fake://zip"] = zipPath,
["fake://checksums"] = checksumsPath,
}) { Release = release };
var step = new DownloadAndExtractStep(client);
var ctx = new InstallContext { InstallDirectory = _installDir };
var result = await step.ExecuteAsync(ctx, new Progress<string>(_ => { }), CancellationToken.None);
Assert.True(result.Success, result.ErrorMessage);
Assert.Equal("hello-app", File.ReadAllText(Path.Combine(_installDir, "app", "a.txt")));
Assert.Equal("hello-worker", File.ReadAllText(Path.Combine(_installDir, "worker", "b.txt")));
Assert.Equal("0.1.0", ctx.InstalledVersion);
}
[Fact]
public async Task Fails_On_ChecksumMismatch_Without_Overwriting_InstallDir()
{
var zipPath = Path.Combine(_tempDir, "release.zip");
using (var fs = File.Create(zipPath))
using (var zip = new ZipArchive(fs, ZipArchiveMode.Create))
{
var a = zip.CreateEntry("app/a.txt");
using (var w = new StreamWriter(a.Open())) w.Write("x");
}
var checksumsPath = Path.Combine(_tempDir, "checksums.txt");
File.WriteAllText(checksumsPath, $"{new string('0', 64)} ClaudeDo-0.1.0-win-x64.zip\n");
File.WriteAllText(Path.Combine(_installDir, "marker.txt"), "untouched");
var release = new GiteaRelease("v0.1.0", "v0.1.0", new[]
{
new ReleaseAsset("ClaudeDo-0.1.0-win-x64.zip", "fake://zip", 0),
new ReleaseAsset("checksums.txt", "fake://checksums", 0),
});
var client = new FileCopyReleaseClient(new()
{
["fake://zip"] = zipPath,
["fake://checksums"] = checksumsPath,
}) { Release = release };
var step = new DownloadAndExtractStep(client);
var ctx = new InstallContext { InstallDirectory = _installDir };
var result = await step.ExecuteAsync(ctx, new Progress<string>(_ => { }), CancellationToken.None);
Assert.False(result.Success);
Assert.Contains("checksum", result.ErrorMessage!, StringComparison.OrdinalIgnoreCase);
Assert.True(File.Exists(Path.Combine(_installDir, "marker.txt")));
Assert.False(Directory.Exists(Path.Combine(_installDir, "app")));
}
[Fact]
public async Task Fails_When_Release_Has_No_Zip_Asset()
{
var release = new GiteaRelease("v0.1.0", "v0.1.0", Array.Empty<ReleaseAsset>());
var client = new FileCopyReleaseClient(new()) { Release = release };
var step = new DownloadAndExtractStep(client);
var ctx = new InstallContext { InstallDirectory = _installDir };
var result = await step.ExecuteAsync(ctx, new Progress<string>(_ => { }), CancellationToken.None);
Assert.False(result.Success);
Assert.Contains("not found", result.ErrorMessage!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task Fails_When_ReleaseClient_Returns_Null()
{
var client = new FileCopyReleaseClient(new()) { Release = null };
var step = new DownloadAndExtractStep(client);
var ctx = new InstallContext { InstallDirectory = _installDir };
var result = await step.ExecuteAsync(ctx, new Progress<string>(_ => { }), CancellationToken.None);
Assert.False(result.Success);
}
}