feat(claude-do): merge [D2] Die 7 batch_*-MCP-Tools mit Element-Fortschritt (i/n)
ClaudeDo-Task: 286efaaf-b38e-47d4-9655-0c2d87e4b329
This commit is contained in:
@@ -14,10 +14,19 @@ using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
using ClaudeDo.Worker.Usage;
|
||||
using ClaudeDo.Worker.Worktrees;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ModelContextProtocol;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.External;
|
||||
|
||||
// A synchronously-collecting IProgress<T>: Progress<T> marshals through the SynchronizationContext
|
||||
// captured at construction, which is unreliable to assert on immediately in a test.
|
||||
file sealed class SyncProgress<T> : IProgress<T>
|
||||
{
|
||||
public readonly List<T> Reports = new();
|
||||
public void Report(T value) => Reports.Add(value);
|
||||
}
|
||||
|
||||
public sealed class BatchMcpToolsTests : IDisposable
|
||||
{
|
||||
private readonly DbFixture _db = new();
|
||||
@@ -498,4 +507,118 @@ public sealed class BatchMcpToolsTests : IDisposable
|
||||
() => sut.BatchGetTasks(ids, cancellationToken: CancellationToken.None));
|
||||
Assert.Contains("max", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchGetTasks_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var a = await SeedTaskAsync(listId);
|
||||
var b = await SeedTaskAsync(listId);
|
||||
var c = await SeedTaskAsync(listId);
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
await sut.BatchGetTasks(new[] { a.Id, b.Id, c.Id }, cancellationToken: CancellationToken.None, progress: progress);
|
||||
|
||||
Assert.Equal(3, progress.Reports.Count);
|
||||
Assert.Equal(3, progress.Reports[2].Total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchAddTasks_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
await sut.BatchAddTasks(listId, new[]
|
||||
{
|
||||
new BatchAddTaskInput("a"),
|
||||
new BatchAddTaskInput("b"),
|
||||
}, cancellationToken: CancellationToken.None, progress: progress);
|
||||
|
||||
Assert.Equal(2, progress.Reports.Count);
|
||||
Assert.Equal(2, progress.Reports[1].Total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchUpdateTaskStatus_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var t1 = await SeedTaskAsync(listId, "a", TaskStatus.Idle);
|
||||
var t2 = await SeedTaskAsync(listId, "b", TaskStatus.Idle);
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
await sut.BatchUpdateTaskStatus(new[] { t1.Id, t2.Id }, "Queued", CancellationToken.None, progress);
|
||||
|
||||
Assert.Equal(2, progress.Reports.Count);
|
||||
Assert.Equal(2, progress.Reports[1].Total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchCancelTasks_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var t1 = await SeedTaskAsync(listId, "a", TaskStatus.Idle);
|
||||
var t2 = await SeedTaskAsync(listId, "b", TaskStatus.Idle);
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
await sut.BatchCancelTasks(new[] { t1.Id, t2.Id }, CancellationToken.None, progress);
|
||||
|
||||
Assert.Equal(2, progress.Reports.Count);
|
||||
Assert.Equal(2, progress.Reports[1].Total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchDeleteTasks_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var t1 = await SeedTaskAsync(listId, "a", TaskStatus.Idle);
|
||||
var t2 = await SeedTaskAsync(listId, "b", TaskStatus.Idle);
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
await sut.BatchDeleteTasks(new[] { t1.Id, t2.Id }, CancellationToken.None, progress);
|
||||
|
||||
Assert.Equal(2, progress.Reports.Count);
|
||||
Assert.Equal(2, progress.Reports[1].Total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchSetMyDay_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var t1 = await SeedTaskAsync(listId, "a", TaskStatus.Idle);
|
||||
var t2 = await SeedTaskAsync(listId, "b", TaskStatus.Idle);
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
await sut.BatchSetMyDay(new[]
|
||||
{
|
||||
new BatchSetMyDayInput(t1.Id, true),
|
||||
new BatchSetMyDayInput(t2.Id, true),
|
||||
}, CancellationToken.None, progress);
|
||||
|
||||
Assert.Equal(2, progress.Reports.Count);
|
||||
Assert.Equal(2, progress.Reports[1].Total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchCleanupTaskWorktrees_ReportsProgressPerItem()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var t1 = await SeedTaskAsync(listId, "a", TaskStatus.Idle);
|
||||
var t2 = await SeedTaskAsync(listId, "b", TaskStatus.Idle);
|
||||
var sut = BuildSut();
|
||||
var progress = new SyncProgress<ProgressNotificationValue>();
|
||||
|
||||
// Neither task has a worktree -- each item fails individually (ok=false), but the batch
|
||||
// still reports progress per item since the loop doesn't abort on a per-item error.
|
||||
await sut.BatchCleanupTaskWorktrees(new[] { t1.Id, t2.Id }, cancellationToken: CancellationToken.None, progress: progress);
|
||||
|
||||
Assert.Equal(2, progress.Reports.Count);
|
||||
Assert.Equal(2, progress.Reports[1].Total);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user