fix(claude-do): merge fix(worker): Interaktive Session auf einem Nicht-Repo-Workin
ClaudeDo-Task: 15e126d564874973be2a5bbe7d796646
This commit is contained in:
@@ -148,21 +148,77 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForTaskAsync_NoWorktreeRow_NotAGitRepo_ThrowsInvalidOperation()
|
||||
public async Task BuildForTaskAsync_NoWorktreeRow_NotAGitRepo_OpensDirectlyInWorkingDir_NoWorktreeCreated()
|
||||
{
|
||||
var listId = await SeedListAsync(); // default WorkingDir (_tempDir) is not a git repo
|
||||
var taskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
|
||||
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(_tempDir, spec.Cwd);
|
||||
Assert.Equal(_claudeStubPath, spec.Exe);
|
||||
|
||||
using var readCtx = _db.CreateContext();
|
||||
Assert.Null(await new WorktreeRepository(readCtx).GetByTaskIdAsync(taskId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForTaskAsync_NoWorktreeRow_NotAGitRepo_SeedsSessionSkillsWithIsWorktreeFalse()
|
||||
{
|
||||
_registry.Installed.Add(new SessionSkillEntity
|
||||
{
|
||||
Name = "installed-skill", SourceUrl = "https://example.com/x.git",
|
||||
PinnedRef = "abc", Subpath = "skills/installed-skill", Description = "d",
|
||||
AddedAt = DateTimeOffset.UtcNow,
|
||||
});
|
||||
|
||||
var listId = await SeedListAsync();
|
||||
var taskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle,
|
||||
sessionSkillsJson: "[\"installed-skill\"]");
|
||||
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(_tempDir, spec.Cwd);
|
||||
var call = Assert.Single(_seeder.Calls);
|
||||
Assert.Equal(_tempDir, call.WorkingDir);
|
||||
Assert.False(call.IsWorktree);
|
||||
Assert.Equal(new[] { "installed-skill" }, call.SkillNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForTaskAsync_NoWorktreeRow_NotAGitRepo_ReopeningResumesItsOwnPriorSessionId()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var taskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
|
||||
|
||||
var svc = BuildService();
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => svc.BuildForTaskAsync(taskId, CancellationToken.None));
|
||||
var firstSpec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
var (_, _, _, firstSessionId) = ParseFreshTaskArgs(firstSpec);
|
||||
|
||||
var secondSpec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(_tempDir, secondSpec.Cwd);
|
||||
Assert.Equal(new[] { "--resume", firstSessionId }, ArgsAfterEffort(secondSpec));
|
||||
|
||||
using var readCtx = _db.CreateContext();
|
||||
Assert.Null(await new WorktreeRepository(readCtx).GetByTaskIdAsync(taskId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForTaskAsync_NoWorktreeRow_NoWorkingDirConfigured_ThrowsInvalidOperation()
|
||||
{
|
||||
var listId = await SeedListAsync(workingDir: null);
|
||||
var listId = await SeedListAsync();
|
||||
using (var ctx = _db.CreateContext())
|
||||
{
|
||||
var list = await ctx.Lists.FindAsync(listId);
|
||||
list!.WorkingDir = null; // distinct from "not a git repo" -- no directory at all
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
var taskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
|
||||
|
||||
@@ -202,16 +258,45 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
[Theory]
|
||||
[InlineData(WorktreeState.Merged)]
|
||||
[InlineData(WorktreeState.Discarded)]
|
||||
public async Task BuildForTaskAsync_WorktreeNotActiveOrKept_ThrowsInvalidOperation(WorktreeState state)
|
||||
public async Task BuildForTaskAsync_WorktreeNotActiveOrKept_GitRepoConfigured_CreatesFreshWorktree(WorktreeState state)
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }
|
||||
|
||||
var repo = CreateRepo();
|
||||
var listId = await SeedListAsync(workingDir: repo.RepoDir);
|
||||
var taskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
|
||||
await SeedWorktreeAsync(taskId, state);
|
||||
|
||||
var svc = BuildService();
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => svc.BuildForTaskAsync(taskId, CancellationToken.None));
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
using var readCtx = _db.CreateContext();
|
||||
var wtRow = await new WorktreeRepository(readCtx).GetByTaskIdAsync(taskId);
|
||||
Assert.NotNull(wtRow);
|
||||
Assert.Equal(WorktreeState.Active, wtRow!.State);
|
||||
Assert.Equal(wtRow.Path, spec.Cwd);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WorktreeState.Merged)]
|
||||
[InlineData(WorktreeState.Discarded)]
|
||||
public async Task BuildForTaskAsync_WorktreeNotActiveOrKept_NotAGitRepo_OpensDirectlyInWorkingDir(WorktreeState state)
|
||||
{
|
||||
var listId = await SeedListAsync(); // default WorkingDir (_tempDir) is not a git repo
|
||||
var taskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
|
||||
await SeedWorktreeAsync(taskId, state);
|
||||
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||
|
||||
Assert.Equal(_tempDir, spec.Cwd);
|
||||
|
||||
using var readCtx = _db.CreateContext();
|
||||
var wtRow = await new WorktreeRepository(readCtx).GetByTaskIdAsync(taskId);
|
||||
Assert.NotNull(wtRow);
|
||||
Assert.Equal(state, wtRow!.State); // the stale row is left untouched, not deleted or reused
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user