using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Services; using ClaudeDo.Worker.Tests.Infrastructure; using Microsoft.Extensions.Logging.Abstractions; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.Tests.Services; public sealed class StaleTaskRecoveryTests : IDisposable { private readonly DbFixture _db = new(); private readonly TaskRepository _tasks; private readonly ListRepository _lists; public StaleTaskRecoveryTests() { _tasks = new TaskRepository(_db.Factory); _lists = new ListRepository(_db.Factory); } public void Dispose() => _db.Dispose(); [Fact] public async Task StartAsync_Flips_Running_Tasks_To_Failed() { var listId = Guid.NewGuid().ToString(); await _lists.AddAsync(new ListEntity { Id = listId, Name = "Test", CreatedAt = DateTime.UtcNow }); var running = new TaskEntity { Id = Guid.NewGuid().ToString(), ListId = listId, Title = "Running task", Status = TaskStatus.Running, CreatedAt = DateTime.UtcNow, }; var queued = new TaskEntity { Id = Guid.NewGuid().ToString(), ListId = listId, Title = "Queued task", Status = TaskStatus.Queued, CreatedAt = DateTime.UtcNow, }; await _tasks.AddAsync(running); await _tasks.AddAsync(queued); var recovery = new StaleTaskRecovery(_tasks, NullLogger.Instance); await recovery.StartAsync(CancellationToken.None); var r = await _tasks.GetByIdAsync(running.Id); Assert.Equal(TaskStatus.Failed, r!.Status); Assert.StartsWith("[stale] ", r.Result); var q = await _tasks.GetByIdAsync(queued.Id); Assert.Equal(TaskStatus.Queued, q!.Status); } }