add tests
This commit is contained in:
@@ -160,4 +160,451 @@ public class AISettingsValidatorTests
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
280
ChatBot.Tests/Integration/ProgramIntegrationTests.cs
Normal file
280
ChatBot.Tests/Integration/ProgramIntegrationTests.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using ChatBot.Tests.TestUtilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace ChatBot.Tests.Integration;
|
||||
|
||||
public class ProgramIntegrationTests : TestBase
|
||||
{
|
||||
protected override void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Add configuration
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
["TelegramBot:BotToken"] = "test_token",
|
||||
["Ollama:Url"] = "http://localhost:11434",
|
||||
["Ollama:DefaultModel"] = "llama2",
|
||||
["AI:MaxTokens"] = "1000",
|
||||
["AI:Temperature"] = "0.7",
|
||||
["Database:ConnectionString"] =
|
||||
"Host=localhost;Port=5432;Database=test_chatbot;Username=postgres;Password=postgres",
|
||||
["Database:CommandTimeout"] = "30",
|
||||
["Database:EnableSensitiveDataLogging"] = "false",
|
||||
}
|
||||
)
|
||||
.Build();
|
||||
|
||||
services.AddSingleton<IConfiguration>(configuration);
|
||||
services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHaveRequiredUsingStatements()
|
||||
{
|
||||
// This test verifies that Program.cs has the necessary using statements
|
||||
// by checking if the types can be resolved
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Verify that key services can be resolved
|
||||
Assert.NotNull(serviceProvider.GetService<IConfiguration>());
|
||||
Assert.NotNull(serviceProvider.GetService<ILoggerFactory>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldConfigureTelegramBotSettings()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Assert
|
||||
var botToken = serviceProvider.GetRequiredService<IConfiguration>()["TelegramBot:BotToken"];
|
||||
Assert.Equal("test_token", botToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldConfigureOllamaSettings()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert
|
||||
var ollamaUrl = configuration["Ollama:Url"];
|
||||
var defaultModel = configuration["Ollama:DefaultModel"];
|
||||
Assert.Equal("http://localhost:11434", ollamaUrl);
|
||||
Assert.Equal("llama2", defaultModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldConfigureAISettings()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert
|
||||
var maxTokens = configuration["AI:MaxTokens"];
|
||||
var temperature = configuration["AI:Temperature"];
|
||||
Assert.Equal("1000", maxTokens);
|
||||
Assert.Equal("0.7", temperature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldConfigureDatabaseSettings()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert
|
||||
var connectionString = configuration["Database:ConnectionString"];
|
||||
var commandTimeout = configuration["Database:CommandTimeout"];
|
||||
|
||||
Assert.Equal(
|
||||
"Host=localhost;Port=5432;Database=test_chatbot;Username=postgres;Password=postgres",
|
||||
connectionString
|
||||
);
|
||||
Assert.Equal("30", commandTimeout);
|
||||
Assert.Equal("false", configuration["Database:EnableSensitiveDataLogging"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHandleEnvironmentVariables()
|
||||
{
|
||||
// Arrange
|
||||
Environment.SetEnvironmentVariable("TELEGRAM_BOT_TOKEN", "env_token");
|
||||
Environment.SetEnvironmentVariable("OLLAMA_URL", "http://env:11434");
|
||||
Environment.SetEnvironmentVariable("OLLAMA_DEFAULT_MODEL", "env_model");
|
||||
Environment.SetEnvironmentVariable("DB_HOST", "env_host");
|
||||
Environment.SetEnvironmentVariable("DB_PORT", "8080");
|
||||
Environment.SetEnvironmentVariable("DB_NAME", "env_db");
|
||||
Environment.SetEnvironmentVariable("DB_USER", "env_user");
|
||||
Environment.SetEnvironmentVariable("DB_PASSWORD", "env_pass");
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("env_token", Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN"));
|
||||
Assert.Equal("http://env:11434", Environment.GetEnvironmentVariable("OLLAMA_URL"));
|
||||
Assert.Equal("env_model", Environment.GetEnvironmentVariable("OLLAMA_DEFAULT_MODEL"));
|
||||
Assert.Equal("env_host", Environment.GetEnvironmentVariable("DB_HOST"));
|
||||
Assert.Equal("8080", Environment.GetEnvironmentVariable("DB_PORT"));
|
||||
Assert.Equal("env_db", Environment.GetEnvironmentVariable("DB_NAME"));
|
||||
Assert.Equal("env_user", Environment.GetEnvironmentVariable("DB_USER"));
|
||||
Assert.Equal("env_pass", Environment.GetEnvironmentVariable("DB_PASSWORD"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
Environment.SetEnvironmentVariable("TELEGRAM_BOT_TOKEN", null);
|
||||
Environment.SetEnvironmentVariable("OLLAMA_URL", null);
|
||||
Environment.SetEnvironmentVariable("OLLAMA_DEFAULT_MODEL", null);
|
||||
Environment.SetEnvironmentVariable("DB_HOST", null);
|
||||
Environment.SetEnvironmentVariable("DB_PORT", null);
|
||||
Environment.SetEnvironmentVariable("DB_NAME", null);
|
||||
Environment.SetEnvironmentVariable("DB_USER", null);
|
||||
Environment.SetEnvironmentVariable("DB_PASSWORD", null);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHaveValidConfigurationStructure()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert - Check that all required configuration sections exist
|
||||
Assert.NotNull(configuration.GetSection("TelegramBot"));
|
||||
Assert.NotNull(configuration.GetSection("Ollama"));
|
||||
Assert.NotNull(configuration.GetSection("AI"));
|
||||
Assert.NotNull(configuration.GetSection("Database"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHaveRequiredConfigurationValues()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert - Check that all required configuration values are present
|
||||
Assert.NotNull(configuration["TelegramBot:BotToken"]);
|
||||
Assert.NotNull(configuration["Ollama:Url"]);
|
||||
Assert.NotNull(configuration["Ollama:DefaultModel"]);
|
||||
Assert.NotNull(configuration["AI:MaxTokens"]);
|
||||
Assert.NotNull(configuration["AI:Temperature"]);
|
||||
Assert.NotNull(configuration["Database:ConnectionString"]);
|
||||
Assert.NotNull(configuration["Database:CommandTimeout"]);
|
||||
Assert.NotNull(configuration["Database:EnableSensitiveDataLogging"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHaveValidNumericConfigurationValues()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert - Check that numeric values can be parsed
|
||||
var maxTokensValue = configuration["AI:MaxTokens"];
|
||||
Assert.NotNull(maxTokensValue);
|
||||
Assert.True(int.TryParse(maxTokensValue, out var maxTokens));
|
||||
Assert.True(maxTokens > 0);
|
||||
|
||||
var temperatureValue = configuration["AI:Temperature"];
|
||||
Assert.NotNull(temperatureValue);
|
||||
Assert.True(
|
||||
double.TryParse(
|
||||
temperatureValue,
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var temperature
|
||||
)
|
||||
);
|
||||
Assert.True(temperature >= 0 && temperature <= 2);
|
||||
|
||||
Assert.True(int.TryParse(configuration["Database:CommandTimeout"], out var commandTimeout));
|
||||
Assert.True(commandTimeout > 0);
|
||||
|
||||
Assert.True(bool.TryParse(configuration["Database:EnableSensitiveDataLogging"], out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHaveValidUrlConfigurationValues()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert - Check that URL values are valid
|
||||
var ollamaUrl = configuration["Ollama:Url"];
|
||||
Assert.NotNull(ollamaUrl);
|
||||
Assert.True(Uri.TryCreate(ollamaUrl, UriKind.Absolute, out var uri));
|
||||
Assert.True(uri.Scheme == "http" || uri.Scheme == "https");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Program_ShouldHaveValidDatabaseConnectionString()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
|
||||
// Act
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
// Assert - Check that connection string has required components
|
||||
var connectionString = configuration["Database:ConnectionString"];
|
||||
Assert.NotNull(connectionString);
|
||||
Assert.Contains("Host=", connectionString);
|
||||
Assert.Contains("Port=", connectionString);
|
||||
Assert.Contains("Database=", connectionString);
|
||||
Assert.Contains("Username=", connectionString);
|
||||
Assert.Contains("Password=", connectionString);
|
||||
}
|
||||
}
|
||||
47
ChatBot.Tests/Services/DatabaseInitializationServiceTests.cs
Normal file
47
ChatBot.Tests/Services/DatabaseInitializationServiceTests.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using ChatBot.Data;
|
||||
using ChatBot.Services;
|
||||
using ChatBot.Tests.TestUtilities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace ChatBot.Tests.Services;
|
||||
|
||||
public class DatabaseInitializationServiceTests : UnitTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void DatabaseInitializationService_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
|
||||
// Act
|
||||
var service = new DatabaseInitializationService(
|
||||
serviceProviderMock.Object,
|
||||
loggerMock.Object
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DatabaseInitializationService_StopAsync_ShouldComplete()
|
||||
{
|
||||
// Arrange
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
var service = new DatabaseInitializationService(
|
||||
serviceProviderMock.Object,
|
||||
loggerMock.Object
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// If we reach here, the method completed successfully
|
||||
Assert.True(true);
|
||||
}
|
||||
}
|
||||
289
ChatBot.Tests/Services/OllamaClientAdapterTests.cs
Normal file
289
ChatBot.Tests/Services/OllamaClientAdapterTests.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
using ChatBot.Services;
|
||||
using ChatBot.Services.Interfaces;
|
||||
using OllamaSharp.Models;
|
||||
using OllamaSharp.Models.Chat;
|
||||
|
||||
namespace ChatBot.Tests.Services;
|
||||
|
||||
public class OllamaClientAdapterTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithValidUrl_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullUrl_ShouldThrowArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
string? url = null;
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentException>(() => new OllamaClientAdapter(url!));
|
||||
Assert.Equal("URL cannot be empty (Parameter 'url')", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyUrl_ShouldThrowArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var url = "";
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentException>(() => new OllamaClientAdapter(url));
|
||||
Assert.Equal("URL cannot be empty (Parameter 'url')", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithWhitespaceUrl_ShouldThrowArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var url = " ";
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentException>(() => new OllamaClientAdapter(url));
|
||||
Assert.Equal("URL cannot be empty (Parameter 'url')", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithHttpsUrl_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "https://ollama.example.com";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithCustomPort_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:8080";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedModel_GetAndSet_ShouldWork()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434";
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
var modelName = "llama2";
|
||||
|
||||
// Act
|
||||
adapter.SelectedModel = modelName;
|
||||
var result = adapter.SelectedModel;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(modelName, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedModel_SetMultipleTimes_ShouldUpdateValue()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434";
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Act
|
||||
adapter.SelectedModel = "llama2";
|
||||
adapter.SelectedModel = "codellama";
|
||||
var result = adapter.SelectedModel;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("codellama", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChatAsync_ShouldReturnAsyncEnumerable()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434";
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
var request = new ChatRequest
|
||||
{
|
||||
Model = "llama2",
|
||||
Messages = new List<Message>
|
||||
{
|
||||
new() { Role = "user", Content = "Hello" },
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = adapter.ChatAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsAssignableFrom<IAsyncEnumerable<ChatResponseStream?>>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListLocalModelsAsync_ShouldReturnTask()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434";
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Act
|
||||
var result = adapter.ListLocalModelsAsync();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsAssignableFrom<Task<IEnumerable<Model>>>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithSpecialCharactersInUrl_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://user:pass@localhost:11434/path?query=value#fragment";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithIpAddress_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://192.168.1.100:11434";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithLocalhostVariations_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var urls = new[]
|
||||
{
|
||||
"http://localhost:11434",
|
||||
"http://127.0.0.1:11434",
|
||||
"http://0.0.0.0:11434",
|
||||
};
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithDifferentProtocols_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var urls = new[] { "http://localhost:11434", "https://localhost:11434" };
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithTrailingSlash_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434/";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithPath_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434/api/v1";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithUnicodeUrl_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://тест.рф:11434";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithVeryLongUrl_ShouldThrowException()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://" + new string('a', 1000) + ".example.com:11434";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<UriFormatException>(() => new OllamaClientAdapter(url));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithUrlContainingSpaces_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434/path with spaces";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithUrlContainingSpecialCharacters_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://localhost:11434/path!@#$%^&*()";
|
||||
|
||||
// Act
|
||||
var adapter = new OllamaClientAdapter(url);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(adapter);
|
||||
}
|
||||
}
|
||||
121
ChatBot.Tests/Services/Telegram/TelegramServicesTests.cs
Normal file
121
ChatBot.Tests/Services/Telegram/TelegramServicesTests.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using ChatBot.Services.Telegram.Services;
|
||||
using ChatBot.Tests.TestUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace ChatBot.Tests.Services.Telegram;
|
||||
|
||||
public class TelegramServicesTests : UnitTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void TelegramBotService_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var loggerMock = new Mock<ILogger<TelegramBotService>>();
|
||||
var botClientMock = new Mock<ITelegramBotClient>();
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
|
||||
// Act
|
||||
var service = new TelegramBotService(
|
||||
loggerMock.Object,
|
||||
botClientMock.Object,
|
||||
serviceProviderMock.Object
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TelegramMessageHandler_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var loggerMock = new Mock<ILogger<TelegramMessageHandler>>();
|
||||
var commandProcessorMock =
|
||||
new Mock<ChatBot.Services.Telegram.Interfaces.ITelegramCommandProcessor>();
|
||||
var messageSenderMock =
|
||||
new Mock<ChatBot.Services.Telegram.Interfaces.ITelegramMessageSender>();
|
||||
|
||||
// Act
|
||||
var handler = new TelegramMessageHandler(
|
||||
loggerMock.Object,
|
||||
commandProcessorMock.Object,
|
||||
messageSenderMock.Object
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(handler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TelegramErrorHandler_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var loggerMock = new Mock<ILogger<TelegramErrorHandler>>();
|
||||
|
||||
// Act
|
||||
var handler = new TelegramErrorHandler(loggerMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(handler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TelegramMessageSender_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var loggerMock = new Mock<ILogger<TelegramMessageSender>>();
|
||||
|
||||
// Act
|
||||
var sender = new TelegramMessageSender(loggerMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(sender);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BotInfoService_ShouldCreateInstance()
|
||||
{
|
||||
// Arrange
|
||||
var botClientMock = new Mock<ITelegramBotClient>();
|
||||
var loggerMock = new Mock<ILogger<BotInfoService>>();
|
||||
|
||||
// Act
|
||||
var service = new BotInfoService(botClientMock.Object, loggerMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BotInfoService_IsCacheValid_ShouldReturnFalseInitially()
|
||||
{
|
||||
// Arrange
|
||||
var botClientMock = new Mock<ITelegramBotClient>();
|
||||
var loggerMock = new Mock<ILogger<BotInfoService>>();
|
||||
var service = new BotInfoService(botClientMock.Object, loggerMock.Object);
|
||||
|
||||
// Act
|
||||
var isValid = service.IsCacheValid();
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BotInfoService_InvalidateCache_ShouldWork()
|
||||
{
|
||||
// Arrange
|
||||
var botClientMock = new Mock<ITelegramBotClient>();
|
||||
var loggerMock = new Mock<ILogger<BotInfoService>>();
|
||||
var service = new BotInfoService(botClientMock.Object, loggerMock.Object);
|
||||
|
||||
// Act
|
||||
service.InvalidateCache();
|
||||
|
||||
// Assert
|
||||
Assert.False(service.IsCacheValid());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user