Files
ChatBot/ChatBot.Tests/Configuration/Validators/AISettingsValidatorTests.cs
Leonid Pershin 6c34b9cbb9
All checks were successful
SonarQube / Build and analyze (push) Successful in 3m46s
Unit Tests / Run Tests (push) Successful in 2m21s
add latest tests
2025-10-20 09:29:08 +03:00

697 lines
20 KiB
C#

using System.IO;
using ChatBot.Models.Configuration;
using ChatBot.Models.Configuration.Validators;
using FluentAssertions;
using Microsoft.Extensions.Options;
namespace ChatBot.Tests.Configuration.Validators;
public class AISettingsValidatorTests : IDisposable
{
private readonly string _testPromptPath;
private readonly AISettingsValidator _validator;
private bool _disposed;
public AISettingsValidatorTests()
{
_testPromptPath = Path.Combine(Path.GetTempPath(), "test-prompt.txt");
_validator = new AISettingsValidator();
}
[Fact]
public void Validate_WithValidSettings_ShouldReturnSuccess()
{
// Arrange
var settings = CreateValidAISettings();
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
result.Failed.Should().BeFalse();
result.FailureMessage.Should().BeNullOrEmpty();
}
[Theory]
[InlineData(-0.1)]
[InlineData(2.1)]
[InlineData(-1.0)]
[InlineData(3.0)]
public void Validate_WithInvalidTemperature_ShouldReturnFailure(double temperature)
{
// Arrange
var settings = CreateValidAISettings();
settings.Temperature = temperature;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.Failed.Should().BeTrue();
result.FailureMessage.Should().Contain("Temperature must be between 0.0 and 2.0");
}
[Theory]
[InlineData(0.0)]
[InlineData(1.0)]
[InlineData(2.0)]
[InlineData(0.5)]
[InlineData(1.5)]
public void Validate_WithValidTemperature_ShouldReturnSuccess(double temperature)
{
// Arrange
var settings = CreateValidAISettings();
settings.Temperature = temperature;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Fact]
public void Validate_WithEmptySystemPromptPath_ShouldReturnFailure()
{
// Arrange
var settings = CreateValidAISettings();
settings.SystemPromptPath = string.Empty;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("System prompt path cannot be empty");
}
[Fact]
public void Validate_WithNullSystemPromptPath_ShouldReturnFailure()
{
// Arrange
var settings = CreateValidAISettings();
settings.SystemPromptPath = null!;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("System prompt path cannot be empty");
}
[Fact]
public void Validate_WithNonExistentSystemPromptFile_ShouldReturnFailure()
{
// Arrange
var settings = CreateValidAISettings();
settings.SystemPromptPath = "non-existent-file.txt";
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("System prompt file not found at path");
}
[Fact]
public void Validate_WithEmptySystemPromptFile_ShouldReturnFailure()
{
// Arrange
var testPromptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test-prompt.txt");
File.WriteAllText(testPromptPath, "");
var settings = CreateValidAISettings();
settings.SystemPromptPath = "test-prompt.txt";
try
{
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("System prompt file is empty");
}
finally
{
if (File.Exists(testPromptPath))
{
File.Delete(testPromptPath);
}
}
}
[Fact]
public void Validate_WithTooLongSystemPromptFile_ShouldReturnFailure()
{
// Arrange
var testPromptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test-prompt.txt");
var longContent = new string('A', 4001);
File.WriteAllText(testPromptPath, longContent);
var settings = CreateValidAISettings();
settings.SystemPromptPath = "test-prompt.txt";
try
{
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result
.FailureMessage.Should()
.Contain("System prompt content cannot exceed 4000 characters");
}
finally
{
if (File.Exists(testPromptPath))
{
File.Delete(testPromptPath);
}
}
}
[Fact]
public void Validate_WithValidSystemPromptFile_ShouldReturnSuccess()
{
// Arrange
var testPromptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test-prompt.txt");
var content = "Valid system prompt content";
File.WriteAllText(testPromptPath, content);
var settings = CreateValidAISettings();
settings.SystemPromptPath = "test-prompt.txt";
try
{
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
finally
{
if (File.Exists(testPromptPath))
{
File.Delete(testPromptPath);
}
}
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(11)]
[InlineData(100)]
public void Validate_WithInvalidMaxRetryAttempts_ShouldReturnFailure(int maxRetryAttempts)
{
// Arrange
var settings = CreateValidAISettings();
settings.MaxRetryAttempts = maxRetryAttempts;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Max retry attempts");
}
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(10)]
public void Validate_WithValidMaxRetryAttempts_ShouldReturnSuccess(int maxRetryAttempts)
{
// Arrange
var settings = CreateValidAISettings();
settings.MaxRetryAttempts = maxRetryAttempts;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(99)]
[InlineData(30001)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidRetryDelay_ShouldReturnFailure(int retryDelayMs)
{
// Arrange
var settings = CreateValidAISettings();
settings.RetryDelayMs = retryDelayMs;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Retry delay");
}
[Theory]
[InlineData(100)]
[InlineData(1000)]
[InlineData(30000)]
public void Validate_WithValidRetryDelay_ShouldReturnSuccess(int retryDelayMs)
{
// Arrange
var settings = CreateValidAISettings();
settings.RetryDelayMs = retryDelayMs;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(9)]
[InlineData(301)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidRequestTimeout_ShouldReturnFailure(int requestTimeoutSeconds)
{
// Arrange
var settings = CreateValidAISettings();
settings.RequestTimeoutSeconds = requestTimeoutSeconds;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Request timeout");
}
[Theory]
[InlineData(10)]
[InlineData(60)]
[InlineData(300)]
public void Validate_WithValidRequestTimeout_ShouldReturnSuccess(int requestTimeoutSeconds)
{
// Arrange
var settings = CreateValidAISettings();
settings.RequestTimeoutSeconds = requestTimeoutSeconds;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(4)]
[InlineData(101)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidCompressionThreshold_ShouldReturnFailure(
int compressionThreshold
)
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionThreshold = compressionThreshold;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Compression threshold");
}
[Theory]
[InlineData(20)]
[InlineData(50)]
[InlineData(100)]
public void Validate_WithValidCompressionThreshold_ShouldReturnSuccess(int compressionThreshold)
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionThreshold = compressionThreshold;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(2)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidCompressionTarget_ShouldReturnFailure(int compressionTarget)
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionTarget = compressionTarget;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Compression target");
}
[Fact]
public void Validate_WithCompressionTargetGreaterThanOrEqualToThreshold_ShouldReturnFailure()
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionThreshold = 20;
settings.CompressionTarget = 20; // Equal to threshold
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result
.FailureMessage.Should()
.Contain("Compression target must be less than compression threshold");
}
[Theory]
[InlineData(3)]
[InlineData(10)]
[InlineData(19)]
public void Validate_WithValidCompressionTarget_ShouldReturnSuccess(int compressionTarget)
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionTarget = compressionTarget;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(9)]
[InlineData(501)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidMinMessageLengthForSummarization_ShouldReturnFailure(
int minLength
)
{
// Arrange
var settings = CreateValidAISettings();
settings.MinMessageLengthForSummarization = minLength;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Minimum message length for summarization");
}
[Theory]
[InlineData(10)]
[InlineData(50)]
[InlineData(100)]
public void Validate_WithValidMinMessageLengthForSummarization_ShouldReturnSuccess(
int minLength
)
{
// Arrange
var settings = CreateValidAISettings();
settings.MinMessageLengthForSummarization = minLength;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(19)]
[InlineData(1001)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidMaxSummarizedMessageLength_ShouldReturnFailure(int maxLength)
{
// Arrange
var settings = CreateValidAISettings();
settings.MaxSummarizedMessageLength = maxLength;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Maximum summarized message length");
}
[Theory]
[InlineData(100)]
[InlineData(200)]
[InlineData(1000)]
public void Validate_WithValidMaxSummarizedMessageLength_ShouldReturnSuccess(int maxLength)
{
// Arrange
var settings = CreateValidAISettings();
settings.MaxSummarizedMessageLength = maxLength;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Fact]
public void Validate_WithMaxSummarizedMessageLengthLessThanOrEqualToMinLength_ShouldReturnFailure()
{
// Arrange
var settings = CreateValidAISettings();
settings.MinMessageLengthForSummarization = 50;
settings.MaxSummarizedMessageLength = 50; // Equal to min length
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result
.FailureMessage.Should()
.Contain(
"Maximum summarized message length must be greater than minimum message length for summarization"
);
}
[Theory]
[InlineData(999)]
[InlineData(300001)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidMaxRetryDelay_ShouldReturnFailure(int maxRetryDelayMs)
{
// Arrange
var settings = CreateValidAISettings();
settings.MaxRetryDelayMs = maxRetryDelayMs;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Maximum retry delay");
}
[Theory]
[InlineData(1000)]
[InlineData(30000)]
[InlineData(300000)]
public void Validate_WithValidMaxRetryDelay_ShouldReturnSuccess(int maxRetryDelayMs)
{
// Arrange
var settings = CreateValidAISettings();
settings.MaxRetryDelayMs = maxRetryDelayMs;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(4)]
[InlineData(301)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidCompressionTimeout_ShouldReturnFailure(
int compressionTimeoutSeconds
)
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionTimeoutSeconds = compressionTimeoutSeconds;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Compression timeout");
}
[Theory]
[InlineData(5)]
[InlineData(30)]
[InlineData(300)]
public void Validate_WithValidCompressionTimeout_ShouldReturnSuccess(
int compressionTimeoutSeconds
)
{
// Arrange
var settings = CreateValidAISettings();
settings.CompressionTimeoutSeconds = compressionTimeoutSeconds;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Theory]
[InlineData(1)]
[InlineData(61)]
[InlineData(0)]
[InlineData(-1)]
public void Validate_WithInvalidStatusCheckTimeout_ShouldReturnFailure(
int statusCheckTimeoutSeconds
)
{
// Arrange
var settings = CreateValidAISettings();
settings.StatusCheckTimeoutSeconds = statusCheckTimeoutSeconds;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Status check timeout");
}
[Theory]
[InlineData(2)]
[InlineData(10)]
[InlineData(60)]
public void Validate_WithValidStatusCheckTimeout_ShouldReturnSuccess(
int statusCheckTimeoutSeconds
)
{
// Arrange
var settings = CreateValidAISettings();
settings.StatusCheckTimeoutSeconds = statusCheckTimeoutSeconds;
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeTrue();
}
[Fact]
public void Validate_WithMultipleValidationErrors_ShouldReturnAllErrors()
{
// Arrange
var settings = new AISettings
{
Temperature = -1.0, // Invalid
SystemPromptPath = string.Empty, // Invalid
MaxRetryAttempts = 0, // Invalid
RetryDelayMs = 50, // Invalid
RequestTimeoutSeconds = 5, // Invalid
CompressionThreshold = 2, // Invalid
CompressionTarget = 1, // Invalid
MinMessageLengthForSummarization = 5, // Invalid
MaxSummarizedMessageLength = 10, // Invalid
MaxRetryDelayMs = 500, // Invalid
CompressionTimeoutSeconds = 2, // Invalid
StatusCheckTimeoutSeconds = 1, // Invalid
};
// Act
var result = _validator.Validate(null, settings);
// Assert
result.Succeeded.Should().BeFalse();
result.FailureMessage.Should().Contain("Temperature must be between 0.0 and 2.0");
result.FailureMessage.Should().Contain("System prompt path cannot be empty");
result.FailureMessage.Should().Contain("Max retry attempts must be at least 1");
result.FailureMessage.Should().Contain("Retry delay must be at least 100ms");
result.FailureMessage.Should().Contain("Request timeout must be at least 10 seconds");
result.FailureMessage.Should().Contain("Compression threshold must be at least 5 messages");
result.FailureMessage.Should().Contain("Compression target must be at least 3 messages");
result
.FailureMessage.Should()
.Contain("Minimum message length for summarization must be at least 10 characters");
result
.FailureMessage.Should()
.Contain("Maximum summarized message length must be at least 20 characters");
result.FailureMessage.Should().Contain("Maximum retry delay must be at least 1000ms");
result.FailureMessage.Should().Contain("Compression timeout must be at least 5 seconds");
result.FailureMessage.Should().Contain("Status check timeout must be at least 2 seconds");
}
private static AISettings CreateValidAISettings()
{
return new AISettings
{
Temperature = 0.7,
SystemPromptPath = "Prompts/system-prompt.txt",
MaxRetryAttempts = 3,
RetryDelayMs = 1000,
RequestTimeoutSeconds = 60,
EnableHistoryCompression = true,
CompressionThreshold = 20,
CompressionTarget = 10,
MinMessageLengthForSummarization = 50,
MaxSummarizedMessageLength = 200,
EnableExponentialBackoff = true,
MaxRetryDelayMs = 30000,
CompressionTimeoutSeconds = 30,
StatusCheckTimeoutSeconds = 10,
};
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
if (File.Exists(_testPromptPath))
{
File.Delete(_testPromptPath);
}
_disposed = true;
}
}
}