252 lines
8.1 KiB
C#
252 lines
8.1 KiB
C#
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<object> { "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<object[]> 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" }];
|
|
}
|
|
}
|