mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 10:48:49 -04:00
235 lines
8.1 KiB
C#
235 lines
8.1 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text.RegularExpressions;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Infrastructure.Features.MalwareBlocker;
|
|
using Cleanuparr.Infrastructure.Helpers;
|
|
using Cleanuparr.Persistence;
|
|
using Cleanuparr.Persistence.Models.Configuration.MalwareBlocker;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using NSubstitute;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace Cleanuparr.Infrastructure.Tests.Features.MalwareBlocker;
|
|
|
|
public class BlocklistProviderTests : IDisposable
|
|
{
|
|
private readonly IMemoryCache _cache;
|
|
private readonly ILogger<BlocklistProvider> _logger;
|
|
private readonly IServiceScopeFactory _scopeFactory;
|
|
private readonly BlocklistProvider _provider;
|
|
|
|
public BlocklistProviderTests()
|
|
{
|
|
_cache = new MemoryCache(new MemoryCacheOptions());
|
|
_logger = Substitute.For<ILogger<BlocklistProvider>>();
|
|
_scopeFactory = Substitute.For<IServiceScopeFactory>();
|
|
|
|
_provider = new BlocklistProvider(_logger, _scopeFactory, _cache, TimeProvider.System);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_cache.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoadBlocklistsAsync_ReloadsOnlyOnceTheLoadIntervalHasElapsedOnTheInjectedClock()
|
|
{
|
|
string blocklistPath = Path.Combine(Path.GetTempPath(), $"cleanuparr-blocklist-{Guid.NewGuid():N}.txt");
|
|
await File.WriteAllTextAsync(blocklistPath, "first-pattern");
|
|
|
|
using SqliteConnection connection = new("DataSource=:memory:");
|
|
connection.Open();
|
|
|
|
DbContextOptions<DataContext> options = new DbContextOptionsBuilder<DataContext>()
|
|
.UseSqlite(connection)
|
|
.Options;
|
|
|
|
await using (DataContext seedContext = new(options))
|
|
{
|
|
await seedContext.Database.EnsureCreatedAsync();
|
|
seedContext.ContentBlockerConfigs.Add(new ContentBlockerConfig
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Enabled = true,
|
|
IgnoredDownloads = [],
|
|
Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = blocklistPath },
|
|
Radarr = new BlocklistSettings { Enabled = false },
|
|
Lidarr = new BlocklistSettings { Enabled = false },
|
|
Readarr = new BlocklistSettings { Enabled = false },
|
|
Whisparr = new BlocklistSettings { Enabled = false },
|
|
Sportarr = new BlocklistSettings { Enabled = false },
|
|
LazyLibrarian = new BlocklistSettings { Enabled = false },
|
|
});
|
|
await seedContext.SaveChangesAsync();
|
|
}
|
|
|
|
ServiceCollection services = new();
|
|
services.AddScoped(_ => new DataContext(options));
|
|
services.AddScoped(_ => new FileReader(Substitute.For<IHttpClientFactory>()));
|
|
await using ServiceProvider serviceProvider = services.BuildServiceProvider();
|
|
|
|
using MemoryCache cache = new(new MemoryCacheOptions());
|
|
FakeTimeProvider timeProvider = new(new DateTimeOffset(2026, 1, 1, 12, 0, 0, TimeSpan.Zero));
|
|
BlocklistProvider provider = new(
|
|
_logger,
|
|
serviceProvider.GetRequiredService<IServiceScopeFactory>(),
|
|
cache,
|
|
timeProvider);
|
|
|
|
try
|
|
{
|
|
await provider.LoadBlocklistsAsync();
|
|
provider.GetPatterns(InstanceType.Sonarr).ShouldContain("first-pattern");
|
|
|
|
// Same settings, same path: the load interval has not elapsed, so the new content stays unread.
|
|
await File.WriteAllTextAsync(blocklistPath, "second-pattern");
|
|
await provider.LoadBlocklistsAsync();
|
|
|
|
provider.GetPatterns(InstanceType.Sonarr).ShouldContain("first-pattern");
|
|
provider.GetPatterns(InstanceType.Sonarr).ShouldNotContain("second-pattern");
|
|
|
|
// An absolute path parses as a file:// URI, so it takes the 4h interval.
|
|
timeProvider.Advance(TimeSpan.FromHours(5));
|
|
await provider.LoadBlocklistsAsync();
|
|
|
|
provider.GetPatterns(InstanceType.Sonarr).ShouldContain("second-pattern");
|
|
provider.GetPatterns(InstanceType.Sonarr).ShouldNotContain("first-pattern");
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(blocklistPath);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void GetBlocklistType_NotInCache_ReturnsDefaultBlacklist()
|
|
{
|
|
// Act
|
|
var result = _provider.GetBlocklistType(InstanceType.Sonarr);
|
|
|
|
// Assert
|
|
result.ShouldBe(BlocklistType.Blacklist);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(InstanceType.Sonarr)]
|
|
[InlineData(InstanceType.Radarr)]
|
|
[InlineData(InstanceType.Lidarr)]
|
|
[InlineData(InstanceType.Readarr)]
|
|
[InlineData(InstanceType.Whisparr)]
|
|
public void GetBlocklistType_InCache_ReturnsCachedValue(InstanceType instanceType)
|
|
{
|
|
// Arrange
|
|
_cache.Set(CacheKeys.BlocklistType(instanceType), BlocklistType.Whitelist);
|
|
|
|
// Act
|
|
var result = _provider.GetBlocklistType(instanceType);
|
|
|
|
// Assert
|
|
result.ShouldBe(BlocklistType.Whitelist);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetPatterns_NotInCache_ReturnsEmptyBag()
|
|
{
|
|
// Act
|
|
var result = _provider.GetPatterns(InstanceType.Sonarr);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void GetPatterns_InCache_ReturnsCachedPatterns()
|
|
{
|
|
// Arrange
|
|
var patterns = new ConcurrentBag<string> { "*.exe", "*.dll", "malware*" };
|
|
_cache.Set(CacheKeys.BlocklistPatterns(InstanceType.Radarr), patterns);
|
|
|
|
// Act
|
|
var result = _provider.GetPatterns(InstanceType.Radarr);
|
|
|
|
// Assert
|
|
result.Count.ShouldBe(3);
|
|
result.ShouldContain("*.exe");
|
|
result.ShouldContain("*.dll");
|
|
result.ShouldContain("malware*");
|
|
}
|
|
|
|
[Fact]
|
|
public void GetRegexes_NotInCache_ReturnsEmptyBag()
|
|
{
|
|
// Act
|
|
var result = _provider.GetRegexes(InstanceType.Lidarr);
|
|
|
|
// Assert
|
|
result.ShouldNotBeNull();
|
|
result.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void GetRegexes_InCache_ReturnsCachedRegexes()
|
|
{
|
|
// Arrange
|
|
var regexes = new ConcurrentBag<Regex>
|
|
{
|
|
new Regex(@"^\d+$"),
|
|
new Regex(@"test\d+\.exe")
|
|
};
|
|
_cache.Set(CacheKeys.BlocklistRegexes(InstanceType.Readarr), regexes);
|
|
|
|
// Act
|
|
var result = _provider.GetRegexes(InstanceType.Readarr);
|
|
|
|
// Assert
|
|
result.Count.ShouldBe(2);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(InstanceType.Sonarr)]
|
|
[InlineData(InstanceType.Radarr)]
|
|
[InlineData(InstanceType.Lidarr)]
|
|
[InlineData(InstanceType.Readarr)]
|
|
[InlineData(InstanceType.Whisparr)]
|
|
public void GetPatterns_DifferentInstanceTypes_UsesCorrectCacheKey(InstanceType instanceType)
|
|
{
|
|
// Arrange - set patterns for each instance type differently
|
|
var patterns = new ConcurrentBag<string> { $"pattern_for_{instanceType}" };
|
|
_cache.Set(CacheKeys.BlocklistPatterns(instanceType), patterns);
|
|
|
|
// Act
|
|
var result = _provider.GetPatterns(instanceType);
|
|
|
|
// Assert
|
|
result.ShouldContain($"pattern_for_{instanceType}");
|
|
}
|
|
|
|
[Fact]
|
|
public void GetPatterns_DifferentInstanceTypes_ReturnsDifferentPatterns()
|
|
{
|
|
// Arrange
|
|
var sonarrPatterns = new ConcurrentBag<string> { "sonarr_pattern" };
|
|
var radarrPatterns = new ConcurrentBag<string> { "radarr_pattern" };
|
|
_cache.Set(CacheKeys.BlocklistPatterns(InstanceType.Sonarr), sonarrPatterns);
|
|
_cache.Set(CacheKeys.BlocklistPatterns(InstanceType.Radarr), radarrPatterns);
|
|
|
|
// Act
|
|
var sonarrResult = _provider.GetPatterns(InstanceType.Sonarr);
|
|
var radarrResult = _provider.GetPatterns(InstanceType.Radarr);
|
|
|
|
// Assert
|
|
sonarrResult.ShouldContain("sonarr_pattern");
|
|
sonarrResult.ShouldNotContain("radarr_pattern");
|
|
radarrResult.ShouldContain("radarr_pattern");
|
|
radarrResult.ShouldNotContain("sonarr_pattern");
|
|
}
|
|
}
|