Merge claudedo/d8199f1f3df3447f8de2e7aca9ec5064
This commit is contained in:
@@ -16,6 +16,7 @@ using ClaudeDo.Worker.Usage;
|
||||
using ClaudeDo.Worker.Worktrees;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ModelContextProtocol;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.External;
|
||||
@@ -566,6 +567,79 @@ public sealed class ExternalMcpServiceTests : IDisposable
|
||||
Assert.Equal(WorktreeState.Merged, verify.Worktrees.Single(w => w.TaskId == task.Id).State);
|
||||
}
|
||||
|
||||
// Regression coverage for the incident this fixes: an approve whose post-merge verify gate
|
||||
// runs long enough gets no MCP traffic at all until RunVerifyGateAsync returns, so Claude
|
||||
// Code's ~300s idle-silence abort kills the call -- the merge had already landed and
|
||||
// committed, but the task never reached Done and every dependsOn successor stayed blocked
|
||||
// until someone noticed and force-set the status by hand. Shrinks TaskMergeService's own
|
||||
// progress interval (not TaskWaitMcpTools') to observe a report without waiting 30s real time.
|
||||
[Fact]
|
||||
public async Task ReviewTask_ApproveWithSlowVerifyCommand_ReportsProgressWellBeforeIdleTimeout()
|
||||
{
|
||||
if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }
|
||||
|
||||
var original = TaskMergeService.ProgressReportInterval;
|
||||
TaskMergeService.ProgressReportInterval = TimeSpan.FromMilliseconds(50);
|
||||
try
|
||||
{
|
||||
var (task, list, wt) = await SeedWorktreeAsync(TaskStatus.WaitingForReview);
|
||||
File.WriteAllText(Path.Combine(wt.WorktreePath, "feature.txt"), "x\n");
|
||||
var cfg = new WorkerConfig { WorktreeRootStrategy = "sibling" };
|
||||
var mgr = new WorktreeManager(new GitService(), _db.CreateFactory(), cfg, NullLogger<WorktreeManager>.Instance);
|
||||
await mgr.CommitIfChangedAsync(wt, task, list, CancellationToken.None);
|
||||
|
||||
using (var ctx = _db.CreateContext())
|
||||
await new ListRepository(ctx).SetConfigAsync(new ListConfigEntity { ListId = list.Id, VerifyCommand = "dotnet build" });
|
||||
|
||||
var fakeVerify = new FakeVerifyCommandRunner
|
||||
{
|
||||
Result = new VerifyCommandResult(0, false, "ok"),
|
||||
Delay = TimeSpan.FromMilliseconds(300),
|
||||
};
|
||||
var sut = BuildSut(CreateQueue(), fakeVerify);
|
||||
|
||||
var reports = new List<ProgressNotificationValue>();
|
||||
var progress = new Progress<ProgressNotificationValue>(reports.Add);
|
||||
|
||||
var result = await sut.ReviewTask(
|
||||
task.Id, "approve", null, null, cancellationToken: CancellationToken.None, progress: progress);
|
||||
|
||||
Assert.Equal(TaskMergeService.StatusMerged, result.MergeStatus);
|
||||
Assert.Equal("Done", result.Task.Status);
|
||||
// Progress<T> marshals via the SynchronizationContext captured at construction; give
|
||||
// any queued callbacks a beat to run before asserting on `reports`.
|
||||
await Task.Delay(200);
|
||||
Assert.Contains(reports, r => r.Message != null && r.Message.Contains("verify gate running"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
TaskMergeService.ProgressReportInterval = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReviewTask_ApproveWithVerifyCommand_NoProgressToken_DoesNotThrow()
|
||||
{
|
||||
if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; }
|
||||
|
||||
var (task, list, wt) = await SeedWorktreeAsync(TaskStatus.WaitingForReview);
|
||||
File.WriteAllText(Path.Combine(wt.WorktreePath, "feature.txt"), "x\n");
|
||||
var cfg = new WorkerConfig { WorktreeRootStrategy = "sibling" };
|
||||
var mgr = new WorktreeManager(new GitService(), _db.CreateFactory(), cfg, NullLogger<WorktreeManager>.Instance);
|
||||
await mgr.CommitIfChangedAsync(wt, task, list, CancellationToken.None);
|
||||
|
||||
using (var ctx = _db.CreateContext())
|
||||
await new ListRepository(ctx).SetConfigAsync(new ListConfigEntity { ListId = list.Id, VerifyCommand = "dotnet build" });
|
||||
|
||||
var fakeVerify = new FakeVerifyCommandRunner { Result = new VerifyCommandResult(0, false, "ok") };
|
||||
var sut = BuildSut(CreateQueue(), fakeVerify);
|
||||
|
||||
var result = await sut.ReviewTask(task.Id, "approve", null, null, cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(TaskMergeService.StatusMerged, result.MergeStatus);
|
||||
Assert.Equal("Done", result.Task.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReviewTask_Approve_ParentWithChildren_RunsUnitMerge()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user