fix covverage
All checks were successful
SonarQube / Build and analyze (push) Successful in 3m33s

This commit is contained in:
Leonid Pershin
2025-10-21 03:17:43 +03:00
parent 2a26e84100
commit b8fc79992a
8 changed files with 837 additions and 0 deletions

View File

@@ -4,6 +4,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<!-- Exclude migrations and auto-generated files from code coverage -->
<ExcludeFromCodeCoverage>**/Migrations/**/*.cs</ExcludeFromCodeCoverage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4">

View File

@@ -0,0 +1,146 @@
using ChatBot.Data;
using ChatBot.Services;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
namespace ChatBot.Tests.Services;
public class DatabaseInitializationServiceExceptionTests
{
[Fact]
public async Task StartAsync_WhenDatabaseDoesNotExist_ShouldRetryWithMigration()
{
// Arrange
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
// Ensure database does not exist
if (File.Exists(dbPath))
{
File.Delete(dbPath);
}
var services = new ServiceCollection();
services.AddDbContext<ChatBotDbContext>(options =>
options.UseSqlite($"Data Source={dbPath}")
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
);
var serviceProvider = services.BuildServiceProvider();
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
try
{
// Act
await service.StartAsync(CancellationToken.None);
// Assert - database should be created
File.Exists(dbPath).Should().BeTrue();
loggerMock.Verify(
x =>
x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>(
(v, t) =>
v.ToString()!.Contains("Database initialization completed successfully")
),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
),
Times.Once
);
}
finally
{
// Cleanup
serviceProvider.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
if (File.Exists(dbPath))
{
try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ }
}
}
}
[Fact]
public async Task StartAsync_WhenCanConnectThrowsSpecificException_ShouldHandleGracefully()
{
// Arrange
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
var services = new ServiceCollection();
// Use SQLite with a valid connection string
services.AddDbContext<ChatBotDbContext>(options =>
options.UseSqlite($"Data Source={dbPath}")
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
);
var serviceProvider = services.BuildServiceProvider();
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
try
{
// Act
await service.StartAsync(CancellationToken.None);
// Assert - should complete successfully even if database didn't exist initially
loggerMock.Verify(
x =>
x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>(
(v, t) =>
v.ToString()!.Contains("Database initialization completed successfully")
),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
),
Times.Once
);
}
finally
{
// Cleanup
serviceProvider.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
if (File.Exists(dbPath))
{
try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ }
}
}
}
[Fact]
public async Task StartAsync_WithCanceledToken_ShouldThrowOperationCanceledException()
{
// Arrange
var serviceProviderMock = new Mock<IServiceProvider>();
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
var cts = new CancellationTokenSource();
cts.Cancel(); // Cancel before starting
serviceProviderMock
.Setup(x => x.GetService(typeof(IServiceScopeFactory)))
.Returns((IServiceScopeFactory)null!);
var service = new DatabaseInitializationService(
serviceProviderMock.Object,
loggerMock.Object
);
// Act & Assert
var act = async () => await service.StartAsync(cts.Token);
await act.Should().ThrowAsync<InvalidOperationException>();
}
}

View File

@@ -0,0 +1,54 @@
using ChatBot.Services.Telegram.Services;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using Telegram.Bot;
namespace ChatBot.Tests.Services.Telegram;
/// <summary>
/// Simple tests for BotInfoService that don't rely on mocking extension methods
/// </summary>
public class BotInfoServiceSimpleTests
{
[Fact]
public void Constructor_ShouldCreateInstance()
{
// Arrange
var botClientMock = new Mock<ITelegramBotClient>();
var loggerMock = new Mock<ILogger<BotInfoService>>();
// Act
var service = new BotInfoService(botClientMock.Object, loggerMock.Object);
// Assert
service.Should().NotBeNull();
}
[Fact]
public void IsCacheValid_InitiallyFalse()
{
// Arrange
var botClientMock = new Mock<ITelegramBotClient>();
var loggerMock = new Mock<ILogger<BotInfoService>>();
var service = new BotInfoService(botClientMock.Object, loggerMock.Object);
// Act & Assert
service.IsCacheValid().Should().BeFalse();
}
[Fact]
public void 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
service.IsCacheValid().Should().BeFalse();
}
}

View File

@@ -0,0 +1,100 @@
using ChatBot.Models.Configuration;
using ChatBot.Services;
using ChatBot.Services.Interfaces;
using ChatBot.Services.Telegram.Commands;
using ChatBot.Tests.TestUtilities;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
namespace ChatBot.Tests.Services.Telegram;
/// <summary>
/// Additional edge case tests for StatusCommand to improve code coverage
/// </summary>
public class StatusCommandEdgeCaseTests : UnitTestBase
{
private readonly Mock<IOllamaClient> _ollamaClientMock;
private readonly StatusCommand _statusCommand;
public StatusCommandEdgeCaseTests()
{
_ollamaClientMock = TestDataBuilder.Mocks.CreateOllamaClientMock();
var ollamaSettings = TestDataBuilder.Configurations.CreateOllamaSettings();
var ollamaSettingsMock = TestDataBuilder.Mocks.CreateOptionsMock(ollamaSettings);
var chatServiceMock = new Mock<ChatService>(
TestDataBuilder.Mocks.CreateLoggerMock<ChatService>().Object,
TestDataBuilder.Mocks.CreateAIServiceMock().Object,
TestDataBuilder.Mocks.CreateSessionStorageMock().Object,
TestDataBuilder.Mocks.CreateOptionsMock(TestDataBuilder.Configurations.CreateAISettings()).Object,
TestDataBuilder.Mocks.CreateCompressionServiceMock().Object
);
var modelServiceMock = new Mock<ModelService>(
TestDataBuilder.Mocks.CreateLoggerMock<ModelService>().Object,
ollamaSettingsMock.Object
);
var aiSettingsMock = TestDataBuilder.Mocks.CreateOptionsMock(new AISettings());
_statusCommand = new StatusCommand(
chatServiceMock.Object,
modelServiceMock.Object,
aiSettingsMock.Object,
_ollamaClientMock.Object
);
}
[Fact]
public async Task ExecuteAsync_WhenOllamaThrowsHttpRequestException_ShouldHandleGracefully()
{
// Arrange
var context = new TelegramCommandContext
{
ChatId = 12345,
Username = "testuser",
MessageText = "/status",
ChatType = "private",
ChatTitle = "Test Chat"
};
_ollamaClientMock
.Setup(x => x.ListLocalModelsAsync())
.ThrowsAsync(new HttpRequestException("502 Bad Gateway"));
// Act
var result = await _statusCommand.ExecuteAsync(context);
// Assert
result.Should().NotBeNullOrEmpty();
result.Should().Contain("Статус системы");
// StatusCommand handles exceptions internally and returns formatted status
}
[Fact]
public async Task ExecuteAsync_WhenOllamaThrowsTaskCanceledException_ShouldHandleGracefully()
{
// Arrange
var context = new TelegramCommandContext
{
ChatId = 12345,
Username = "testuser",
MessageText = "/status",
ChatType = "private",
ChatTitle = "Test Chat"
};
_ollamaClientMock
.Setup(x => x.ListLocalModelsAsync())
.ThrowsAsync(new TaskCanceledException("Operation timed out"));
// Act
var result = await _statusCommand.ExecuteAsync(context);
// Assert
result.Should().NotBeNullOrEmpty();
result.Should().Contain("Статус системы");
// StatusCommand handles timeouts internally and returns formatted status
}
}