From af9773e7d699e78a83f9f8ae454a5245df2c9360 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Mon, 20 Oct 2025 06:07:45 +0300 Subject: [PATCH] add tests --- ChatBot.Tests/Models/AISettingsTests.cs | 693 ++++++++++++++++ .../Models/ChatMessageEntityTests.cs | 422 ++++++++++ ChatBot.Tests/Models/ChatMessageTests.cs | 363 +++++++++ .../Models/ChatSessionEntityTests.cs | 637 +++++++++++++++ ChatBot.Tests/Models/DatabaseSettingsTests.cs | 452 +++++++++++ ChatBot.Tests/Models/OllamaSettingsTests.cs | 568 ++++++++++++++ .../Models/TelegramBotSettingsTests.cs | 742 ++++++++++++++++++ test_coverage_report.md | 154 ++++ 8 files changed, 4031 insertions(+) create mode 100644 ChatBot.Tests/Models/AISettingsTests.cs create mode 100644 ChatBot.Tests/Models/ChatMessageEntityTests.cs create mode 100644 ChatBot.Tests/Models/ChatMessageTests.cs create mode 100644 ChatBot.Tests/Models/ChatSessionEntityTests.cs create mode 100644 ChatBot.Tests/Models/DatabaseSettingsTests.cs create mode 100644 ChatBot.Tests/Models/OllamaSettingsTests.cs create mode 100644 ChatBot.Tests/Models/TelegramBotSettingsTests.cs create mode 100644 test_coverage_report.md diff --git a/ChatBot.Tests/Models/AISettingsTests.cs b/ChatBot.Tests/Models/AISettingsTests.cs new file mode 100644 index 0000000..6cbc466 --- /dev/null +++ b/ChatBot.Tests/Models/AISettingsTests.cs @@ -0,0 +1,693 @@ +using ChatBot.Models.Configuration; +using FluentAssertions; + +namespace ChatBot.Tests.Models; + +public class AISettingsTests +{ + [Fact] + public void Constructor_ShouldInitializePropertiesWithDefaultValues() + { + // Arrange & Act + var settings = new AISettings(); + + // Assert + settings.Temperature.Should().Be(0.7); + settings.SystemPromptPath.Should().Be("Prompts/system-prompt.txt"); + settings.SystemPrompt.Should().Be(string.Empty); + settings.MaxRetryAttempts.Should().Be(3); + settings.RetryDelayMs.Should().Be(1000); + settings.RequestTimeoutSeconds.Should().Be(60); + settings.EnableHistoryCompression.Should().BeTrue(); + settings.CompressionThreshold.Should().Be(20); + settings.CompressionTarget.Should().Be(10); + settings.MinMessageLengthForSummarization.Should().Be(50); + settings.MaxSummarizedMessageLength.Should().Be(200); + settings.EnableExponentialBackoff.Should().BeTrue(); + settings.MaxRetryDelayMs.Should().Be(30000); + settings.CompressionTimeoutSeconds.Should().Be(30); + settings.StatusCheckTimeoutSeconds.Should().Be(10); + } + + [Fact] + public void Temperature_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedTemperature = 1.5; + + // Act + settings.Temperature = expectedTemperature; + + // Assert + settings.Temperature.Should().Be(expectedTemperature); + } + + [Fact] + public void SystemPromptPath_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedPath = "Custom/prompt.txt"; + + // Act + settings.SystemPromptPath = expectedPath; + + // Assert + settings.SystemPromptPath.Should().Be(expectedPath); + } + + [Fact] + public void SystemPrompt_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedPrompt = "You are a helpful assistant."; + + // Act + settings.SystemPrompt = expectedPrompt; + + // Assert + settings.SystemPrompt.Should().Be(expectedPrompt); + } + + [Fact] + public void MaxRetryAttempts_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedAttempts = 5; + + // Act + settings.MaxRetryAttempts = expectedAttempts; + + // Assert + settings.MaxRetryAttempts.Should().Be(expectedAttempts); + } + + [Fact] + public void RetryDelayMs_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedDelay = 2000; + + // Act + settings.RetryDelayMs = expectedDelay; + + // Assert + settings.RetryDelayMs.Should().Be(expectedDelay); + } + + [Fact] + public void RequestTimeoutSeconds_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedTimeout = 120; + + // Act + settings.RequestTimeoutSeconds = expectedTimeout; + + // Assert + settings.RequestTimeoutSeconds.Should().Be(expectedTimeout); + } + + [Fact] + public void EnableHistoryCompression_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.EnableHistoryCompression = false; + + // Assert + settings.EnableHistoryCompression.Should().BeFalse(); + } + + [Fact] + public void CompressionThreshold_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedThreshold = 50; + + // Act + settings.CompressionThreshold = expectedThreshold; + + // Assert + settings.CompressionThreshold.Should().Be(expectedThreshold); + } + + [Fact] + public void CompressionTarget_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedTarget = 15; + + // Act + settings.CompressionTarget = expectedTarget; + + // Assert + settings.CompressionTarget.Should().Be(expectedTarget); + } + + [Fact] + public void MinMessageLengthForSummarization_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedLength = 100; + + // Act + settings.MinMessageLengthForSummarization = expectedLength; + + // Assert + settings.MinMessageLengthForSummarization.Should().Be(expectedLength); + } + + [Fact] + public void MaxSummarizedMessageLength_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedLength = 300; + + // Act + settings.MaxSummarizedMessageLength = expectedLength; + + // Assert + settings.MaxSummarizedMessageLength.Should().Be(expectedLength); + } + + [Fact] + public void EnableExponentialBackoff_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.EnableExponentialBackoff = false; + + // Assert + settings.EnableExponentialBackoff.Should().BeFalse(); + } + + [Fact] + public void MaxRetryDelayMs_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedDelay = 60000; + + // Act + settings.MaxRetryDelayMs = expectedDelay; + + // Assert + settings.MaxRetryDelayMs.Should().Be(expectedDelay); + } + + [Fact] + public void CompressionTimeoutSeconds_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedTimeout = 60; + + // Act + settings.CompressionTimeoutSeconds = expectedTimeout; + + // Assert + settings.CompressionTimeoutSeconds.Should().Be(expectedTimeout); + } + + [Fact] + public void StatusCheckTimeoutSeconds_ShouldBeSettable() + { + // Arrange + var settings = new AISettings(); + var expectedTimeout = 20; + + // Act + settings.StatusCheckTimeoutSeconds = expectedTimeout; + + // Assert + settings.StatusCheckTimeoutSeconds.Should().Be(expectedTimeout); + } + + [Theory] + [InlineData(0.0)] + [InlineData(0.5)] + [InlineData(1.0)] + [InlineData(1.5)] + [InlineData(2.0)] + public void Temperature_ShouldAcceptValidRange(double temperature) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.Temperature = temperature; + + // Assert + settings.Temperature.Should().Be(temperature); + } + + [Theory] + [InlineData(-1.0)] + [InlineData(2.5)] + [InlineData(10.0)] + public void Temperature_ShouldAcceptOutOfRangeValues(double temperature) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.Temperature = temperature; + + // Assert + settings.Temperature.Should().Be(temperature); + } + + [Theory] + [InlineData("")] + [InlineData("prompt.txt")] + [InlineData("Custom/path/prompt.txt")] + [InlineData("C:\\Windows\\System32\\prompt.txt")] + [InlineData("/usr/local/bin/prompt.txt")] + public void SystemPromptPath_ShouldAcceptVariousPaths(string path) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.SystemPromptPath = path; + + // Assert + settings.SystemPromptPath.Should().Be(path); + } + + [Theory] + [InlineData("")] + [InlineData("Short prompt")] + [InlineData( + "A very long system prompt that contains detailed instructions for the AI assistant on how to behave and respond to user queries in a helpful and informative manner." + )] + public void SystemPrompt_ShouldAcceptVariousContent(string prompt) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.SystemPrompt = prompt; + + // Assert + settings.SystemPrompt.Should().Be(prompt); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(int.MaxValue)] + public void MaxRetryAttempts_ShouldAcceptVariousValues(int attempts) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.MaxRetryAttempts = attempts; + + // Assert + settings.MaxRetryAttempts.Should().Be(attempts); + } + + [Theory] + [InlineData(0)] + [InlineData(100)] + [InlineData(1000)] + [InlineData(5000)] + [InlineData(int.MaxValue)] + public void RetryDelayMs_ShouldAcceptVariousValues(int delay) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.RetryDelayMs = delay; + + // Assert + settings.RetryDelayMs.Should().Be(delay); + } + + [Theory] + [InlineData(1)] + [InlineData(30)] + [InlineData(60)] + [InlineData(300)] + [InlineData(int.MaxValue)] + public void RequestTimeoutSeconds_ShouldAcceptVariousValues(int timeout) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.RequestTimeoutSeconds = timeout; + + // Assert + settings.RequestTimeoutSeconds.Should().Be(timeout); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EnableHistoryCompression_ShouldAcceptBooleanValues(bool enabled) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.EnableHistoryCompression = enabled; + + // Assert + settings.EnableHistoryCompression.Should().Be(enabled); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(10)] + [InlineData(50)] + [InlineData(100)] + [InlineData(int.MaxValue)] + public void CompressionThreshold_ShouldAcceptVariousValues(int threshold) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.CompressionThreshold = threshold; + + // Assert + settings.CompressionThreshold.Should().Be(threshold); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + [InlineData(20)] + [InlineData(100)] + [InlineData(int.MaxValue)] + public void CompressionTarget_ShouldAcceptVariousValues(int target) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.CompressionTarget = target; + + // Assert + settings.CompressionTarget.Should().Be(target); + } + + [Theory] + [InlineData(0)] + [InlineData(10)] + [InlineData(50)] + [InlineData(100)] + [InlineData(1000)] + [InlineData(int.MaxValue)] + public void MinMessageLengthForSummarization_ShouldAcceptVariousValues(int length) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.MinMessageLengthForSummarization = length; + + // Assert + settings.MinMessageLengthForSummarization.Should().Be(length); + } + + [Theory] + [InlineData(0)] + [InlineData(50)] + [InlineData(200)] + [InlineData(500)] + [InlineData(1000)] + [InlineData(int.MaxValue)] + public void MaxSummarizedMessageLength_ShouldAcceptVariousValues(int length) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.MaxSummarizedMessageLength = length; + + // Assert + settings.MaxSummarizedMessageLength.Should().Be(length); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EnableExponentialBackoff_ShouldAcceptBooleanValues(bool enabled) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.EnableExponentialBackoff = enabled; + + // Assert + settings.EnableExponentialBackoff.Should().Be(enabled); + } + + [Theory] + [InlineData(0)] + [InlineData(1000)] + [InlineData(30000)] + [InlineData(60000)] + [InlineData(int.MaxValue)] + public void MaxRetryDelayMs_ShouldAcceptVariousValues(int delay) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.MaxRetryDelayMs = delay; + + // Assert + settings.MaxRetryDelayMs.Should().Be(delay); + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + [InlineData(30)] + [InlineData(60)] + [InlineData(300)] + [InlineData(int.MaxValue)] + public void CompressionTimeoutSeconds_ShouldAcceptVariousValues(int timeout) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.CompressionTimeoutSeconds = timeout; + + // Assert + settings.CompressionTimeoutSeconds.Should().Be(timeout); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(30)] + [InlineData(60)] + [InlineData(int.MaxValue)] + public void StatusCheckTimeoutSeconds_ShouldAcceptVariousValues(int timeout) + { + // Arrange + var settings = new AISettings(); + + // Act + settings.StatusCheckTimeoutSeconds = timeout; + + // Assert + settings.StatusCheckTimeoutSeconds.Should().Be(timeout); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.Temperature = 1.2; + settings.SystemPromptPath = "custom/prompt.txt"; + settings.SystemPrompt = "Custom system prompt"; + settings.MaxRetryAttempts = 5; + settings.RetryDelayMs = 2000; + settings.RequestTimeoutSeconds = 120; + settings.EnableHistoryCompression = false; + settings.CompressionThreshold = 50; + settings.CompressionTarget = 15; + settings.MinMessageLengthForSummarization = 100; + settings.MaxSummarizedMessageLength = 300; + settings.EnableExponentialBackoff = false; + settings.MaxRetryDelayMs = 60000; + settings.CompressionTimeoutSeconds = 60; + settings.StatusCheckTimeoutSeconds = 20; + + // Assert + settings.Temperature.Should().Be(1.2); + settings.SystemPromptPath.Should().Be("custom/prompt.txt"); + settings.SystemPrompt.Should().Be("Custom system prompt"); + settings.MaxRetryAttempts.Should().Be(5); + settings.RetryDelayMs.Should().Be(2000); + settings.RequestTimeoutSeconds.Should().Be(120); + settings.EnableHistoryCompression.Should().BeFalse(); + settings.CompressionThreshold.Should().Be(50); + settings.CompressionTarget.Should().Be(15); + settings.MinMessageLengthForSummarization.Should().Be(100); + settings.MaxSummarizedMessageLength.Should().Be(300); + settings.EnableExponentialBackoff.Should().BeFalse(); + settings.MaxRetryDelayMs.Should().Be(60000); + settings.CompressionTimeoutSeconds.Should().Be(60); + settings.StatusCheckTimeoutSeconds.Should().Be(20); + } + + [Fact] + public void Settings_ShouldSupportHighTemperature() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.Temperature = 2.0; + + // Assert + settings.Temperature.Should().Be(2.0); + } + + [Fact] + public void Settings_ShouldSupportLowTemperature() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.Temperature = 0.0; + + // Assert + settings.Temperature.Should().Be(0.0); + } + + [Fact] + public void Settings_ShouldSupportDisabledCompression() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.EnableHistoryCompression = false; + + // Assert + settings.EnableHistoryCompression.Should().BeFalse(); + } + + [Fact] + public void Settings_ShouldSupportDisabledExponentialBackoff() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.EnableExponentialBackoff = false; + + // Assert + settings.EnableExponentialBackoff.Should().BeFalse(); + } + + [Fact] + public void Settings_ShouldSupportZeroRetryAttempts() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.MaxRetryAttempts = 0; + + // Assert + settings.MaxRetryAttempts.Should().Be(0); + } + + [Fact] + public void Settings_ShouldSupportZeroDelays() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.RetryDelayMs = 0; + settings.MaxRetryDelayMs = 0; + + // Assert + settings.RetryDelayMs.Should().Be(0); + settings.MaxRetryDelayMs.Should().Be(0); + } + + [Fact] + public void Settings_ShouldSupportZeroThresholds() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.CompressionThreshold = 0; + settings.CompressionTarget = 0; + + // Assert + settings.CompressionThreshold.Should().Be(0); + settings.CompressionTarget.Should().Be(0); + } + + [Fact] + public void Settings_ShouldSupportZeroLengths() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.MinMessageLengthForSummarization = 0; + settings.MaxSummarizedMessageLength = 0; + + // Assert + settings.MinMessageLengthForSummarization.Should().Be(0); + settings.MaxSummarizedMessageLength.Should().Be(0); + } + + [Fact] + public void Settings_ShouldSupportZeroTimeouts() + { + // Arrange + var settings = new AISettings(); + + // Act + settings.RequestTimeoutSeconds = 0; + settings.CompressionTimeoutSeconds = 0; + settings.StatusCheckTimeoutSeconds = 0; + + // Assert + settings.RequestTimeoutSeconds.Should().Be(0); + settings.CompressionTimeoutSeconds.Should().Be(0); + settings.StatusCheckTimeoutSeconds.Should().Be(0); + } +} diff --git a/ChatBot.Tests/Models/ChatMessageEntityTests.cs b/ChatBot.Tests/Models/ChatMessageEntityTests.cs new file mode 100644 index 0000000..802b777 --- /dev/null +++ b/ChatBot.Tests/Models/ChatMessageEntityTests.cs @@ -0,0 +1,422 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using ChatBot.Models.Entities; +using FluentAssertions; + +namespace ChatBot.Tests.Models; + +public class ChatMessageEntityTests +{ + [Fact] + public void Constructor_ShouldInitializePropertiesWithDefaultValues() + { + // Arrange & Act + var entity = new ChatMessageEntity(); + + // Assert + entity.Id.Should().Be(0); + entity.SessionId.Should().Be(0); + entity.Content.Should().Be(string.Empty); + entity.Role.Should().Be(string.Empty); + entity.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + entity.MessageOrder.Should().Be(0); + entity.Session.Should().BeNull(); + } + + [Fact] + public void Id_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedId = 123; + + // Act + entity.Id = expectedId; + + // Assert + entity.Id.Should().Be(expectedId); + } + + [Fact] + public void SessionId_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedSessionId = 456; + + // Act + entity.SessionId = expectedSessionId; + + // Assert + entity.SessionId.Should().Be(expectedSessionId); + } + + [Fact] + public void Content_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedContent = "Hello, world!"; + + // Act + entity.Content = expectedContent; + + // Assert + entity.Content.Should().Be(expectedContent); + } + + [Fact] + public void Role_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedRole = "user"; + + // Act + entity.Role = expectedRole; + + // Assert + entity.Role.Should().Be(expectedRole); + } + + [Fact] + public void CreatedAt_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedDate = DateTime.UtcNow.AddDays(-1); + + // Act + entity.CreatedAt = expectedDate; + + // Assert + entity.CreatedAt.Should().Be(expectedDate); + } + + [Fact] + public void MessageOrder_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedOrder = 5; + + // Act + entity.MessageOrder = expectedOrder; + + // Assert + entity.MessageOrder.Should().Be(expectedOrder); + } + + [Fact] + public void Session_ShouldBeSettable() + { + // Arrange + var entity = new ChatMessageEntity(); + var expectedSession = new ChatSessionEntity(); + + // Act + entity.Session = expectedSession; + + // Assert + entity.Session.Should().Be(expectedSession); + } + + [Theory] + [InlineData("user")] + [InlineData("assistant")] + [InlineData("system")] + [InlineData("function")] + public void Role_ShouldAcceptValidRoles(string role) + { + // Arrange + var entity = new ChatMessageEntity(); + + // Act + entity.Role = role; + + // Assert + entity.Role.Should().Be(role); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("very long role name that exceeds limit")] + public void Role_ShouldAcceptVariousLengths(string role) + { + // Arrange + var entity = new ChatMessageEntity(); + + // Act + entity.Role = role; + + // Assert + entity.Role.Should().Be(role); + entity.Role.Length.Should().Be(role.Length); + } + + [Theory] + [InlineData("")] + [InlineData("Short message")] + [InlineData( + "A very long message that contains a lot of text and should still be valid for the content field" + )] + public void Content_ShouldAcceptVariousLengths(string content) + { + // Arrange + var entity = new ChatMessageEntity(); + + // Act + entity.Content = content; + + // Assert + entity.Content.Should().Be(content); + } + + [Fact] + public void CreatedAt_ShouldDefaultToCurrentUtcTime() + { + // Arrange + var beforeCreation = DateTime.UtcNow; + + // Act + var entity = new ChatMessageEntity(); + + // Assert + entity.CreatedAt.Should().BeOnOrAfter(beforeCreation); + entity.CreatedAt.Should().BeOnOrBefore(DateTime.UtcNow); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var entity = new ChatMessageEntity(); + var session = new ChatSessionEntity(); + + // Act + entity.Id = 1; + entity.SessionId = 2; + entity.Content = "Test content"; + entity.Role = "user"; + entity.CreatedAt = DateTime.UtcNow.AddDays(-1); + entity.MessageOrder = 3; + entity.Session = session; + + // Assert + entity.Id.Should().Be(1); + entity.SessionId.Should().Be(2); + entity.Content.Should().Be("Test content"); + entity.Role.Should().Be("user"); + entity.CreatedAt.Should().BeCloseTo(DateTime.UtcNow.AddDays(-1), TimeSpan.FromSeconds(1)); + entity.MessageOrder.Should().Be(3); + entity.Session.Should().Be(session); + } + + [Fact] + public void Entity_ShouldHaveCorrectTableAttribute() + { + // Arrange + var entityType = typeof(ChatMessageEntity); + var tableAttribute = + entityType.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault() + as TableAttribute; + + // Assert + tableAttribute.Should().NotBeNull(); + tableAttribute!.Name.Should().Be("chat_messages"); + } + + [Fact] + public void Id_ShouldHaveKeyAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Id)); + var keyAttribute = + property?.GetCustomAttributes(typeof(KeyAttribute), false).FirstOrDefault() + as KeyAttribute; + + // Assert + keyAttribute.Should().NotBeNull(); + } + + [Fact] + public void Id_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Id)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("id"); + } + + [Fact] + public void SessionId_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.SessionId)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void SessionId_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.SessionId)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("session_id"); + } + + [Fact] + public void Content_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Content)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void Content_ShouldHaveStringLengthAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Content)); + var stringLengthAttribute = + property?.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() + as StringLengthAttribute; + + // Assert + stringLengthAttribute.Should().NotBeNull(); + stringLengthAttribute!.MaximumLength.Should().Be(10000); + } + + [Fact] + public void Content_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Content)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("content"); + } + + [Fact] + public void Role_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Role)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void Role_ShouldHaveStringLengthAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Role)); + var stringLengthAttribute = + property?.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() + as StringLengthAttribute; + + // Assert + stringLengthAttribute.Should().NotBeNull(); + stringLengthAttribute!.MaximumLength.Should().Be(20); + } + + [Fact] + public void Role_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Role)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("role"); + } + + [Fact] + public void CreatedAt_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.CreatedAt)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void CreatedAt_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.CreatedAt)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("created_at"); + } + + [Fact] + public void MessageOrder_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty( + nameof(ChatMessageEntity.MessageOrder) + ); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("message_order"); + } + + [Fact] + public void Session_ShouldHaveForeignKeyAttribute() + { + // Arrange + var property = typeof(ChatMessageEntity).GetProperty(nameof(ChatMessageEntity.Session)); + var foreignKeyAttribute = + property?.GetCustomAttributes(typeof(ForeignKeyAttribute), false).FirstOrDefault() + as ForeignKeyAttribute; + + // Assert + foreignKeyAttribute.Should().NotBeNull(); + foreignKeyAttribute!.Name.Should().Be("SessionId"); + } +} diff --git a/ChatBot.Tests/Models/ChatMessageTests.cs b/ChatBot.Tests/Models/ChatMessageTests.cs new file mode 100644 index 0000000..7146cb3 --- /dev/null +++ b/ChatBot.Tests/Models/ChatMessageTests.cs @@ -0,0 +1,363 @@ +using ChatBot.Models.Dto; +using FluentAssertions; +using OllamaSharp.Models.Chat; + +namespace ChatBot.Tests.Models; + +public class ChatMessageTests +{ + [Fact] + public void Constructor_WithRequiredProperties_ShouldInitializeCorrectly() + { + // Arrange + var content = "Hello, world!"; + var role = ChatRole.User; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Content_ShouldBeSettable() + { + // Arrange + var message = new ChatMessage { Content = "Initial content", Role = ChatRole.User }; + var newContent = "Updated content"; + + // Act + message.Content = newContent; + + // Assert + message.Content.Should().Be(newContent); + } + + [Fact] + public void Role_ShouldBeSettable() + { + // Arrange + var message = new ChatMessage { Content = "Test content", Role = ChatRole.User }; + var newRole = ChatRole.Assistant; + + // Act + message.Role = newRole; + + // Assert + message.Role.Should().Be(newRole); + } + + [Theory] + [InlineData("")] + [InlineData("Short message")] + [InlineData( + "A very long message that contains a lot of text and should still be valid for the content field" + )] + [InlineData("Message with special characters: !@#$%^&*()_+-=[]{}|;':\",./<>?")] + [InlineData("Message with unicode: Привет мир! 🌍")] + [InlineData("Message with newlines:\nLine 1\nLine 2")] + [InlineData("Message with tabs:\tTab content")] + public void Content_ShouldAcceptVariousValues(string content) + { + // Arrange + var message = new ChatMessage { Content = "Initial", Role = ChatRole.User }; + + // Act + message.Content = content; + + // Assert + message.Content.Should().Be(content); + } + + [Fact] + public void Role_ShouldAcceptSystemRole() + { + // Arrange + var message = new ChatMessage { Content = "Test content", Role = ChatRole.User }; + + // Act + message.Role = ChatRole.System; + + // Assert + message.Role.Should().Be(ChatRole.System); + } + + [Fact] + public void Role_ShouldAcceptUserRole() + { + // Arrange + var message = new ChatMessage { Content = "Test content", Role = ChatRole.System }; + + // Act + message.Role = ChatRole.User; + + // Assert + message.Role.Should().Be(ChatRole.User); + } + + [Fact] + public void Role_ShouldAcceptAssistantRole() + { + // Arrange + var message = new ChatMessage { Content = "Test content", Role = ChatRole.User }; + + // Act + message.Role = ChatRole.Assistant; + + // Assert + message.Role.Should().Be(ChatRole.Assistant); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var message = new ChatMessage { Content = "Initial content", Role = ChatRole.User }; + + // Act + message.Content = "Updated content"; + message.Role = ChatRole.Assistant; + + // Assert + message.Content.Should().Be("Updated content"); + message.Role.Should().Be(ChatRole.Assistant); + } + + [Fact] + public void Message_ShouldSupportSystemRole() + { + // Arrange + var content = "You are a helpful assistant."; + var role = ChatRole.System; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(ChatRole.System); + } + + [Fact] + public void Message_ShouldSupportUserRole() + { + // Arrange + var content = "Hello, how are you?"; + var role = ChatRole.User; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(ChatRole.User); + } + + [Fact] + public void Message_ShouldSupportAssistantRole() + { + // Arrange + var content = "I'm doing well, thank you!"; + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(ChatRole.Assistant); + } + + [Fact] + public void Message_WithEmptyContent_ShouldBeValid() + { + // Arrange + var content = ""; + var role = ChatRole.User; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_WithWhitespaceContent_ShouldBeValid() + { + // Arrange + var content = " \t\n "; + var role = ChatRole.User; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_WithVeryLongContent_ShouldBeValid() + { + // Arrange + var content = new string('A', 10000); // 10,000 characters + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + message.Content.Length.Should().Be(10000); + } + + [Fact] + public void Message_WithJsonContent_ShouldBeValid() + { + // Arrange + var content = """{"key": "value", "number": 123, "array": [1, 2, 3]}"""; + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_WithXmlContent_ShouldBeValid() + { + // Arrange + var content = "value"; + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_WithMarkdownContent_ShouldBeValid() + { + // Arrange + var content = "# Header\n\n**Bold text** and *italic text*\n\n- List item 1\n- List item 2"; + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_WithCodeContent_ShouldBeValid() + { + // Arrange + var content = "```csharp\npublic class Test { }\n```"; + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_WithDefaultRole_ShouldBeValid() + { + // Arrange + var content = "Test message"; + + // Act + var message = new ChatMessage { Content = content, Role = default(ChatRole) }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(default(ChatRole)); + } + + [Fact] + public void Message_ShouldBeComparableByContent() + { + // Arrange + var message1 = new ChatMessage { Content = "Same content", Role = ChatRole.User }; + var message2 = new ChatMessage { Content = "Same content", Role = ChatRole.Assistant }; + + // Act & Assert + message1.Content.Should().Be(message2.Content); + message1.Role.Should().NotBe(message2.Role); + } + + [Fact] + public void Message_ShouldBeComparableByRole() + { + // Arrange + var message1 = new ChatMessage { Content = "Different content 1", Role = ChatRole.User }; + var message2 = new ChatMessage { Content = "Different content 2", Role = ChatRole.User }; + + // Act & Assert + message1.Role.Should().Be(message2.Role); + message1.Content.Should().NotBe(message2.Content); + } + + [Fact] + public void Message_ShouldSupportAllRoleTypes() + { + // Arrange & Act + var systemMessage = new ChatMessage { Content = "System message", Role = ChatRole.System }; + var userMessage = new ChatMessage { Content = "User message", Role = ChatRole.User }; + var assistantMessage = new ChatMessage + { + Content = "Assistant message", + Role = ChatRole.Assistant, + }; + // Assert + systemMessage.Role.Should().Be(ChatRole.System); + userMessage.Role.Should().Be(ChatRole.User); + assistantMessage.Role.Should().Be(ChatRole.Assistant); + } + + [Fact] + public void Message_ShouldHandleSpecialCharacters() + { + // Arrange + var content = "Special chars: !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + var role = ChatRole.User; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } + + [Fact] + public void Message_ShouldHandleUnicodeCharacters() + { + // Arrange + var content = "Unicode: 中文 العربية русский 日本語 한국어"; + var role = ChatRole.Assistant; + + // Act + var message = new ChatMessage { Content = content, Role = role }; + + // Assert + message.Content.Should().Be(content); + message.Role.Should().Be(role); + } +} diff --git a/ChatBot.Tests/Models/ChatSessionEntityTests.cs b/ChatBot.Tests/Models/ChatSessionEntityTests.cs new file mode 100644 index 0000000..9136c66 --- /dev/null +++ b/ChatBot.Tests/Models/ChatSessionEntityTests.cs @@ -0,0 +1,637 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using ChatBot.Models.Entities; +using FluentAssertions; + +namespace ChatBot.Tests.Models; + +public class ChatSessionEntityTests +{ + [Fact] + public void Constructor_ShouldInitializePropertiesWithDefaultValues() + { + // Arrange & Act + var entity = new ChatSessionEntity(); + + // Assert + entity.Id.Should().Be(0); + entity.SessionId.Should().Be(string.Empty); + entity.ChatId.Should().Be(0); + entity.ChatType.Should().Be("private"); + entity.ChatTitle.Should().Be(string.Empty); + entity.Model.Should().Be(string.Empty); + entity.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + entity.LastUpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + entity.MaxHistoryLength.Should().Be(30); + entity.Messages.Should().NotBeNull(); + entity.Messages.Should().BeEmpty(); + } + + [Fact] + public void Id_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedId = 123; + + // Act + entity.Id = expectedId; + + // Assert + entity.Id.Should().Be(expectedId); + } + + [Fact] + public void SessionId_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedSessionId = "session-123"; + + // Act + entity.SessionId = expectedSessionId; + + // Assert + entity.SessionId.Should().Be(expectedSessionId); + } + + [Fact] + public void ChatId_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedChatId = 987654321L; + + // Act + entity.ChatId = expectedChatId; + + // Assert + entity.ChatId.Should().Be(expectedChatId); + } + + [Fact] + public void ChatType_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedChatType = "group"; + + // Act + entity.ChatType = expectedChatType; + + // Assert + entity.ChatType.Should().Be(expectedChatType); + } + + [Fact] + public void ChatTitle_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedChatTitle = "My Test Group"; + + // Act + entity.ChatTitle = expectedChatTitle; + + // Assert + entity.ChatTitle.Should().Be(expectedChatTitle); + } + + [Fact] + public void Model_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedModel = "llama3.1:8b"; + + // Act + entity.Model = expectedModel; + + // Assert + entity.Model.Should().Be(expectedModel); + } + + [Fact] + public void CreatedAt_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedDate = DateTime.UtcNow.AddDays(-1); + + // Act + entity.CreatedAt = expectedDate; + + // Assert + entity.CreatedAt.Should().Be(expectedDate); + } + + [Fact] + public void LastUpdatedAt_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedDate = DateTime.UtcNow.AddHours(-2); + + // Act + entity.LastUpdatedAt = expectedDate; + + // Assert + entity.LastUpdatedAt.Should().Be(expectedDate); + } + + [Fact] + public void MaxHistoryLength_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedLength = 50; + + // Act + entity.MaxHistoryLength = expectedLength; + + // Assert + entity.MaxHistoryLength.Should().Be(expectedLength); + } + + [Fact] + public void Messages_ShouldBeSettable() + { + // Arrange + var entity = new ChatSessionEntity(); + var expectedMessages = new List + { + new() { Content = "Test message 1", Role = "user" }, + new() { Content = "Test message 2", Role = "assistant" }, + }; + + // Act + entity.Messages = expectedMessages; + + // Assert + entity.Messages.Should().BeSameAs(expectedMessages); + entity.Messages.Should().HaveCount(2); + } + + [Theory] + [InlineData("private")] + [InlineData("group")] + [InlineData("supergroup")] + [InlineData("channel")] + public void ChatType_ShouldAcceptValidTypes(string chatType) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.ChatType = chatType; + + // Assert + entity.ChatType.Should().Be(chatType); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("very long chat type name")] + public void ChatType_ShouldAcceptVariousLengths(string chatType) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.ChatType = chatType; + + // Assert + entity.ChatType.Should().Be(chatType); + } + + [Theory] + [InlineData("")] + [InlineData("Short title")] + [InlineData("A very long chat title that contains a lot of text and should still be valid")] + public void ChatTitle_ShouldAcceptVariousLengths(string chatTitle) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.ChatTitle = chatTitle; + + // Assert + entity.ChatTitle.Should().Be(chatTitle); + } + + [Theory] + [InlineData("")] + [InlineData("llama3.1:8b")] + [InlineData("gpt-4")] + [InlineData("claude-3-sonnet")] + public void Model_ShouldAcceptVariousModels(string model) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.Model = model; + + // Assert + entity.Model.Should().Be(model); + } + + [Theory] + [InlineData("")] + [InlineData("session-123")] + [InlineData("very-long-session-id-that-exceeds-normal-length")] + public void SessionId_ShouldAcceptVariousLengths(string sessionId) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.SessionId = sessionId; + + // Assert + entity.SessionId.Should().Be(sessionId); + } + + [Theory] + [InlineData(0L)] + [InlineData(123456789L)] + [InlineData(-987654321L)] + [InlineData(long.MaxValue)] + [InlineData(long.MinValue)] + public void ChatId_ShouldAcceptVariousValues(long chatId) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.ChatId = chatId; + + // Assert + entity.ChatId.Should().Be(chatId); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + [InlineData(100)] + [InlineData(int.MaxValue)] + public void MaxHistoryLength_ShouldAcceptVariousValues(int maxHistoryLength) + { + // Arrange + var entity = new ChatSessionEntity(); + + // Act + entity.MaxHistoryLength = maxHistoryLength; + + // Assert + entity.MaxHistoryLength.Should().Be(maxHistoryLength); + } + + [Fact] + public void CreatedAt_ShouldDefaultToCurrentUtcTime() + { + // Arrange + var beforeCreation = DateTime.UtcNow; + + // Act + var entity = new ChatSessionEntity(); + + // Assert + entity.CreatedAt.Should().BeOnOrAfter(beforeCreation); + entity.CreatedAt.Should().BeOnOrBefore(DateTime.UtcNow); + } + + [Fact] + public void LastUpdatedAt_ShouldDefaultToCurrentUtcTime() + { + // Arrange + var beforeCreation = DateTime.UtcNow; + + // Act + var entity = new ChatSessionEntity(); + + // Assert + entity.LastUpdatedAt.Should().BeOnOrAfter(beforeCreation); + entity.LastUpdatedAt.Should().BeOnOrBefore(DateTime.UtcNow); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var entity = new ChatSessionEntity(); + var messages = new List + { + new() { Content = "Test", Role = "user" }, + }; + + // Act + entity.Id = 1; + entity.SessionId = "test-session"; + entity.ChatId = 123456789L; + entity.ChatType = "group"; + entity.ChatTitle = "Test Group"; + entity.Model = "llama3.1:8b"; + entity.CreatedAt = DateTime.UtcNow.AddDays(-1); + entity.LastUpdatedAt = DateTime.UtcNow.AddHours(-1); + entity.MaxHistoryLength = 50; + entity.Messages = messages; + + // Assert + entity.Id.Should().Be(1); + entity.SessionId.Should().Be("test-session"); + entity.ChatId.Should().Be(123456789L); + entity.ChatType.Should().Be("group"); + entity.ChatTitle.Should().Be("Test Group"); + entity.Model.Should().Be("llama3.1:8b"); + entity.CreatedAt.Should().BeCloseTo(DateTime.UtcNow.AddDays(-1), TimeSpan.FromSeconds(1)); + entity + .LastUpdatedAt.Should() + .BeCloseTo(DateTime.UtcNow.AddHours(-1), TimeSpan.FromSeconds(1)); + entity.MaxHistoryLength.Should().Be(50); + entity.Messages.Should().BeSameAs(messages); + } + + [Fact] + public void Entity_ShouldHaveCorrectTableAttribute() + { + // Arrange + var entityType = typeof(ChatSessionEntity); + var tableAttribute = + entityType.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault() + as TableAttribute; + + // Assert + tableAttribute.Should().NotBeNull(); + tableAttribute!.Name.Should().Be("chat_sessions"); + } + + [Fact] + public void Id_ShouldHaveKeyAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.Id)); + var keyAttribute = + property?.GetCustomAttributes(typeof(KeyAttribute), false).FirstOrDefault() + as KeyAttribute; + + // Assert + keyAttribute.Should().NotBeNull(); + } + + [Fact] + public void Id_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.Id)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("id"); + } + + [Fact] + public void SessionId_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.SessionId)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void SessionId_ShouldHaveStringLengthAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.SessionId)); + var stringLengthAttribute = + property?.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() + as StringLengthAttribute; + + // Assert + stringLengthAttribute.Should().NotBeNull(); + stringLengthAttribute!.MaximumLength.Should().Be(50); + } + + [Fact] + public void SessionId_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.SessionId)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("session_id"); + } + + [Fact] + public void ChatId_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatId)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void ChatId_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatId)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("chat_id"); + } + + [Fact] + public void ChatType_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatType)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void ChatType_ShouldHaveStringLengthAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatType)); + var stringLengthAttribute = + property?.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() + as StringLengthAttribute; + + // Assert + stringLengthAttribute.Should().NotBeNull(); + stringLengthAttribute!.MaximumLength.Should().Be(20); + } + + [Fact] + public void ChatType_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatType)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("chat_type"); + } + + [Fact] + public void ChatTitle_ShouldHaveStringLengthAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatTitle)); + var stringLengthAttribute = + property?.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() + as StringLengthAttribute; + + // Assert + stringLengthAttribute.Should().NotBeNull(); + stringLengthAttribute!.MaximumLength.Should().Be(200); + } + + [Fact] + public void ChatTitle_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.ChatTitle)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("chat_title"); + } + + [Fact] + public void Model_ShouldHaveStringLengthAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.Model)); + var stringLengthAttribute = + property?.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() + as StringLengthAttribute; + + // Assert + stringLengthAttribute.Should().NotBeNull(); + stringLengthAttribute!.MaximumLength.Should().Be(100); + } + + [Fact] + public void Model_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.Model)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("model"); + } + + [Fact] + public void CreatedAt_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.CreatedAt)); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void CreatedAt_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty(nameof(ChatSessionEntity.CreatedAt)); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("created_at"); + } + + [Fact] + public void LastUpdatedAt_ShouldHaveRequiredAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty( + nameof(ChatSessionEntity.LastUpdatedAt) + ); + var requiredAttribute = + property?.GetCustomAttributes(typeof(RequiredAttribute), false).FirstOrDefault() + as RequiredAttribute; + + // Assert + requiredAttribute.Should().NotBeNull(); + } + + [Fact] + public void LastUpdatedAt_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty( + nameof(ChatSessionEntity.LastUpdatedAt) + ); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("last_updated_at"); + } + + [Fact] + public void MaxHistoryLength_ShouldHaveColumnAttribute() + { + // Arrange + var property = typeof(ChatSessionEntity).GetProperty( + nameof(ChatSessionEntity.MaxHistoryLength) + ); + var columnAttribute = + property?.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() + as ColumnAttribute; + + // Assert + columnAttribute.Should().NotBeNull(); + columnAttribute!.Name.Should().Be("max_history_length"); + } +} diff --git a/ChatBot.Tests/Models/DatabaseSettingsTests.cs b/ChatBot.Tests/Models/DatabaseSettingsTests.cs new file mode 100644 index 0000000..ca29ec3 --- /dev/null +++ b/ChatBot.Tests/Models/DatabaseSettingsTests.cs @@ -0,0 +1,452 @@ +using ChatBot.Models.Configuration; +using FluentAssertions; + +namespace ChatBot.Tests.Models; + +public class DatabaseSettingsTests +{ + [Fact] + public void Constructor_ShouldInitializePropertiesWithDefaultValues() + { + // Arrange & Act + var settings = new DatabaseSettings(); + + // Assert + settings.ConnectionString.Should().Be(string.Empty); + settings.EnableSensitiveDataLogging.Should().BeFalse(); + settings.CommandTimeout.Should().Be(30); + } + + [Fact] + public void ConnectionString_ShouldBeSettable() + { + // Arrange + var settings = new DatabaseSettings(); + var expectedConnectionString = + "Server=localhost;Database=testdb;User Id=user;Password=testpass;"; + + // Act + settings.ConnectionString = expectedConnectionString; + + // Assert + settings.ConnectionString.Should().Be(expectedConnectionString); + } + + [Fact] + public void EnableSensitiveDataLogging_ShouldBeSettable() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.EnableSensitiveDataLogging = true; + + // Assert + settings.EnableSensitiveDataLogging.Should().BeTrue(); + } + + [Fact] + public void CommandTimeout_ShouldBeSettable() + { + // Arrange + var settings = new DatabaseSettings(); + var expectedTimeout = 60; + + // Act + settings.CommandTimeout = expectedTimeout; + + // Assert + settings.CommandTimeout.Should().Be(expectedTimeout); + } + + [Theory] + [InlineData("")] + [InlineData("Server=localhost;Database=testdb;")] + [InlineData("Server=localhost;Port=5432;Database=chatbot;User Id=admin;Password=testpass;")] + [InlineData( + "Host=localhost;Port=5432;Database=chatbot;Username=admin;Password=testpass;Pooling=true;MinPoolSize=0;MaxPoolSize=100;" + )] + [InlineData("Data Source=localhost;Initial Catalog=chatbot;Integrated Security=true;")] + [InlineData( + "Server=localhost;Database=chatbot;Trusted_Connection=true;MultipleActiveResultSets=true;" + )] + public void ConnectionString_ShouldAcceptVariousFormats(string connectionString) + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.ConnectionString = connectionString; + + // Assert + settings.ConnectionString.Should().Be(connectionString); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EnableSensitiveDataLogging_ShouldAcceptBooleanValues(bool enabled) + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.EnableSensitiveDataLogging = enabled; + + // Assert + settings.EnableSensitiveDataLogging.Should().Be(enabled); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + [InlineData(60)] + [InlineData(300)] + [InlineData(int.MaxValue)] + public void CommandTimeout_ShouldAcceptVariousValues(int timeout) + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.CommandTimeout = timeout; + + // Assert + settings.CommandTimeout.Should().Be(timeout); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.ConnectionString = "Server=test;Database=testdb;"; + settings.EnableSensitiveDataLogging = true; + settings.CommandTimeout = 120; + + // Assert + settings.ConnectionString.Should().Be("Server=test;Database=testdb;"); + settings.EnableSensitiveDataLogging.Should().BeTrue(); + settings.CommandTimeout.Should().Be(120); + } + + [Fact] + public void Settings_ShouldSupportEmptyConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.ConnectionString = ""; + + // Assert + settings.ConnectionString.Should().Be(""); + } + + [Fact] + public void Settings_ShouldSupportNullConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.ConnectionString = null!; + + // Assert + settings.ConnectionString.Should().BeNull(); + } + + [Fact] + public void Settings_ShouldSupportVeryLongConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + var longConnectionString = new string('A', 1000); + + // Act + settings.ConnectionString = longConnectionString; + + // Assert + settings.ConnectionString.Should().Be(longConnectionString); + settings.ConnectionString.Length.Should().Be(1000); + } + + [Fact] + public void Settings_ShouldSupportPostgreSQLConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + var postgresConnectionString = + "Host=localhost;Port=5432;Database=chatbot;Username=admin;Password=testpass;Pooling=true;MinPoolSize=0;MaxPoolSize=100;Connection Lifetime=0;"; + + // Act + settings.ConnectionString = postgresConnectionString; + + // Assert + settings.ConnectionString.Should().Be(postgresConnectionString); + } + + [Fact] + public void Settings_ShouldSupportSQLServerConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + var sqlServerConnectionString = + "Server=localhost;Database=chatbot;User Id=admin;Password=testpass;TrustServerCertificate=true;"; + + // Act + settings.ConnectionString = sqlServerConnectionString; + + // Assert + settings.ConnectionString.Should().Be(sqlServerConnectionString); + } + + [Fact] + public void Settings_ShouldSupportSQLiteConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + var sqliteConnectionString = "Data Source=chatbot.db;Cache=Shared;"; + + // Act + settings.ConnectionString = sqliteConnectionString; + + // Assert + settings.ConnectionString.Should().Be(sqliteConnectionString); + } + + [Fact] + public void Settings_ShouldSupportMySQLConnectionString() + { + // Arrange + var settings = new DatabaseSettings(); + var mysqlConnectionString = + "Server=localhost;Port=3306;Database=chatbot;Uid=admin;Pwd=testpass;"; + + // Act + settings.ConnectionString = mysqlConnectionString; + + // Assert + settings.ConnectionString.Should().Be(mysqlConnectionString); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithSpecialCharacters() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithSpecialChars = + "Server=localhost;Database=test-db;User Id=user@domain.com;Password=testpass123!;"; + + // Act + settings.ConnectionString = connectionStringWithSpecialChars; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithSpecialChars); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithUnicode() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithUnicode = + "Server=localhost;Database=тестовая_база;User Id=пользователь;Password=testpass;"; + + // Act + settings.ConnectionString = connectionStringWithUnicode; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithUnicode); + } + + [Fact] + public void Settings_ShouldSupportZeroCommandTimeout() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.CommandTimeout = 0; + + // Assert + settings.CommandTimeout.Should().Be(0); + } + + [Fact] + public void Settings_ShouldSupportNegativeCommandTimeout() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.CommandTimeout = -1; + + // Assert + settings.CommandTimeout.Should().Be(-1); + } + + [Fact] + public void Settings_ShouldSupportMaxIntCommandTimeout() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.CommandTimeout = int.MaxValue; + + // Assert + settings.CommandTimeout.Should().Be(int.MaxValue); + } + + [Fact] + public void Settings_ShouldSupportMinIntCommandTimeout() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.CommandTimeout = int.MinValue; + + // Assert + settings.CommandTimeout.Should().Be(int.MinValue); + } + + [Fact] + public void Settings_ShouldSupportDevelopmentMode() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.EnableSensitiveDataLogging = true; + settings.ConnectionString = "Server=localhost;Database=chatbot_dev;"; + + // Assert + settings.EnableSensitiveDataLogging.Should().BeTrue(); + settings.ConnectionString.Should().Be("Server=localhost;Database=chatbot_dev;"); + } + + [Fact] + public void Settings_ShouldSupportProductionMode() + { + // Arrange + var settings = new DatabaseSettings(); + + // Act + settings.EnableSensitiveDataLogging = false; + settings.ConnectionString = "Server=prod-server;Database=chatbot_prod;"; + + // Assert + settings.EnableSensitiveDataLogging.Should().BeFalse(); + settings.ConnectionString.Should().Be("Server=prod-server;Database=chatbot_prod;"); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithMultipleParameters() + { + // Arrange + var settings = new DatabaseSettings(); + var complexConnectionString = + "Server=localhost;Port=5432;Database=chatbot;Username=admin;Password=testpass;Pooling=true;MinPoolSize=5;MaxPoolSize=100;Connection Lifetime=300;Command Timeout=60;"; + + // Act + settings.ConnectionString = complexConnectionString; + + // Assert + settings.ConnectionString.Should().Be(complexConnectionString); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithSpaces() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithSpaces = + "Server = localhost ; Database = chatbot ; User Id = admin ; Password = testpass ;"; + + // Act + settings.ConnectionString = connectionStringWithSpaces; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithSpaces); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithQuotes() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithQuotes = + "Server=\"localhost\";Database=\"chatbot\";User Id=\"admin\";Password=\"testpass\";"; + + // Act + settings.ConnectionString = connectionStringWithQuotes; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithQuotes); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithSemicolons() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithSemicolons = + "Server=localhost;;Database=chatbot;;User Id=admin;;Password=testpass;;"; + + // Act + settings.ConnectionString = connectionStringWithSemicolons; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithSemicolons); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithEquals() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithEquals = + "Server=localhost;Database=chatbot;User Id=admin;Password=testpass=123;"; + + // Act + settings.ConnectionString = connectionStringWithEquals; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithEquals); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithNewlines() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithNewlines = + "Server=localhost;\nDatabase=chatbot;\nUser Id=admin;\nPassword=testpass;"; + + // Act + settings.ConnectionString = connectionStringWithNewlines; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithNewlines); + } + + [Fact] + public void Settings_ShouldSupportConnectionStringWithTabs() + { + // Arrange + var settings = new DatabaseSettings(); + var connectionStringWithTabs = + "Server=localhost;\tDatabase=chatbot;\tUser Id=admin;\tPassword=testpass;"; + + // Act + settings.ConnectionString = connectionStringWithTabs; + + // Assert + settings.ConnectionString.Should().Be(connectionStringWithTabs); + } +} diff --git a/ChatBot.Tests/Models/OllamaSettingsTests.cs b/ChatBot.Tests/Models/OllamaSettingsTests.cs new file mode 100644 index 0000000..f96ff58 --- /dev/null +++ b/ChatBot.Tests/Models/OllamaSettingsTests.cs @@ -0,0 +1,568 @@ +using ChatBot.Models.Configuration; +using FluentAssertions; + +namespace ChatBot.Tests.Models; + +public class OllamaSettingsTests +{ + [Fact] + public void Constructor_ShouldInitializePropertiesWithDefaultValues() + { + // Arrange & Act + var settings = new OllamaSettings(); + + // Assert + settings.Url.Should().Be("http://localhost:11434"); + settings.DefaultModel.Should().Be("llama3"); + } + + [Fact] + public void Url_ShouldBeSettable() + { + // Arrange + var settings = new OllamaSettings(); + var expectedUrl = "http://ollama.example.com:11434"; + + // Act + settings.Url = expectedUrl; + + // Assert + settings.Url.Should().Be(expectedUrl); + } + + [Fact] + public void DefaultModel_ShouldBeSettable() + { + // Arrange + var settings = new OllamaSettings(); + var expectedModel = "llama3.1:8b"; + + // Act + settings.DefaultModel = expectedModel; + + // Assert + settings.DefaultModel.Should().Be(expectedModel); + } + + [Theory] + [InlineData("")] + [InlineData("http://localhost:11434")] + [InlineData("https://ollama.example.com:11434")] + [InlineData("http://192.168.1.100:11434")] + [InlineData("https://api.ollama.com")] + [InlineData("http://localhost")] + [InlineData("https://ollama.example.com")] + public void Url_ShouldAcceptVariousFormats(string url) + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = url; + + // Assert + settings.Url.Should().Be(url); + } + + [Theory] + [InlineData("")] + [InlineData("llama3")] + [InlineData("llama3.1")] + [InlineData("llama3.1:8b")] + [InlineData("llama3.1:70b")] + [InlineData("gemma2")] + [InlineData("gemma2:2b")] + [InlineData("gemma2:9b")] + [InlineData("mistral")] + [InlineData("mistral:7b")] + [InlineData("codellama")] + [InlineData("codellama:7b")] + [InlineData("phi3")] + [InlineData("phi3:3.8b")] + [InlineData("qwen2")] + [InlineData("qwen2:7b")] + public void DefaultModel_ShouldAcceptVariousModels(string model) + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = model; + + // Assert + settings.DefaultModel.Should().Be(model); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://custom-ollama.example.com:8080"; + settings.DefaultModel = "custom-model:latest"; + + // Assert + settings.Url.Should().Be("https://custom-ollama.example.com:8080"); + settings.DefaultModel.Should().Be("custom-model:latest"); + } + + [Fact] + public void Settings_ShouldSupportEmptyUrl() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = ""; + + // Assert + settings.Url.Should().Be(""); + } + + [Fact] + public void Settings_ShouldSupportEmptyModel() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = ""; + + // Assert + settings.DefaultModel.Should().Be(""); + } + + [Fact] + public void Settings_ShouldSupportNullUrl() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = null!; + + // Assert + settings.Url.Should().BeNull(); + } + + [Fact] + public void Settings_ShouldSupportNullModel() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = null!; + + // Assert + settings.DefaultModel.Should().BeNull(); + } + + [Fact] + public void Settings_ShouldSupportLocalhostUrl() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://localhost:11434"; + + // Assert + settings.Url.Should().Be("http://localhost:11434"); + } + + [Fact] + public void Settings_ShouldSupportHttpsUrl() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://ollama.example.com:11434"; + + // Assert + settings.Url.Should().Be("https://ollama.example.com:11434"); + } + + [Fact] + public void Settings_ShouldSupportCustomPort() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://localhost:8080"; + + // Assert + settings.Url.Should().Be("http://localhost:8080"); + } + + [Fact] + public void Settings_ShouldSupportIpAddress() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://192.168.1.100:11434"; + + // Assert + settings.Url.Should().Be("http://192.168.1.100:11434"); + } + + [Fact] + public void Settings_ShouldSupportDomainName() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://api.ollama.com"; + + // Assert + settings.Url.Should().Be("https://api.ollama.com"); + } + + [Fact] + public void Settings_ShouldSupportLlama3Model() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "llama3"; + + // Assert + settings.DefaultModel.Should().Be("llama3"); + } + + [Fact] + public void Settings_ShouldSupportLlama31Model() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "llama3.1:8b"; + + // Assert + settings.DefaultModel.Should().Be("llama3.1:8b"); + } + + [Fact] + public void Settings_ShouldSupportGemmaModel() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "gemma2:9b"; + + // Assert + settings.DefaultModel.Should().Be("gemma2:9b"); + } + + [Fact] + public void Settings_ShouldSupportMistralModel() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "mistral:7b"; + + // Assert + settings.DefaultModel.Should().Be("mistral:7b"); + } + + [Fact] + public void Settings_ShouldSupportCodeLlamaModel() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "codellama:7b"; + + // Assert + settings.DefaultModel.Should().Be("codellama:7b"); + } + + [Fact] + public void Settings_ShouldSupportPhi3Model() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "phi3:3.8b"; + + // Assert + settings.DefaultModel.Should().Be("phi3:3.8b"); + } + + [Fact] + public void Settings_ShouldSupportQwen2Model() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "qwen2:7b"; + + // Assert + settings.DefaultModel.Should().Be("qwen2:7b"); + } + + [Fact] + public void Settings_ShouldSupportVeryLongUrl() + { + // Arrange + var settings = new OllamaSettings(); + var longUrl = + "https://very-long-domain-name-that-might-be-used-in-some-cases.example.com:11434"; + + // Act + settings.Url = longUrl; + + // Assert + settings.Url.Should().Be(longUrl); + } + + [Fact] + public void Settings_ShouldSupportVeryLongModelName() + { + // Arrange + var settings = new OllamaSettings(); + var longModelName = "very-long-model-name-that-might-be-used-in-some-cases:latest"; + + // Act + settings.DefaultModel = longModelName; + + // Assert + settings.DefaultModel.Should().Be(longModelName); + } + + [Fact] + public void Settings_ShouldSupportUrlWithPath() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://ollama.example.com/api/v1"; + + // Assert + settings.Url.Should().Be("https://ollama.example.com/api/v1"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithQueryParameters() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://ollama.example.com:11434?timeout=30&retries=3"; + + // Assert + settings.Url.Should().Be("https://ollama.example.com:11434?timeout=30&retries=3"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithFragment() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://ollama.example.com:11434#api"; + + // Assert + settings.Url.Should().Be("https://ollama.example.com:11434#api"); + } + + [Fact] + public void Settings_ShouldSupportModelWithVersion() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "llama3.1:8b-instruct-q4_0"; + + // Assert + settings.DefaultModel.Should().Be("llama3.1:8b-instruct-q4_0"); + } + + [Fact] + public void Settings_ShouldSupportModelWithCustomTag() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "custom-model:my-tag"; + + // Assert + settings.DefaultModel.Should().Be("custom-model:my-tag"); + } + + [Fact] + public void Settings_ShouldSupportModelWithSpecialCharacters() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "model-name_with.special+chars:version-tag"; + + // Assert + settings.DefaultModel.Should().Be("model-name_with.special+chars:version-tag"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithSpecialCharacters() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://user:pass@ollama.example.com:11434"; + + // Assert + settings.Url.Should().Be("https://user:pass@ollama.example.com:11434"); + } + + [Fact] + public void Settings_ShouldSupportModelWithUnicode() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "модель:версия"; + + // Assert + settings.DefaultModel.Should().Be("модель:версия"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithUnicode() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "https://оллама.пример.ком:11434"; + + // Assert + settings.Url.Should().Be("https://оллама.пример.ком:11434"); + } + + [Fact] + public void Settings_ShouldSupportModelWithNumbers() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "model123:version456"; + + // Assert + settings.DefaultModel.Should().Be("model123:version456"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithNumbers() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://192.168.1.100:11434"; + + // Assert + settings.Url.Should().Be("http://192.168.1.100:11434"); + } + + [Fact] + public void Settings_ShouldSupportModelWithUnderscores() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "my_model_name:my_version_tag"; + + // Assert + settings.DefaultModel.Should().Be("my_model_name:my_version_tag"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithUnderscores() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://my_ollama_server.example.com:11434"; + + // Assert + settings.Url.Should().Be("http://my_ollama_server.example.com:11434"); + } + + [Fact] + public void Settings_ShouldSupportModelWithHyphens() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "my-model-name:my-version-tag"; + + // Assert + settings.DefaultModel.Should().Be("my-model-name:my-version-tag"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithHyphens() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://my-ollama-server.example.com:11434"; + + // Assert + settings.Url.Should().Be("http://my-ollama-server.example.com:11434"); + } + + [Fact] + public void Settings_ShouldSupportModelWithDots() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.DefaultModel = "my.model.name:my.version.tag"; + + // Assert + settings.DefaultModel.Should().Be("my.model.name:my.version.tag"); + } + + [Fact] + public void Settings_ShouldSupportUrlWithDots() + { + // Arrange + var settings = new OllamaSettings(); + + // Act + settings.Url = "http://my.ollama.server.example.com:11434"; + + // Assert + settings.Url.Should().Be("http://my.ollama.server.example.com:11434"); + } +} diff --git a/ChatBot.Tests/Models/TelegramBotSettingsTests.cs b/ChatBot.Tests/Models/TelegramBotSettingsTests.cs new file mode 100644 index 0000000..9b29d8e --- /dev/null +++ b/ChatBot.Tests/Models/TelegramBotSettingsTests.cs @@ -0,0 +1,742 @@ +using ChatBot.Models.Configuration; +using FluentAssertions; + +namespace ChatBot.Tests.Models; + +public class TelegramBotSettingsTests +{ + [Fact] + public void Constructor_ShouldInitializePropertiesWithDefaultValues() + { + // Arrange & Act + var settings = new TelegramBotSettings(); + + // Assert + settings.BotToken.Should().Be(string.Empty); + } + + [Fact] + public void BotToken_ShouldBeSettable() + { + // Arrange + var settings = new TelegramBotSettings(); + var expectedToken = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"; + + // Act + settings.BotToken = expectedToken; + + // Assert + settings.BotToken.Should().Be(expectedToken); + } + + [Theory] + [InlineData("")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk-")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk_")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk.")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk+")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk/")] + [InlineData("1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk=")] + public void BotToken_ShouldAcceptVariousFormats(string token) + { + // Arrange + var settings = new TelegramBotSettings(); + + // Act + settings.BotToken = token; + + // Assert + settings.BotToken.Should().Be(token); + } + + [Fact] + public void AllProperties_ShouldBeMutable() + { + // Arrange + var settings = new TelegramBotSettings(); + + // Act + settings.BotToken = "9876543210:ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba"; + + // Assert + settings + .BotToken.Should() + .Be("9876543210:ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba"); + } + + [Fact] + public void Settings_ShouldSupportEmptyToken() + { + // Arrange + var settings = new TelegramBotSettings(); + + // Act + settings.BotToken = ""; + + // Assert + settings.BotToken.Should().Be(""); + } + + [Fact] + public void Settings_ShouldSupportNullToken() + { + // Arrange + var settings = new TelegramBotSettings(); + + // Act + settings.BotToken = null!; + + // Assert + settings.BotToken.Should().BeNull(); + } + + [Fact] + public void Settings_ShouldSupportValidBotToken() + { + // Arrange + var settings = new TelegramBotSettings(); + var validToken = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"; + + // Act + settings.BotToken = validToken; + + // Assert + settings.BotToken.Should().Be(validToken); + } + + [Fact] + public void Settings_ShouldSupportShortBotToken() + { + // Arrange + var settings = new TelegramBotSettings(); + var shortToken = "123:ABC"; + + // Act + settings.BotToken = shortToken; + + // Assert + settings.BotToken.Should().Be(shortToken); + } + + [Fact] + public void Settings_ShouldSupportLongBotToken() + { + // Arrange + var settings = new TelegramBotSettings(); + var longToken = + "12345678901234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; + + // Act + settings.BotToken = longToken; + + // Assert + settings.BotToken.Should().Be(longToken); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithNumbers() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithNumbers = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk1234567890"; + + // Act + settings.BotToken = tokenWithNumbers; + + // Assert + settings.BotToken.Should().Be(tokenWithNumbers); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithLetters() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithLetters = "abcdefghij:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"; + + // Act + settings.BotToken = tokenWithLetters; + + // Assert + settings.BotToken.Should().Be(tokenWithLetters); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithMixedCase() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithMixedCase = "1234567890:AbCdEfGhIjKlMnOpQrStUvWxYz"; + + // Act + settings.BotToken = tokenWithMixedCase; + + // Assert + settings.BotToken.Should().Be(tokenWithMixedCase); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithSpecialCharacters() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithSpecialChars = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk-_.+/="; + + // Act + settings.BotToken = tokenWithSpecialChars; + + // Assert + settings.BotToken.Should().Be(tokenWithSpecialChars); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithColon() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithColon = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk:"; + + // Act + settings.BotToken = tokenWithColon; + + // Assert + settings.BotToken.Should().Be(tokenWithColon); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithUnderscores() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithUnderscores = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk_"; + + // Act + settings.BotToken = tokenWithUnderscores; + + // Assert + settings.BotToken.Should().Be(tokenWithUnderscores); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithHyphens() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithHyphens = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk-"; + + // Act + settings.BotToken = tokenWithHyphens; + + // Assert + settings.BotToken.Should().Be(tokenWithHyphens); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithDots() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithDots = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk."; + + // Act + settings.BotToken = tokenWithDots; + + // Assert + settings.BotToken.Should().Be(tokenWithDots); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithPlus() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithPlus = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk+"; + + // Act + settings.BotToken = tokenWithPlus; + + // Assert + settings.BotToken.Should().Be(tokenWithPlus); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithSlash() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithSlash = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk/"; + + // Act + settings.BotToken = tokenWithSlash; + + // Assert + settings.BotToken.Should().Be(tokenWithSlash); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithEquals() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithEquals = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk="; + + // Act + settings.BotToken = tokenWithEquals; + + // Assert + settings.BotToken.Should().Be(tokenWithEquals); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithUnicode() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithUnicode = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkабвгд"; + + // Act + settings.BotToken = tokenWithUnicode; + + // Assert + settings.BotToken.Should().Be(tokenWithUnicode); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithSpaces() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithSpaces = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk "; + + // Act + settings.BotToken = tokenWithSpaces; + + // Assert + settings.BotToken.Should().Be(tokenWithSpaces); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithTabs() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithTabs = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk\t"; + + // Act + settings.BotToken = tokenWithTabs; + + // Assert + settings.BotToken.Should().Be(tokenWithTabs); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithNewlines() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithNewlines = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk\n"; + + // Act + settings.BotToken = tokenWithNewlines; + + // Assert + settings.BotToken.Should().Be(tokenWithNewlines); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithCarriageReturn() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithCarriageReturn = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk\r"; + + // Act + settings.BotToken = tokenWithCarriageReturn; + + // Assert + settings.BotToken.Should().Be(tokenWithCarriageReturn); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithQuotes() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithQuotes = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk\""; + + // Act + settings.BotToken = tokenWithQuotes; + + // Assert + settings.BotToken.Should().Be(tokenWithQuotes); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithSingleQuotes() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithSingleQuotes = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk'"; + + // Act + settings.BotToken = tokenWithSingleQuotes; + + // Assert + settings.BotToken.Should().Be(tokenWithSingleQuotes); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithBackslashes() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithBackslashes = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk\\"; + + // Act + settings.BotToken = tokenWithBackslashes; + + // Assert + settings.BotToken.Should().Be(tokenWithBackslashes); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithForwardSlashes() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithForwardSlashes = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk/"; + + // Act + settings.BotToken = tokenWithForwardSlashes; + + // Assert + settings.BotToken.Should().Be(tokenWithForwardSlashes); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithPipes() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithPipes = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk|"; + + // Act + settings.BotToken = tokenWithPipes; + + // Assert + settings.BotToken.Should().Be(tokenWithPipes); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithAmpersands() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithAmpersands = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk&"; + + // Act + settings.BotToken = tokenWithAmpersands; + + // Assert + settings.BotToken.Should().Be(tokenWithAmpersands); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithPercents() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithPercents = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk%"; + + // Act + settings.BotToken = tokenWithPercents; + + // Assert + settings.BotToken.Should().Be(tokenWithPercents); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithDollarSigns() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithDollarSigns = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk$"; + + // Act + settings.BotToken = tokenWithDollarSigns; + + // Assert + settings.BotToken.Should().Be(tokenWithDollarSigns); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithAtSigns() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithAtSigns = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk@"; + + // Act + settings.BotToken = tokenWithAtSigns; + + // Assert + settings.BotToken.Should().Be(tokenWithAtSigns); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithHashSigns() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithHashSigns = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk#"; + + // Act + settings.BotToken = tokenWithHashSigns; + + // Assert + settings.BotToken.Should().Be(tokenWithHashSigns); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithExclamationMarks() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithExclamationMarks = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk!"; + + // Act + settings.BotToken = tokenWithExclamationMarks; + + // Assert + settings.BotToken.Should().Be(tokenWithExclamationMarks); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithQuestionMarks() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithQuestionMarks = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk?"; + + // Act + settings.BotToken = tokenWithQuestionMarks; + + // Assert + settings.BotToken.Should().Be(tokenWithQuestionMarks); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithBrackets() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithBrackets = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk[]"; + + // Act + settings.BotToken = tokenWithBrackets; + + // Assert + settings.BotToken.Should().Be(tokenWithBrackets); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithBraces() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithBraces = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk{}"; + + // Act + settings.BotToken = tokenWithBraces; + + // Assert + settings.BotToken.Should().Be(tokenWithBraces); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithParentheses() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithParentheses = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk()"; + + // Act + settings.BotToken = tokenWithParentheses; + + // Assert + settings.BotToken.Should().Be(tokenWithParentheses); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithAngleBrackets() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithAngleBrackets = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk<>"; + + // Act + settings.BotToken = tokenWithAngleBrackets; + + // Assert + settings.BotToken.Should().Be(tokenWithAngleBrackets); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithTildes() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithTildes = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk~"; + + // Act + settings.BotToken = tokenWithTildes; + + // Assert + settings.BotToken.Should().Be(tokenWithTildes); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithCaret() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithCaret = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk^"; + + // Act + settings.BotToken = tokenWithCaret; + + // Assert + settings.BotToken.Should().Be(tokenWithCaret); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithBackticks() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithBackticks = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk`"; + + // Act + settings.BotToken = tokenWithBackticks; + + // Assert + settings.BotToken.Should().Be(tokenWithBackticks); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithSemicolons() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithSemicolons = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk;"; + + // Act + settings.BotToken = tokenWithSemicolons; + + // Assert + settings.BotToken.Should().Be(tokenWithSemicolons); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithCommas() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithCommas = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk,"; + + // Act + settings.BotToken = tokenWithCommas; + + // Assert + settings.BotToken.Should().Be(tokenWithCommas); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithPeriods() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithPeriods = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk."; + + // Act + settings.BotToken = tokenWithPeriods; + + // Assert + settings.BotToken.Should().Be(tokenWithPeriods); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithAllSpecialCharacters() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithAllSpecialChars = + "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk-_.+/=:;\"'\\|&%$@#!?[]{}()<>~^`"; + + // Act + settings.BotToken = tokenWithAllSpecialChars; + + // Assert + settings.BotToken.Should().Be(tokenWithAllSpecialChars); + } + + [Fact] + public void Settings_ShouldSupportVeryLongBotToken() + { + // Arrange + var settings = new TelegramBotSettings(); + var veryLongToken = + "12345678901234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // Act + settings.BotToken = veryLongToken; + + // Assert + settings.BotToken.Should().Be(veryLongToken); + settings.BotToken.Length.Should().BeGreaterThan(100); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithOnlyNumbers() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithOnlyNumbers = "1234567890:123456789012345678901234567890"; + + // Act + settings.BotToken = tokenWithOnlyNumbers; + + // Assert + settings.BotToken.Should().Be(tokenWithOnlyNumbers); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithOnlyLetters() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithOnlyLetters = "abcdefghij:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"; + + // Act + settings.BotToken = tokenWithOnlyLetters; + + // Assert + settings.BotToken.Should().Be(tokenWithOnlyLetters); + } + + [Fact] + public void Settings_ShouldSupportBotTokenWithMixedContent() + { + // Arrange + var settings = new TelegramBotSettings(); + var tokenWithMixedContent = + "1234567890:AbC123dEf456GhI789jKl012MnO345pQr678sTu901vWx234yZ567"; + + // Act + settings.BotToken = tokenWithMixedContent; + + // Assert + settings.BotToken.Should().Be(tokenWithMixedContent); + } +} diff --git a/test_coverage_report.md b/test_coverage_report.md new file mode 100644 index 0000000..6472505 --- /dev/null +++ b/test_coverage_report.md @@ -0,0 +1,154 @@ +# Отчет о покрытии тестами проекта 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. Константы +- [ ] `AIResponseConstants` - тесты констант +- [ ] `ChatTypes` - тесты типов чатов + +### 3. Сервисы (дополнительные тесты) +- [ ] `SystemPromptService` - тесты обработки ошибок при загрузке файлов +- [ ] `ModelService` - тесты с различными настройками +- [ ] `AIService` - тесты обработки ошибок и retry логики +- [ ] `ChatService` - тесты edge cases и обработки ошибок +- [ ] `DatabaseInitializationService` - тесты обработки ошибок БД +- [ ] `HistoryCompressionService` - тесты различных сценариев сжатия + +### 4. Telegram команды (дополнительные тесты) +- [ ] `TelegramCommandBase` - тесты базового класса команд +- [ ] `TelegramCommandProcessor` - тесты обработки команд +- [ ] `TelegramCommandContext` - тесты контекста команд +- [ ] `ReplyInfo` - тесты информации о ответах +- [ ] `CommandAttribute` - тесты атрибутов команд + +### 5. Telegram сервисы (дополнительные тесты) +- [ ] `TelegramBotClientWrapper` - тесты обертки клиента +- [ ] `TelegramMessageHandler` - тесты различных типов сообщений +- [ ] `TelegramErrorHandler` - тесты различных типов ошибок +- [ ] `TelegramMessageSender` - тесты отправки различных типов сообщений + +### 6. Интерфейсы +- [ ] `IAIService` - тесты интерфейса +- [ ] `ISessionStorage` - тесты интерфейса +- [ ] `IHistoryCompressionService` - тесты интерфейса +- [ ] `IOllamaClient` - тесты интерфейса +- [ ] `ITelegramBotClientWrapper` - тесты интерфейса +- [ ] `IChatSessionRepository` - тесты интерфейса + +### 7. Контекст базы данных +- [ ] `ChatBotDbContext` - тесты контекста БД +- [ ] Миграции - тесты миграций + +### 8. Основной файл приложения +- [ ] `Program.cs` - тесты конфигурации и инициализации + +### 9. Валидаторы (дополнительные тесты) +- [ ] `AISettingsValidator` - тесты всех валидационных правил +- [ ] `DatabaseSettingsValidator` - тесты всех валидационных правил +- [ ] `OllamaSettingsValidator` - тесты всех валидационных правил +- [ ] `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. Создать дополнительные интеграционные тесты