Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/Repositories/TaskRepositoryPlanningTests.cs

191 lines
6.1 KiB
C#

using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Tests.Infrastructure;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Worker.Tests.Repositories;
public sealed class TaskRepositoryPlanningTests : IDisposable
{
private readonly DbFixture _db = new();
private readonly ClaudeDoDbContext _ctx;
private readonly TaskRepository _tasks;
private readonly ListRepository _lists;
private readonly TagRepository _tags;
public TaskRepositoryPlanningTests()
{
_ctx = _db.CreateContext();
_tasks = new TaskRepository(_ctx);
_lists = new ListRepository(_ctx);
_tags = new TagRepository(_ctx);
}
public void Dispose()
{
_ctx.Dispose();
_db.Dispose();
}
private async Task<string> CreateListAsync(string? id = null)
{
var listId = id ?? Guid.NewGuid().ToString();
await _lists.AddAsync(new ListEntity
{
Id = listId,
Name = "Test List",
CreatedAt = DateTime.UtcNow,
});
return listId;
}
private TaskEntity MakeTask(string listId, TaskStatus status = TaskStatus.Manual, string? parentId = null) => new()
{
Id = Guid.NewGuid().ToString(),
ListId = listId,
Title = "t",
Status = status,
CreatedAt = DateTime.UtcNow,
CommitType = "feat",
ParentTaskId = parentId,
};
[Fact]
public async Task GetChildrenAsync_ReturnsOnlyDirectChildren_Sorted()
{
var listId = await CreateListAsync();
var parent = MakeTask(listId, TaskStatus.Planning);
parent.Title = "parent";
await _tasks.AddAsync(parent);
var childA = MakeTask(listId, TaskStatus.Draft, parentId: parent.Id);
childA.Title = "a";
await _tasks.AddAsync(childA);
childA.SortOrder = 1;
await _tasks.UpdateAsync(childA);
var childB = MakeTask(listId, TaskStatus.Draft, parentId: parent.Id);
childB.Title = "b";
await _tasks.AddAsync(childB);
childB.SortOrder = 0;
await _tasks.UpdateAsync(childB);
var unrelated = MakeTask(listId, TaskStatus.Manual);
await _tasks.AddAsync(unrelated);
var children = await _tasks.GetChildrenAsync(parent.Id);
Assert.Equal(2, children.Count);
Assert.Equal("b", children[0].Title);
Assert.Equal("a", children[1].Title);
}
[Fact]
public async Task CreateChildAsync_CreatesDraftUnderParent()
{
var listId = await CreateListAsync();
var parent = MakeTask(listId, TaskStatus.Planning);
await _tasks.AddAsync(parent);
var child = await _tasks.CreateChildAsync(
parent.Id,
title: "child title",
description: "child desc",
tagNames: new[] { "agent" },
commitType: "feat");
Assert.Equal(TaskStatus.Draft, child.Status);
Assert.Equal(parent.Id, child.ParentTaskId);
Assert.Equal(listId, child.ListId);
Assert.Equal("child title", child.Title);
Assert.Equal("child desc", child.Description);
Assert.Equal("feat", child.CommitType);
var loaded = await _tasks.GetByIdAsync(child.Id);
Assert.NotNull(loaded);
Assert.Equal(TaskStatus.Draft, loaded!.Status);
var tags = await _tasks.GetTagsAsync(child.Id);
Assert.Contains(tags, t => t.Name == "agent");
}
[Fact]
public async Task CreateChildAsync_ThrowsIfParentNotFound()
{
var listId = await CreateListAsync();
_ = listId; // just to create the DB
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_tasks.CreateChildAsync("nonexistent-parent-id", "t", null, null, null));
}
[Fact]
public async Task SetPlanningStartedAsync_ManualTask_TransitionsToPlanning()
{
var listId = await CreateListAsync();
var task = MakeTask(listId, TaskStatus.Manual);
await _tasks.AddAsync(task);
var result = await _tasks.SetPlanningStartedAsync(task.Id, "tok-abc");
Assert.NotNull(result);
Assert.Equal(TaskStatus.Planning, result!.Status);
Assert.Equal("tok-abc", result.PlanningSessionToken);
var loaded = await _tasks.GetByIdAsync(task.Id);
Assert.Equal(TaskStatus.Planning, loaded!.Status);
Assert.Equal("tok-abc", loaded.PlanningSessionToken);
}
[Fact]
public async Task SetPlanningStartedAsync_NonManualTask_ReturnsNull()
{
var listId = await CreateListAsync();
var task = MakeTask(listId, TaskStatus.Queued);
await _tasks.AddAsync(task);
var result = await _tasks.SetPlanningStartedAsync(task.Id, "tok-xyz");
Assert.Null(result);
var loaded = await _tasks.GetByIdAsync(task.Id);
Assert.Equal(TaskStatus.Queued, loaded!.Status);
Assert.Null(loaded.PlanningSessionToken);
}
[Fact]
public async Task UpdatePlanningSessionIdAsync_StoresClaudeSessionId()
{
var listId = await CreateListAsync();
var task = MakeTask(listId, TaskStatus.Manual);
await _tasks.AddAsync(task);
await _tasks.SetPlanningStartedAsync(task.Id, "tok");
await _tasks.UpdatePlanningSessionIdAsync(task.Id, "claude-session-42");
var loaded = await _tasks.GetByIdAsync(task.Id);
Assert.Equal("claude-session-42", loaded!.PlanningSessionId);
}
[Fact]
public async Task FindByPlanningTokenAsync_ReturnsTask_WhenTokenMatches()
{
var listId = await CreateListAsync();
var task = MakeTask(listId, TaskStatus.Manual);
await _tasks.AddAsync(task);
await _tasks.SetPlanningStartedAsync(task.Id, "unique-token-123");
var found = await _tasks.FindByPlanningTokenAsync("unique-token-123");
Assert.NotNull(found);
Assert.Equal(task.Id, found!.Id);
}
[Fact]
public async Task FindByPlanningTokenAsync_ReturnsNull_WhenTokenUnknown()
{
var found = await _tasks.FindByPlanningTokenAsync("no-such-token");
Assert.Null(found);
}
}