feat(worker): chain a normal parent's children on WaitingForChildren
A standalone parent (PlanningPhase.None) with existing Idle/Queued children now runs itself first, then sequences its children instead of flat-enqueuing all of them at once: SubmitForChildrenAsync calls PlanningChainCoordinator.SetupChainAsync(enqueue: true) right after the Running -> WaitingForChildren transition, mirroring the planning path but firing immediately instead of waiting for an explicit "Queue plan". Planning parents are left untouched (they queue via QueuePlanAsync), and a child already Running makes SetupChainAsync throw, which is now caught and logged so it can't fail the parent's own transition.
This commit is contained in:
@@ -54,6 +54,81 @@ public sealed class WaitingForChildrenLifecycleTests : IDisposable
|
||||
Assert.False(second.Ok);
|
||||
}
|
||||
|
||||
private async Task<string> SeedRunningParentWithChildrenAsync(
|
||||
PlanningPhase parentPhase, params TaskStatus[] childStatuses)
|
||||
{
|
||||
using var ctx = _db.CreateContext();
|
||||
if (!ctx.Lists.Any())
|
||||
ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
|
||||
ctx.Tasks.Add(new TaskEntity { Id = "p1", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
|
||||
Status = TaskStatus.Running, PlanningPhase = parentPhase, CreatedAt = DateTime.UtcNow });
|
||||
int i = 0;
|
||||
foreach (var cs in childStatuses)
|
||||
ctx.Tasks.Add(new TaskEntity { Id = $"c{i++}", ListId = "l1", Title = "Child", Number = ++_numberSeed,
|
||||
Status = cs, ParentTaskId = "p1", CreatedAt = DateTime.UtcNow });
|
||||
await ctx.SaveChangesAsync();
|
||||
return "p1";
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitForChildren_NormalParent_ChainsIdleChildren()
|
||||
{
|
||||
var id = await SeedRunningParentWithChildrenAsync(
|
||||
PlanningPhase.None, TaskStatus.Idle, TaskStatus.Idle, TaskStatus.Idle);
|
||||
|
||||
var result = await _built.State.SubmitForChildrenAsync(id, DateTime.UtcNow, "ran ok", default);
|
||||
Assert.True(result.Ok);
|
||||
|
||||
using var ctx = _db.CreateContext();
|
||||
var repo = new TaskRepository(ctx);
|
||||
var c0 = await repo.GetByIdAsync("c0");
|
||||
var c1 = await repo.GetByIdAsync("c1");
|
||||
var c2 = await repo.GetByIdAsync("c2");
|
||||
|
||||
Assert.Equal(TaskStatus.Queued, c0!.Status);
|
||||
Assert.Null(c0.BlockedByTaskId);
|
||||
|
||||
Assert.Equal(TaskStatus.Queued, c1!.Status);
|
||||
Assert.Equal("c0", c1.BlockedByTaskId);
|
||||
|
||||
Assert.Equal(TaskStatus.Queued, c2!.Status);
|
||||
Assert.Equal("c1", c2.BlockedByTaskId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitForChildren_PlanningParent_DoesNotChainChildren()
|
||||
{
|
||||
var id = await SeedRunningParentWithChildrenAsync(PlanningPhase.Active, TaskStatus.Idle, TaskStatus.Idle);
|
||||
|
||||
var result = await _built.State.SubmitForChildrenAsync(id, DateTime.UtcNow, "ran ok", default);
|
||||
Assert.True(result.Ok);
|
||||
|
||||
using var ctx = _db.CreateContext();
|
||||
var repo = new TaskRepository(ctx);
|
||||
var c0 = await repo.GetByIdAsync("c0");
|
||||
var c1 = await repo.GetByIdAsync("c1");
|
||||
|
||||
Assert.Equal(TaskStatus.Idle, c0!.Status);
|
||||
Assert.Null(c0.BlockedByTaskId);
|
||||
Assert.Equal(TaskStatus.Idle, c1!.Status);
|
||||
Assert.Null(c1.BlockedByTaskId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitForChildren_RunningChild_DoesNotFailParentTransition()
|
||||
{
|
||||
var id = await SeedRunningParentWithChildrenAsync(PlanningPhase.None, TaskStatus.Running, TaskStatus.Idle);
|
||||
|
||||
var result = await _built.State.SubmitForChildrenAsync(id, DateTime.UtcNow, "ran ok", default);
|
||||
Assert.True(result.Ok);
|
||||
|
||||
using var ctx = _db.CreateContext();
|
||||
var repo = new TaskRepository(ctx);
|
||||
Assert.Equal(TaskStatus.WaitingForChildren, (await repo.GetByIdAsync(id))!.Status);
|
||||
// Reshape aborted (a running child cannot be reshaped) — the untouched sibling stays Idle.
|
||||
Assert.Equal(TaskStatus.Idle, (await repo.GetByIdAsync("c1"))!.Status);
|
||||
}
|
||||
|
||||
private async Task SeedParentWithChildrenAsync(string parentStatus, params TaskStatus[] childStatuses)
|
||||
{
|
||||
using var ctx = _db.CreateContext();
|
||||
|
||||
Reference in New Issue
Block a user