fix(worker): route done-toggle and dequeue through guarded TaskStateService transitions
The task-list done toggle (both islands) and RemoveFromQueue wrote TaskEntity.Status directly via EF, bypassing TaskStateService: no TaskUpdated broadcast, no guard against a concurrent picker claim (lost update), and no status-based filter. Added guarded MarkDoneAsync/UnmarkDoneAsync/DequeueToIdleAsync transitions plus matching hub methods (SetTaskDone/UnsetTaskDone/DequeueTask) and IWorkerClient wrappers; the three UI call sites now route through the hub with optimistic-then-revert row updates and ErrorReported on failure. RemoveFromQueueAsync dequeues each queued child individually through the same guarded path instead of cascading via a raw EF update. Also closes two hub guard gaps: UpdateListConfig's delete branch now preserves a list's SerializeOnFileOverlap flag instead of dropping it, and SubmitTaskForReview's Idle/Failed status gate now runs before either mutation branch so a Done/Cancelled task can't get committed or stamped and then rejected.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
@@ -39,8 +40,31 @@ public class TasksIslandRemoveFromQueueTests : IDisposable
|
||||
public ClaudeDoDbContext CreateDbContext() => _create();
|
||||
}
|
||||
|
||||
private TasksIslandViewModel BuildViewModel() =>
|
||||
new(new TestDbFactory(NewContext), worker: null);
|
||||
// RemoveFromQueueAsync now routes the dequeue through IWorkerClient.DequeueTaskAsync (the
|
||||
// guarded TaskStateService transition) instead of writing the DB directly. This fake performs
|
||||
// the same write the real hub would, against the same DB, so these tests still exercise the
|
||||
// ViewModel's cascade/read logic without needing a live worker.
|
||||
private sealed class DequeuingWorkerClient : StubWorkerClient
|
||||
{
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
public DequeuingWorkerClient(IDbContextFactory<ClaudeDoDbContext> dbFactory) => _dbFactory = dbFactory;
|
||||
|
||||
public override async Task DequeueTaskAsync(string taskId)
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == taskId && t.Status == TaskStatus.Queued);
|
||||
if (entity is null) return;
|
||||
entity.Status = TaskStatus.Idle;
|
||||
entity.BlockedByTaskId = null;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private TasksIslandViewModel BuildViewModel()
|
||||
{
|
||||
var factory = new TestDbFactory(NewContext);
|
||||
return new(factory, worker: new DequeuingWorkerClient(factory));
|
||||
}
|
||||
|
||||
private async Task SeedParentWithChainAsync(
|
||||
string parentId,
|
||||
|
||||
Reference in New Issue
Block a user