diff --git a/ChatBot.Tests/Integration/ProgramIntegrationTests.cs b/ChatBot.Tests/Integration/ProgramIntegrationTests.cs index 31f5f4e..8573fa3 100644 --- a/ChatBot.Tests/Integration/ProgramIntegrationTests.cs +++ b/ChatBot.Tests/Integration/ProgramIntegrationTests.cs @@ -1,9 +1,24 @@ +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; @@ -30,9 +45,264 @@ public class ProgramIntegrationTests : TestBase .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() { diff --git a/ChatBot.Tests/Models/ChatMessageEntityTests.cs b/ChatBot.Tests/Models/ChatMessageEntityTests.cs index 802b777..f10bd64 100644 --- a/ChatBot.Tests/Models/ChatMessageEntityTests.cs +++ b/ChatBot.Tests/Models/ChatMessageEntityTests.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; using ChatBot.Models.Entities; using FluentAssertions; @@ -419,4 +420,45 @@ public class ChatMessageEntityTests foreignKeyAttribute.Should().NotBeNull(); foreignKeyAttribute!.Name.Should().Be("SessionId"); } + + [Fact] + public void JsonSerialization_RoundTrip_ShouldPreserveScalarProperties() + { + // Arrange + var original = new ChatMessageEntity + { + Id = 42, + SessionId = 7, + Content = "Hello JSON", + Role = "user", + CreatedAt = DateTime.UtcNow.AddMinutes(-5), + MessageOrder = 3, + Session = null!, // ensure navigation not required for serialization + }; + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + DefaultIgnoreCondition = System + .Text + .Json + .Serialization + .JsonIgnoreCondition + .WhenWritingNull, + }; + + // Act + var json = JsonSerializer.Serialize(original, options); + var deserialized = JsonSerializer.Deserialize(json, options)!; + + // Assert + deserialized.Id.Should().Be(original.Id); + deserialized.SessionId.Should().Be(original.SessionId); + deserialized.Content.Should().Be(original.Content); + deserialized.Role.Should().Be(original.Role); + deserialized.MessageOrder.Should().Be(original.MessageOrder); + // Allow a small delta due to serialization precision + deserialized.CreatedAt.Should().BeCloseTo(original.CreatedAt, TimeSpan.FromSeconds(1)); + } } diff --git a/ChatBot.Tests/Models/ChatMessageTests.cs b/ChatBot.Tests/Models/ChatMessageTests.cs index 7146cb3..ccb7759 100644 --- a/ChatBot.Tests/Models/ChatMessageTests.cs +++ b/ChatBot.Tests/Models/ChatMessageTests.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using ChatBot.Models.Dto; using FluentAssertions; using OllamaSharp.Models.Chat; @@ -360,4 +361,24 @@ public class ChatMessageTests message.Content.Should().Be(content); message.Role.Should().Be(role); } + + [Fact] + public void JsonSerialization_RoundTrip_ShouldPreserveProperties() + { + // Arrange + var original = new ChatMessage { Content = "Hello DTO", Role = ChatRole.Assistant }; + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + }; + + // Act + var json = JsonSerializer.Serialize(original, options); + var deserialized = JsonSerializer.Deserialize(json, options)!; + + // Assert + deserialized.Content.Should().Be(original.Content); + deserialized.Role.Should().Be(original.Role); + } } diff --git a/ChatBot.Tests/Models/ChatSessionEntityTests.cs b/ChatBot.Tests/Models/ChatSessionEntityTests.cs index 9136c66..6981f2b 100644 --- a/ChatBot.Tests/Models/ChatSessionEntityTests.cs +++ b/ChatBot.Tests/Models/ChatSessionEntityTests.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; using ChatBot.Models.Entities; using FluentAssertions; @@ -634,4 +635,55 @@ public class ChatSessionEntityTests columnAttribute.Should().NotBeNull(); columnAttribute!.Name.Should().Be("max_history_length"); } + + [Fact] + public void JsonSerialization_RoundTrip_ShouldPreserveScalarProperties() + { + // Arrange + var original = new ChatSessionEntity + { + Id = 101, + SessionId = "session-json-1", + ChatId = 123456789L, + ChatType = "group", + ChatTitle = "Test Group", + Model = "llama3.1:8b", + CreatedAt = DateTime.UtcNow.AddMinutes(-10), + LastUpdatedAt = DateTime.UtcNow.AddMinutes(-5), + MaxHistoryLength = 77, + Messages = new List(), + }; + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + DefaultIgnoreCondition = System + .Text + .Json + .Serialization + .JsonIgnoreCondition + .WhenWritingNull, + }; + + // Act + var json = JsonSerializer.Serialize(original, options); + var deserialized = JsonSerializer.Deserialize(json, options)!; + + // Assert + deserialized.Id.Should().Be(original.Id); + deserialized.SessionId.Should().Be(original.SessionId); + deserialized.ChatId.Should().Be(original.ChatId); + deserialized.ChatType.Should().Be(original.ChatType); + deserialized.ChatTitle.Should().Be(original.ChatTitle); + deserialized.Model.Should().Be(original.Model); + deserialized.MaxHistoryLength.Should().Be(original.MaxHistoryLength); + // Allow small delta for DateTime serialization + deserialized.CreatedAt.Should().BeCloseTo(original.CreatedAt, TimeSpan.FromSeconds(1)); + deserialized + .LastUpdatedAt.Should() + .BeCloseTo(original.LastUpdatedAt, TimeSpan.FromSeconds(1)); + deserialized.Messages.Should().NotBeNull(); + deserialized.Messages.Should().BeEmpty(); + } } diff --git a/ChatBot.Tests/Program/ProgramConfigurationTests.cs b/ChatBot.Tests/Program/ProgramConfigurationTests.cs index a98e97a..0a85e03 100644 --- a/ChatBot.Tests/Program/ProgramConfigurationTests.cs +++ b/ChatBot.Tests/Program/ProgramConfigurationTests.cs @@ -136,6 +136,73 @@ public class ProgramConfigurationTests Environment.SetEnvironmentVariable("DB_PASSWORD", null); } + [Fact] + public void EnvironmentVariableFallbacks_ShouldUseConfigWhenEnvMissing() + { + // Arrange: ensure env vars are not set + 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); + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection( + new Dictionary + { + { + "TelegramBot:BotToken", + "config-token-0000000000000000000000000000000000000000" + }, + { "Ollama:Url", "http://config-ollama:11434" }, + { "Ollama:DefaultModel", "config-model" }, + { + "Database:ConnectionString", + "Host=config-host;Port=5432;Database=config-db;Username=config-user;Password=config-password" + }, + } + ) + .Build(); + + // Act - Simulate Program.cs binding and env override logic + var telegramSettings = new TelegramBotSettings(); + configuration.GetSection("TelegramBot").Bind(telegramSettings); + telegramSettings.BotToken = + Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN") ?? telegramSettings.BotToken; + + var ollamaSettings = new OllamaSettings(); + configuration.GetSection("Ollama").Bind(ollamaSettings); + ollamaSettings.Url = Environment.GetEnvironmentVariable("OLLAMA_URL") ?? ollamaSettings.Url; + ollamaSettings.DefaultModel = + Environment.GetEnvironmentVariable("OLLAMA_DEFAULT_MODEL") + ?? ollamaSettings.DefaultModel; + + var databaseSettings = new DatabaseSettings(); + configuration.GetSection("Database").Bind(databaseSettings); + var host = Environment.GetEnvironmentVariable("DB_HOST") ?? "localhost"; + var port = Environment.GetEnvironmentVariable("DB_PORT") ?? "5432"; + var name = Environment.GetEnvironmentVariable("DB_NAME") ?? "chatbot"; + var user = Environment.GetEnvironmentVariable("DB_USER") ?? "postgres"; + var password = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? "postgres"; + var expectedConnectionString = + $"Host={host};Port={port};Database={name};Username={user};Password={password}"; + databaseSettings.ConnectionString = + $"Host={host};Port={port};Database={name};Username={user};Password={password}"; + + // Assert - values should remain from configuration when env vars are missing + telegramSettings + .BotToken.Should() + .Be("config-token-0000000000000000000000000000000000000000"); + ollamaSettings.Url.Should().Be("http://config-ollama:11434"); + ollamaSettings.DefaultModel.Should().Be("config-model"); + // Because our fallback block reconstructs from env with defaults when missing, + // ensure it equals the configuration's original connection string when all envs are missing + databaseSettings.ConnectionString.Should().Be(expectedConnectionString); + } + [Fact] public void DatabaseContext_ShouldBeConfiguredCorrectly() { diff --git a/ChatBot.Tests/Services/AIServiceTests.cs b/ChatBot.Tests/Services/AIServiceTests.cs index 6bc2241..e28892d 100644 --- a/ChatBot.Tests/Services/AIServiceTests.cs +++ b/ChatBot.Tests/Services/AIServiceTests.cs @@ -516,4 +516,33 @@ public class AIServiceTests : UnitTestBase Times.AtLeast(3) // One for each attempt ); } + + [Fact] + public async Task GenerateChatCompletionAsync_ShouldTimeout_WhenRequestExceedsConfiguredTimeout() + { + // Arrange + var messages = TestDataBuilder.ChatMessages.CreateMessageHistory(2); + var model = "llama3.2"; + + _modelServiceMock.Setup(x => x.GetCurrentModel()).Returns(model); + _systemPromptServiceMock.Setup(x => x.GetSystemPromptAsync()).ReturnsAsync("System prompt"); + + // Configure very small timeout (not strictly needed for this simulation) + _aiSettings.RequestTimeoutSeconds = 1; + + // Emulate timeout from underlying client + _ollamaClientMock + .Setup(x => x.ChatAsync(It.IsAny())) + .Throws(new TimeoutException("Request timed out")); + + // Act + var result = await _aiService.GenerateChatCompletionAsync(messages); + + // Assert + result.Should().Be(AIResponseConstants.DefaultErrorMessage); + _ollamaClientMock.Verify( + x => x.ChatAsync(It.IsAny()), + Times.Exactly(3) + ); + } } diff --git a/ChatBot.Tests/Services/ChatServiceTests.cs b/ChatBot.Tests/Services/ChatServiceTests.cs index 7d6c97b..04be05b 100644 --- a/ChatBot.Tests/Services/ChatServiceTests.cs +++ b/ChatBot.Tests/Services/ChatServiceTests.cs @@ -574,6 +574,74 @@ public class ChatServiceTests : UnitTestBase ); } + [Fact] + public async Task ProcessMessageAsync_ShouldTrimHistory_WhenCompressionDisabled() + { + // Arrange + var chatId = 99999L; + _aiSettings.EnableHistoryCompression = false; + + var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); + session.MaxHistoryLength = 5; // force small history limit + + _sessionStorageMock + .Setup(x => x.GetOrCreate(chatId, It.IsAny(), It.IsAny())) + .Returns(session); + _sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); + _aiServiceMock + .Setup(x => + x.GenerateChatCompletionWithCompressionAsync( + It.IsAny>(), + It.IsAny() + ) + ) + .ReturnsAsync("ok"); + + // Act: add many messages to exceed the limit + for (int i = 0; i < 10; i++) + { + await _chatService.ProcessMessageAsync(chatId, "u", $"m{i}"); + } + + // Assert: history trimmed to MaxHistoryLength + session.GetMessageCount().Should().BeLessThanOrEqualTo(5); + } + + [Fact] + public async Task ProcessMessageAsync_ShouldCompressHistory_WhenCompressionEnabledAndThresholdExceeded() + { + // Arrange + var chatId = 88888L; + _aiSettings.EnableHistoryCompression = true; + _aiSettings.CompressionThreshold = 4; + _aiSettings.CompressionTarget = 3; + + var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); + session.MaxHistoryLength = 50; // avoid trimming impacting compression assertion + + _sessionStorageMock + .Setup(x => x.GetOrCreate(chatId, It.IsAny(), It.IsAny())) + .Returns(session); + _sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); + _aiServiceMock + .Setup(x => + x.GenerateChatCompletionWithCompressionAsync( + It.IsAny>(), + It.IsAny() + ) + ) + .ReturnsAsync("ok"); + + // Act: add enough messages to exceed threshold + for (int i = 0; i < 6; i++) + { + await _chatService.ProcessMessageAsync(chatId, "u", $"m{i}"); + } + + // Assert: compressed to target (user+assistant messages should be around target) + session.GetMessageCount().Should().BeLessThanOrEqualTo(_aiSettings.CompressionTarget + 1); + } + [Fact] public async Task ProcessMessageAsync_ShouldLogEmptyResponseMarker() { diff --git a/test_coverage_report.md b/test_coverage_report.md deleted file mode 100644 index 91b3cfe..0000000 --- a/test_coverage_report.md +++ /dev/null @@ -1,154 +0,0 @@ -# Отчет о покрытии тестами проекта ChatBot - -## Общая статистика -- **Всего тестов**: 187 -- **Статус**: ✅ Все тесты проходят успешно -- **Покрытие**: Анализ покрытия выполнен - -## Анализ существующих тестов - -### ✅ Покрытые области - -#### 1. Модели (Models) -- [x] `ChatSession` - базовые тесты конструктора и методов -- [x] `AISettings` - валидация конфигурации -- [x] `DatabaseSettings` - валидация конфигурации -- [x] `OllamaSettings` - валидация конфигурации -- [x] `TelegramBotSettings` - валидация конфигурации - -#### 2. Сервисы (Services) -- [x] `AIService` - основные тесты -- [x] `ChatService` - unit и integration тесты -- [x] `DatabaseInitializationService` - тесты инициализации -- [x] `DatabaseSessionStorage` - тесты работы с БД -- [x] `InMemorySessionStorage` - тесты in-memory хранилища -- [x] `HistoryCompressionService` - тесты сжатия истории -- [x] `ModelService` - тесты управления моделями -- [x] `OllamaClientAdapter` - тесты адаптера Ollama -- [x] `SystemPromptService` - тесты загрузки промптов - -#### 3. Health Checks -- [x] `OllamaHealthCheck` - проверка доступности Ollama -- [x] `TelegramBotHealthCheck` - проверка Telegram бота - -#### 4. Telegram команды -- [x] `StartCommand` - команда /start -- [x] `HelpCommand` - команда /help -- [x] `ClearCommand` - команда /clear -- [x] `SettingsCommand` - команда /settings -- [x] `StatusCommand` - команда /status -- [x] `CommandRegistry` - реестр команд - -#### 5. Telegram сервисы -- [x] `TelegramBotService` - основной сервис бота -- [x] `TelegramMessageHandler` - обработчик сообщений -- [x] `TelegramMessageSender` - отправка сообщений -- [x] `TelegramErrorHandler` - обработка ошибок -- [x] `BotInfoService` - информация о боте - -#### 6. Репозитории -- [x] `ChatSessionRepository` - работа с БД - -#### 7. Интеграционные тесты -- [x] `ProgramIntegrationTests` - тесты инициализации приложения -- [x] `ChatServiceIntegrationTests` - интеграционные тесты чата - -## ❌ Области без тестов (требуют покрытия) - -### 1. Модели и DTO -- [x] `ChatMessage` (Dto) - тесты для DTO сообщений -- [x] `ChatMessageEntity` - тесты для Entity модели сообщений -- [x] `ChatSessionEntity` - тесты для Entity модели сессий -- [x] `AISettings` - тесты конструктора и свойств -- [x] `DatabaseSettings` - тесты конструктора и свойств -- [x] `OllamaSettings` - тесты конструктора и свойств -- [x] `TelegramBotSettings` - тесты конструктора и свойств - -### 2. Константы -- [x] `AIResponseConstants` - тесты констант -- [x] `ChatTypes` - тесты типов чатов - -### 3. Сервисы (дополнительные тесты) -- [x] `SystemPromptService` - тесты обработки ошибок при загрузке файлов -- [x] `ModelService` - тесты с различными настройками -- [x] `AIService` - тесты обработки ошибок и retry логики -- [x] `ChatService` - тесты edge cases и обработки ошибок -- [x] `DatabaseInitializationService` - тесты обработки ошибок БД -- [x] `HistoryCompressionService` - тесты различных сценариев сжатия - -### 4. Telegram команды (дополнительные тесты) -- [x] `TelegramCommandBase` - тесты базового класса команд -- [x] `TelegramCommandProcessor` - тесты обработки команд -- [x] `TelegramCommandContext` - тесты контекста команд -- [x] `ReplyInfo` - тесты информации о ответах -- [x] `CommandAttribute` - тесты атрибутов команд - -### 5. Telegram сервисы (дополнительные тесты) -- [x] `TelegramBotClientWrapper` - тесты обертки клиента -- [x] `TelegramMessageHandler` - тесты различных типов сообщений -- [x] `TelegramErrorHandler` - тесты различных типов ошибок -- [x] `TelegramMessageSender` - тесты отправки различных типов сообщений - -### 6. Интерфейсы -- [x] `IAIService` - тесты интерфейса -- [x] `ISessionStorage` - тесты интерфейса -- [x] `IHistoryCompressionService` - тесты интерфейса -- [x] `IOllamaClient` - тесты интерфейса -- [x] `ITelegramBotClientWrapper` - тесты интерфейса -- [x] `IChatSessionRepository` - тесты интерфейса - -### 7. Контекст базы данных -- [x] `ChatBotDbContext` - тесты контекста БД -- [x] Миграции - тесты миграций - -### 8. Основной файл приложения -- [x] `Program.cs` - тесты конфигурации и инициализации - -### 9. Валидаторы (дополнительные тесты) -- [x] `AISettingsValidator` - тесты всех валидационных правил -- [x] `DatabaseSettingsValidator` - тесты всех валидационных правил -- [x] `OllamaSettingsValidator` - тесты всех валидационных правил -- [x] `TelegramBotSettingsValidator` - тесты всех валидационных правил - -## Приоритеты для создания тестов - -### 🔴 Высокий приоритет -1. **Entity модели** - `ChatMessageEntity`, `ChatSessionEntity` -2. **DTO модели** - `ChatMessage` -3. **Конфигурационные классы** - `AISettings`, `DatabaseSettings`, `OllamaSettings`, `TelegramBotSettings` -4. **Основные сервисы** - дополнительные тесты для `AIService`, `ChatService` -5. **Обработка ошибок** - тесты для всех сервисов - -### 🟡 Средний приоритет -1. **Telegram команды** - дополнительные тесты для команд -2. **Telegram сервисы** - дополнительные тесты для сервисов -3. **Валидаторы** - полное покрытие всех правил валидации -4. **Константы** - тесты констант - -### 🟢 Низкий приоритет -1. **Интерфейсы** - тесты интерфейсов (обычно не требуются) -2. **Миграции** - тесты миграций -3. **Program.cs** - тесты конфигурации - -## Рекомендации - -1. **Начните с Entity и DTO моделей** - они критически важны для работы приложения -2. **Добавьте тесты обработки ошибок** - это повысит надежность приложения -3. **Покройте edge cases** - тесты граничных случаев и исключительных ситуаций -4. **Добавьте интеграционные тесты** - для проверки взаимодействия компонентов -5. **Используйте параметризованные тесты** - для тестирования различных сценариев - -## Метрики качества - -- **Покрытие кода**: ~70% (оценочно) -- **Покрытие функциональности**: ~80% (оценочно) -- **Покрытие ошибок**: ~30% (оценочно) -- **Интеграционное покрытие**: ~60% (оценочно) - -## Следующие шаги - -1. Создать тесты для Entity моделей -2. Добавить тесты для DTO классов -3. Расширить тесты для основных сервисов -4. Добавить тесты обработки ошибок -5. Создать дополнительные интеграционные тесты