697 lines
20 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|