Per-list optional VerifyCommand (list_config.verify_command) runs via VerifyCommandRunner in the list's working dir right after a successful merge/continue-merge, before the task is allowed to reach Done. A non-zero exit or timeout leaves the merge in place but keeps the task out of Done and reports StatusVerifyFailed with an output excerpt through MergeResultDto/review_task; no command configured behaves exactly as before. Merges against the same repo are now serialized per working dir so a running verify can't be interrupted by a second merge landing mid-build. Adds the field to the List Settings modal (en/de localized) and covers success/failure/timeout in TaskMergeServiceTests + VerifyCommandRunnerTests.
137 lines
4.5 KiB
C#
137 lines
4.5 KiB
C#
using ClaudeDo.Data;
|
|
using ClaudeDo.Data.Models;
|
|
using ClaudeDo.Data.Repositories;
|
|
using ClaudeDo.Worker.Tests.Infrastructure;
|
|
|
|
namespace ClaudeDo.Worker.Tests.Repositories;
|
|
|
|
public sealed class ListRepositoryConfigTests : IDisposable
|
|
{
|
|
private readonly DbFixture _db = new();
|
|
private readonly ClaudeDoDbContext _ctx;
|
|
private readonly ListRepository _repo;
|
|
private readonly string _listId;
|
|
|
|
public ListRepositoryConfigTests()
|
|
{
|
|
_ctx = _db.CreateContext();
|
|
_repo = new ListRepository(_ctx);
|
|
_listId = Guid.NewGuid().ToString();
|
|
_repo.AddAsync(new ListEntity
|
|
{
|
|
Id = _listId, Name = "Test", CreatedAt = DateTime.UtcNow
|
|
}).GetAwaiter().GetResult();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetConfig_Returns_Null_When_No_Config()
|
|
{
|
|
var config = await _repo.GetConfigAsync(_listId);
|
|
Assert.Null(config);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_And_GetConfig_Roundtrips()
|
|
{
|
|
var config = new ListConfigEntity
|
|
{
|
|
ListId = _listId,
|
|
Model = "sonnet-4-6",
|
|
SystemPrompt = "You are helpful.",
|
|
AgentPath = "/home/user/.todo-app/agents/dev.md",
|
|
};
|
|
await _repo.SetConfigAsync(config);
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Equal("sonnet-4-6", fetched.Model);
|
|
Assert.Equal("You are helpful.", fetched.SystemPrompt);
|
|
Assert.Equal("/home/user/.todo-app/agents/dev.md", fetched.AgentPath);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Upserts_On_Duplicate()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, Model = "opus-4-6" });
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, Model = "haiku-4-5" });
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Equal("haiku-4-5", fetched.Model);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Persists_SessionSkills_On_Insert()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity
|
|
{
|
|
ListId = _listId,
|
|
SessionSkills = "[\"ponytail\"]",
|
|
});
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Equal("[\"ponytail\"]", fetched.SessionSkills);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Persists_SessionSkills_On_Update()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, SessionSkills = "[\"a\"]" });
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, SessionSkills = "[\"b\",\"c\"]" });
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Equal("[\"b\",\"c\"]", fetched.SessionSkills);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Null_SessionSkills_Clears_On_Update()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, SessionSkills = "[\"a\"]" });
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, SessionSkills = null });
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Null(fetched.SessionSkills);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Persists_VerifyCommand_On_Insert()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, VerifyCommand = "dotnet test" });
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Equal("dotnet test", fetched.VerifyCommand);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Persists_VerifyCommand_On_Update()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, VerifyCommand = "dotnet build" });
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, VerifyCommand = "dotnet test" });
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Equal("dotnet test", fetched.VerifyCommand);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetConfig_Null_VerifyCommand_Clears_On_Update()
|
|
{
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, VerifyCommand = "dotnet test" });
|
|
await _repo.SetConfigAsync(new ListConfigEntity { ListId = _listId, VerifyCommand = null });
|
|
|
|
var fetched = await _repo.GetConfigAsync(_listId);
|
|
Assert.NotNull(fetched);
|
|
Assert.Null(fetched.VerifyCommand);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_ctx.Dispose();
|
|
_db.Dispose();
|
|
}
|
|
}
|