Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/External/ListHandlerMcpToolsTests.cs
T
mika kuns 451c01848c fix(worker): start_list_handler ehrlich machen, wenn keine App verbunden ist
ConnectedClientTracker zaehlt verbundene UI-Clients (WorkerHub.OnConnectedAsync/
OnDisconnectedAsync). StartListHandler prueft ihn vor dem Broadcast und liefert
bei 0 verbundenen Clients Requested=false plus Reason statt unbedingtem true.
Tool-Beschreibung und external-mcp.md an das reale Verhalten angepasst.
2026-09-10 13:33:30 +02:00

114 lines
3.8 KiB
C#

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 ListHandlerMcpToolsTests : IDisposable
{
private readonly DbFixture _db = new();
private readonly ClaudeDoDbContext _ctx;
private readonly TaskRepository _tasks;
private readonly ListRepository _lists;
private readonly CapturingHubContext _hubContext = new();
public ListHandlerMcpToolsTests()
{
_ctx = _db.CreateContext();
_tasks = new TaskRepository(_ctx);
_lists = new ListRepository(_ctx);
}
public void Dispose()
{
_ctx.Dispose();
_db.Dispose();
}
private ListHandlerMcpTools BuildSut(ConnectedClientTracker tracker) =>
new(_lists, _tasks, new HubBroadcaster(_hubContext), tracker);
private async Task<string> SeedListAsync()
{
var listId = Guid.NewGuid().ToString();
await _lists.AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow });
return listId;
}
private async Task<TaskEntity> 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;
}
[Fact]
public async Task StartListHandler_NoConnectedClient_DoesNotBroadcastAndReportsNotRequested()
{
var listId = await SeedListAsync();
await SeedTaskAsync(listId);
var tracker = new ConnectedClientTracker();
var result = await BuildSut(tracker).StartListHandler(listId, cancellationToken: CancellationToken.None);
Assert.False(result.Requested);
Assert.False(string.IsNullOrWhiteSpace(result.Reason));
Assert.Equal(1, result.TaskCount);
Assert.Empty(_hubContext.Proxy.Calls);
}
[Fact]
public async Task StartListHandler_ConnectedClient_BroadcastsAndReportsRequested()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId);
var tracker = new ConnectedClientTracker();
tracker.Increment();
var result = await BuildSut(tracker).StartListHandler(listId, cancellationToken: CancellationToken.None);
Assert.True(result.Requested);
Assert.Null(result.Reason);
Assert.Equal(1, result.TaskCount);
Assert.Equal(new[] { task.Number }, result.TaskNumbers);
var call = Assert.Single(_hubContext.Proxy.Calls);
Assert.Equal("ListHandlerStartRequested", call.Method);
Assert.Equal(listId, call.Args[0]);
}
[Fact]
public async Task StartListHandler_LastClientDisconnects_StopsReportingRequested()
{
var listId = await SeedListAsync();
await SeedTaskAsync(listId);
var tracker = new ConnectedClientTracker();
tracker.Increment();
tracker.Decrement();
var result = await BuildSut(tracker).StartListHandler(listId, cancellationToken: CancellationToken.None);
Assert.False(result.Requested);
Assert.Empty(_hubContext.Proxy.Calls);
}
[Fact]
public async Task StartListHandler_NoTasksToHandle_ThrowsRegardlessOfConnection()
{
var listId = await SeedListAsync();
var tracker = new ConnectedClientTracker();
tracker.Increment();
await Assert.ThrowsAsync<InvalidOperationException>(() =>
BuildSut(tracker).StartListHandler(listId, cancellationToken: CancellationToken.None));
}
}