using Cleanuparr.Domain.Enums; using Cleanuparr.Persistence.Models.Configuration.MalwareBlocker; using Shouldly; using Xunit; using ValidationException = System.ComponentModel.DataAnnotations.ValidationException; namespace Cleanuparr.Persistence.Tests.Models.Configuration.MalwareBlocker; public sealed class ContentBlockerConfigTests { #region Validate - Disabled Config [Fact] public void Validate_WhenDisabled_DoesNotThrow() { var config = new ContentBlockerConfig { Enabled = false }; Should.NotThrow(() => config.Validate()); } [Fact] public void Validate_WhenDisabledWithNoBlocklists_DoesNotThrow() { var config = new ContentBlockerConfig { Enabled = false, Sonarr = new BlocklistSettings { Enabled = false }, Radarr = new BlocklistSettings { Enabled = false }, Lidarr = new BlocklistSettings { Enabled = false }, Readarr = new BlocklistSettings { Enabled = false }, Whisparr = new BlocklistSettings { Enabled = false } }; Should.NotThrow(() => config.Validate()); } #endregion #region Validate - No Blocklists Enabled [Fact] public void Validate_WhenEnabledWithNoBlocklists_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = false }, Radarr = new BlocklistSettings { Enabled = false }, Lidarr = new BlocklistSettings { Enabled = false }, Readarr = new BlocklistSettings { Enabled = false }, Whisparr = new BlocklistSettings { Enabled = false } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldBe("At least one blocklist must be configured when Malware Blocker is enabled"); } #endregion #region Validate - Blocklist Settings Validation [Fact] public void Validate_WhenEnabledWithValidSonarrBlocklist_DoesNotThrow() { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = "https://example.com/blocklist.txt" } }; Should.NotThrow(() => config.Validate()); } [Fact] public void Validate_WhenEnabledWithValidRadarrBlocklist_DoesNotThrow() { var config = new ContentBlockerConfig { Enabled = true, Radarr = new BlocklistSettings { Enabled = true, BlocklistPath = "http://example.com/blocklist.txt" } }; Should.NotThrow(() => config.Validate()); } [Fact] public void Validate_WhenBlocklistEnabledWithEmptyPath_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = "" } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldBe("Sonarr blocklist is enabled but path is not specified"); } [Fact] public void Validate_WhenBlocklistEnabledWithNullPath_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Radarr = new BlocklistSettings { Enabled = true, BlocklistPath = null } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldBe("Radarr blocklist is enabled but path is not specified"); } [Fact] public void Validate_WhenBlocklistEnabledWithWhitespacePath_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Lidarr = new BlocklistSettings { Enabled = true, BlocklistPath = " " } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldBe("Lidarr blocklist is enabled but path is not specified"); } [Fact] public void Validate_WhenBlocklistEnabledWithNonExistentFilePath_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Readarr = new BlocklistSettings { Enabled = true, BlocklistPath = "/non/existent/path/blocklist.txt" } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldContain("Readarr blocklist does not exist"); } [Fact] public void Validate_WhenBlocklistEnabledWithInvalidPath_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Whisparr = new BlocklistSettings { Enabled = true, BlocklistPath = "not-a-valid-path" } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldContain("Whisparr blocklist does not exist"); } #endregion #region Validate - URL Paths [Theory] [InlineData("http://example.com/blocklist.txt")] [InlineData("https://example.com/blocklist.txt")] [InlineData("http://localhost:8080/api/blocklist")] [InlineData("https://raw.githubusercontent.com/user/repo/main/blocklist.txt")] public void Validate_WhenBlocklistEnabledWithValidHttpUrl_DoesNotThrow(string url) { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = url } }; Should.NotThrow(() => config.Validate()); } [Theory] [InlineData("ftp://example.com/blocklist.txt")] [InlineData("file:///path/to/file")] public void Validate_WhenBlocklistEnabledWithNonHttpUrl_ThrowsValidationException(string url) { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = url } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldContain("Sonarr blocklist does not exist"); } #endregion #region Validate - Multiple Blocklists [Fact] public void Validate_WhenMultipleBlocklistsEnabled_ValidatesAll() { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = "https://example.com/sonarr-blocklist.txt" }, Radarr = new BlocklistSettings { Enabled = true, BlocklistPath = "https://example.com/radarr-blocklist.txt" } }; Should.NotThrow(() => config.Validate()); } [Fact] public void Validate_WhenOneBlocklistInvalid_ThrowsValidationException() { var config = new ContentBlockerConfig { Enabled = true, Sonarr = new BlocklistSettings { Enabled = true, BlocklistPath = "https://example.com/sonarr-blocklist.txt" }, Radarr = new BlocklistSettings { Enabled = true, BlocklistPath = "" // Invalid } }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldBe("Radarr blocklist is enabled but path is not specified"); } #endregion #region Default Values [Fact] public void CronExpression_HasDefaultValue() { var config = new ContentBlockerConfig(); config.CronExpression.ShouldBe("0/5 * * * * ?"); } #endregion }