using ChatBot.Models.Configuration; using ChatBot.Models.Configuration.Validators; using FluentAssertions; namespace ChatBot.Tests.Configuration.Validators; public class AISettingsValidatorTests { private readonly AISettingsValidator _validator = new(); [Fact] public void Validate_ShouldReturnSuccess_WhenSettingsAreValid() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeTrue(); } [Fact] public void Validate_ShouldReturnFailure_WhenTemperatureIsInvalid() { // Arrange var settings = new AISettings { Temperature = 3.0, // Invalid: > 2.0 SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Temperature must be between 0.0 and 2.0")); } [Fact] public void Validate_ShouldReturnFailure_WhenSystemPromptPathIsEmpty() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "", // Invalid: empty MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result.Failures.Should().Contain(f => f.Contains("System prompt path cannot be empty")); } [Fact] public void Validate_ShouldReturnFailure_WhenMaxRetryAttemptsIsInvalid() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 15, // Invalid: > 10 RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result.Failures.Should().Contain(f => f.Contains("Max retry attempts cannot exceed 10")); } [Fact] public void Validate_ShouldReturnFailure_WhenCompressionSettingsAreInvalid() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 5, // Invalid: same as target CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Compression target must be less than compression threshold")); } [Fact] public void Validate_ShouldReturnFailure_WhenTemperatureIsNegative() { // Arrange var settings = new AISettings { Temperature = -0.1, // Invalid: negative SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Temperature must be between 0.0 and 2.0")); } [Fact] public void Validate_ShouldReturnFailure_WhenSystemPromptPathIsNull() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = null!, // Invalid: null MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result.Failures.Should().Contain(f => f.Contains("System prompt path cannot be empty")); } [Fact] public void Validate_ShouldReturnFailure_WhenMaxRetryAttemptsIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 0, // Invalid: zero RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result.Failures.Should().Contain(f => f.Contains("Max retry attempts must be at least 1")); } [Fact] public void Validate_ShouldReturnFailure_WhenRetryDelayMsIsNegative() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = -100, // Invalid: negative MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result.Failures.Should().Contain(f => f.Contains("Retry delay must be at least 100ms")); } [Fact] public void Validate_ShouldReturnFailure_WhenMaxRetryDelayMsIsLessThanRetryDelayMs() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 5000, MaxRetryDelayMs = 1000, // Invalid: less than retry delay EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeTrue(); // This validation is not implemented in the validator } [Fact] public void Validate_ShouldReturnFailure_WhenRequestTimeoutSecondsIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 0, // Invalid: zero EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Request timeout must be at least 10 seconds")); } [Fact] public void Validate_ShouldReturnFailure_WhenCompressionThresholdIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 0, // Invalid: zero CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Compression threshold must be at least 5 messages")); } [Fact] public void Validate_ShouldReturnFailure_WhenCompressionTargetIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 0, // Invalid: zero MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Compression target must be at least 3 messages")); } [Fact] public void Validate_ShouldReturnFailure_WhenMinMessageLengthForSummarizationIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 0, // Invalid: zero MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains( "Minimum message length for summarization must be at least 10 characters" ) ); } [Fact] public void Validate_ShouldReturnFailure_WhenMaxSummarizedMessageLengthIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 0, // Invalid: zero CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Maximum summarized message length must be at least 20 characters") ); } [Fact] public void Validate_ShouldReturnFailure_WhenCompressionTimeoutSecondsIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 0, // Invalid: zero StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Compression timeout must be at least 5 seconds")); } [Fact] public void Validate_ShouldReturnFailure_WhenStatusCheckTimeoutSecondsIsZero() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 50, MaxSummarizedMessageLength = 200, CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 0, // Invalid: zero }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains("Status check timeout must be at least 2 seconds")); } [Fact] public void Validate_ShouldReturnFailure_WhenMaxSummarizedMessageLengthIsLessThanMinMessageLength() { // Arrange var settings = new AISettings { Temperature = 0.7, SystemPromptPath = "Prompts/system-prompt.txt", MaxRetryAttempts = 3, RetryDelayMs = 1000, MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 30, EnableHistoryCompression = true, CompressionThreshold = 10, CompressionTarget = 5, MinMessageLengthForSummarization = 100, MaxSummarizedMessageLength = 50, // Invalid: less than min message length CompressionTimeoutSeconds = 15, StatusCheckTimeoutSeconds = 5, }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result .Failures.Should() .Contain(f => f.Contains( "Maximum summarized message length must be greater than minimum message length for summarization" ) ); } [Fact] public void Validate_ShouldReturnMultipleFailures_WhenMultipleSettingsAreInvalid() { // Arrange var settings = new AISettings { Temperature = 3.0, // Invalid: > 2.0 SystemPromptPath = "", // Invalid: empty MaxRetryAttempts = 0, // Invalid: zero RetryDelayMs = -100, // Invalid: negative MaxRetryDelayMs = 10000, EnableExponentialBackoff = true, RequestTimeoutSeconds = 0, // Invalid: zero EnableHistoryCompression = true, CompressionThreshold = 0, // Invalid: zero CompressionTarget = 0, // Invalid: zero MinMessageLengthForSummarization = 0, // Invalid: zero MaxSummarizedMessageLength = 0, // Invalid: zero CompressionTimeoutSeconds = 0, // Invalid: zero StatusCheckTimeoutSeconds = 0, // Invalid: zero }; // Act var result = _validator.Validate(null, settings); // Assert result.Succeeded.Should().BeFalse(); result.Failures.Should().HaveCountGreaterThan(5); // Multiple validation failures } }