feat(refine): add RefineRunner, prompt/args helper, and interfaces
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
138
tests/ClaudeDo.Worker.Tests/Refine/RefineRunnerTests.cs
Normal file
138
tests/ClaudeDo.Worker.Tests/Refine/RefineRunnerTests.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Refine;
|
||||
using ClaudeDo.Worker.Runner;
|
||||
using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.Refine;
|
||||
|
||||
public sealed class RefineRunnerTests : IDisposable
|
||||
{
|
||||
private readonly DbFixture _db = new();
|
||||
private readonly ClaudeDoDbContext _ctx;
|
||||
|
||||
public RefineRunnerTests()
|
||||
{
|
||||
_ctx = _db.CreateContext();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ctx.Dispose();
|
||||
_db.Dispose();
|
||||
}
|
||||
|
||||
private async Task<string> SeedListAsync()
|
||||
{
|
||||
var listId = Guid.NewGuid().ToString();
|
||||
await new ListRepository(_ctx).AddAsync(new ListEntity
|
||||
{
|
||||
Id = listId,
|
||||
Name = "Test",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
WorkingDir = null,
|
||||
});
|
||||
return listId;
|
||||
}
|
||||
|
||||
private async Task<TaskEntity> SeedTaskAsync(string listId, TaskStatus status)
|
||||
{
|
||||
var task = new TaskEntity
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ListId = listId,
|
||||
Title = "Test task",
|
||||
Status = status,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
await new TaskRepository(_ctx).AddAsync(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
private RefineRunner BuildRunner(RecordingClaudeProcess claude, RecordingRefineBroadcaster broadcaster)
|
||||
{
|
||||
return new RefineRunner(
|
||||
claude,
|
||||
_db.CreateFactory(),
|
||||
NullLogger<RefineRunner>.Instance,
|
||||
broadcaster);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Refuses_when_task_not_idle()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var task = await SeedTaskAsync(listId, TaskStatus.Queued);
|
||||
|
||||
var claude = new RecordingClaudeProcess(success: true);
|
||||
var broadcaster = new RecordingRefineBroadcaster();
|
||||
var runner = BuildRunner(claude, broadcaster);
|
||||
|
||||
var outcome = await runner.RefineAsync(task.Id, CancellationToken.None);
|
||||
|
||||
Assert.False(outcome.Success);
|
||||
Assert.Equal(0, claude.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Idle_task_invokes_claude_once_and_brackets_with_events()
|
||||
{
|
||||
var listId = await SeedListAsync();
|
||||
var task = await SeedTaskAsync(listId, TaskStatus.Idle);
|
||||
|
||||
var claude = new RecordingClaudeProcess(success: true);
|
||||
var broadcaster = new RecordingRefineBroadcaster();
|
||||
var runner = BuildRunner(claude, broadcaster);
|
||||
|
||||
var outcome = await runner.RefineAsync(task.Id, CancellationToken.None);
|
||||
|
||||
Assert.True(outcome.Success);
|
||||
Assert.Equal(1, claude.CallCount);
|
||||
Assert.Equal(1, broadcaster.StartedCount);
|
||||
Assert.Equal(1, broadcaster.FinishedCount);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RecordingClaudeProcess : IClaudeProcess
|
||||
{
|
||||
private readonly bool _success;
|
||||
private int _callCount;
|
||||
|
||||
public int CallCount => _callCount;
|
||||
|
||||
public RecordingClaudeProcess(bool success) => _success = success;
|
||||
|
||||
public Task<RunResult> RunAsync(string arguments, string prompt, string workingDirectory,
|
||||
Func<string, Task> onStdoutLine, CancellationToken ct)
|
||||
{
|
||||
Interlocked.Increment(ref _callCount);
|
||||
var result = _success
|
||||
? new RunResult { ExitCode = 0, ResultMarkdown = "ok" }
|
||||
: new RunResult { ExitCode = 1, ResultMarkdown = null };
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RecordingRefineBroadcaster : IRefineBroadcaster
|
||||
{
|
||||
private int _startedCount;
|
||||
private int _finishedCount;
|
||||
|
||||
public int StartedCount => _startedCount;
|
||||
public int FinishedCount => _finishedCount;
|
||||
|
||||
public Task RefineStartedAsync(string taskId)
|
||||
{
|
||||
Interlocked.Increment(ref _startedCount);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RefineFinishedAsync(string taskId, bool success, string? error)
|
||||
{
|
||||
Interlocked.Increment(ref _finishedCount);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user