using MaksIT.UScheduler;
using MaksIT.UScheduler.BackgroundServices;
using MaksIT.UScheduler.Services;
using MaksIT.UScheduler.Shared;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace MaksIT.UScheduler.Tests.BackgroundServices;
///
/// Unit tests for ProcessBackgroundService.
///
public class ProcessBackgroundServiceTests {
private readonly Mock> _loggerMock;
private readonly Mock _processServiceMock;
public ProcessBackgroundServiceTests() {
_loggerMock = new Mock>();
_processServiceMock = new Mock();
}
private ProcessBackgroundService CreateService(Configuration config) {
var optionsMonitorMock = new Mock>();
optionsMonitorMock.Setup(m => m.CurrentValue).Returns(config);
return new ProcessBackgroundService(_loggerMock.Object, optionsMonitorMock.Object, _processServiceMock.Object);
}
[Fact]
public async Task ExecuteAsync_WithNoProcesses_DoesNotCallRunProcessAsync() {
// Arrange
var config = new Configuration { LogDir = ".\\Logs", Processes = [] };
var service = CreateService(config);
using var cts = new CancellationTokenSource();
// Act - cancel immediately to stop the service
cts.Cancel();
// Start the service - it should complete without errors
await service.StartAsync(cts.Token);
// Assert
_processServiceMock.Verify(
x => x.RunProcessAsync(It.IsAny(), It.IsAny(), It.IsAny()),
Times.Never);
}
[Fact]
public async Task ExecuteAsync_WithDisabledProcess_DoesNotRunDisabledProcess() {
// Arrange
var config = new Configuration {
LogDir = ".\\Logs",
Processes = [
new ProcessConfiguration { Path = @"C:\app.exe", Disabled = true }
]
};
var service = CreateService(config);
using var cts = new CancellationTokenSource();
// Act
cts.Cancel();
await service.StartAsync(cts.Token);
// Assert - disabled processes should not be run
_processServiceMock.Verify(
x => x.RunProcessAsync(It.IsAny(), It.IsAny(), It.IsAny()),
Times.Never);
}
[Fact]
public async Task ExecuteAsync_WithEmptyPath_DoesNotRunProcess() {
// Arrange
var config = new Configuration {
LogDir = ".\\Logs",
Processes = [ new ProcessConfiguration { Path = "" } ]
};
var service = CreateService(config);
using var cts = new CancellationTokenSource();
// Act
cts.Cancel();
await service.StartAsync(cts.Token);
// Assert - processes with empty paths should not be run
_processServiceMock.Verify(
x => x.RunProcessAsync(It.IsAny(), It.IsAny(), It.IsAny()),
Times.Never);
}
[Fact]
public async Task StopAsync_TerminatesAllProcesses() {
// Arrange
var config = new Configuration { LogDir = ".\\Logs" };
var service = CreateService(config);
// Act
await service.StopAsync(CancellationToken.None);
// Assert
_processServiceMock.Verify(x => x.TerminateAllProcesses(), Times.Once);
}
[Fact]
public async Task ExecuteAsync_WithEnabledProcess_CallsRunProcessAsync() {
// Arrange
var config = new Configuration {
LogDir = ".\\Logs",
Processes = [
new ProcessConfiguration { Path = @"C:\app.exe", Disabled = false }
]
};
var service = CreateService(config);
using var cts = new CancellationTokenSource();
// Setup mock to complete immediately
_processServiceMock
.Setup(x => x.RunProcessAsync(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(Task.CompletedTask);
// Act - start the service then cancel after a short delay
var executeTask = service.StartAsync(cts.Token);
// Give it a moment to start processing, then cancel
await Task.Delay(100);
cts.Cancel();
// Wait for the service to stop gracefully
try {
await service.StopAsync(CancellationToken.None);
}
catch (OperationCanceledException) {
// Expected when cancelling
}
// Assert - the enabled process should have been run at least once
_processServiceMock.Verify(
x => x.RunProcessAsync(@"C:\app.exe", It.IsAny(), It.IsAny()),
Times.AtLeastOnce);
}
[Fact]
public async Task ExecuteAsync_WithProcessArgs_PassesArgsToService() {
// Arrange
var expectedArgs = new[] { "--verbose", "--log" };
var config = new Configuration {
LogDir = ".\\Logs",
Processes = [
new ProcessConfiguration {
Path = @"C:\app.exe",
Disabled = false,
Args = expectedArgs
}
]
};
var service = CreateService(config);
using var cts = new CancellationTokenSource();
_processServiceMock
.Setup(x => x.RunProcessAsync(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(Task.CompletedTask);
// Act
var executeTask = service.StartAsync(cts.Token);
await Task.Delay(100);
cts.Cancel();
try {
await service.StopAsync(CancellationToken.None);
}
catch (OperationCanceledException) {
// Expected
}
// Assert - verify args were passed correctly
_processServiceMock.Verify(
x => x.RunProcessAsync(@"C:\app.exe", expectedArgs, It.IsAny()),
Times.AtLeastOnce);
}
}