156 lines
5.5 KiB
C#
156 lines
5.5 KiB
C#
using FluentAssertions;
|
|
using FreeCode.Tasks;
|
|
using Xunit;
|
|
using TaskStatus = FreeCode.Core.Enums.TaskStatus;
|
|
|
|
namespace FreeCode.Tests.Integration;
|
|
|
|
public sealed class TaskManagerTests
|
|
{
|
|
[Fact]
|
|
public async Task BackgroundTaskManager_ShellTask_CompletesLifecycle()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
var states = new List<TaskStatus>();
|
|
sut.TaskStateChanged += (_, args) => states.Add(args.NewStatus);
|
|
|
|
var processStartInfo = new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
FileName = "/bin/zsh",
|
|
Arguments = "-lc \"printf completed\"",
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
var task = await sut.CreateShellTaskAsync("printf completed", processStartInfo);
|
|
|
|
await WaitUntilAsync(() => sut.GetTask(task.TaskId)?.Status is TaskStatus.Completed or TaskStatus.Failed, TimeSpan.FromSeconds(5));
|
|
|
|
var updated = sut.GetTask(task.TaskId);
|
|
updated.Should().NotBeNull();
|
|
updated!.Status.Should().Be(TaskStatus.Completed);
|
|
states.Should().Contain(TaskStatus.Running);
|
|
states.Should().Contain(TaskStatus.Completed);
|
|
(await sut.GetTaskOutputAsync(task.TaskId)).Should().Contain("completed");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShellTask_CapturesStdout()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
var task = await sut.CreateShellTaskAsync("printf test output", CreateShellProcess("printf 'test output'"));
|
|
|
|
await WaitUntilAsync(() => sut.GetTask(task.TaskId)?.Status is TaskStatus.Completed or TaskStatus.Failed, TimeSpan.FromSeconds(5));
|
|
|
|
var shellTask = sut.GetTask(task.TaskId).Should().BeOfType<FreeCode.Core.Models.LocalShellTask>().Subject;
|
|
shellTask.Stdout.Should().Contain("test output");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShellTask_CapturesStderr()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
var task = await sut.CreateShellTaskAsync("write stderr", CreateShellProcess("printf 'err' >&2"));
|
|
|
|
await WaitUntilAsync(() => sut.GetTask(task.TaskId)?.Status is TaskStatus.Completed or TaskStatus.Failed, TimeSpan.FromSeconds(5));
|
|
|
|
var shellTask = sut.GetTask(task.TaskId).Should().BeOfType<FreeCode.Core.Models.LocalShellTask>().Subject;
|
|
shellTask.Stderr.Should().Contain("err");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShellTask_ExitCode_Captured()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
var task = await sut.CreateShellTaskAsync("exit 42", CreateShellProcess("exit 42"));
|
|
|
|
await WaitUntilAsync(() => sut.GetTask(task.TaskId)?.Status is TaskStatus.Completed or TaskStatus.Failed, TimeSpan.FromSeconds(5));
|
|
|
|
var shellTask = sut.GetTask(task.TaskId).Should().BeOfType<FreeCode.Core.Models.LocalShellTask>().Subject;
|
|
shellTask.ExitCode.Should().Be(42);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StopTask_StopsRunningTask()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
var task = await sut.CreateShellTaskAsync("sleep 10", CreateShellProcess("sleep 10"));
|
|
await WaitUntilAsync(() => sut.GetTask(task.TaskId)?.Status == TaskStatus.Running, TimeSpan.FromSeconds(5));
|
|
|
|
await sut.StopTaskAsync(task.TaskId);
|
|
await WaitUntilAsync(() => sut.GetTask(task.TaskId)?.Status == TaskStatus.Stopped, TimeSpan.FromSeconds(5));
|
|
|
|
sut.GetTask(task.TaskId).Should().NotBeNull();
|
|
sut.GetTask(task.TaskId)!.Status.Should().Be(TaskStatus.Stopped);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ListTasks_ReturnsAllTasks()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
var first = await sut.CreateShellTaskAsync("printf first", CreateShellProcess("printf first"));
|
|
var second = await sut.CreateShellTaskAsync("printf second", CreateShellProcess("printf second"));
|
|
|
|
await WaitUntilAsync(() => sut.ListTasks().Count >= 2, TimeSpan.FromSeconds(5));
|
|
|
|
sut.ListTasks().Select(task => task.TaskId).Should().Contain([first.TaskId, second.TaskId]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetTask_ReturnsTaskById()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
var task = await sut.CreateShellTaskAsync("printf lookup", CreateShellProcess("printf lookup"));
|
|
|
|
sut.GetTask(task.TaskId).Should().NotBeNull();
|
|
sut.GetTask(task.TaskId)!.TaskId.Should().Be(task.TaskId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetTask_UnknownId_ReturnsNull()
|
|
{
|
|
await using var sut = new BackgroundTaskManager();
|
|
|
|
await Task.CompletedTask;
|
|
|
|
sut.GetTask("missing-task").Should().BeNull();
|
|
}
|
|
|
|
private static System.Diagnostics.ProcessStartInfo CreateShellProcess(string command)
|
|
{
|
|
return new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
FileName = "/bin/zsh",
|
|
Arguments = $"-lc \"{command}\"",
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
}
|
|
|
|
private static async Task WaitUntilAsync(Func<bool> condition, TimeSpan timeout)
|
|
{
|
|
var deadline = DateTime.UtcNow + timeout;
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
if (condition())
|
|
{
|
|
return;
|
|
}
|
|
|
|
await Task.Delay(50);
|
|
}
|
|
|
|
throw new TimeoutException("Condition was not met before timeout.");
|
|
}
|
|
}
|