diff --git a/DiscordTranslationBot.Tests/Configuration/DiscordOptionsValidatorTests.cs b/DiscordTranslationBot.Tests/Configuration/DiscordOptionsValidatorTests.cs new file mode 100644 index 0000000..84dd12a --- /dev/null +++ b/DiscordTranslationBot.Tests/Configuration/DiscordOptionsValidatorTests.cs @@ -0,0 +1,38 @@ +using DiscordTranslationBot.Configuration; +using FluentValidation.TestHelper; + +namespace DiscordTranslationBot.Tests.Configuration; + +public sealed class DiscordOptionsValidatorTests +{ + private readonly DiscordOptionsValidator _sut = new(); + + [Fact] + public void Valid_Options_ValidatesWithoutErrors() + { + // Arrange + var options = new DiscordOptions { BotToken = "token" }; + + // Act + var result = _sut.TestValidate(options); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Invalid_BotToken_HasValidationErrors(string? botToken) + { + // Arrange + var options = new DiscordOptions { BotToken = botToken! }; + + // Act + var result = _sut.TestValidate(options); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.BotToken).Only(); + } +} diff --git a/DiscordTranslationBot.Tests/Configuration/TranslationProviders/TranslationProvidersOptionsValidatorTests.cs b/DiscordTranslationBot.Tests/Configuration/TranslationProviders/TranslationProvidersOptionsValidatorTests.cs new file mode 100644 index 0000000..b82648c --- /dev/null +++ b/DiscordTranslationBot.Tests/Configuration/TranslationProviders/TranslationProvidersOptionsValidatorTests.cs @@ -0,0 +1,61 @@ +using DiscordTranslationBot.Configuration.TranslationProviders; +using FluentValidation.TestHelper; + +namespace DiscordTranslationBot.Tests.Configuration.TranslationProviders; + +public sealed class TranslationProvidersOptionsValidatorTests +{ + private readonly TranslationProvidersOptionsValidator _sut = new(); + + [Fact] + public void Valid_Options_ValidatesWithoutErrors() + { + // Arrange + var options = new TranslationProvidersOptions + { + AzureTranslator = new AzureTranslatorOptions + { + Enabled = true, + ApiUrl = new Uri("http://localhost", UriKind.Absolute), + Region = "westus", + SecretKey = "secret" + }, + LibreTranslate = new LibreTranslateOptions { Enabled = false } + }; + + // Act + var result = _sut.TestValidate(options); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Invalid_ProviderOptions_HasValidationErrors(string? stringValue) + { + // Arrange + var options = new TranslationProvidersOptions + { + AzureTranslator = new AzureTranslatorOptions + { + Enabled = true, + ApiUrl = null, + Region = stringValue, + SecretKey = stringValue + }, + LibreTranslate = new LibreTranslateOptions { Enabled = true, ApiUrl = null } + }; + + // Act + var result = _sut.TestValidate(options); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.AzureTranslator.ApiUrl); + result.ShouldHaveValidationErrorFor(x => x.AzureTranslator.Region); + result.ShouldHaveValidationErrorFor(x => x.AzureTranslator.SecretKey); + result.ShouldHaveValidationErrorFor(x => x.LibreTranslate.ApiUrl); + } +} diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs b/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs index 4fdb925..4098005 100644 --- a/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs +++ b/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs @@ -1,15 +1,12 @@ -namespace DiscordTranslationBot.Configuration.TranslationProviders; +using FluentValidation; + +namespace DiscordTranslationBot.Configuration.TranslationProviders; /// /// Options for the Azure Translator provider. /// public sealed class AzureTranslatorOptions : TranslationProviderOptionsBase { - /// - /// The API URL for Azure Translator. - /// - public Uri? ApiUrl { get; init; } - /// /// The secret key for the Azure Translator API. /// @@ -20,3 +17,26 @@ public sealed class AzureTranslatorOptions : TranslationProviderOptionsBase /// public string? Region { get; init; } } + +/// +/// Validator for . +/// +public sealed class AzureTranslatorOptionsValidator : AbstractValidator +{ + /// + /// Initializes validation rules. + /// + public AzureTranslatorOptionsValidator() + { + Include(new TranslationProviderOptionsBaseValidator()); + + When( + x => x.Enabled, + () => + { + RuleFor(x => x.SecretKey).NotEmpty(); + RuleFor(x => x.Region).NotEmpty(); + } + ); + } +} diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs b/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs index 7df0b5f..6d05650 100644 --- a/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs +++ b/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs @@ -1,12 +1,22 @@ -namespace DiscordTranslationBot.Configuration.TranslationProviders; +using FluentValidation; + +namespace DiscordTranslationBot.Configuration.TranslationProviders; /// /// Options for the LibreTranslate provider. /// -public sealed class LibreTranslateOptions : TranslationProviderOptionsBase +public sealed class LibreTranslateOptions : TranslationProviderOptionsBase { } + +/// +/// Validator for . +/// +public sealed class LibreTranslateOptionsValidator : AbstractValidator { /// - /// The API URL for LibreTranslate. + /// Initializes validation rules. /// - public Uri? ApiUrl { get; init; } + public LibreTranslateOptionsValidator() + { + Include(new TranslationProviderOptionsBaseValidator()); + } } diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs index 1569dfc..6596ec0 100644 --- a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs +++ b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs @@ -1,4 +1,6 @@ -namespace DiscordTranslationBot.Configuration.TranslationProviders; +using FluentValidation; + +namespace DiscordTranslationBot.Configuration.TranslationProviders; /// /// Base class for a translation provider's options. @@ -9,4 +11,23 @@ public abstract class TranslationProviderOptionsBase /// Flag indicating whether this provider is enabled. /// public bool Enabled { get; init; } + + /// + /// The API URL for Azure Translator. + /// + public Uri? ApiUrl { get; init; } +} + +/// +/// Validator for . +/// +public sealed class TranslationProviderOptionsBaseValidator : AbstractValidator +{ + /// + /// Initializes validation rules. + /// + public TranslationProviderOptionsBaseValidator() + { + When(x => x.Enabled, () => RuleFor(x => x.ApiUrl).NotNull()); + } } diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs index 386c36f..5384a16 100644 --- a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs +++ b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs @@ -29,22 +29,11 @@ public sealed class TranslationProvidersOptions public sealed class TranslationProvidersOptionsValidator : AbstractValidator { /// - /// Initializes a new instance of the class. + /// Initializes validation rules. /// public TranslationProvidersOptionsValidator() { - // Validate Azure Translator options. - When( - x => x.AzureTranslator.Enabled, - () => - { - RuleFor(x => x.AzureTranslator.ApiUrl).NotNull(); - RuleFor(x => x.AzureTranslator.SecretKey).NotEmpty(); - RuleFor(x => x.AzureTranslator.Region).NotEmpty(); - } - ); - - // Validate Libre Translate options. - When(x => x.LibreTranslate.Enabled, () => RuleFor(x => x.LibreTranslate.ApiUrl).NotNull()); + RuleFor(x => x.AzureTranslator).SetValidator(new AzureTranslatorOptionsValidator()); + RuleFor(x => x.LibreTranslate).SetValidator(new LibreTranslateOptionsValidator()); } } diff --git a/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs b/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs index d97a21f..c39872d 100644 --- a/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs +++ b/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs @@ -26,7 +26,6 @@ IConfigurationSection configurationSection where TValidator : class, IValidator { services.AddTransient, TValidator>(); - services.AddOptions().Bind(configurationSection).ValidateWithFluentValidation().ValidateOnStart(); }