From 9f90e75a857235f506396ccc32f2a81480d0f50f Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Wed, 2 Sep 2026 12:17:00 +0200 Subject: [PATCH] refactor(tests): share StubWorkerClient between Ui.Tests and Worker.Tests Worker.Tests carried its own hand-rolled FakeWorkerClient implementing the whole IWorkerClient surface, duplicating StubWorkerClient in Ui.Tests. Every hub-signature change had to be applied twice. Link StubWorkerClient.cs into Worker.Tests via a csproj Compile Include and subclass it there; add SetTaskStatusCalls tracking to the shared stub (the one behavior Worker.Tests needed that Ui.Tests didn't have). --- tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs | 7 +- .../ClaudeDo.Worker.Tests.csproj | 4 + .../UiVm/TasksIslandViewModelPlanningTests.cs | 184 +----------------- 3 files changed, 12 insertions(+), 183 deletions(-) diff --git a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs index d9a05bec..5e7a2e35 100644 --- a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs +++ b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs @@ -98,7 +98,12 @@ public abstract class StubWorkerClient : IWorkerClient public virtual Task> InstallSessionSkillAsync(string url) => Task.FromResult(new List()); public virtual Task UpdateSessionSkillAsync(string sourceUrl) => Task.CompletedTask; public virtual Task RemoveSessionSkillAsync(string sourceUrl) => Task.CompletedTask; - public virtual Task SetTaskStatusAsync(string taskId, TaskStatus status) => Task.FromResult(null); + public List<(string TaskId, TaskStatus Status)> SetTaskStatusCalls { get; } = new(); + public virtual Task SetTaskStatusAsync(string taskId, TaskStatus status) + { + SetTaskStatusCalls.Add((taskId, status)); + return Task.FromResult(null); + } public virtual Task SetTaskDoneAsync(string taskId) => Task.CompletedTask; public virtual Task UnsetTaskDoneAsync(string taskId) => Task.CompletedTask; public virtual Task DequeueTaskAsync(string taskId) => Task.CompletedTask; diff --git a/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj b/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj index c7d07e4d..87d78bf4 100644 --- a/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj +++ b/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj @@ -22,6 +22,10 @@ + + + + diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs index 2956b686..fc4e597d 100644 --- a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs +++ b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs @@ -2,6 +2,7 @@ using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Ui.Services; +using ClaudeDo.Ui.Tests; using ClaudeDo.Ui.ViewModels.Islands; using ClaudeDo.Worker.Tests.Infrastructure; using CommunityToolkit.Mvvm.Input; @@ -13,188 +14,7 @@ namespace ClaudeDo.Worker.Tests.UiVm; // ── Fake worker client ──────────────────────────────────────────────────────── -sealed class FakeWorkerClient : IWorkerClient -{ - public int StartPlanningCalls { get; private set; } - public int ResumePlanningCalls { get; private set; } - public int DiscardPlanningCalls { get; private set; } - public int FinalizePlanningCalls { get; private set; } - public int WakeQueueCalls { get; private set; } - public List<(string TaskId, TaskStatus Status)> SetTaskStatusCalls { get; } = new(); - - public bool IsConnected => false; - public bool IsReconnecting => false; - public string? LastApproveTarget => null; -#pragma warning disable CS0067 // events required by IWorkerClient but not exercised by this fake - public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; - public event Action? TaskStartedEvent; - public event Action? TaskFinishedEvent; - public event Action? TaskUpdatedEvent; - public event Action? ConnectionRestoredEvent; - public event Action? WorktreeUpdatedEvent; - public event Action? ListUpdatedEvent; - public event Action? TaskMessageEvent; - public event Action? WorkerLogReceivedEvent; - public event Action? TaskQuestionAskedEvent; - public event Action? TaskQuestionResolvedEvent; - public event Action, string>? HandoffRequestedEvent; - public void RaiseTaskUpdated(string taskId) => TaskUpdatedEvent?.Invoke(taskId); - public void RaiseWorktreeUpdated(string taskId) => WorktreeUpdatedEvent?.Invoke(taskId); - public void RaiseTaskMessage(string taskId, string line) => TaskMessageEvent?.Invoke(taskId, line); - public void RaiseOperationProgress(string opKey, string phase, int current, int total) => OperationProgressEvent?.Invoke(opKey, phase, current, total); - - public Task RunNowAsync(string taskId) => Task.CompletedTask; - public Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask; - public Task AnswerTaskQuestionAsync(string taskId, string questionId, string answer) => Task.CompletedTask; - public Task GetPendingQuestionAsync(string taskId) => Task.FromResult(null); - public Task ResetTaskAsync(string taskId) => Task.CompletedTask; - public Task CancelTaskAsync(string taskId) => Task.CompletedTask; - public Task<(bool Ok, string? Error)> DeleteTaskAsync(string taskId) => Task.FromResult<(bool, string?)>((true, null)); - public Task> GetAgentsAsync() => Task.FromResult(new List()); - public Task RefreshAgentsAsync() => Task.CompletedTask; - public Task RestoreDefaultAgentsAsync() => Task.FromResult(null); - public Task GetListConfigAsync(string listId) => Task.FromResult(null); - public Task> GetRepoImportFoldersAsync() => Task.FromResult(new List()); - public Task SetRepoImportFoldersAsync(List folders) => Task.CompletedTask; - public Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask; - public Task> GetSessionSkillsAsync() => Task.FromResult(new List()); - public Task> InstallSessionSkillAsync(string url) => Task.FromResult(new List()); - public Task UpdateSessionSkillAsync(string sourceUrl) => Task.CompletedTask; - public Task RemoveSessionSkillAsync(string sourceUrl) => Task.CompletedTask; - public Task SetTaskStatusAsync(string taskId, TaskStatus status) - { - SetTaskStatusCalls.Add((taskId, status)); - return Task.FromResult(null); - } - public Task SetTaskDoneAsync(string taskId) => Task.CompletedTask; - public Task UnsetTaskDoneAsync(string taskId) => Task.CompletedTask; - public Task DequeueTaskAsync(string taskId) => Task.CompletedTask; - public Task ApproveReviewAsync(string taskId, string targetBranch) => Task.FromResult(null); - public Task PreviewMergeAsync(string taskId, string targetBranch) => Task.FromResult(null); - public Task MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); - public Task StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty(), null)); - public Task GetMergeConflictDocumentsAsync(string taskId) => Task.FromResult(new MergeConflictDocumentsDto(taskId, System.Array.Empty())); - public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask; - public Task ContinueConflictMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); - public Task AbortConflictMergeAsync(string taskId) => Task.CompletedTask; - public Task RejectReviewToQueueAsync(string taskId, string feedback) => Task.CompletedTask; - public Task RejectReviewToIdleAsync(string taskId) => Task.CompletedTask; - public Task CancelReviewAsync(string taskId) => Task.CompletedTask; - public Task WakeQueueAsync() { WakeQueueCalls++; return Task.CompletedTask; } - public Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) { StartPlanningCalls++; return Task.CompletedTask; } - public Task SubmitTaskForReviewAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask; - public Task GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default) - => Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary())); - public Task GetMergeHelperLaunchSpecAsync(IReadOnlyList taskIds, string listId, string handlerTaskId, CancellationToken ct = default) - => Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary())); - public Task CreateMergeHelperTaskAsync( - IReadOnlyList taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default) - => Task.FromResult(Guid.NewGuid().ToString()); - public Task GetMergeHelperHandoffLaunchSpecAsync( - string taskId, IReadOnlyList survivingTaskIds, string nextPhase, CancellationToken ct = default) - => Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary())); - public Task GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default) - => Task.FromResult(new LaunchSpec(directory, "claude", Array.Empty(), new Dictionary())); - public int PlanningStartSpecCalls { get; private set; } - public int PlanningResumeSpecCalls { get; private set; } - public Task GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default) - { PlanningStartSpecCalls++; return Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary())); } - public Task GetPlanningResumeLaunchSpecAsync(string taskId, CancellationToken ct = default) - { PlanningResumeSpecCalls++; return Task.FromResult(new LaunchSpec(".", "claude", Array.Empty(), new Dictionary())); } - public Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask; - public Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) { ResumePlanningCalls++; return Task.CompletedTask; } - public Task DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default) - { - DiscardPlanningCalls++; - return Task.FromResult(new DiscardPlanningOutcome(DiscardPlanningResult.Discarded, 0, 0)); - } - public Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) { FinalizePlanningCalls++; return Task.CompletedTask; } - public Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => Task.FromResult(0); - public Task> GetRecentLogsAsync() => Task.FromResult>(System.Array.Empty()); - - public event Action? NotesUpdatedEvent; - public event Action? PrepStartedEvent; - public event Action? PrepLineEvent; - public event Action? PrepFinishedEvent; - public event Action? RefineStartedEvent; - public event Action? RefineFinishedEvent; - public event Action? OperationProgressEvent; - public event Action? MergeProgressEvent; - public event Action? PlanningMergeStartedEvent; - public event Action? PlanningSubtaskMergedEvent; - public event Action, bool>? PlanningMergeConflictEvent; - public event Action? PlanningMergeAbortedEvent; - public event Action? PlanningCompletedEvent; - public event Action? PrimeFired; - public event Action? UsageUpdatedEvent; -#pragma warning restore CS0067 - - public Task GetMergeTargetsAsync(string taskId) => Task.FromResult(null); - public Task> GetPlanningAggregateAsync(string planningTaskId) => Task.FromResult>(Array.Empty()); - public Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch) => Task.FromResult(null); - public Task ContinuePlanningMergeAsync(string planningTaskId) => Task.CompletedTask; - public Task AbortPlanningMergeAsync(string planningTaskId) => Task.CompletedTask; - public Task> GetActiveExternalPlanningMergeConflictsAsync() - => Task.FromResult>(Array.Empty()); - - public Task GetAppSettingsAsync() => Task.FromResult(null); - public Task UpdateAppSettingsAsync(AppSettingsDto dto) => Task.CompletedTask; - public Task> GetPrimeSchedulesAsync() => Task.FromResult(new List()); - public Task UpsertPrimeScheduleAsync(PrimeScheduleDto dto) => Task.FromResult(null); - public Task DeletePrimeScheduleAsync(Guid id) => Task.CompletedTask; - public Task UpdateListAsync(UpdateListDto dto) => Task.CompletedTask; - public Task UpdateListConfigAsync(UpdateListConfigDto dto) => Task.CompletedTask; - public Task CleanupFinishedWorktreesAsync(string? listId = null) => Task.FromResult(null); - public Task ResetAllWorktreesAsync() => Task.FromResult(null); - public Task> GetWorktreesOverviewAsync(string? listId) => Task.FromResult(new List()); - public Task<(bool Ok, string? Error)> SetWorktreeStateAsync(string taskId, WorktreeState newState) => Task.FromResult((true, (string?)null)); - public Task ForceRemoveWorktreeAsync(string taskId) => Task.FromResult(null); - public Task GetWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(null); - public Task GenerateWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(""); - public Task RunDailyPrepNowAsync() => Task.FromResult(false); - public Task ClearMyDayAsync() => Task.CompletedTask; - public Task> GetDailyNotesAsync(DateOnly day) => Task.FromResult(new List()); - public Task> GetDailyNotesBetweenAsync(DateOnly start, DateOnly end) => Task.FromResult(new List()); - public Task AddDailyNoteAsync(DateOnly day, string text) => Task.FromResult(null); - public Task UpdateDailyNoteAsync(string id, string text) => Task.CompletedTask; - public Task DeleteDailyNoteAsync(string id) => Task.CompletedTask; - public Task GetLastPrepLogAsync() => Task.FromResult(string.Empty); - public Task RefineTaskAsync(string taskId) => Task.CompletedTask; - public Task GetClaudeBinAsync() => Task.FromResult(null); - public Task SetClaudeBinAsync(string? value) => Task.FromResult(null); - public Task GetOnlineInboxStateAsync() => Task.FromResult(null); - public Task SetOnlineInboxConfigAsync(OnlineInboxConfigInputDto input) => Task.CompletedTask; - public Task SetOnlineInboxAuthAsync(string refreshToken) => Task.CompletedTask; - public Task ClearOnlineInboxAuthAsync() => Task.CompletedTask; - public IReadOnlyList GetActiveTasks() => System.Array.Empty(); - - public Task GetUsageSnapshotAsync() => Task.FromResult(null); - public Task RefreshUsageAsync() => Task.FromResult(null); - public Task> GetModelUsageAsync(DateOnly from, DateOnly to) - => Task.FromResult>(System.Array.Empty()); - public Task> GetTaskUsageAsync(DateOnly from, DateOnly to) - => Task.FromResult>(System.Array.Empty()); - - public Task GetTokenTrackerStatusAsync() => - Task.FromResult(new TokenTrackerStatusDto( - Installed: false, Version: null, NodeOk: true, NodeVersion: "v22.0.0", - LastFetchedUtc: null, LastError: null, FormatVersion: null, SessionCount: 0)); - - public Task RefreshTokenTrackerAsync() => GetTokenTrackerStatusAsync(); - - public Task InstallTokenTrackerAsync() => GetTokenTrackerStatusAsync(); - - public Task OpenTokenTrackerDashboardAsync() => - Task.FromResult(new TokenTrackerDashboardDto(false, null, "not configured")); - - public Task GetTicketSettingsAsync() => Task.FromResult(null); - public Task SetTicketApiBaseUrlAsync(string? baseUrl) => Task.CompletedTask; - public Task SetTicketTokenAsync(string token) => Task.CompletedTask; - public Task ClearTicketTokenAsync() => Task.CompletedTask; - public Task TestTicketConnectionAsync() => Task.FromResult(null); - public Task> GetTicketProjectsAsync() => Task.FromResult(new List()); - public Task ImportTicketsAsync(string listId) => Task.FromResult(new TicketImportResultDto(0, 0)); -} +sealed class FakeWorkerClient : StubWorkerClient; // ── Helper to build VM with pre-seeded Items ──────────────────────────────────