using ChatBot.Data; using ChatBot.Data.Interfaces; using ChatBot.Data.Repositories; using ChatBot.Models.Configuration; using ChatBot.Models.Configuration.Validators; using ChatBot.Services; using ChatBot.Services.Interfaces; using ChatBot.Services.Telegram.Commands; using ChatBot.Services.Telegram.Interfaces; using ChatBot.Services.Telegram.Services; using ChatBot.Tests.TestUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Telegram.Bot; namespace ChatBot.Tests.Integration; public class ProgramIntegrationTests : TestBase { protected override void ConfigureServices(IServiceCollection services) { // Add configuration var configuration = new ConfigurationBuilder() .AddInMemoryCollection( new Dictionary { ["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(configuration); services.AddLogging(builder => builder.AddDebug()); services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>)); services.AddLogging(builder => builder.AddDebug()); services.AddLogging(builder => builder.AddDebug()); services.AddLogging(builder => builder.AddDebug()); services.AddLogging(builder => builder.AddDebug()); services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); } [Fact] public void Program_SuccessfulConfigurationPipeline_ShouldBuildAndValidate() { // Arrange var services = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection( new Dictionary { ["TelegramBot:BotToken"] = "1234567890123456789012345678901234567890", ["Ollama:Url"] = "http://localhost:11434", ["Ollama:DefaultModel"] = "llama3", ["AI:CompressionThreshold"] = "100", ["Database:ConnectionString"] = "Host=localhost;Port=5432;Database=test;Username=test;Password=test", ["Database:CommandTimeout"] = "30", ["Database:EnableSensitiveDataLogging"] = "false", } ) .Build(); services.AddSingleton(configuration); services.AddLogging(builder => builder.AddDebug()); // Configure options and validators similar to Program.cs services.Configure(configuration.GetSection("TelegramBot")); services.AddSingleton< IValidateOptions, TelegramBotSettingsValidator >(); services.Configure(configuration.GetSection("Ollama")); services.AddSingleton, OllamaSettingsValidator>(); services.Configure(configuration.GetSection("AI")); services.AddSingleton, AISettingsValidator>(); services.Configure(configuration.GetSection("Database")); services.AddSingleton, DatabaseSettingsValidator>(); // Register health checks similar to Program.cs services .AddHealthChecks() .AddCheck("ollama") .AddCheck("telegram"); var serviceProvider = services.BuildServiceProvider(); // Act & Assert - validate options using validators var telegramValidator = serviceProvider.GetRequiredService< IValidateOptions >(); var ollamaValidator = serviceProvider.GetRequiredService< IValidateOptions >(); var aiValidator = serviceProvider.GetRequiredService>(); var dbValidator = serviceProvider.GetRequiredService>(); var telegram = serviceProvider.GetRequiredService>().Value; var ollama = serviceProvider.GetRequiredService>().Value; var ai = serviceProvider.GetRequiredService>().Value; var db = serviceProvider.GetRequiredService>().Value; Assert.True(telegramValidator.Validate("TelegramBot", telegram).Succeeded); Assert.True(ollamaValidator.Validate("Ollama", ollama).Succeeded); Assert.True(aiValidator.Validate("AI", ai).Succeeded); Assert.True(dbValidator.Validate("Database", db).Succeeded); // Assert health checks are registered var hcOptions = serviceProvider .GetRequiredService>() .Value; var registrations = hcOptions.Registrations.Select(r => r.Name).ToList(); Assert.Contains("ollama", registrations); Assert.Contains("telegram", registrations); } [Fact] public void ServiceRegistrations_ShouldIncludeCoreServicesAndHealthCheckTags() { // Arrange var services = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection( new Dictionary { ["TelegramBot:BotToken"] = "1234567890123456789012345678901234567890", ["Ollama:Url"] = "http://localhost:11434", ["Ollama:DefaultModel"] = "llama3", ["AI:CompressionThreshold"] = "100", ["Database:ConnectionString"] = "Host=localhost;Port=5432;Database=test;Username=test;Password=test", ["Database:CommandTimeout"] = "30", ["Database:EnableSensitiveDataLogging"] = "false", } ) .Build(); services.AddSingleton(configuration); // Options + validators services.Configure(configuration.GetSection("TelegramBot")); services.AddSingleton< IValidateOptions, TelegramBotSettingsValidator >(); services.Configure(configuration.GetSection("Ollama")); services.AddSingleton, OllamaSettingsValidator>(); services.Configure(configuration.GetSection("AI")); services.AddSingleton, AISettingsValidator>(); services.Configure(configuration.GetSection("Database")); services.AddSingleton, DatabaseSettingsValidator>(); // DbContext (in-memory for test) services.AddDbContext(options => options.UseInMemoryDatabase("test-db")); // Repository and session storage services.AddSingleton>(_ => NullLogger.Instance ); services.AddScoped(); services.AddSingleton>(_ => NullLogger.Instance ); services.AddScoped(); // Core services services.AddSingleton>(_ => NullLogger.Instance); services.AddSingleton(); services.AddSingleton>(_ => NullLogger.Instance ); services.AddSingleton(); services.AddSingleton(); services.AddSingleton>(_ => NullLogger.Instance ); services.AddSingleton>(_ => NullLogger.Instance); services.AddSingleton(); services.AddSingleton>(_ => NullLogger.Instance); services.AddScoped(); // IOllamaClient services.AddSingleton(sp => { var opts = sp.GetRequiredService>(); return new OllamaClientAdapter(opts.Value.Url); }); // Telegram services and commands services.AddSingleton(_ => new Mock().Object); services.AddSingleton>(_ => NullLogger.Instance ); services.AddSingleton(_ => new Mock().Object ); services.AddSingleton(); services.AddSingleton>(_ => NullLogger.Instance ); services.AddSingleton(); services.AddSingleton>(_ => NullLogger.Instance); services.AddScoped(); services.AddSingleton>(_ => NullLogger.Instance); services.AddSingleton(); services.AddSingleton>(_ => NullLogger.Instance ); services.AddScoped(); services.AddSingleton>(_ => NullLogger.Instance ); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Hosted service simulation for Telegram bot service services.AddSingleton>(_ => NullLogger.Instance ); services.AddSingleton(); services.AddSingleton(sp => (IHostedService)sp.GetRequiredService() ); // Simulate HealthCheck registrations via options to avoid bringing in DefaultHealthCheckService var hcOptions = new HealthCheckServiceOptions(); hcOptions.Registrations.Add( new HealthCheckRegistration( "ollama", sp => new Mock().Object, null, new[] { "api", "ollama" } ) ); hcOptions.Registrations.Add( new HealthCheckRegistration( "telegram", sp => new Mock().Object, null, new[] { "api", "telegram" } ) ); services.AddSingleton>(Options.Create(hcOptions)); // ILogger for health checks is provided by AddLogging above; no direct registration for internal types var sp = services.BuildServiceProvider(); // Assert: core services can be resolved Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); // Assert: Telegram stack resolves Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotNull(sp.GetRequiredService()); Assert.NotEmpty(sp.GetServices()); Assert.NotNull(sp.GetRequiredService()); // Assert: health checks names and tags var hcResolved = sp.GetRequiredService>().Value; var regDict = hcResolved.Registrations.ToDictionary(r => r.Name, r => r.Tags); Assert.True(regDict.ContainsKey("ollama")); Assert.True(regDict.ContainsKey("telegram")); Assert.Contains("api", regDict["ollama"]); Assert.Contains("ollama", regDict["ollama"]); Assert.Contains("api", regDict["telegram"]); Assert.Contains("telegram", regDict["telegram"]); } [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()); Assert.NotNull(serviceProvider.GetService()); } [Fact] public void Program_ShouldConfigureTelegramBotSettings() { // Arrange var services = new ServiceCollection(); ConfigureServices(services); // Act var serviceProvider = services.BuildServiceProvider(); // Assert var botToken = serviceProvider.GetRequiredService()["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(); // 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(); // 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(); // 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(); // 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(); // 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(); // 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(); // 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(); // 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); } }