using Xunit; using FluentAssertions; using NSubstitute; using System; using System.Collections.Generic; using System.Diagnostics; using FreeCode.Core.Models; using FreeCode.Core.Enums; using CoreTaskStatus = FreeCode.Core.Enums.TaskStatus; namespace FreeCode.Tests.Unit.Models; public sealed class BackgroundTaskTests { [Fact] public void LocalShellTask_ExposesLocalShellTypeAndCommand() { var task = new LocalShellTask { TaskId = "task-1", Command = "pwd" }; task.TaskType.Should().Be(BackgroundTaskType.LocalShell); task.Command.Should().Be("pwd"); } [Fact] public void LocalShellTask_AllowsProcessAndOutputMutation() { var startInfo = new ProcessStartInfo("bash", "-lc pwd"); var task = new LocalShellTask { TaskId = "task-1", Command = "pwd", ProcessStartInfo = startInfo }; task.Stdout = "output"; task.Stderr = "error"; task.ExitCode = 0; task.ProcessStartInfo.Should().BeSameAs(startInfo); task.Stdout.Should().Be("output"); task.Stderr.Should().Be("error"); task.ExitCode.Should().Be(0); } [Fact] public void LocalAgentTask_ExposesLocalAgentTypeAndMetadata() { var task = new LocalAgentTask { TaskId = "task-1", Prompt = "analyze", Model = "gpt-5.4", AgentType = "explore", WorkingDirectory = "/repo" }; task.TaskType.Should().Be(BackgroundTaskType.LocalAgent); task.Prompt.Should().Be("analyze"); task.Model.Should().Be("gpt-5.4"); task.AgentType.Should().Be("explore"); task.WorkingDirectory.Should().Be("/repo"); } [Fact] public void LocalAgentTask_StartsWithEmptyMessagesCollection() { var task = new LocalAgentTask { TaskId = "task-1", Prompt = "analyze" }; task.Messages.Should().BeEmpty(); } [Fact] public void LocalAgentTask_MessagesCollectionIsMutable() { var task = new LocalAgentTask { TaskId = "task-1", Prompt = "analyze" }; var message = new Message { MessageId = "msg-1", Role = MessageRole.User, Content = new object(), Timestamp = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc) }; task.Messages.Add(message); task.Messages.Should().ContainSingle().Which.Should().BeSameAs(message); } [Fact] public void RemoteAgentTask_ExposesRemoteAgentTypeAndSessionUrl() { var task = new RemoteAgentTask { TaskId = "task-1", SessionUrl = "https://example.test/session/1" }; task.TaskType.Should().Be(BackgroundTaskType.RemoteAgent); task.SessionUrl.Should().Be("https://example.test/session/1"); } [Fact] public void RemoteAgentTask_UsesShadowedStringStatusAlongsideBaseEnumStatus() { var task = new RemoteAgentTask { TaskId = "task-1", SessionUrl = "https://example.test/session/1" }; task.Status = "waiting"; ((BackgroundTask)task).Status = CoreTaskStatus.Running; task.Status.Should().Be("waiting"); ((BackgroundTask)task).Status.Should().Be(CoreTaskStatus.Running); } [Fact] public void InProcessTeammateTask_ExposesTeammateTypeAndProperties() { var task = new InProcessTeammateTask { TaskId = "task-1", AgentName = "Ada", AgentType = "reviewer", Color = "blue", WorkingDirectory = "/repo" }; task.TaskType.Should().Be(BackgroundTaskType.InProcessTeammate); task.AgentName.Should().Be("Ada"); task.AgentType.Should().Be("reviewer"); task.Color.Should().Be("blue"); task.WorkingDirectory.Should().Be("/repo"); } [Fact] public void LocalWorkflowTask_ExposesWorkflowTypeAndSteps() { var steps = new List { "step-1", 2 }; var task = new LocalWorkflowTask { TaskId = "task-1", WorkflowName = "Build", Steps = steps }; task.TaskType.Should().Be(BackgroundTaskType.LocalWorkflow); task.WorkflowName.Should().Be("Build"); task.Steps.Should().BeSameAs(steps); task.CurrentStepIndex.Should().Be(0); } [Fact] public void LocalWorkflowTask_CurrentStepIndexIsMutable() { var task = new LocalWorkflowTask { TaskId = "task-1", WorkflowName = "Build", Steps = [] }; task.CurrentStepIndex = 2; task.CurrentStepIndex.Should().Be(2); } [Fact] public void MonitorMcpTask_ExposesMonitorTypeAndReconnectAttempt() { var task = new MonitorMcpTask { TaskId = "task-1", ServerName = "filesystem" }; task.TaskType.Should().Be(BackgroundTaskType.MonitorMcp); task.ServerName.Should().Be("filesystem"); task.ReconnectAttempt.Should().Be(0); } [Fact] public void DreamTask_ExposesDreamTypeAndReason() { var task = new DreamTask { TaskId = "task-1", TriggerReason = "idle-compaction" }; task.TaskType.Should().Be(BackgroundTaskType.Dream); task.TriggerReason.Should().Be("idle-compaction"); } [Fact] public void BackgroundTask_CommonStateIsMutable() { var task = new LocalShellTask { TaskId = "task-1", Command = "pwd" }; var startedAt = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc); var completedAt = startedAt.AddMinutes(1); task.Status = CoreTaskStatus.Completed; task.StartedAt = startedAt; task.CompletedAt = completedAt; task.ErrorMessage = "none"; task.IsBackgrounded = false; task.Status.Should().Be(CoreTaskStatus.Completed); task.StartedAt.Should().Be(startedAt); task.CompletedAt.Should().Be(completedAt); task.ErrorMessage.Should().Be("none"); task.IsBackgrounded.Should().BeFalse(); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_DefaultStatusIsPending(BackgroundTask task) { task.Status.Should().Be(CoreTaskStatus.Pending); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_DefaultIsBackgroundedIsTrue(BackgroundTask task) { task.IsBackgrounded.Should().BeTrue(); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_DefaultStartedAtIsNull(BackgroundTask task) { task.StartedAt.Should().BeNull(); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_DefaultCompletedAtIsNull(BackgroundTask task) { task.CompletedAt.Should().BeNull(); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_DefaultErrorMessageIsNull(BackgroundTask task) { task.ErrorMessage.Should().BeNull(); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_PreservesTaskId(BackgroundTask task) { task.TaskId.Should().NotBeNullOrEmpty(); } [Theory] [MemberData(nameof(AllTasks))] public void BackgroundTask_ExposesExpectedTaskType(BackgroundTask task) { task.TaskType.Should().BeOneOf( BackgroundTaskType.LocalShell, BackgroundTaskType.LocalAgent, BackgroundTaskType.RemoteAgent, BackgroundTaskType.InProcessTeammate, BackgroundTaskType.LocalWorkflow, BackgroundTaskType.MonitorMcp, BackgroundTaskType.Dream); } public static IEnumerable AllTasks() { yield return [new LocalShellTask { TaskId = "shell-1", Command = "pwd" }]; yield return [new LocalAgentTask { TaskId = "agent-1", Prompt = "analyze" }]; yield return [new RemoteAgentTask { TaskId = "remote-1", SessionUrl = "https://example.test/session/1" }]; yield return [new InProcessTeammateTask { TaskId = "mate-1", AgentName = "Ada", WorkingDirectory = "/repo" }]; yield return [new LocalWorkflowTask { TaskId = "workflow-1", WorkflowName = "Build", Steps = [] }]; yield return [new MonitorMcpTask { TaskId = "mcp-1", ServerName = "filesystem" }]; yield return [new DreamTask { TaskId = "dream-1", TriggerReason = "idle-compaction" }]; } }