mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-03-26 01:52:41 -04:00
783 lines
26 KiB
C#
783 lines
26 KiB
C#
using Cleanuparr.Domain.Entities.Arr;
|
|
using Cleanuparr.Domain.Entities.Arr.Queue;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Infrastructure.Features.Arr;
|
|
using Cleanuparr.Infrastructure.Features.Arr.Interfaces;
|
|
using Cleanuparr.Infrastructure.Features.DownloadClient;
|
|
using Cleanuparr.Infrastructure.Features.DownloadRemover.Models;
|
|
using Cleanuparr.Infrastructure.Features.MalwareBlocker;
|
|
using Cleanuparr.Infrastructure.Tests.Features.Jobs.TestHelpers;
|
|
using Cleanuparr.Persistence.Models.Configuration;
|
|
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
|
using Cleanuparr.Persistence.Models.Configuration.MalwareBlocker;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using Xunit;
|
|
using MalwareBlockerJob = Cleanuparr.Infrastructure.Features.Jobs.MalwareBlocker;
|
|
|
|
namespace Cleanuparr.Infrastructure.Tests.Features.Jobs;
|
|
|
|
[Collection(JobHandlerCollection.Name)]
|
|
public class MalwareBlockerTests : IDisposable
|
|
{
|
|
private readonly JobHandlerFixture _fixture;
|
|
private readonly Mock<ILogger<MalwareBlockerJob>> _logger;
|
|
|
|
public MalwareBlockerTests(JobHandlerFixture fixture)
|
|
{
|
|
_fixture = fixture;
|
|
_fixture.RecreateDataContext();
|
|
_fixture.ResetMocks();
|
|
_logger = _fixture.CreateLogger<MalwareBlockerJob>();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private MalwareBlockerJob CreateSut()
|
|
{
|
|
return new MalwareBlockerJob(
|
|
_logger.Object,
|
|
_fixture.DataContext,
|
|
_fixture.Cache,
|
|
_fixture.MessageBus.Object,
|
|
_fixture.ArrClientFactory.Object,
|
|
_fixture.ArrQueueIterator.Object,
|
|
_fixture.DownloadServiceFactory.Object,
|
|
_fixture.BlocklistProvider.Object,
|
|
_fixture.EventPublisher.Object
|
|
);
|
|
}
|
|
|
|
#region ExecuteInternalAsync Tests
|
|
|
|
[Fact]
|
|
public async Task ExecuteInternalAsync_WhenNoDownloadClientsConfigured_LogsWarningAndReturns()
|
|
{
|
|
// Arrange
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_logger.Verify(
|
|
x => x.Log(
|
|
LogLevel.Warning,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("No download clients configured")),
|
|
It.IsAny<Exception?>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteInternalAsync_WhenNoBlocklistsEnabled_LogsWarningAndReturns()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_logger.Verify(
|
|
x => x.Log(
|
|
LogLevel.Warning,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("No blocklists are enabled")),
|
|
It.IsAny<Exception?>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteInternalAsync_WhenBlocklistEnabled_LoadsBlocklists()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(It.IsAny<InstanceType>(), It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_fixture.BlocklistProvider.Verify(x => x.LoadBlocklistsAsync(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteInternalAsync_WhenSonarrEnabled_ProcessesSonarrInstances()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_fixture.ArrClientFactory.Verify(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()), Times.Once);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(InstanceType.Radarr)]
|
|
[InlineData(InstanceType.Lidarr)]
|
|
[InlineData(InstanceType.Readarr)]
|
|
[InlineData(InstanceType.Whisparr)]
|
|
public async Task ExecuteInternalAsync_WhenArrTypeEnabled_ProcessesCorrectInstances(InstanceType instanceType)
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableBlocklist(instanceType);
|
|
AddArrInstance(instanceType);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(instanceType, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_fixture.ArrClientFactory.Verify(x => x.GetClient(instanceType, It.IsAny<float>()), Times.Once);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ProcessInstanceAsync Tests
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_SkipsIgnoredDownloads()
|
|
{
|
|
// Arrange
|
|
var generalConfig = _fixture.DataContext.GeneralConfigs.First();
|
|
generalConfig.IgnoredDownloads = ["ignored-download-id"];
|
|
_fixture.DataContext.SaveChanges();
|
|
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(true);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "ignored-download-id",
|
|
Title = "Ignored Download",
|
|
Protocol = "torrent",
|
|
SeriesId = 1,
|
|
EpisodeId = 1
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_logger.Verify(
|
|
x => x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("ignored")),
|
|
It.IsAny<Exception?>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_ChecksTorrentClientsForBlockedFiles()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(true);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "torrent-download-id",
|
|
Title = "Torrent Download",
|
|
Protocol = "torrent",
|
|
SeriesId = 1,
|
|
EpisodeId = 1
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService
|
|
.Setup(x => x.BlockUnwantedFilesAsync(
|
|
It.IsAny<string>(),
|
|
It.IsAny<List<string>>()
|
|
))
|
|
.ReturnsAsync(new BlockFilesResult { Found = true, ShouldRemove = false });
|
|
|
|
_fixture.DownloadServiceFactory
|
|
.Setup(x => x.GetDownloadService(It.IsAny<DownloadClientConfig>()))
|
|
.Returns(mockDownloadService.Object);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
mockDownloadService.Verify(
|
|
x => x.BlockUnwantedFilesAsync("torrent-download-id", It.IsAny<List<string>>()),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_WhenShouldRemove_PublishesRemoveRequest()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
var sonarrInstance = TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(true);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "malware-download-id",
|
|
Title = "Malware Download",
|
|
Protocol = "torrent",
|
|
SeriesId = 1,
|
|
EpisodeId = 1
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService
|
|
.Setup(x => x.BlockUnwantedFilesAsync(
|
|
It.IsAny<string>(),
|
|
It.IsAny<List<string>>()
|
|
))
|
|
.ReturnsAsync(new BlockFilesResult
|
|
{
|
|
Found = true,
|
|
ShouldRemove = true,
|
|
IsPrivate = false,
|
|
DeleteReason = DeleteReason.AllFilesBlocked
|
|
});
|
|
|
|
_fixture.DownloadServiceFactory
|
|
.Setup(x => x.GetDownloadService(It.IsAny<DownloadClientConfig>()))
|
|
.Returns(mockDownloadService.Object);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_fixture.MessageBus.Verify(
|
|
x => x.Publish(
|
|
It.Is<QueueItemRemoveRequest<SeriesSearchItem>>(r =>
|
|
r.DeleteReason == DeleteReason.AllFilesBlocked
|
|
),
|
|
It.IsAny<CancellationToken>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_WhenPrivateAndDeletePrivateFalse_DoesNotRemoveFromClient()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
// Ensure DeletePrivate is false
|
|
var contentBlockerConfig = _fixture.DataContext.ContentBlockerConfigs.First();
|
|
contentBlockerConfig.DeletePrivate = false;
|
|
_fixture.DataContext.SaveChanges();
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(true);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "private-malware-id",
|
|
Title = "Private Malware",
|
|
Protocol = "torrent",
|
|
SeriesId = 1,
|
|
EpisodeId = 1
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService
|
|
.Setup(x => x.BlockUnwantedFilesAsync(
|
|
It.IsAny<string>(),
|
|
It.IsAny<List<string>>()
|
|
))
|
|
.ReturnsAsync(new BlockFilesResult
|
|
{
|
|
Found = true,
|
|
ShouldRemove = true,
|
|
IsPrivate = true,
|
|
DeleteReason = DeleteReason.AllFilesBlocked
|
|
});
|
|
|
|
_fixture.DownloadServiceFactory
|
|
.Setup(x => x.GetDownloadService(It.IsAny<DownloadClientConfig>()))
|
|
.Returns(mockDownloadService.Object);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert - RemoveFromClient should be false
|
|
_fixture.MessageBus.Verify(
|
|
x => x.Publish(
|
|
It.Is<QueueItemRemoveRequest<SeriesSearchItem>>(r =>
|
|
r.RemoveFromClient == false
|
|
),
|
|
It.IsAny<CancellationToken>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_WhenDownloadNotFoundInTorrentClient_LogsWarning()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(true);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "missing-download-id",
|
|
Title = "Missing Download",
|
|
Protocol = "torrent",
|
|
SeriesId = 1,
|
|
EpisodeId = 1
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService
|
|
.Setup(x => x.BlockUnwantedFilesAsync(
|
|
It.IsAny<string>(),
|
|
It.IsAny<List<string>>()
|
|
))
|
|
.ReturnsAsync(new BlockFilesResult { Found = false });
|
|
|
|
_fixture.DownloadServiceFactory
|
|
.Setup(x => x.GetDownloadService(It.IsAny<DownloadClientConfig>()))
|
|
.Returns(mockDownloadService.Object);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_logger.Verify(
|
|
x => x.Log(
|
|
LogLevel.Warning,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("Download not found in any torrent client")),
|
|
It.IsAny<Exception?>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_SkipsItem_WhenMissingContentId_AndProcessNoContentIdIsFalse()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(false);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "no-content-id-download",
|
|
Title = "No Content ID Download",
|
|
Protocol = "torrent"
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_logger.Verify(
|
|
x => x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("skip | item is missing the content id")),
|
|
It.IsAny<Exception?>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
|
|
_fixture.MessageBus.Verify(
|
|
x => x.Publish(
|
|
It.IsAny<QueueItemRemoveRequest<SeriesSearchItem>>(),
|
|
It.IsAny<CancellationToken>()
|
|
),
|
|
Times.Never
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_WhenMissingContentId_AndProcessNoContentIdIsTrue_PublishesRemoveRequestWithSkipSearch()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var contentBlockerConfig = _fixture.DataContext.ContentBlockerConfigs.First();
|
|
contentBlockerConfig.ProcessNoContentId = true;
|
|
_fixture.DataContext.SaveChanges();
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(false);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "no-content-id-download",
|
|
Title = "No Content ID Download",
|
|
Protocol = "torrent"
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService
|
|
.Setup(x => x.BlockUnwantedFilesAsync(
|
|
It.IsAny<string>(),
|
|
It.IsAny<List<string>>()
|
|
))
|
|
.ReturnsAsync(new BlockFilesResult
|
|
{
|
|
Found = true,
|
|
ShouldRemove = true,
|
|
IsPrivate = false,
|
|
DeleteReason = DeleteReason.AllFilesBlocked
|
|
});
|
|
|
|
_fixture.DownloadServiceFactory
|
|
.Setup(x => x.GetDownloadService(It.IsAny<DownloadClientConfig>()))
|
|
.Returns(mockDownloadService.Object);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert - SkipSearch must be true because the item has no content ID
|
|
_fixture.MessageBus.Verify(
|
|
x => x.Publish(
|
|
It.Is<QueueItemRemoveRequest<SeriesSearchItem>>(r =>
|
|
r.SkipSearch == true &&
|
|
r.DeleteReason == DeleteReason.AllFilesBlocked
|
|
),
|
|
It.IsAny<CancellationToken>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Error Handling Tests
|
|
|
|
[Fact]
|
|
public async Task ProcessInstanceAsync_WhenDownloadServiceThrows_LogsErrorAndContinues()
|
|
{
|
|
// Arrange
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
EnableSonarrBlocklist();
|
|
TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
|
|
|
var mockArrClient = new Mock<IArrClient>();
|
|
mockArrClient.Setup(x => x.IsRecordValid(It.IsAny<QueueRecord>())).Returns(true);
|
|
mockArrClient.Setup(x => x.HasContentId(It.IsAny<QueueRecord>())).Returns(true);
|
|
|
|
_fixture.ArrClientFactory
|
|
.Setup(x => x.GetClient(InstanceType.Sonarr, It.IsAny<float>()))
|
|
.Returns(mockArrClient.Object);
|
|
|
|
var queueRecord = new QueueRecord
|
|
{
|
|
Id = 1,
|
|
DownloadId = "error-download-id",
|
|
Title = "Error Download",
|
|
Protocol = "torrent",
|
|
SeriesId = 1,
|
|
EpisodeId = 1
|
|
};
|
|
|
|
_fixture.ArrQueueIterator
|
|
.Setup(x => x.Iterate(
|
|
It.IsAny<IArrClient>(),
|
|
It.IsAny<ArrInstance>(),
|
|
It.IsAny<Func<IReadOnlyList<QueueRecord>, Task>>()
|
|
))
|
|
.Returns(async (IArrClient client, ArrInstance instance, Func<IReadOnlyList<QueueRecord>, Task> callback) =>
|
|
{
|
|
await callback([queueRecord]);
|
|
});
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService
|
|
.Setup(x => x.BlockUnwantedFilesAsync(
|
|
It.IsAny<string>(),
|
|
It.IsAny<List<string>>()
|
|
))
|
|
.ThrowsAsync(new Exception("Connection failed"));
|
|
|
|
_fixture.DownloadServiceFactory
|
|
.Setup(x => x.GetDownloadService(It.IsAny<DownloadClientConfig>()))
|
|
.Returns(mockDownloadService.Object);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert
|
|
_logger.Verify(
|
|
x => x.Log(
|
|
LogLevel.Error,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("Error checking download")),
|
|
It.IsAny<Exception?>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private void EnableSonarrBlocklist()
|
|
{
|
|
var contentBlockerConfig = _fixture.DataContext.ContentBlockerConfigs.First();
|
|
contentBlockerConfig.Sonarr = new BlocklistSettings { Enabled = true };
|
|
_fixture.DataContext.SaveChanges();
|
|
}
|
|
|
|
private void EnableBlocklist(InstanceType instanceType)
|
|
{
|
|
var config = _fixture.DataContext.ContentBlockerConfigs.First();
|
|
var settings = new BlocklistSettings { Enabled = true };
|
|
switch (instanceType)
|
|
{
|
|
case InstanceType.Radarr: config.Radarr = settings; break;
|
|
case InstanceType.Lidarr: config.Lidarr = settings; break;
|
|
case InstanceType.Readarr: config.Readarr = settings; break;
|
|
case InstanceType.Whisparr: config.Whisparr = settings; break;
|
|
default: throw new ArgumentOutOfRangeException(nameof(instanceType));
|
|
}
|
|
_fixture.DataContext.SaveChanges();
|
|
}
|
|
|
|
private void AddArrInstance(InstanceType instanceType)
|
|
{
|
|
switch (instanceType)
|
|
{
|
|
case InstanceType.Radarr: TestDataContextFactory.AddRadarrInstance(_fixture.DataContext); break;
|
|
case InstanceType.Lidarr: TestDataContextFactory.AddLidarrInstance(_fixture.DataContext); break;
|
|
case InstanceType.Readarr: TestDataContextFactory.AddReadarrInstance(_fixture.DataContext); break;
|
|
case InstanceType.Whisparr: TestDataContextFactory.AddWhisparrInstance(_fixture.DataContext); break;
|
|
default: throw new ArgumentOutOfRangeException(nameof(instanceType));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|