Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/Planning/PlanningSessionManagerTests.cs
mika kuns b6bec1e63c feat(worker): PlanningSessionManager.StartAsync
Add PlanningSessionFiles, PlanningSessionStartContext/ResumeContext DTOs,
PlanningSessionManager.StartAsync (file scaffolding + status transition),
and integration tests. Also fix migration discovery by adding [DbContext]
attribute to all migration classes and switch DbFixture to EnsureCreated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 20:49:41 +02:00

123 lines
4.1 KiB
C#

using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Planning;
using ClaudeDo.Worker.Tests.Infrastructure;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Worker.Tests.Planning;
public sealed class PlanningSessionManagerTests : IDisposable
{
private readonly DbFixture _db = new();
private readonly ClaudeDoDbContext _ctx;
private readonly TaskRepository _tasks;
private readonly ListRepository _lists;
private readonly string _rootDir;
private readonly PlanningSessionManager _sut;
public PlanningSessionManagerTests()
{
_ctx = _db.CreateContext();
_tasks = new TaskRepository(_ctx);
_lists = new ListRepository(_ctx);
_rootDir = Path.Combine(Path.GetTempPath(), $"cd_planning_{Guid.NewGuid():N}");
_sut = new PlanningSessionManager(_tasks, _lists, _rootDir);
}
public void Dispose()
{
_ctx.Dispose();
_db.Dispose();
try { Directory.Delete(_rootDir, recursive: true); } catch { /* ignore */ }
}
private async Task<(string listId, string workingDir)> SeedListAsync()
{
var listId = Guid.NewGuid().ToString();
var wd = Path.Combine(Path.GetTempPath(), $"cd_wd_{Guid.NewGuid():N}");
Directory.CreateDirectory(wd);
await _lists.AddAsync(new ListEntity
{
Id = listId,
Name = "Test",
WorkingDir = wd,
CreatedAt = DateTime.UtcNow,
});
return (listId, wd);
}
private async Task<TaskEntity> SeedManualTaskAsync(string listId)
{
var t = new TaskEntity
{
Id = Guid.NewGuid().ToString(),
ListId = listId,
Title = "Brainstorm auth",
Description = "- review tokens\n- plan rollout",
Status = TaskStatus.Manual,
CreatedAt = DateTime.UtcNow,
CommitType = "feat",
};
await _tasks.AddAsync(t);
return t;
}
[Fact]
public async Task StartAsync_CreatesSessionFiles_AndTransitionsTaskToPlanning()
{
var (listId, wd) = await SeedListAsync();
var parent = await SeedManualTaskAsync(listId);
var ctx = await _sut.StartAsync(parent.Id, CancellationToken.None);
Assert.Equal(parent.Id, ctx.ParentTaskId);
Assert.Equal(wd, ctx.WorkingDir);
Assert.True(File.Exists(ctx.Files.McpConfigPath));
Assert.True(File.Exists(ctx.Files.SystemPromptPath));
Assert.True(File.Exists(ctx.Files.InitialPromptPath));
var mcp = await File.ReadAllTextAsync(ctx.Files.McpConfigPath);
Assert.Contains("\"type\": \"http\"", mcp);
Assert.Contains("Bearer ", mcp);
var initial = await File.ReadAllTextAsync(ctx.Files.InitialPromptPath);
Assert.Contains("Brainstorm auth", initial);
Assert.Contains("review tokens", initial);
var loaded = await _tasks.GetByIdAsync(parent.Id);
Assert.Equal(TaskStatus.Planning, loaded!.Status);
Assert.NotNull(loaded.PlanningSessionToken);
}
[Fact]
public async Task StartAsync_TaskNotManual_Throws()
{
var (listId, _) = await SeedListAsync();
var queuedTask = new TaskEntity
{
Id = Guid.NewGuid().ToString(),
ListId = listId,
Title = "x",
Status = TaskStatus.Queued,
CreatedAt = DateTime.UtcNow,
CommitType = "feat",
};
await _tasks.AddAsync(queuedTask);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_sut.StartAsync(queuedTask.Id, CancellationToken.None));
}
[Fact]
public async Task StartAsync_ChildTask_Throws()
{
var (listId, _) = await SeedListAsync();
var parent = await SeedManualTaskAsync(listId);
await _tasks.SetPlanningStartedAsync(parent.Id, "t");
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null, null);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_sut.StartAsync(child.Id, CancellationToken.None));
}
}