diff --git a/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs b/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs index 614161e..c58e8f4 100644 --- a/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Hub/PlanningHubTests.cs @@ -1,6 +1,8 @@ using ClaudeDo.Data; +using ClaudeDo.Data.Git; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; +using ClaudeDo.Worker.Config; using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Planning; using ClaudeDo.Worker.Services; @@ -28,7 +30,11 @@ public sealed class PlanningHubTests : IDisposable _tasks = new TaskRepository(_ctx); _lists = new ListRepository(_ctx); _rootDir = Path.Combine(Path.GetTempPath(), $"cd_hub_planning_{Guid.NewGuid():N}"); - _planning = new PlanningSessionManager(_tasks, _lists, _rootDir); + var git = new GitService(); + var cfg = new WorkerConfig { CentralWorktreeRoot = Path.Combine(_rootDir, "central") }; + var settingsRepo = new AppSettingsRepository(_ctx); + settingsRepo.UpdateAsync(new AppSettingsEntity { WorktreeStrategy = "sibling" }).GetAwaiter().GetResult(); + _planning = new PlanningSessionManager(_tasks, _lists, settingsRepo, git, cfg, _rootDir); _launcher = new FakePlanningLauncher(); _proxy = new RecordingClientProxy(); } @@ -54,7 +60,7 @@ public sealed class PlanningHubTests : IDisposable { var listId = Guid.NewGuid().ToString(); var wd = Path.Combine(Path.GetTempPath(), $"cd_wd_{Guid.NewGuid():N}"); - Directory.CreateDirectory(wd); + GitRepoFixture.InitRepoWithInitialCommit(wd); await _lists.AddAsync(new ListEntity { Id = listId, Name = "L", WorkingDir = wd, CreatedAt = DateTime.UtcNow, diff --git a/tests/ClaudeDo.Worker.Tests/Infrastructure/GitRepoFixture.cs b/tests/ClaudeDo.Worker.Tests/Infrastructure/GitRepoFixture.cs index e7c788a..4631ed3 100644 --- a/tests/ClaudeDo.Worker.Tests/Infrastructure/GitRepoFixture.cs +++ b/tests/ClaudeDo.Worker.Tests/Infrastructure/GitRepoFixture.cs @@ -78,6 +78,21 @@ public sealed class GitRepoFixture : IDisposable return stdout; } + /// + /// Creates a new git repo at with a seed commit. + /// Used by planning tests that need a real git repo as list working directory. + /// + public static void InitRepoWithInitialCommit(string dir) + { + Directory.CreateDirectory(dir); + RunGit(dir, "init", "-b", "main"); + RunGit(dir, "config", "user.email", "test@claudedo.local"); + RunGit(dir, "config", "user.name", "test"); + File.WriteAllText(Path.Combine(dir, "README.md"), "seed\n"); + RunGit(dir, "add", "-A"); + RunGit(dir, "commit", "-m", "chore: seed"); + } + private static void ForceDeleteDirectory(string path) { if (!Directory.Exists(path)) return; diff --git a/tests/ClaudeDo.Worker.Tests/Planning/PlanningEndToEndTests.cs b/tests/ClaudeDo.Worker.Tests/Planning/PlanningEndToEndTests.cs index 4451cde..843608f 100644 --- a/tests/ClaudeDo.Worker.Tests/Planning/PlanningEndToEndTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Planning/PlanningEndToEndTests.cs @@ -1,6 +1,8 @@ using ClaudeDo.Data; +using ClaudeDo.Data.Git; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; +using ClaudeDo.Worker.Config; using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Planning; using ClaudeDo.Worker.Tests.Infrastructure; @@ -60,7 +62,11 @@ public sealed class PlanningEndToEndTests : IDisposable _lists = new ListRepository(_ctx); var root = Path.Combine(Path.GetTempPath(), $"cd_e2e_{Guid.NewGuid():N}"); - _manager = new PlanningSessionManager(_tasks, _lists, root); + var git = new GitService(); + var cfg = new WorkerConfig { CentralWorktreeRoot = Path.Combine(root, "central") }; + var settingsRepo = new AppSettingsRepository(_ctx); + settingsRepo.UpdateAsync(new AppSettingsEntity { WorktreeStrategy = "sibling" }).GetAwaiter().GetResult(); + _manager = new PlanningSessionManager(_tasks, _lists, settingsRepo, git, cfg, root); _httpContext = new DefaultHttpContext(); _accessor = new PlanningMcpContextAccessor(new E2EFakeHttpContextAccessor { HttpContext = _httpContext }); @@ -74,7 +80,8 @@ public sealed class PlanningEndToEndTests : IDisposable public async Task StartThenCreateThenFinalize_FullFlow() { var listId = Guid.NewGuid().ToString(); - var wd = Path.GetTempPath(); + var wd = Path.Combine(Path.GetTempPath(), $"cd_e2e_wd_{Guid.NewGuid():N}"); + GitRepoFixture.InitRepoWithInitialCommit(wd); await _lists.AddAsync(new ListEntity { Id = listId, Name = "L", WorkingDir = wd, CreatedAt = DateTime.UtcNow }); var parent = new TaskEntity @@ -89,7 +96,7 @@ public sealed class PlanningEndToEndTests : IDisposable await _tasks.AddAsync(parent); var startCtx = await _manager.StartAsync(parent.Id, CancellationToken.None); - Assert.True(File.Exists(startCtx.Files.McpConfigPath)); + Assert.True(File.Exists(Path.Combine(startCtx.WorktreePath, ".mcp.json"))); // Wire the ambient context so _svc reads the correct parent _httpContext.Items["PlanningContext"] = new PlanningMcpContext { ParentTaskId = parent.Id }; diff --git a/tests/ClaudeDo.Worker.Tests/Planning/PlanningSessionManagerTests.cs b/tests/ClaudeDo.Worker.Tests/Planning/PlanningSessionManagerTests.cs index b0196f6..e3794a1 100644 --- a/tests/ClaudeDo.Worker.Tests/Planning/PlanningSessionManagerTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Planning/PlanningSessionManagerTests.cs @@ -1,6 +1,8 @@ using ClaudeDo.Data; +using ClaudeDo.Data.Git; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; +using ClaudeDo.Worker.Config; using ClaudeDo.Worker.Planning; using ClaudeDo.Worker.Tests.Infrastructure; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; @@ -14,6 +16,9 @@ public sealed class PlanningSessionManagerTests : IDisposable private readonly TaskRepository _tasks; private readonly ListRepository _lists; private readonly string _rootDir; + private readonly GitService _git; + private readonly WorkerConfig _cfg; + private readonly AppSettingsRepository _settingsRepo; private readonly PlanningSessionManager _sut; public PlanningSessionManagerTests() @@ -22,7 +27,11 @@ public sealed class PlanningSessionManagerTests : IDisposable _tasks = new TaskRepository(_ctx); _lists = new ListRepository(_ctx); _rootDir = Path.Combine(Path.GetTempPath(), $"cd_planning_{Guid.NewGuid():N}"); - _sut = new PlanningSessionManager(_tasks, _lists, _rootDir); + _git = new GitService(); + _cfg = new WorkerConfig { CentralWorktreeRoot = Path.Combine(_rootDir, "central") }; + _settingsRepo = new AppSettingsRepository(_ctx); + _settingsRepo.UpdateAsync(new AppSettingsEntity { WorktreeStrategy = "sibling" }).GetAwaiter().GetResult(); + _sut = new PlanningSessionManager(_tasks, _lists, _settingsRepo, _git, _cfg, _rootDir); } public void Dispose() @@ -36,7 +45,7 @@ public sealed class PlanningSessionManagerTests : IDisposable { var listId = Guid.NewGuid().ToString(); var wd = Path.Combine(Path.GetTempPath(), $"cd_wd_{Guid.NewGuid():N}"); - Directory.CreateDirectory(wd); + GitRepoFixture.InitRepoWithInitialCommit(wd); await _lists.AddAsync(new ListEntity { Id = listId, @@ -72,14 +81,17 @@ public sealed class PlanningSessionManagerTests : IDisposable var ctx = await _sut.StartAsync(parent.Id, CancellationToken.None); Assert.Equal(parent.Id, ctx.ParentTaskId); - Assert.Equal(wd, ctx.WorkingDir); - Assert.True(File.Exists(ctx.Files.McpConfigPath)); + Assert.Equal(ctx.WorktreePath, ctx.WorkingDir); + Assert.True(Directory.Exists(ctx.WorktreePath)); + var mcpPath = Path.Combine(ctx.WorktreePath, ".mcp.json"); + Assert.True(File.Exists(mcpPath)); + Assert.True(File.Exists(Path.Combine(ctx.WorktreePath, ".claude", "settings.local.json"))); Assert.True(File.Exists(ctx.Files.SystemPromptPath)); Assert.True(File.Exists(ctx.Files.InitialPromptPath)); - var mcp = await File.ReadAllTextAsync(ctx.Files.McpConfigPath); - Assert.Contains("\"type\": \"http\"", mcp); - Assert.Contains("Bearer ", mcp); + var mcp = await File.ReadAllTextAsync(mcpPath); + Assert.Contains("${CLAUDEDO_PLANNING_TOKEN}", mcp); + Assert.DoesNotContain(ctx.Token, mcp); var initial = await File.ReadAllTextAsync(ctx.Files.InitialPromptPath); Assert.Contains("Brainstorm auth", initial); @@ -132,10 +144,10 @@ public sealed class PlanningSessionManagerTests : IDisposable var resumeCtx = await _sut.ResumeAsync(parent.Id, CancellationToken.None); Assert.Equal(parent.Id, resumeCtx.ParentTaskId); - Assert.Equal(wd, resumeCtx.WorkingDir); + Assert.Equal(resumeCtx.WorktreePath, resumeCtx.WorkingDir); Assert.Equal("claude-session-42", resumeCtx.ClaudeSessionId); - Assert.Equal(startCtx.Files.McpConfigPath, resumeCtx.McpConfigPath); - Assert.True(File.Exists(resumeCtx.McpConfigPath)); + Assert.True(Directory.Exists(resumeCtx.WorktreePath)); + Assert.True(File.Exists(Path.Combine(resumeCtx.WorktreePath, ".mcp.json"))); } [Fact] diff --git a/tests/ClaudeDo.Worker.Tests/Planning/WindowsTerminalPlanningLauncherTests.cs b/tests/ClaudeDo.Worker.Tests/Planning/WindowsTerminalPlanningLauncherTests.cs index cc19d05..e547bb4 100644 --- a/tests/ClaudeDo.Worker.Tests/Planning/WindowsTerminalPlanningLauncherTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Planning/WindowsTerminalPlanningLauncherTests.cs @@ -12,9 +12,11 @@ public sealed class WindowsTerminalPlanningLauncherTests return new PlanningSessionStartContext( ParentTaskId: "task-1", WorkingDir: workingDir, + Token: "test-token", + WorktreePath: workingDir, + BranchName: "claudedo/planning/task1", Files: new PlanningSessionFiles( SessionDirectory: dir, - McpConfigPath: Path.Combine(dir, "mcp.json"), SystemPromptPath: Path.Combine(dir, "system-prompt.md"), InitialPromptPath: Path.Combine(dir, "initial-prompt.txt"))); } @@ -33,7 +35,6 @@ public sealed class WindowsTerminalPlanningLauncherTests public async Task LaunchStartAsync_WtMissing_Throws() { var ctx = MakeStartCtx(); - File.WriteAllText(ctx.Files.McpConfigPath, "{}"); File.WriteAllText(ctx.Files.SystemPromptPath, "sp"); File.WriteAllText(ctx.Files.InitialPromptPath, "ip");