set_task_config/set_list_config now echo which fields were set vs cleared, get_list_config/get_task_config return an explicit found=false instead of null, and delete_list/run_task_now/reset_failed_task/remove_task_attachment return a confirmation record — matching the found/ok convention already used by batch_get_tasks and get_task_log.
93 lines
3.1 KiB
C#
93 lines
3.1 KiB
C#
using ClaudeDo.Data;
|
|
using ClaudeDo.Data.Models;
|
|
using ClaudeDo.Data.Repositories;
|
|
using ClaudeDo.Worker.External;
|
|
using ClaudeDo.Worker.Hub;
|
|
using ClaudeDo.Worker.Lifecycle;
|
|
using ClaudeDo.Worker.Runner;
|
|
using ClaudeDo.Worker.State;
|
|
using ClaudeDo.Worker.Tests.Infrastructure;
|
|
using ClaudeDo.Data.Git;
|
|
using ClaudeDo.Worker.Config;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
|
|
|
namespace ClaudeDo.Worker.Tests.External;
|
|
|
|
public sealed class LifecycleMcpToolsTests : IDisposable
|
|
{
|
|
private readonly DbFixture _db = new();
|
|
private readonly ClaudeDoDbContext _ctx;
|
|
private readonly TaskRepository _tasks;
|
|
private readonly ListRepository _lists;
|
|
|
|
public LifecycleMcpToolsTests()
|
|
{
|
|
_ctx = _db.CreateContext();
|
|
_tasks = new TaskRepository(_ctx);
|
|
_lists = new ListRepository(_ctx);
|
|
}
|
|
|
|
public void Dispose() { _ctx.Dispose(); _db.Dispose(); }
|
|
|
|
private LifecycleMcpTools BuildSut()
|
|
{
|
|
var cfg = new WorkerConfig
|
|
{
|
|
SandboxRoot = Path.Combine(Path.GetTempPath(), $"cd_{Guid.NewGuid():N}"),
|
|
LogRoot = Path.Combine(Path.GetTempPath(), $"cdl_{Guid.NewGuid():N}"),
|
|
};
|
|
var dbFactory = _db.CreateFactory();
|
|
var broadcaster = new HubBroadcaster(new CapturingHubContext());
|
|
var wtManager = new WorktreeManager(new GitService(), dbFactory, cfg, NullLogger<WorktreeManager>.Instance);
|
|
var state = TaskStateServiceBuilder.Build(dbFactory).State;
|
|
var reset = new TaskResetService(dbFactory, wtManager, broadcaster, state, NullLogger<TaskResetService>.Instance);
|
|
return new LifecycleMcpTools(_tasks, reset);
|
|
}
|
|
|
|
private async Task<TaskEntity> SeedTaskAsync(TaskStatus status)
|
|
{
|
|
var listId = Guid.NewGuid().ToString();
|
|
await _lists.AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow });
|
|
var task = new TaskEntity
|
|
{
|
|
Id = Guid.NewGuid().ToString(), ListId = listId, Title = "t",
|
|
Status = status, CreatedAt = DateTime.UtcNow, CommitType = "chore",
|
|
};
|
|
await _tasks.AddAsync(task);
|
|
return task;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResetFailedTask_OnFailed_ResetsToIdle()
|
|
{
|
|
var task = await SeedTaskAsync(TaskStatus.Failed);
|
|
var sut = BuildSut();
|
|
|
|
var result = await sut.ResetFailedTask(task.Id, CancellationToken.None);
|
|
|
|
Assert.True(result.Reset);
|
|
Assert.Equal(task.Id, result.TaskId);
|
|
var loaded = await _tasks.GetByIdAsync(task.Id);
|
|
Assert.Equal(TaskStatus.Idle, loaded!.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResetFailedTask_OnNonFailed_Throws()
|
|
{
|
|
var task = await SeedTaskAsync(TaskStatus.Done);
|
|
var sut = BuildSut();
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
sut.ResetFailedTask(task.Id, CancellationToken.None));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResetFailedTask_NotFound_Throws()
|
|
{
|
|
var sut = BuildSut();
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
sut.ResetFailedTask("missing", CancellationToken.None));
|
|
}
|
|
}
|