add tests
All checks were successful
SonarQube / Build and analyze (push) Successful in 3m54s
Unit Tests / Run Tests (push) Successful in 2m23s

This commit is contained in:
Leonid Pershin
2025-10-20 15:11:42 +03:00
parent f8fd16edb2
commit 1c910d7b7f
8 changed files with 549 additions and 154 deletions

View File

@@ -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<IConfiguration>(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<string, string?>
{
["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<IConfiguration>(configuration);
services.AddLogging(builder => builder.AddDebug());
// Configure options and validators similar to Program.cs
services.Configure<TelegramBotSettings>(configuration.GetSection("TelegramBot"));
services.AddSingleton<
IValidateOptions<TelegramBotSettings>,
TelegramBotSettingsValidator
>();
services.Configure<OllamaSettings>(configuration.GetSection("Ollama"));
services.AddSingleton<IValidateOptions<OllamaSettings>, OllamaSettingsValidator>();
services.Configure<AISettings>(configuration.GetSection("AI"));
services.AddSingleton<IValidateOptions<AISettings>, AISettingsValidator>();
services.Configure<DatabaseSettings>(configuration.GetSection("Database"));
services.AddSingleton<IValidateOptions<DatabaseSettings>, DatabaseSettingsValidator>();
// Register health checks similar to Program.cs
services
.AddHealthChecks()
.AddCheck<ChatBot.Services.HealthChecks.OllamaHealthCheck>("ollama")
.AddCheck<ChatBot.Services.HealthChecks.TelegramBotHealthCheck>("telegram");
var serviceProvider = services.BuildServiceProvider();
// Act & Assert - validate options using validators
var telegramValidator = serviceProvider.GetRequiredService<
IValidateOptions<TelegramBotSettings>
>();
var ollamaValidator = serviceProvider.GetRequiredService<
IValidateOptions<OllamaSettings>
>();
var aiValidator = serviceProvider.GetRequiredService<IValidateOptions<AISettings>>();
var dbValidator = serviceProvider.GetRequiredService<IValidateOptions<DatabaseSettings>>();
var telegram = serviceProvider.GetRequiredService<IOptions<TelegramBotSettings>>().Value;
var ollama = serviceProvider.GetRequiredService<IOptions<OllamaSettings>>().Value;
var ai = serviceProvider.GetRequiredService<IOptions<AISettings>>().Value;
var db = serviceProvider.GetRequiredService<IOptions<DatabaseSettings>>().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<IOptions<HealthCheckServiceOptions>>()
.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<string, string?>
{
["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<IConfiguration>(configuration);
// Options + validators
services.Configure<TelegramBotSettings>(configuration.GetSection("TelegramBot"));
services.AddSingleton<
IValidateOptions<TelegramBotSettings>,
TelegramBotSettingsValidator
>();
services.Configure<OllamaSettings>(configuration.GetSection("Ollama"));
services.AddSingleton<IValidateOptions<OllamaSettings>, OllamaSettingsValidator>();
services.Configure<AISettings>(configuration.GetSection("AI"));
services.AddSingleton<IValidateOptions<AISettings>, AISettingsValidator>();
services.Configure<DatabaseSettings>(configuration.GetSection("Database"));
services.AddSingleton<IValidateOptions<DatabaseSettings>, DatabaseSettingsValidator>();
// DbContext (in-memory for test)
services.AddDbContext<ChatBotDbContext>(options => options.UseInMemoryDatabase("test-db"));
// Repository and session storage
services.AddSingleton<ILogger<ChatSessionRepository>>(_ =>
NullLogger<ChatSessionRepository>.Instance
);
services.AddScoped<IChatSessionRepository, ChatSessionRepository>();
services.AddSingleton<ILogger<DatabaseSessionStorage>>(_ =>
NullLogger<DatabaseSessionStorage>.Instance
);
services.AddScoped<ISessionStorage, DatabaseSessionStorage>();
// Core services
services.AddSingleton<ILogger<ModelService>>(_ => NullLogger<ModelService>.Instance);
services.AddSingleton<ModelService>();
services.AddSingleton<ILogger<SystemPromptService>>(_ =>
NullLogger<SystemPromptService>.Instance
);
services.AddSingleton<SystemPromptService>();
services.AddSingleton<IHistoryCompressionService, HistoryCompressionService>();
services.AddSingleton<ILogger<HistoryCompressionService>>(_ =>
NullLogger<HistoryCompressionService>.Instance
);
services.AddSingleton<ILogger<AIService>>(_ => NullLogger<AIService>.Instance);
services.AddSingleton<IAIService, AIService>();
services.AddSingleton<ILogger<ChatService>>(_ => NullLogger<ChatService>.Instance);
services.AddScoped<ChatService>();
// IOllamaClient
services.AddSingleton<IOllamaClient>(sp =>
{
var opts = sp.GetRequiredService<IOptions<OllamaSettings>>();
return new OllamaClientAdapter(opts.Value.Url);
});
// Telegram services and commands
services.AddSingleton<ITelegramBotClient>(_ => new Mock<ITelegramBotClient>().Object);
services.AddSingleton<ILogger<TelegramMessageSender>>(_ =>
NullLogger<TelegramMessageSender>.Instance
);
services.AddSingleton<ITelegramMessageSenderWrapper>(_ =>
new Mock<ITelegramMessageSenderWrapper>().Object
);
services.AddSingleton<ITelegramMessageSender, TelegramMessageSender>();
services.AddSingleton<ILogger<TelegramErrorHandler>>(_ =>
NullLogger<TelegramErrorHandler>.Instance
);
services.AddSingleton<ITelegramErrorHandler, TelegramErrorHandler>();
services.AddSingleton<ILogger<CommandRegistry>>(_ => NullLogger<CommandRegistry>.Instance);
services.AddScoped<CommandRegistry>();
services.AddSingleton<ILogger<BotInfoService>>(_ => NullLogger<BotInfoService>.Instance);
services.AddSingleton<BotInfoService>();
services.AddSingleton<ILogger<TelegramCommandProcessor>>(_ =>
NullLogger<TelegramCommandProcessor>.Instance
);
services.AddScoped<ITelegramCommandProcessor, TelegramCommandProcessor>();
services.AddSingleton<ILogger<TelegramMessageHandler>>(_ =>
NullLogger<TelegramMessageHandler>.Instance
);
services.AddScoped<ITelegramMessageHandler, TelegramMessageHandler>();
services.AddScoped<ITelegramCommand, StartCommand>();
services.AddScoped<ITelegramCommand, HelpCommand>();
services.AddScoped<ITelegramCommand, ClearCommand>();
services.AddScoped<ITelegramCommand, SettingsCommand>();
services.AddScoped<ITelegramCommand, StatusCommand>();
// Hosted service simulation for Telegram bot service
services.AddSingleton<ILogger<TelegramBotService>>(_ =>
NullLogger<TelegramBotService>.Instance
);
services.AddSingleton<ITelegramBotService, TelegramBotService>();
services.AddSingleton<IHostedService>(sp =>
(IHostedService)sp.GetRequiredService<ITelegramBotService>()
);
// Simulate HealthCheck registrations via options to avoid bringing in DefaultHealthCheckService
var hcOptions = new HealthCheckServiceOptions();
hcOptions.Registrations.Add(
new HealthCheckRegistration(
"ollama",
sp => new Mock<IHealthCheck>().Object,
null,
new[] { "api", "ollama" }
)
);
hcOptions.Registrations.Add(
new HealthCheckRegistration(
"telegram",
sp => new Mock<IHealthCheck>().Object,
null,
new[] { "api", "telegram" }
)
);
services.AddSingleton<IOptions<HealthCheckServiceOptions>>(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<ChatBotDbContext>());
Assert.NotNull(sp.GetRequiredService<IChatSessionRepository>());
Assert.NotNull(sp.GetRequiredService<ISessionStorage>());
Assert.NotNull(sp.GetRequiredService<ModelService>());
Assert.NotNull(sp.GetRequiredService<SystemPromptService>());
Assert.NotNull(sp.GetRequiredService<IHistoryCompressionService>());
Assert.NotNull(sp.GetRequiredService<IAIService>());
Assert.NotNull(sp.GetRequiredService<ChatService>());
Assert.NotNull(sp.GetRequiredService<IOllamaClient>());
// Assert: Telegram stack resolves
Assert.NotNull(sp.GetRequiredService<ITelegramBotClient>());
Assert.NotNull(sp.GetRequiredService<ITelegramMessageSender>());
Assert.NotNull(sp.GetRequiredService<ITelegramErrorHandler>());
Assert.NotNull(sp.GetRequiredService<ITelegramCommandProcessor>());
Assert.NotNull(sp.GetRequiredService<ITelegramMessageHandler>());
Assert.NotEmpty(sp.GetServices<ITelegramCommand>());
Assert.NotNull(sp.GetRequiredService<IHostedService>());
// Assert: health checks names and tags
var hcResolved = sp.GetRequiredService<IOptions<HealthCheckServiceOptions>>().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()
{

View File

@@ -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<ChatMessageEntity>(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));
}
}

View File

@@ -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<ChatMessage>(json, options)!;
// Assert
deserialized.Content.Should().Be(original.Content);
deserialized.Role.Should().Be(original.Role);
}
}

View File

@@ -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<ChatMessageEntity>(),
};
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<ChatSessionEntity>(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();
}
}

View File

@@ -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<string, string?>
{
{
"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()
{

View File

@@ -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<OllamaSharp.Models.Chat.ChatRequest>()))
.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<OllamaSharp.Models.Chat.ChatRequest>()),
Times.Exactly(3)
);
}
}

View File

@@ -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<string>(), It.IsAny<string>()))
.Returns(session);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
_aiServiceMock
.Setup(x =>
x.GenerateChatCompletionWithCompressionAsync(
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
It.IsAny<CancellationToken>()
)
)
.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<string>(), It.IsAny<string>()))
.Returns(session);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
_aiServiceMock
.Setup(x =>
x.GenerateChatCompletionWithCompressionAsync(
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
It.IsAny<CancellationToken>()
)
)
.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()
{

View File

@@ -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. Создать дополнительные интеграционные тесты