Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/Repositories/TaskRunRepositoryTests.cs
Mika Kuns 19a210406e feat(data): add TaskRunRepository with CRUD and query methods
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 11:31:34 +02:00

171 lines
5.5 KiB
C#

using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Tests.Infrastructure;
namespace ClaudeDo.Worker.Tests.Repositories;
public sealed class TaskRunRepositoryTests : IDisposable
{
private readonly DbFixture _db = new();
private readonly TaskRunRepository _runs;
private readonly string _taskId;
public TaskRunRepositoryTests()
{
_runs = new TaskRunRepository(_db.Factory);
// Seed a list and task for all tests
var lists = new ListRepository(_db.Factory);
var tasks = new TaskRepository(_db.Factory);
var listId = Guid.NewGuid().ToString();
lists.AddAsync(new ListEntity
{
Id = listId,
Name = "Test List",
CreatedAt = DateTime.UtcNow,
}).GetAwaiter().GetResult();
_taskId = Guid.NewGuid().ToString();
tasks.AddAsync(new TaskEntity
{
Id = _taskId,
ListId = listId,
Title = "Test Task",
Status = Data.Models.TaskStatus.Queued,
CreatedAt = DateTime.UtcNow,
CommitType = "feat",
}).GetAwaiter().GetResult();
}
public void Dispose() => _db.Dispose();
private TaskRunEntity MakeRun(int runNumber, bool isRetry = false) => new()
{
Id = Guid.NewGuid().ToString(),
TaskId = _taskId,
RunNumber = runNumber,
IsRetry = isRetry,
Prompt = $"Do something (run {runNumber})",
StartedAt = DateTime.UtcNow,
};
[Fact]
public async Task Add_And_GetById_Roundtrips()
{
var entity = new TaskRunEntity
{
Id = Guid.NewGuid().ToString(),
TaskId = _taskId,
RunNumber = 1,
SessionId = "sess-abc",
IsRetry = false,
Prompt = "Fix the bug",
ResultMarkdown = "All done",
StructuredOutputJson = """{"ok":true}""",
ErrorMarkdown = null,
ExitCode = 0,
TurnCount = 5,
TokensIn = 1000,
TokensOut = 2000,
LogPath = "/tmp/run1.ndjson",
StartedAt = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
FinishedAt = new DateTime(2026, 1, 1, 0, 5, 0, DateTimeKind.Utc),
};
await _runs.AddAsync(entity);
var loaded = await _runs.GetByIdAsync(entity.Id);
Assert.NotNull(loaded);
Assert.Equal(entity.Id, loaded.Id);
Assert.Equal(entity.TaskId, loaded.TaskId);
Assert.Equal(entity.RunNumber, loaded.RunNumber);
Assert.Equal(entity.SessionId, loaded.SessionId);
Assert.Equal(entity.IsRetry, loaded.IsRetry);
Assert.Equal(entity.Prompt, loaded.Prompt);
Assert.Equal(entity.ResultMarkdown, loaded.ResultMarkdown);
Assert.Equal(entity.StructuredOutputJson, loaded.StructuredOutputJson);
Assert.Null(loaded.ErrorMarkdown);
Assert.Equal(entity.ExitCode, loaded.ExitCode);
Assert.Equal(entity.TurnCount, loaded.TurnCount);
Assert.Equal(entity.TokensIn, loaded.TokensIn);
Assert.Equal(entity.TokensOut, loaded.TokensOut);
Assert.Equal(entity.LogPath, loaded.LogPath);
Assert.Equal(entity.StartedAt, loaded.StartedAt);
Assert.Equal(entity.FinishedAt, loaded.FinishedAt);
}
[Fact]
public async Task GetByTaskId_Returns_Ordered_By_RunNumber()
{
var run3 = MakeRun(3);
var run1 = MakeRun(1);
var run2 = MakeRun(2);
await _runs.AddAsync(run3);
await _runs.AddAsync(run1);
await _runs.AddAsync(run2);
var runs = await _runs.GetByTaskIdAsync(_taskId);
Assert.Equal(3, runs.Count);
Assert.Equal(1, runs[0].RunNumber);
Assert.Equal(2, runs[1].RunNumber);
Assert.Equal(3, runs[2].RunNumber);
}
[Fact]
public async Task GetLatestByTaskId_Returns_Highest_RunNumber()
{
var run1 = MakeRun(1);
var run2 = MakeRun(2);
await _runs.AddAsync(run1);
await _runs.AddAsync(run2);
var latest = await _runs.GetLatestByTaskIdAsync(_taskId);
Assert.NotNull(latest);
Assert.Equal(run2.Id, latest.Id);
Assert.Equal(2, latest.RunNumber);
}
[Fact]
public async Task Update_Persists_Completion_Fields()
{
var run = MakeRun(1);
await _runs.AddAsync(run);
run.SessionId = "sess-xyz";
run.ResultMarkdown = "Task completed";
run.StructuredOutputJson = """{"status":"done"}""";
run.ErrorMarkdown = null;
run.ExitCode = 0;
run.TurnCount = 12;
run.TokensIn = 5000;
run.TokensOut = 8000;
run.FinishedAt = new DateTime(2026, 6, 1, 12, 0, 0, DateTimeKind.Utc);
await _runs.UpdateAsync(run);
var loaded = await _runs.GetByIdAsync(run.Id);
Assert.NotNull(loaded);
Assert.Equal("sess-xyz", loaded.SessionId);
Assert.Equal("Task completed", loaded.ResultMarkdown);
Assert.Equal("""{"status":"done"}""", loaded.StructuredOutputJson);
Assert.Null(loaded.ErrorMarkdown);
Assert.Equal(0, loaded.ExitCode);
Assert.Equal(12, loaded.TurnCount);
Assert.Equal(5000, loaded.TokensIn);
Assert.Equal(8000, loaded.TokensOut);
Assert.Equal(new DateTime(2026, 6, 1, 12, 0, 0, DateTimeKind.Utc), loaded.FinishedAt);
}
[Fact]
public async Task GetLatestByTaskId_Returns_Null_When_No_Runs()
{
var latest = await _runs.GetLatestByTaskIdAsync(Guid.NewGuid().ToString());
Assert.Null(latest);
}
}