143 lines
4.9 KiB
C#
143 lines
4.9 KiB
C#
using ClaudeDo.Data;
|
|
using ClaudeDo.Ui.Services;
|
|
using ClaudeDo.Ui.ViewModels;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Xunit;
|
|
|
|
namespace ClaudeDo.Ui.Tests.ViewModels;
|
|
|
|
public class MissionControlRunningOperationsTests : IDisposable
|
|
{
|
|
private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_mcops_{Guid.NewGuid():N}.db");
|
|
|
|
public MissionControlRunningOperationsTests()
|
|
{
|
|
using var ctx = NewContext();
|
|
ctx.Database.EnsureCreated();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var suffix in new[] { "", "-wal", "-shm" })
|
|
try { File.Delete(_dbPath + suffix); } catch { }
|
|
}
|
|
|
|
private ClaudeDoDbContext NewContext() =>
|
|
new(new DbContextOptionsBuilder<ClaudeDoDbContext>().UseSqlite($"Data Source={_dbPath}").Options);
|
|
|
|
private sealed class FakeWorker : StubWorkerClient
|
|
{
|
|
public IReadOnlyList<RunningOperationDto> Snapshot { get; set; } = Array.Empty<RunningOperationDto>();
|
|
/// <summary>When set, the snapshot call completes only once this source is resolved.</summary>
|
|
public TaskCompletionSource? Gate { get; set; }
|
|
public override async Task<IReadOnlyList<RunningOperationDto>> GetActiveOperationsAsync()
|
|
{
|
|
if (Gate is not null) await Gate.Task;
|
|
return Snapshot;
|
|
}
|
|
}
|
|
|
|
private static RunningOperationDto Op(string id, string kind, int? number, string title, string? listId = null) =>
|
|
new(id, kind, number is null ? null : "t-" + id, number, listId, title, DateTime.UtcNow.AddSeconds(-5));
|
|
|
|
private MissionControlViewModel BuildVm(FakeWorker worker) =>
|
|
new(new TestDbFactory(NewContext), worker, new UsagePillViewModel(worker));
|
|
|
|
[Fact]
|
|
public async Task Snapshot_LoadsRunningOperations_AndShowsActivity()
|
|
{
|
|
var worker = new FakeWorker
|
|
{
|
|
Snapshot = new[]
|
|
{
|
|
Op("a", OperationKinds.VerifyMerge, 12, "Fix login"),
|
|
Op("b", OperationKinds.VerifyBatch, null, "My list", listId: "l1"),
|
|
},
|
|
};
|
|
var vm = BuildVm(worker);
|
|
|
|
await vm.RefreshOperationsAsync();
|
|
|
|
Assert.Equal(new[] { "#12 Fix login", "My list" }, vm.RunningOperations.Select(r => r.Subject));
|
|
Assert.True(vm.HasRunningOperations);
|
|
Assert.True(vm.HasActivity);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartedAndEndedEvents_UpdateTheList()
|
|
{
|
|
var worker = new FakeWorker();
|
|
var vm = BuildVm(worker);
|
|
await vm.RefreshOperationsAsync();
|
|
Assert.False(vm.HasRunningOperations);
|
|
|
|
worker.RaiseOperationStarted(Op("a", OperationKinds.Merging, 3, "Task"));
|
|
worker.RaiseOperationStarted(Op("a", OperationKinds.Merging, 3, "Task")); // duplicate ignored
|
|
worker.RaiseOperationStarted(Op("b", OperationKinds.Rebasing, 4, "Other"));
|
|
Assert.Equal(2, vm.RunningOperations.Count);
|
|
|
|
worker.RaiseOperationEnded("a");
|
|
Assert.Equal("b", Assert.Single(vm.RunningOperations).Id);
|
|
|
|
worker.RaiseOperationEnded("unknown");
|
|
worker.RaiseOperationEnded("b");
|
|
Assert.False(vm.HasRunningOperations);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EndedDuringPendingSnapshot_DoesNotReappear()
|
|
{
|
|
var gate = new TaskCompletionSource();
|
|
var worker = new FakeWorker { Snapshot = new[] { Op("a", OperationKinds.Merging, 1, "Task") }, Gate = gate };
|
|
var vm = BuildVm(worker);
|
|
|
|
var refresh = vm.RefreshOperationsAsync();
|
|
worker.RaiseOperationEnded("a");
|
|
gate.SetResult();
|
|
await refresh;
|
|
|
|
Assert.Empty(vm.RunningOperations);
|
|
Assert.False(vm.HasRunningOperations);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EndedBeforeStarted_LeavesNoRow()
|
|
{
|
|
var worker = new FakeWorker();
|
|
var vm = BuildVm(worker);
|
|
await vm.RefreshOperationsAsync();
|
|
|
|
worker.RaiseOperationEnded("a");
|
|
worker.RaiseOperationStarted(Op("a", OperationKinds.Merging, 1, "Task"));
|
|
|
|
Assert.Empty(vm.RunningOperations);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ConnectionRestored_ReplacesListWithFreshSnapshot()
|
|
{
|
|
var worker = new FakeWorker { Snapshot = new[] { Op("a", OperationKinds.Merging, 1, "Old") } };
|
|
var vm = BuildVm(worker);
|
|
await vm.RefreshOperationsAsync();
|
|
|
|
worker.Snapshot = new[] { Op("c", OperationKinds.CreatingWorktree, 2, "New") };
|
|
await vm.RefreshOperationsAsync();
|
|
|
|
Assert.Equal("c", Assert.Single(vm.RunningOperations).Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TickOperations_UpdatesElapsed()
|
|
{
|
|
var worker = new FakeWorker();
|
|
var vm = BuildVm(worker);
|
|
var started = new DateTime(2026, 10, 1, 8, 0, 0, DateTimeKind.Utc);
|
|
worker.RaiseOperationStarted(new RunningOperationDto("a", OperationKinds.VerifyMerge, "t", 1, null, "T", started));
|
|
|
|
vm.TickOperations(started.AddSeconds(75));
|
|
|
|
Assert.Equal("01:15", vm.RunningOperations.Single().Elapsed);
|
|
await Task.CompletedTask;
|
|
}
|
|
}
|