using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Tests.Infrastructure; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Xunit; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.Tests.Hub; public sealed class SetTaskStatusHubTests : IDisposable { private readonly DbFixture _db = new(); public void Dispose() => _db.Dispose(); private WorkerHub CreateHub() { var factory = _db.CreateFactory(); var built = TaskStateServiceBuilder.Build(factory); var broadcaster = new HubBroadcaster(new CapturingHubContext()); var hub = new WorkerHub( null!, null!, null!, null!, broadcaster, factory, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, built.State, null!, null!, null!, new ClaudeDo.Worker.Online.OnlineInboxConfig(), new ClaudeDo.Worker.OnlineRefreshTokenStore(new ClaudeDo.Worker.DpapiTokenStore("unused.token")), new ClaudeDo.Worker.Runner.PendingQuestionRegistry(), null!); hub.Clients = new FakeHubCallerClients(new RecordingClientProxy()); hub.Context = new FakeHubCallerContext(); return hub; } private async Task SeedTaskAsync(TaskStatus status) { using var ctx = _db.CreateContext(); var listId = Guid.NewGuid().ToString(); await new ListRepository(ctx).AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow, }); var taskId = Guid.NewGuid().ToString(); await new TaskRepository(ctx).AddAsync(new TaskEntity { Id = taskId, ListId = listId, Title = "T", Status = status, CreatedAt = DateTime.UtcNow, CommitType = "feat", }); return taskId; } [Fact] public async Task SetTaskStatus_RunningTask_Throws_AndLeavesStatusUnchanged() { var taskId = await SeedTaskAsync(TaskStatus.Running); var hub = CreateHub(); await Assert.ThrowsAsync(() => hub.SetTaskStatus(taskId, "Done")); await using var ctx = _db.CreateContext(); var task = await ctx.Tasks.FindAsync(taskId); Assert.Equal(TaskStatus.Running, task!.Status); } [Fact] public async Task SetTaskStatus_IdleTask_StillSucceeds() { var taskId = await SeedTaskAsync(TaskStatus.Idle); var hub = CreateHub(); await hub.SetTaskStatus(taskId, "Cancelled"); await using var ctx = _db.CreateContext(); var task = await ctx.Tasks.FindAsync(taskId); Assert.Equal(TaskStatus.Cancelled, task!.Status); } [Fact] public async Task SetTaskStatus_Missing_Throws() { var hub = CreateHub(); await Assert.ThrowsAsync(() => hub.SetTaskStatus("does-not-exist", "Done")); } }