mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-05-08 23:03:13 -04:00
269 lines
11 KiB
C#
269 lines
11 KiB
C#
using System.Text.Json;
|
|
using Cleanuparr.Domain.Entities.Arr.Queue;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Infrastructure.Features.Arr.Interfaces;
|
|
using Cleanuparr.Infrastructure.Features.DownloadClient;
|
|
using Cleanuparr.Infrastructure.Features.ItemStriker;
|
|
using Cleanuparr.Infrastructure.Features.MalwareBlocker;
|
|
using Cleanuparr.Infrastructure.Tests.Features.Jobs.TestHelpers;
|
|
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
|
using Cleanuparr.Persistence.Models.Configuration.MalwareBlocker;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using NSubstitute;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using MalwareBlockerJob = Cleanuparr.Infrastructure.Features.Jobs.MalwareBlocker;
|
|
|
|
namespace Cleanuparr.Infrastructure.Tests.Features.Jobs.Integration;
|
|
|
|
[Collection(IntegrationTestCollection.Name)]
|
|
public class MalwareBlockerIntegrationTests : IDisposable
|
|
{
|
|
private readonly IntegrationTestFixture _fixture;
|
|
|
|
public MalwareBlockerIntegrationTests(IntegrationTestFixture fixture)
|
|
{
|
|
_fixture = fixture;
|
|
_fixture.Reset();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Striker.RecurringHashes.Clear();
|
|
}
|
|
|
|
private MalwareBlockerJob CreateSut()
|
|
{
|
|
return new MalwareBlockerJob(
|
|
Substitute.For<ILogger<MalwareBlockerJob>>(),
|
|
_fixture.DataContext,
|
|
_fixture.Cache,
|
|
_fixture.MessageBus,
|
|
_fixture.ArrClientFactory,
|
|
_fixture.ArrQueueIterator,
|
|
_fixture.DownloadServiceFactory,
|
|
_fixture.BlocklistProvider,
|
|
_fixture.EventPublisher);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MalwareDetected_RemovesFromArr_SavesEvent_SendsNotification()
|
|
{
|
|
// Arrange
|
|
var instance = TestDataContextFactory.AddRadarrInstance(_fixture.DataContext);
|
|
var downloadClient = TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
|
|
// Enable Radarr blocklist
|
|
var contentBlockerConfig = await _fixture.DataContext.ContentBlockerConfigs.FirstAsync();
|
|
contentBlockerConfig.Radarr = new BlocklistSettings { Enabled = true };
|
|
await _fixture.DataContext.SaveChangesAsync();
|
|
|
|
var record = CreateQueueRecord(movieId: 77);
|
|
|
|
_fixture.SetupArrQueueIterator(record);
|
|
_fixture.ArrClient.IsRecordValid(Arg.Any<QueueRecord>()).Returns(true);
|
|
_fixture.ArrClient.HasContentId(Arg.Any<QueueRecord>()).Returns(true);
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService.BlockUnwantedFilesAsync(Arg.Any<string>(), Arg.Any<IReadOnlyList<string>>())
|
|
.Returns(new BlockFilesResult
|
|
{
|
|
Found = true,
|
|
ShouldRemove = true,
|
|
DeleteReason = DeleteReason.AllFilesBlocked,
|
|
IsPrivate = false
|
|
});
|
|
_fixture.DownloadServiceFactory.GetDownloadService(Arg.Any<Cleanuparr.Persistence.Models.Configuration.DownloadClientConfig>())
|
|
.Returns(mockDownloadService);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert Phase 1: Remove request was published
|
|
var removeRequests = _fixture.GetCapturedRemoveRequests();
|
|
removeRequests.Count.ShouldBe(1);
|
|
|
|
// Process through real QueueItemRemover
|
|
_fixture.ArrClient.DeleteQueueItemAsync(
|
|
Arg.Any<ArrInstance>(), Arg.Any<QueueRecord>(), Arg.Any<bool>(), Arg.Any<DeleteReason>())
|
|
.Returns(Task.CompletedTask);
|
|
|
|
await _fixture.ProcessCapturedRemoveRequestsAsync();
|
|
|
|
// Assert: Arr client was told to delete with AllFilesBlocked reason
|
|
await _fixture.ArrClient.Received(1).DeleteQueueItemAsync(
|
|
Arg.Is<ArrInstance>(i => i.Id == instance.Id),
|
|
Arg.Is<QueueRecord>(r => r.DownloadId == record.DownloadId),
|
|
true,
|
|
DeleteReason.AllFilesBlocked);
|
|
|
|
// Assert: Full event property verification
|
|
var events = await _fixture.EventsContext.Events.ToListAsync();
|
|
events.Count.ShouldBe(2);
|
|
|
|
// DownloadMarkedForDeletion event
|
|
var markedEvent = events.First(e => e.EventType == EventType.DownloadMarkedForDeletion);
|
|
markedEvent.Message.ShouldBe("Download marked for deletion");
|
|
markedEvent.Severity.ShouldBe(EventSeverity.Important);
|
|
markedEvent.JobRunId.ShouldBe(_fixture.JobRunId);
|
|
markedEvent.ArrInstanceId.ShouldBe(instance.Id);
|
|
markedEvent.DownloadClientId.ShouldBe(mockDownloadService.ClientConfig.Id);
|
|
markedEvent.IsDryRun.ShouldBe(false);
|
|
markedEvent.StrikeId.ShouldBeNull();
|
|
markedEvent.TrackingId.ShouldBeNull();
|
|
markedEvent.SearchStatus.ShouldBeNull();
|
|
markedEvent.CompletedAt.ShouldBeNull();
|
|
markedEvent.CycleId.ShouldBeNull();
|
|
markedEvent.Data.ShouldNotBeNull();
|
|
using (var markedData = JsonDocument.Parse(markedEvent.Data!))
|
|
{
|
|
markedData.RootElement.GetProperty("itemName").GetString().ShouldBe("Suspicious.Movie.2024.1080p");
|
|
markedData.RootElement.GetProperty("hash").GetString().ShouldBe("MALWARE_HASH_789");
|
|
}
|
|
|
|
// QueueItemDeleted event
|
|
var deletedEvent = events.First(e => e.EventType == EventType.QueueItemDeleted);
|
|
deletedEvent.Message.ShouldBe("Deleting item from queue with reason: AllFilesBlocked");
|
|
deletedEvent.Severity.ShouldBe(EventSeverity.Important);
|
|
deletedEvent.JobRunId.ShouldBe(_fixture.JobRunId);
|
|
deletedEvent.ArrInstanceId.ShouldBe(instance.Id);
|
|
deletedEvent.DownloadClientId.ShouldBe(mockDownloadService.ClientConfig.Id);
|
|
deletedEvent.IsDryRun.ShouldBe(false);
|
|
deletedEvent.StrikeId.ShouldBeNull();
|
|
deletedEvent.TrackingId.ShouldBeNull();
|
|
deletedEvent.SearchStatus.ShouldBeNull();
|
|
deletedEvent.CompletedAt.ShouldBeNull();
|
|
deletedEvent.CycleId.ShouldBeNull();
|
|
deletedEvent.Data.ShouldNotBeNull();
|
|
using (var deletedData = JsonDocument.Parse(deletedEvent.Data!))
|
|
{
|
|
deletedData.RootElement.GetProperty("itemName").GetString().ShouldBe("Suspicious.Movie.2024.1080p");
|
|
deletedData.RootElement.GetProperty("hash").GetString().ShouldBe("MALWARE_HASH_789");
|
|
deletedData.RootElement.GetProperty("removeFromClient").GetBoolean().ShouldBe(true);
|
|
deletedData.RootElement.GetProperty("deleteReason").GetString().ShouldBe("AllFilesBlocked");
|
|
}
|
|
|
|
// Assert: Notification sent
|
|
await _fixture.NotificationPublisher.Received(1)
|
|
.NotifyQueueItemDeleted(true, DeleteReason.AllFilesBlocked);
|
|
|
|
// Assert: Search queue item added for replacement search
|
|
var searchItems = await _fixture.DataContext.SearchQueue.ToListAsync();
|
|
searchItems.Count.ShouldBe(1);
|
|
searchItems[0].ItemId.ShouldBe(77);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NoBlocklistsEnabled_ExitsEarly_NothingProcessed()
|
|
{
|
|
// Arrange: Default seed data has all blocklists disabled
|
|
TestDataContextFactory.AddRadarrInstance(_fixture.DataContext);
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert: Blocklists were never loaded, no processing happened
|
|
await _fixture.BlocklistProvider.DidNotReceive().LoadBlocklistsAsync();
|
|
_fixture.GetCapturedRemoveRequests().ShouldBeEmpty();
|
|
var events = await _fixture.EventsContext.Events.ToListAsync();
|
|
events.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PrivateTorrent_DeletePrivateFalse_RemoveFromClientIsFalse()
|
|
{
|
|
// Arrange
|
|
var instance = TestDataContextFactory.AddRadarrInstance(_fixture.DataContext);
|
|
TestDataContextFactory.AddDownloadClient(_fixture.DataContext);
|
|
|
|
// Enable Radarr blocklist, but keep DeletePrivate = false (default)
|
|
var contentBlockerConfig = await _fixture.DataContext.ContentBlockerConfigs.FirstAsync();
|
|
contentBlockerConfig.Radarr = new BlocklistSettings { Enabled = true };
|
|
contentBlockerConfig.DeletePrivate = false;
|
|
await _fixture.DataContext.SaveChangesAsync();
|
|
|
|
var record = CreateQueueRecord(movieId: 88);
|
|
|
|
_fixture.SetupArrQueueIterator(record);
|
|
_fixture.ArrClient.IsRecordValid(Arg.Any<QueueRecord>()).Returns(true);
|
|
_fixture.ArrClient.HasContentId(Arg.Any<QueueRecord>()).Returns(true);
|
|
|
|
var mockDownloadService = _fixture.CreateMockDownloadService();
|
|
mockDownloadService.BlockUnwantedFilesAsync(Arg.Any<string>(), Arg.Any<IReadOnlyList<string>>())
|
|
.Returns(new BlockFilesResult
|
|
{
|
|
Found = true,
|
|
ShouldRemove = true,
|
|
DeleteReason = DeleteReason.AllFilesBlocked,
|
|
IsPrivate = true
|
|
});
|
|
_fixture.DownloadServiceFactory.GetDownloadService(Arg.Any<Cleanuparr.Persistence.Models.Configuration.DownloadClientConfig>())
|
|
.Returns(mockDownloadService);
|
|
|
|
var sut = CreateSut();
|
|
|
|
// Act
|
|
await sut.ExecuteAsync();
|
|
|
|
// Assert: Remove request has RemoveFromClient = false
|
|
_fixture.GetCapturedRemoveRequests().Count.ShouldBe(1);
|
|
|
|
_fixture.ArrClient.DeleteQueueItemAsync(
|
|
Arg.Any<ArrInstance>(), Arg.Any<QueueRecord>(), Arg.Any<bool>(), Arg.Any<DeleteReason>())
|
|
.Returns(Task.CompletedTask);
|
|
|
|
await _fixture.ProcessCapturedRemoveRequestsAsync();
|
|
|
|
await _fixture.ArrClient.Received(1).DeleteQueueItemAsync(
|
|
Arg.Any<ArrInstance>(),
|
|
Arg.Any<QueueRecord>(),
|
|
false,
|
|
DeleteReason.AllFilesBlocked);
|
|
|
|
// Full event property verification
|
|
var events = await _fixture.EventsContext.Events.ToListAsync();
|
|
var deletedEvent = events.First(e => e.EventType == EventType.QueueItemDeleted);
|
|
deletedEvent.Message.ShouldBe("Deleting item from queue with reason: AllFilesBlocked");
|
|
deletedEvent.Severity.ShouldBe(EventSeverity.Important);
|
|
deletedEvent.JobRunId.ShouldBe(_fixture.JobRunId);
|
|
deletedEvent.ArrInstanceId.ShouldBe(instance.Id);
|
|
deletedEvent.IsDryRun.ShouldBe(false);
|
|
deletedEvent.StrikeId.ShouldBeNull();
|
|
deletedEvent.SearchStatus.ShouldBeNull();
|
|
deletedEvent.Data.ShouldNotBeNull();
|
|
using (var data = JsonDocument.Parse(deletedEvent.Data!))
|
|
{
|
|
data.RootElement.GetProperty("itemName").GetString().ShouldBe("Suspicious.Movie.2024.1080p");
|
|
data.RootElement.GetProperty("hash").GetString().ShouldBe("MALWARE_HASH_789");
|
|
data.RootElement.GetProperty("removeFromClient").GetBoolean().ShouldBe(false);
|
|
data.RootElement.GetProperty("deleteReason").GetString().ShouldBe("AllFilesBlocked");
|
|
}
|
|
|
|
await _fixture.NotificationPublisher.Received(1)
|
|
.NotifyQueueItemDeleted(false, DeleteReason.AllFilesBlocked);
|
|
}
|
|
|
|
private static QueueRecord CreateQueueRecord(
|
|
long movieId = 1,
|
|
string downloadId = "MALWARE_HASH_789",
|
|
string title = "Suspicious.Movie.2024.1080p")
|
|
{
|
|
return new QueueRecord
|
|
{
|
|
Id = 1,
|
|
Title = title,
|
|
Protocol = "torrent",
|
|
DownloadId = downloadId,
|
|
MovieId = movieId,
|
|
Status = "warning",
|
|
StatusMessages = []
|
|
};
|
|
}
|
|
}
|