using ClaudeDo.Data; using ClaudeDo.Ui.ViewModels.Islands; using Microsoft.EntityFrameworkCore; using Xunit; namespace ClaudeDo.Ui.Tests.ViewModels; public class TaskMonitorViewModelTests : IDisposable { private readonly string _dbPath; public TaskMonitorViewModelTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_monitor_{Guid.NewGuid():N}.db"); using var ctx = NewContext(); ctx.Database.EnsureCreated(); } public void Dispose() { try { File.Delete(_dbPath); } catch { } try { File.Delete(_dbPath + "-wal"); } catch { } try { File.Delete(_dbPath + "-shm"); } catch { } } private ClaudeDoDbContext NewContext() { var opts = new DbContextOptionsBuilder() .UseSqlite($"Data Source={_dbPath}") .Options; return new ClaudeDoDbContext(opts); } private sealed class TestDbFactory : IDbContextFactory { private readonly Func _create; public TestDbFactory(Func create) => _create = create; public ClaudeDoDbContext CreateDbContext() => _create(); } private sealed class FakeWorker : StubWorkerClient { } private TaskMonitorViewModel Build(FakeWorker worker) => new TaskMonitorViewModel(new TestDbFactory(NewContext), worker); [Fact] public void Feeds_AccumulateLogLines_WithKinds() { var worker = new FakeWorker(); using var vm = Build(worker); vm.SetTaskId("t1"); worker.RaiseTaskMessage("t1", "[sys] boot"); worker.RaiseTaskMessage("t1", "[tool] read file"); worker.RaiseTaskMessage("t1", "[claude] hello"); Assert.Equal(3, vm.Log.Count); Assert.Equal(LogKind.Sys, vm.Log[0].Kind); Assert.Equal(LogKind.Tool, vm.Log[1].Kind); Assert.Equal(LogKind.Claude, vm.Log[2].Kind); } [Fact] public void Feeds_ForOtherTask_AreIgnored() { var worker = new FakeWorker(); using var vm = Build(worker); vm.SetTaskId("t1"); worker.RaiseTaskMessage("other", "[sys] not mine"); Assert.Empty(vm.Log); } [Fact] public void TaskFinished_FlipsState_AndAppendsDoneLine() { var worker = new FakeWorker(); using var vm = Build(worker); vm.SetTaskId("t1"); worker.RaiseTaskFinished("slot-1", "t1", "done", DateTime.UtcNow); Assert.True(vm.IsDone); Assert.Equal("done", vm.AgentState); Assert.Equal(LogKind.Done, vm.Log[^1].Kind); } [Fact] public void ApplyOutcome_SplitsRoadblockMarker() { var worker = new FakeWorker(); using var vm = Build(worker); vm.ApplyOutcome( "Summary text\n\nRoadblocks reported during the run:\n- something broke", errorFallback: null); Assert.Equal("Summary text", vm.SessionOutcome); Assert.Equal("- something broke", vm.Roadblocks); } [Fact] public void HasRoadblock_TrueAfterRoadblockOutcome() { var worker = new FakeWorker(); using var vm = Build(worker); vm.ApplyOutcome("Summary\n\nRoadblocks reported during the run:\n- broke", errorFallback: null); Assert.True(vm.HasRoadblock); } [Fact] public void Detach_WhenNotDetached_InvokesDetachRequested() { var worker = new FakeWorker(); using var vm = Build(worker); vm.SetTaskId("t1"); TaskMonitorViewModel? requested = null; vm.DetachRequested = m => requested = m; vm.DetachCommand.Execute(null); Assert.Same(vm, requested); } }