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()); 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 ClaudeDo.Data.Git.GitService(), new HubBroadcaster(hubContext), new ClaudeDo.Worker.Config.WorkerConfig(), NullLogger.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"); } }