using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using RdtClient.Data.Models.Data; using RdtClient.Service.BackgroundServices; using RdtClient.Service.Services; namespace RdtClient.Service.Test.BackgroundServices; public class WatchFolderCheckerTests : IDisposable { private readonly Mock> _loggerMock; private readonly Mock _scopeServiceProviderMock; private readonly Mock _serviceProviderMock; private readonly Mock _serviceScopeMock; private readonly String _testPath; private readonly TestSettings _settings; private readonly Mock _torrentsServiceMock; public WatchFolderCheckerTests() { _loggerMock = new(); _serviceProviderMock = new(); _serviceScopeMock = new(); _scopeServiceProviderMock = new(); _settings = new(new() { Watch = new() { Interval = 0, Default = new() }, DownloadClient = new() }); _torrentsServiceMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState()); _serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns(new MockScopeFactory(_serviceScopeMock.Object)); _serviceScopeMock .Setup(x => x.ServiceProvider) .Returns(_scopeServiceProviderMock.Object); _scopeServiceProviderMock .Setup(x => x.GetService(typeof(Torrents))) .Returns(_torrentsServiceMock.Object); _testPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(_testPath); SetStartupReady(true); _settings.Current.Watch.Path = _testPath; } public void Dispose() { if (Directory.Exists(_testPath)) { Directory.Delete(_testPath, true); } } private static void SetStartupReady(Boolean ready) { var property = typeof(Startup).GetProperty("Ready", BindingFlags.Public | BindingFlags.Static); property?.SetValue(null, ready); } private static void ResetPrevCheck(WatchFolderChecker checker) { var field = typeof(WatchFolderChecker).GetField("_prevCheck", BindingFlags.NonPublic | BindingFlags.Instance); field?.SetValue(checker, DateTime.MinValue); } [Fact] public async Task ExecuteAsync_ShouldProcessTorrentFile() { // Arrange var filePath = Path.Combine(_testPath, "test.torrent"); var content = "torrent content"u8.ToArray(); await File.WriteAllBytesAsync(filePath, content); var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings); ResetPrevCheck(checker); var cts = new CancellationTokenSource(); _torrentsServiceMock .Setup(x => x.AddFileToDebridQueue(It.IsAny(), It.IsAny())) .ReturnsAsync(new Torrent()); // Act var task = checker.StartAsync(cts.Token); await Task.Delay(500); // Give it some time to process await cts.CancelAsync(); try { await task; } catch (OperationCanceledException) { } // Assert _torrentsServiceMock.Verify(x => x.AddFileToDebridQueue(It.Is(b => b.AsEnumerable().SequenceEqual(content)), It.IsAny()), Times.AtLeastOnce); Assert.False(File.Exists(filePath)); Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.torrent"))); } [Fact] public async Task ExecuteAsync_ShouldProcessMagnetFile() { // Arrange var filePath = Path.Combine(_testPath, "test.magnet"); var content = "magnet:?xt=urn:btih:123"; await File.WriteAllTextAsync(filePath, content); var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings); ResetPrevCheck(checker); var cts = new CancellationTokenSource(); _torrentsServiceMock .Setup(x => x.AddMagnetToDebridQueue(It.IsAny(), It.IsAny())) .ReturnsAsync(new Torrent()); // Act var task = checker.StartAsync(cts.Token); await Task.Delay(500); await cts.CancelAsync(); try { await task; } catch (OperationCanceledException) { } // Assert _torrentsServiceMock.Verify(x => x.AddMagnetToDebridQueue(content, It.IsAny()), Times.AtLeastOnce); Assert.False(File.Exists(filePath)); Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.magnet"))); } [Fact] public async Task ExecuteAsync_ShouldProcessNzbFile() { // Arrange var filePath = Path.Combine(_testPath, "test.nzb"); var content = "nzb content"u8.ToArray(); await File.WriteAllBytesAsync(filePath, content); var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings); ResetPrevCheck(checker); var cts = new CancellationTokenSource(); _torrentsServiceMock .Setup(x => x.AddNzbFileToDebridQueue(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Torrent()); // Act var task = checker.StartAsync(cts.Token); await Task.Delay(500); await cts.CancelAsync(); try { await task; } catch (OperationCanceledException) { } // Assert _torrentsServiceMock.Verify(x => x.AddNzbFileToDebridQueue(It.Is(b => b.AsEnumerable().SequenceEqual(content)), "test.nzb", It.IsAny()), Times.AtLeastOnce); Assert.False(File.Exists(filePath)); Assert.True(File.Exists(Path.Combine(_testPath, "processed", "test.nzb"))); } [Fact] public async Task ExecuteAsync_ShouldIgnoreOtherFiles() { // Arrange var filePath = Path.Combine(_testPath, "test.txt"); await File.WriteAllTextAsync(filePath, "ignore me"); var checker = new WatchFolderChecker(_loggerMock.Object, _serviceProviderMock.Object, _settings); ResetPrevCheck(checker); var cts = new CancellationTokenSource(); // Act var task = checker.StartAsync(cts.Token); await Task.Delay(500); await cts.CancelAsync(); try { await task; } catch (OperationCanceledException) { } // Assert _torrentsServiceMock.Verify(x => x.AddFileToDebridQueue(It.IsAny(), It.IsAny()), Times.Never); _torrentsServiceMock.Verify(x => x.AddMagnetToDebridQueue(It.IsAny(), It.IsAny()), Times.Never); _torrentsServiceMock.Verify(x => x.AddNzbFileToDebridQueue(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); Assert.True(File.Exists(filePath)); } private class MockScopeFactory(IServiceScope scope) : IServiceScopeFactory { public IServiceScope CreateScope() { return scope; } } }