Merge claudedo/973ea49ec0a642ea9b1ddcff9b71b6fe
This commit is contained in:
@@ -20,7 +20,8 @@ public static class TaskStateServiceBuilder
|
||||
CountingQueueWaker Waker,
|
||||
RunCancellationRegistry RunCancels);
|
||||
|
||||
public static Built Build(IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
||||
public static Built Build(
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory, Func<IActiveMergeState>? mergeState = null)
|
||||
{
|
||||
var hub = new CapturingHubContext();
|
||||
var broadcaster = new HubBroadcaster(hub);
|
||||
@@ -35,12 +36,19 @@ public static class TaskStateServiceBuilder
|
||||
waker,
|
||||
chain,
|
||||
runCancels,
|
||||
mergeState ?? (() => NoActiveMergeState.Instance),
|
||||
NullLogger<TaskStateService>.Instance);
|
||||
|
||||
return new Built(state, chain, hub, () => waker.Count, waker, runCancels);
|
||||
}
|
||||
}
|
||||
|
||||
file sealed class NoActiveMergeState : IActiveMergeState
|
||||
{
|
||||
public static readonly NoActiveMergeState Instance = new();
|
||||
public bool HasActiveMerge(string taskId) => false;
|
||||
}
|
||||
|
||||
public sealed class CountingQueueWaker : IQueueWaker
|
||||
{
|
||||
private int _count;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Planning;
|
||||
using ClaudeDo.Worker.State;
|
||||
using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -8,6 +9,12 @@ using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.State;
|
||||
|
||||
file sealed class FakeActiveMergeState : IActiveMergeState
|
||||
{
|
||||
public HashSet<string> ActiveTaskIds { get; } = new();
|
||||
public bool HasActiveMerge(string taskId) => ActiveTaskIds.Contains(taskId);
|
||||
}
|
||||
|
||||
public sealed class TaskStateServiceTests : IDisposable
|
||||
{
|
||||
private readonly DbFixture _db = new();
|
||||
@@ -319,6 +326,34 @@ public sealed class TaskStateServiceTests : IDisposable
|
||||
Assert.Equal(TaskStatus.Done, await GetStatusAsync(id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelAsync_WhileUnitMergeDraining_Rejects_AndDoesNotMutate()
|
||||
{
|
||||
var mergeState = new FakeActiveMergeState();
|
||||
var built = TaskStateServiceBuilder.Build(_factory, () => mergeState);
|
||||
var id = await SeedTaskAsync(TaskStatus.WaitingForReview);
|
||||
mergeState.ActiveTaskIds.Add(id);
|
||||
|
||||
var result = await built.State.CancelAsync(id, DateTime.UtcNow, default);
|
||||
|
||||
Assert.False(result.Ok);
|
||||
Assert.Equal(TaskStatus.WaitingForReview, await GetStatusAsync(id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelAsync_AfterUnitMergeDrainCompletes_Succeeds()
|
||||
{
|
||||
var mergeState = new FakeActiveMergeState();
|
||||
var built = TaskStateServiceBuilder.Build(_factory, () => mergeState);
|
||||
var id = await SeedTaskAsync(TaskStatus.WaitingForReview);
|
||||
// Merge drain finished (or never started) for this task — cancel behaves as before.
|
||||
|
||||
var result = await built.State.CancelAsync(id, DateTime.UtcNow, default);
|
||||
|
||||
Assert.True(result.Ok);
|
||||
Assert.Equal(TaskStatus.Cancelled, await GetStatusAsync(id));
|
||||
}
|
||||
|
||||
// ─── ResetToIdleAsync ─────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user