using ChatBot.Models.Configuration; using ChatBot.Services; using ChatBot.Tests.TestUtilities; using FluentAssertions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; namespace ChatBot.Tests.Services; public class ModelServiceTests : UnitTestBase { private readonly Mock> _loggerMock; private readonly Mock> _optionsMock; private readonly ModelService _modelService; public ModelServiceTests() { _loggerMock = TestDataBuilder.Mocks.CreateLoggerMock(); var ollamaSettings = TestDataBuilder.Configurations.CreateOllamaSettings(); _optionsMock = TestDataBuilder.Mocks.CreateOptionsMock(ollamaSettings); _modelService = new ModelService(_loggerMock.Object, _optionsMock.Object); } [Fact] public void GetCurrentModel_ShouldReturnDefaultModel() { // Act var result = _modelService.GetCurrentModel(); // Assert result.Should().Be("llama3.2"); } [Fact] public async Task InitializeAsync_ShouldLogModelInformation() { // Act var act = async () => await _modelService.InitializeAsync(); // Assert await act.Should() .NotThrowAsync("InitializeAsync should complete without throwing exceptions"); // Verify that logging was called (at least once for model information) _loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is((v, t) => v.ToString()!.Contains("model")), It.IsAny(), It.IsAny>() ), Times.AtLeastOnce, "Should log model information" ); } [Fact] public void GetCurrentModel_ShouldReturnCustomModel_WhenDifferentModelIsConfigured() { // Arrange var customSettings = new OllamaSettings { DefaultModel = "custom-model-name", Url = "http://custom-server:8080", }; var customOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(customSettings); var customService = new ModelService(_loggerMock.Object, customOptionsMock.Object); // Act var result = customService.GetCurrentModel(); // Assert result.Should().Be("custom-model-name"); } [Fact] public void GetCurrentModel_ShouldReturnEmptyString_WhenDefaultModelIsEmpty() { // Arrange var emptyModelSettings = new OllamaSettings { DefaultModel = string.Empty, Url = "http://localhost:11434", }; var emptyOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(emptyModelSettings); var emptyService = new ModelService(_loggerMock.Object, emptyOptionsMock.Object); // Act var result = emptyService.GetCurrentModel(); // Assert result.Should().Be(string.Empty); } [Fact] public void GetCurrentModel_ShouldReturnNull_WhenDefaultModelIsNull() { // Arrange var nullModelSettings = new OllamaSettings { DefaultModel = null!, Url = "http://localhost:11434", }; var nullOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(nullModelSettings); var nullService = new ModelService(_loggerMock.Object, nullOptionsMock.Object); // Act var result = nullService.GetCurrentModel(); // Assert result.Should().BeNull(); } [Fact] public void GetCurrentModel_ShouldReturnModelWithSpecialCharacters_WhenModelNameContainsSpecialChars() { // Arrange var specialCharSettings = new OllamaSettings { DefaultModel = "model-with-special_chars.123", Url = "http://localhost:11434", }; var specialOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(specialCharSettings); var specialService = new ModelService(_loggerMock.Object, specialOptionsMock.Object); // Act var result = specialService.GetCurrentModel(); // Assert result.Should().Be("model-with-special_chars.123"); } [Fact] public void GetCurrentModel_ShouldReturnLongModelName_WhenModelNameIsVeryLong() { // Arrange var longModelName = new string('a', 1000); // Very long model name var longModelSettings = new OllamaSettings { DefaultModel = longModelName, Url = "http://localhost:11434", }; var longOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(longModelSettings); var longService = new ModelService(_loggerMock.Object, longOptionsMock.Object); // Act var result = longService.GetCurrentModel(); // Assert result.Should().Be(longModelName); result.Should().HaveLength(1000); } [Fact] public async Task InitializeAsync_ShouldLogCorrectModel_WhenDifferentModelIsConfigured() { // Arrange var customSettings = new OllamaSettings { DefaultModel = "custom-llama-model", Url = "http://custom-server:8080", }; var customOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(customSettings); var customService = new ModelService(_loggerMock.Object, customOptionsMock.Object); // Act await customService.InitializeAsync(); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is((v, t) => v.ToString()!.Contains("custom-llama-model")), It.IsAny(), It.IsAny>() ), Times.Once, "Should log the correct custom model name" ); } [Fact] public async Task InitializeAsync_ShouldLogEmptyModel_WhenModelIsEmpty() { // Arrange var emptyModelSettings = new OllamaSettings { DefaultModel = string.Empty, Url = "http://localhost:11434", }; var emptyOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(emptyModelSettings); var emptyService = new ModelService(_loggerMock.Object, emptyOptionsMock.Object); // Act await emptyService.InitializeAsync(); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is((v, t) => v.ToString()!.Contains("Using model:")), It.IsAny(), It.IsAny>() ), Times.Once, "Should log even when model is empty" ); } [Fact] public void Constructor_ShouldHandleNullOllamaSettings() { // Arrange var nullSettings = new OllamaSettings { DefaultModel = null!, Url = null! }; var nullOptionsMock = TestDataBuilder.Mocks.CreateOptionsMock(nullSettings); // Act & Assert var act = () => new ModelService(_loggerMock.Object, nullOptionsMock.Object); act.Should().NotThrow("Constructor should handle null settings gracefully"); } }