using System.Diagnostics.CodeAnalysis; namespace BGR.Console.Tests.Unit; public class RemovalCommandTests : IDisposable { private bool _isDisposed; private readonly Mock _modelFactoryMock = new(); private readonly Mock _imageProcessorMock = new(); private readonly Mock _inferenceRunnerMock = new(); private readonly TestConsole _console = new(); private readonly Mock> _loggerMock = new(); private readonly RemovalCommand _sut; public RemovalCommandTests() { _sut = new RemovalCommand( _modelFactoryMock.Object, _imageProcessorMock.Object, _inferenceRunnerMock.Object, _console, _loggerMock.Object ); } [Theory] [InlineData("output.png", false)] [InlineData("", true)] [SuppressMessage("Reliability", "CA2025:Do not pass 'IDisposable' instances into unawaited tasks", Justification = "Matching invocations")] public async Task ExecuteAsync_WhenCalled_ItShouldProcessImage(string outputPath, bool includeMask) { var imagePath = $"{Guid.NewGuid()}.png"; using var testImage = new Image(100, 100); await testImage.SaveAsPngAsync(imagePath); var resourceName = "u2net.onnx"; var settings = new RemovalCommand.Settings() { Image = imagePath, Model = "u2net", IncludeMask = includeMask, Output = outputPath, }; var model = new U2NetModel([1, 2, 3]); var image = new SharpImage(100, 100, new MemoryStream()); var inputTensor = new OnnxTensor(1, 3, 320, 320); var outputTensor = new OnnxTensor(1, 1, 320, 320); var maskStream = new MemoryStream(); var outputStream = new MemoryStream(); _modelFactoryMock .Setup(m => m.Create(resourceName)) .Returns(model); _imageProcessorMock .Setup(p => p.LoadImageAsync(imagePath)) .ReturnsAsync(image); _imageProcessorMock .Setup(p => p.CreateTensorInputAsync(image.Data, model)) .ReturnsAsync(inputTensor); _inferenceRunnerMock .Setup(r => r.Run(model.Bytes, inputTensor)) .Returns(outputTensor); _imageProcessorMock .Setup(p => p.GenerateMaskAsync(outputTensor, image.Width, image.Height)) .ReturnsAsync(maskStream); _imageProcessorMock .Setup(p => p.RemoveBackgroundAsync(image.Data, maskStream, It.IsAny(), It.IsAny())) .ReturnsAsync(outputStream); var commandContext = new CommandContext( [], new Mock().Object, "test", new object() ); var result = await _sut.ExecuteAsync(commandContext, settings); result.ShouldBe(0); _modelFactoryMock.Verify(m => m.Create(resourceName), Times.Once); _imageProcessorMock.Verify(p => p.LoadImageAsync(imagePath), Times.Once); _imageProcessorMock.Verify(p => p.CreateTensorInputAsync(image.Data, model), Times.Once); _inferenceRunnerMock.Verify(r => r.Run(model.Bytes, inputTensor), Times.Once); _imageProcessorMock.Verify(p => p.GenerateMaskAsync(outputTensor, image.Width, image.Height), Times.Once); _imageProcessorMock.Verify(p => p.RemoveBackgroundAsync(image.Data, maskStream, It.IsAny(), It.IsAny()), Times.Once); _imageProcessorMock.Verify(p => p.SaveImageAsync(outputStream, It.IsAny()), Times.AtLeastOnce); File.Delete(imagePath); } [Fact] public void Validate_WhenCalledAndFileDoesNotExist_ItShouldReturnError() { var settings = new RemovalCommand.Settings() { Image = "invalid.png", Model = "u2net", IncludeMask = false, Output = "output.png", }; var result = settings.Validate(); result.Successful.ShouldBeFalse(); } [Fact] public void Validate_WhenCalledAndModelIsInvalid_ItShouldReturnError() { var imagePath = $"{Guid.NewGuid()}.png"; using var testImage = new Image(100, 100); testImage.SaveAsPng(imagePath); var settings = new RemovalCommand.Settings() { Image = imagePath, Model = "invalid", IncludeMask = false, Output = "output.png", }; var result = settings.Validate(); result.Successful.ShouldBeFalse(); } [Fact] public void Validate_WhenCalledAndSettingsValid_ItShouldReturnSuccess() { var imagePath = $"{Guid.NewGuid()}.png"; using var testImage = new Image(100, 100); testImage.SaveAsPng(imagePath); var settings = new RemovalCommand.Settings() { Image = imagePath, Model = "u2net", IncludeMask = false, Output = "output.png", }; var result = settings.Validate(); result.Successful.ShouldBeTrue(); File.Delete(imagePath); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_isDisposed) { return; } if (disposing) { _console.Dispose(); } _isDisposed = true; } }