diff --git a/src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs b/src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs index b81a0f7..b2ad18c 100644 --- a/src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs +++ b/src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs @@ -14,8 +14,9 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration TaskStatus.Idle => "idle", TaskStatus.Queued => "queued", TaskStatus.Running => "running", - TaskStatus.WaitingForReview => "waiting_for_review", - TaskStatus.Done => "done", + TaskStatus.WaitingForReview => "waiting_for_review", + TaskStatus.WaitingForChildren => "waiting_for_children", + TaskStatus.Done => "done", TaskStatus.Failed => "failed", TaskStatus.Cancelled => "cancelled", _ => throw new ArgumentOutOfRangeException(nameof(v)), @@ -27,8 +28,9 @@ public class TaskEntityConfiguration : IEntityTypeConfiguration "idle" => TaskStatus.Idle, "queued" => TaskStatus.Queued, "running" => TaskStatus.Running, - "waiting_for_review" => TaskStatus.WaitingForReview, - "done" => TaskStatus.Done, + "waiting_for_review" => TaskStatus.WaitingForReview, + "waiting_for_children" => TaskStatus.WaitingForChildren, + "done" => TaskStatus.Done, "failed" => TaskStatus.Failed, "cancelled" => TaskStatus.Cancelled, _ => throw new ArgumentOutOfRangeException(nameof(v)), diff --git a/src/ClaudeDo.Data/Models/TaskEntity.cs b/src/ClaudeDo.Data/Models/TaskEntity.cs index 06b575f..74af717 100644 --- a/src/ClaudeDo.Data/Models/TaskEntity.cs +++ b/src/ClaudeDo.Data/Models/TaskEntity.cs @@ -6,6 +6,7 @@ public enum TaskStatus Queued, Running, WaitingForReview, + WaitingForChildren, Done, Failed, Cancelled, diff --git a/tests/ClaudeDo.Data.Tests/CreateChildTests.cs b/tests/ClaudeDo.Data.Tests/CreateChildTests.cs new file mode 100644 index 0000000..e95f4f3 --- /dev/null +++ b/tests/ClaudeDo.Data.Tests/CreateChildTests.cs @@ -0,0 +1,96 @@ +using ClaudeDo.Data; +using ClaudeDo.Data.Models; +using ClaudeDo.Data.Repositories; +using Microsoft.EntityFrameworkCore; +using TaskStatus = ClaudeDo.Data.Models.TaskStatus; + +namespace ClaudeDo.Data.Tests; + +public sealed class CreateChildTests : IDisposable +{ + private readonly string _dbPath; + private readonly ClaudeDoDbContext _ctx; + + public CreateChildTests() + { + _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_test_{Guid.NewGuid():N}.db"); + var options = new DbContextOptionsBuilder() + .UseSqlite($"Data Source={_dbPath}") + .Options; + _ctx = new ClaudeDoDbContext(options); + _ctx.Database.EnsureCreated(); + } + + public void Dispose() + { + _ctx.Dispose(); + try { File.Delete(_dbPath); } catch { } + try { File.Delete(_dbPath + "-wal"); } catch { } + try { File.Delete(_dbPath + "-shm"); } catch { } + } + + private async Task SeedListAsync(string id = "l1") + { + _ctx.Lists.Add(new ListEntity { Id = id, Name = "Test List", CreatedAt = DateTime.UtcNow }); + await _ctx.SaveChangesAsync(); + return id; + } + + // ---- Task 1 ---- + + [Fact] + public async Task WaitingForChildren_roundtrips_through_value_converter() + { + await SeedListAsync(); + var taskId = Guid.NewGuid().ToString(); + _ctx.Tasks.Add(new TaskEntity + { + Id = taskId, + ListId = "l1", + Title = "Parent", + Status = TaskStatus.WaitingForChildren, + CreatedAt = DateTime.UtcNow, + }); + await _ctx.SaveChangesAsync(); + _ctx.ChangeTracker.Clear(); + + var loaded = await _ctx.Tasks.AsNoTracking().FirstAsync(t => t.Id == taskId); + Assert.Equal(TaskStatus.WaitingForChildren, loaded.Status); + } + + // ---- Task 2 ---- + + [Fact] + public async Task CreateChild_attaches_to_non_planning_parent_and_stamps_createdBy() + { + await SeedListAsync(); + var parentId = "p1"; + _ctx.Tasks.Add(new TaskEntity + { + Id = parentId, + ListId = "l1", + Title = "Parent", + Status = TaskStatus.Running, + PlanningPhase = PlanningPhase.None, + CreatedAt = DateTime.UtcNow, + }); + await _ctx.SaveChangesAsync(); + + var child = await new TaskRepository(_ctx).CreateChildAsync( + "p1", "Refactor X", "desc", commitType: null, createdBy: "p1"); + + Assert.Equal("p1", child.ParentTaskId); + Assert.Equal("p1", child.CreatedBy); + Assert.Equal(TaskStatus.Idle, child.Status); + Assert.Equal("l1", child.ListId); + } + + [Fact] + public async Task CreateChild_throws_when_parent_missing() + { + await SeedListAsync(); + + await Assert.ThrowsAsync(() => + new TaskRepository(_ctx).CreateChildAsync("nope", "T", null, null, null)); + } +}