Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/Lifecycle/UntrackedWorktreeRecoveryTests.cs
T

114 lines
4.3 KiB
C#

using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Lifecycle;
using ClaudeDo.Worker.Tests.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions;
namespace ClaudeDo.Worker.Tests.Lifecycle;
public sealed class UntrackedWorktreeRecoveryTests : IDisposable
{
private readonly DbFixture _db = new();
public void Dispose() => _db.Dispose();
[Fact]
public void FindUntrackedPaths_PathNotInDb_IsReturned()
{
var registered = new[] { @"C:\repo\.worktrees\abc123" };
var db = new[] { @"C:\repo\.worktrees\def456" };
var untracked = UntrackedWorktreeRecovery.FindUntrackedPaths(registered, db);
Assert.Equal(registered, untracked);
}
[Fact]
public void FindUntrackedPaths_PathInDb_IsExcluded()
{
var registered = new[] { @"C:\repo\.worktrees\abc123" };
var db = new[] { @"C:\repo\.worktrees\abc123" };
var untracked = UntrackedWorktreeRecovery.FindUntrackedPaths(registered, db);
Assert.Empty(untracked);
}
[Fact]
public void FindUntrackedPaths_DifferentCasingAndSeparators_StillMatches()
{
var registered = new[] { @"C:\repo\.worktrees\abc123\" };
var db = new[] { "c:/repo/.worktrees/ABC123" };
var untracked = UntrackedWorktreeRecovery.FindUntrackedPaths(registered, db);
Assert.Empty(untracked);
}
[Fact]
public void FindUntrackedPaths_EmptyDb_ReturnsAllRegistered()
{
var registered = new[] { @"C:\repo\.worktrees\abc123", @"C:\repo\.worktrees\def456" };
var untracked = UntrackedWorktreeRecovery.FindUntrackedPaths(registered, Array.Empty<string>());
Assert.Equal(registered, untracked);
}
[Theory]
[InlineData(@"C:\src\.claudedo-worktrees\lager-app\412-fix", @"C:\Users\x\.claudeDo\worktrees", true)]
[InlineData(@"C:\src\.claudedo-worktrees\planning\abc123", @"C:\Users\x\.claudeDo\worktrees", false)]
[InlineData(@"C:\Users\x\.claudeDo\worktrees\lager-app\412-fix", @"C:\Users\x\.claudeDo\worktrees", true)]
[InlineData(@"C:\Users\x\.claudeDo\worktrees\planning\abc123", @"C:\Users\x\.claudeDo\worktrees", false)]
[InlineData(@"C:\src\my-own-worktree", @"C:\Users\x\.claudeDo\worktrees", false)]
public void IsTaskWorktreePath(string path, string centralRoot, bool expected)
{
if (!OperatingSystem.IsWindows()) return;
Assert.Equal(expected, UntrackedWorktreeRecovery.IsTaskWorktreePath(path, centralRoot));
}
[Fact]
public async Task StartAsync_NoLists_DoesNotThrow()
{
var hubContext = new CapturingHubContext();
var sut = new UntrackedWorktreeRecovery(
_db.CreateFactory(),
new HubBroadcaster(hubContext),
new ClaudeDo.Worker.Config.WorkerConfig(),
NullLogger<UntrackedWorktreeRecovery>.Instance);
await sut.StartAsync(CancellationToken.None);
await sut.StopAsync(CancellationToken.None);
Assert.Contains(hubContext.Proxy.Calls, c =>
c.Method == "OperationProgress" && (string)c.Args[0]! == "startup-recovery");
}
[Fact]
public void ReadLinkedWorktrees_MatchesGitWorktreeList()
{
if (!GitRepoFixture.IsGitAvailable()) return;
using var repo = new GitRepoFixture();
var parent = Path.GetDirectoryName(repo.RepoDir)!;
var onBranch = Path.Combine(parent, "wt-branch");
var detached = Path.Combine(parent, "wt-detached");
GitRepoFixture.RunGit(repo.RepoDir, "worktree", "add", "-b", "feature/1-x", onBranch);
GitRepoFixture.RunGit(repo.RepoDir, "worktree", "add", "--detach", detached);
var fromMain = UntrackedWorktreeRecovery.ReadLinkedWorktrees(repo.RepoDir);
var fromLinked = UntrackedWorktreeRecovery.ReadLinkedWorktrees(onBranch);
foreach (var list in new[] { fromMain, fromLinked })
{
Assert.Equal(2, list.Count);
Assert.Contains(list, w => string.Equals(w.Path, Path.GetFullPath(onBranch), StringComparison.OrdinalIgnoreCase) && w.Branch == "feature/1-x");
Assert.Contains(list, w => string.Equals(w.Path, Path.GetFullPath(detached), StringComparison.OrdinalIgnoreCase) && w.Branch is null);
}
}
[Fact]
public void ReadLinkedWorktrees_NotARepo_ReturnsEmpty()
{
Assert.Empty(UntrackedWorktreeRecovery.ReadLinkedWorktrees(Path.GetTempPath()));
}
}