Merge branch 'claudedo/0b2fbb48d44c41558c21d3464c0bd5c2'
This commit is contained in:
@@ -30,7 +30,8 @@ public class TaskMergeServiceTests : IDisposable
|
||||
foreach (var r in _repos) try { r.Dispose(); } catch { }
|
||||
}
|
||||
|
||||
private static (TaskMergeService svc, MergeRecordingClientProxy proxy) BuildService(DbFixture db)
|
||||
private static (TaskMergeService svc, MergeRecordingClientProxy proxy) BuildService(
|
||||
DbFixture db, IVerifyCommandRunner? verify = null)
|
||||
{
|
||||
var fakeHub = new MergeRecordingHubContext();
|
||||
var broadcaster = new HubBroadcaster(fakeHub);
|
||||
@@ -40,10 +41,17 @@ public class TaskMergeServiceTests : IDisposable
|
||||
new GitService(),
|
||||
broadcaster,
|
||||
state,
|
||||
verify ?? new VerifyCommandRunner(),
|
||||
NullLogger<TaskMergeService>.Instance);
|
||||
return (svc, fakeHub.Proxy);
|
||||
}
|
||||
|
||||
private static async Task SeedVerifyCommand(DbFixture db, string listId, string command)
|
||||
{
|
||||
using var ctx = db.CreateContext();
|
||||
await new ListRepository(ctx).SetConfigAsync(new ListConfigEntity { ListId = listId, VerifyCommand = command });
|
||||
}
|
||||
|
||||
private static WorktreeManager BuildWorktreeManager(DbFixture db)
|
||||
{
|
||||
return new WorktreeManager(
|
||||
@@ -706,6 +714,125 @@ public class TaskMergeServiceTests : IDisposable
|
||||
Assert.Equal(TaskStatus.Done, updated!.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApproveAndMergeAsync_NoVerifyCommandConfigured_NeverInvokesRunnerAndMarksDone()
|
||||
{
|
||||
if (!GitRepoFixture.IsGitAvailable()) return;
|
||||
var repo = NewRepo();
|
||||
var db = NewDb();
|
||||
var (list, task) = await SeedListAndTask(db, repo.RepoDir, TaskStatus.WaitingForReview);
|
||||
|
||||
var wtMgr = BuildWorktreeManager(db);
|
||||
var wtCtx = await wtMgr.CreateAsync(task, list, CancellationToken.None);
|
||||
_wtCleanups.Add((repo.RepoDir, wtCtx.WorktreePath));
|
||||
File.WriteAllText(Path.Combine(wtCtx.WorktreePath, "added.txt"), "new\n");
|
||||
await wtMgr.CommitIfChangedAsync(wtCtx, task, list, CancellationToken.None);
|
||||
|
||||
var fakeVerify = new FakeVerifyCommandRunner { Result = new VerifyCommandResult(1, false, "should never run") };
|
||||
var (svc, _) = BuildService(db, fakeVerify);
|
||||
var target = await new GitService().GetCurrentBranchAsync(repo.RepoDir);
|
||||
|
||||
var result = await svc.ApproveAndMergeAsync(task.Id, target, CancellationToken.None);
|
||||
|
||||
Assert.Equal(TaskMergeService.StatusMerged, result.Status);
|
||||
Assert.Null(fakeVerify.CapturedCommand);
|
||||
using var ctx = db.CreateContext();
|
||||
var updated = await new TaskRepository(ctx).GetByIdAsync(task.Id);
|
||||
Assert.Equal(TaskStatus.Done, updated!.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApproveAndMergeAsync_VerifyCommandSucceeds_RunsInListWorkingDirAndMarksDone()
|
||||
{
|
||||
if (!GitRepoFixture.IsGitAvailable()) return;
|
||||
var repo = NewRepo();
|
||||
var db = NewDb();
|
||||
var (list, task) = await SeedListAndTask(db, repo.RepoDir, TaskStatus.WaitingForReview);
|
||||
await SeedVerifyCommand(db, list.Id, "dotnet test");
|
||||
|
||||
var wtMgr = BuildWorktreeManager(db);
|
||||
var wtCtx = await wtMgr.CreateAsync(task, list, CancellationToken.None);
|
||||
_wtCleanups.Add((repo.RepoDir, wtCtx.WorktreePath));
|
||||
File.WriteAllText(Path.Combine(wtCtx.WorktreePath, "added.txt"), "new\n");
|
||||
await wtMgr.CommitIfChangedAsync(wtCtx, task, list, CancellationToken.None);
|
||||
|
||||
var fakeVerify = new FakeVerifyCommandRunner { Result = new VerifyCommandResult(0, false, "all green") };
|
||||
var (svc, _) = BuildService(db, fakeVerify);
|
||||
var target = await new GitService().GetCurrentBranchAsync(repo.RepoDir);
|
||||
|
||||
var result = await svc.ApproveAndMergeAsync(task.Id, target, CancellationToken.None);
|
||||
|
||||
Assert.Equal(TaskMergeService.StatusMerged, result.Status);
|
||||
Assert.Equal("dotnet test", fakeVerify.CapturedCommand);
|
||||
Assert.Equal(repo.RepoDir, fakeVerify.CapturedWorkingDir);
|
||||
using var ctx = db.CreateContext();
|
||||
var updated = await new TaskRepository(ctx).GetByIdAsync(task.Id);
|
||||
Assert.Equal(TaskStatus.Done, updated!.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApproveAndMergeAsync_VerifyCommandFails_KeepsMergeButNotDone()
|
||||
{
|
||||
if (!GitRepoFixture.IsGitAvailable()) return;
|
||||
var repo = NewRepo();
|
||||
var db = NewDb();
|
||||
var (list, task) = await SeedListAndTask(db, repo.RepoDir, TaskStatus.WaitingForReview);
|
||||
await SeedVerifyCommand(db, list.Id, "dotnet test");
|
||||
|
||||
var wtMgr = BuildWorktreeManager(db);
|
||||
var wtCtx = await wtMgr.CreateAsync(task, list, CancellationToken.None);
|
||||
_wtCleanups.Add((repo.RepoDir, wtCtx.WorktreePath));
|
||||
File.WriteAllText(Path.Combine(wtCtx.WorktreePath, "added.txt"), "new\n");
|
||||
await wtMgr.CommitIfChangedAsync(wtCtx, task, list, CancellationToken.None);
|
||||
|
||||
var fakeVerify = new FakeVerifyCommandRunner { Result = new VerifyCommandResult(1, false, "build broke: CS0103") };
|
||||
var (svc, _) = BuildService(db, fakeVerify);
|
||||
var target = await new GitService().GetCurrentBranchAsync(repo.RepoDir);
|
||||
|
||||
var result = await svc.ApproveAndMergeAsync(task.Id, target, CancellationToken.None);
|
||||
|
||||
Assert.Equal(TaskMergeService.StatusVerifyFailed, result.Status);
|
||||
Assert.Contains("build broke: CS0103", result.ErrorMessage);
|
||||
|
||||
// The git merge itself is left in place — main already has the merged content.
|
||||
Assert.True(File.Exists(Path.Combine(repo.RepoDir, "added.txt")));
|
||||
|
||||
using var ctx = db.CreateContext();
|
||||
var updated = await new TaskRepository(ctx).GetByIdAsync(task.Id);
|
||||
Assert.Equal(TaskStatus.WaitingForReview, updated!.Status);
|
||||
var wt = await new WorktreeRepository(ctx).GetByTaskIdAsync(task.Id);
|
||||
Assert.Equal(WorktreeState.Merged, wt!.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApproveAndMergeAsync_VerifyCommandTimesOut_ReturnsVerifyFailedWithTimeoutMessage()
|
||||
{
|
||||
if (!GitRepoFixture.IsGitAvailable()) return;
|
||||
var repo = NewRepo();
|
||||
var db = NewDb();
|
||||
var (list, task) = await SeedListAndTask(db, repo.RepoDir, TaskStatus.WaitingForReview);
|
||||
await SeedVerifyCommand(db, list.Id, "dotnet test");
|
||||
|
||||
var wtMgr = BuildWorktreeManager(db);
|
||||
var wtCtx = await wtMgr.CreateAsync(task, list, CancellationToken.None);
|
||||
_wtCleanups.Add((repo.RepoDir, wtCtx.WorktreePath));
|
||||
File.WriteAllText(Path.Combine(wtCtx.WorktreePath, "added.txt"), "new\n");
|
||||
await wtMgr.CommitIfChangedAsync(wtCtx, task, list, CancellationToken.None);
|
||||
|
||||
var fakeVerify = new FakeVerifyCommandRunner { Result = new VerifyCommandResult(-1, true, "") };
|
||||
var (svc, _) = BuildService(db, fakeVerify);
|
||||
var target = await new GitService().GetCurrentBranchAsync(repo.RepoDir);
|
||||
|
||||
var result = await svc.ApproveAndMergeAsync(task.Id, target, CancellationToken.None);
|
||||
|
||||
Assert.Equal(TaskMergeService.StatusVerifyFailed, result.Status);
|
||||
Assert.Contains("timed out", result.ErrorMessage ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
using var ctx = db.CreateContext();
|
||||
var updated = await new TaskRepository(ctx).GetByIdAsync(task.Id);
|
||||
Assert.Equal(TaskStatus.WaitingForReview, updated!.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_LeaveConflicts_DoesNotAbortAndReturnsConflictFiles()
|
||||
{
|
||||
@@ -986,6 +1113,20 @@ public class TaskMergeServiceTests : IDisposable
|
||||
|
||||
#region Test doubles
|
||||
|
||||
internal sealed class FakeVerifyCommandRunner : IVerifyCommandRunner
|
||||
{
|
||||
public VerifyCommandResult Result { get; set; } = new(0, false, "");
|
||||
public string? CapturedWorkingDir { get; private set; }
|
||||
public string? CapturedCommand { get; private set; }
|
||||
|
||||
public Task<VerifyCommandResult> RunAsync(string workingDir, string command, TimeSpan timeout, CancellationToken ct)
|
||||
{
|
||||
CapturedWorkingDir = workingDir;
|
||||
CapturedCommand = command;
|
||||
return Task.FromResult(Result);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record MergeHubCall(string Method, object?[] Args);
|
||||
|
||||
internal sealed class MergeRecordingClientProxy : IClientProxy
|
||||
|
||||
Reference in New Issue
Block a user