using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.External; using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Tests.Infrastructure; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.Tests.External; public sealed class HandoffMcpToolsTests : IDisposable { private readonly DbFixture _db = new(); private readonly ClaudeDoDbContext _ctx; private readonly TaskRepository _tasks; private readonly ListRepository _lists; private readonly CapturingHubContext _hubContext = new(); public HandoffMcpToolsTests() { _ctx = _db.CreateContext(); _tasks = new TaskRepository(_ctx); _lists = new ListRepository(_ctx); } public void Dispose() { _ctx.Dispose(); _db.Dispose(); } private HandoffMcpTools BuildSut() => new(_tasks, new HubBroadcaster(_hubContext)); private async Task SeedTaskAsync(string listId, TaskStatus status = TaskStatus.Idle, string title = "t") { var task = new TaskEntity { Id = Guid.NewGuid().ToString(), ListId = listId, Title = title, Status = status, CreatedAt = DateTime.UtcNow, CommitType = "chore", }; await _tasks.AddAsync(task); return task; } private async Task SeedListAsync() { var listId = Guid.NewGuid().ToString(); await _lists.AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow }); return listId; } [Fact] public async Task HandoffListHandler_ValidIds_BroadcastsAndReturnsCount() { var listId = await SeedListAsync(); var handlerTask = await SeedTaskAsync(listId, title: "List handler: Alpha"); var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor"); var sut = BuildSut(); var result = await sut.HandoffListHandler(handlerTask.Id, new[] { survivor.Id }, CancellationToken.None); Assert.True(result.Requested); Assert.Equal(handlerTask.Id, result.TaskId); Assert.Equal(1, result.SurvivingCount); var call = Assert.Single(_hubContext.Proxy.Calls); Assert.Equal("HandoffRequested", call.Method); Assert.Equal(handlerTask.Id, call.Args[0]); } [Fact] public async Task HandoffListHandler_EmptySurvivingIds_Throws() { var listId = await SeedListAsync(); var handlerTask = await SeedTaskAsync(listId); var sut = BuildSut(); await Assert.ThrowsAsync(() => sut.HandoffListHandler(handlerTask.Id, Array.Empty(), CancellationToken.None)); } [Fact] public async Task HandoffListHandler_UnknownHandlerTask_Throws() { var sut = BuildSut(); await Assert.ThrowsAsync(() => sut.HandoffListHandler("missing", new[] { "x" }, CancellationToken.None)); } [Fact] public async Task HandoffListHandler_UnknownSurvivingTask_Throws() { var listId = await SeedListAsync(); var handlerTask = await SeedTaskAsync(listId); var sut = BuildSut(); await Assert.ThrowsAsync(() => sut.HandoffListHandler(handlerTask.Id, new[] { "missing" }, CancellationToken.None)); } }