using System; using System.Net.Http; using System.Threading.Tasks; using GermanApp.Infrastructure.Configuration; using GermanApp.Infrastructure.Services; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace GermanApp.Tests.Unit.Infrastructure.Services; /// /// Unit tests for MistralConnector. /// Tests configuration, BaseAddress setup, and error handling. /// [TestClass] public class MistralConnectorTests { private HttpClient _httpClient; private Mock> _loggerMock; private IMemoryCache _cache; [TestInitialize] public void TestInitialize() { _httpClient = new HttpClient(); _loggerMock = new Mock>(); _cache = new MemoryCache(new MemoryCacheOptions()); } [TestCleanup] public void TestCleanup() { _httpClient?.Dispose(); } // ============================================ // Configuration Tests // ============================================ [TestMethod] public void Constructor_SetsBaseAddress_WhenValidConfigProvided() { // Arrange var config = new MistralConfig { ApiKey = "test-api-key", BaseUrl = "https://api.mistral.ai/v1/" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert Assert.IsNotNull(_httpClient.BaseAddress); Assert.AreEqual(new Uri("https://api.mistral.ai/v1/"), _httpClient.BaseAddress); } [TestMethod] public void Constructor_SetsBaseAddress_WhenApiKeyIsPlaceholder() { // Arrange var config = new MistralConfig { ApiKey = "your-mistral-api-key-here", // Placeholder key BaseUrl = "https://api.mistral.ai/v1/" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert // Even with a placeholder API key, BaseAddress should be set Assert.IsNotNull(_httpClient.BaseAddress); Assert.AreEqual(new Uri("https://api.mistral.ai/v1/"), _httpClient.BaseAddress); } [TestMethod] public void Constructor_SetsBaseAddress_WhenApiKeyIsEmpty() { // Arrange var config = new MistralConfig { ApiKey = string.Empty, // Empty API key BaseUrl = "https://api.mistral.ai/v1/" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert // Even without an API key, BaseAddress should be set for testing Assert.IsNotNull(_httpClient.BaseAddress); Assert.AreEqual(new Uri("https://api.mistral.ai/v1/"), _httpClient.BaseAddress); } [TestMethod] public void Constructor_UsesDefaultConfig_WhenNullConfigProvided() { // Arrange & Act var connector = new MistralConnector(_httpClient, null!, _loggerMock.Object, _cache); // Assert // Should use default BaseUrl from MistralConfig Assert.IsNotNull(_httpClient.BaseAddress); Assert.AreEqual(new Uri("https://api.mistral.ai/v1/"), _httpClient.BaseAddress); } [TestMethod] public void Constructor_SetsTimeout_WhenValidConfigProvided() { // Arrange var config = new MistralConfig { ApiKey = "test-api-key", BaseUrl = "https://api.mistral.ai/v1/", TimeoutSeconds = 60 }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert Assert.AreEqual(TimeSpan.FromSeconds(60), _httpClient.Timeout); } [TestMethod] public void Constructor_AddsAuthHeader_WhenApiKeyIsValid() { // Arrange var config = new MistralConfig { ApiKey = "test-api-key", BaseUrl = "https://api.mistral.ai/v1/" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert Assert.IsTrue(_httpClient.DefaultRequestHeaders.Contains("Authorization")); var authHeader = _httpClient.DefaultRequestHeaders.GetValues("Authorization").FirstOrDefault(); Assert.AreEqual("Bearer test-api-key", authHeader); } [TestMethod] public void Constructor_DoesNotAddAuthHeader_WhenApiKeyIsPlaceholder() { // Arrange var config = new MistralConfig { ApiKey = "your-mistral-api-key-here", // Placeholder BaseUrl = "https://api.mistral.ai/v1/" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert // With placeholder key, auth header should still be added (it's valid non-whitespace) Assert.IsTrue(_httpClient.DefaultRequestHeaders.Contains("Authorization")); } [TestMethod] public void Constructor_DoesNotAddAuthHeader_WhenApiKeyIsEmpty() { // Arrange var config = new MistralConfig { ApiKey = string.Empty, BaseUrl = "https://api.mistral.ai/v1/" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert // With empty API key, auth header should NOT be added Assert.IsFalse(_httpClient.DefaultRequestHeaders.Contains("Authorization")); } // ============================================ // Error Handling Tests // ============================================ [TestMethod] public void Constructor_LogsError_WhenBaseUrlIsInvalid() { // Arrange var config = new MistralConfig { ApiKey = "test-api-key", BaseUrl = "not-a-valid-url" }; // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert // Should have logged an error about invalid BaseUrl _loggerMock.Verify(l => l.Log( LogLevel.Error, It.IsAny(), It.Is((v, t) => v.ToString().Contains("invalid or missing BaseUrl")), It.IsAny(), It.Is>((v, t) => true)), Times.Once); } [TestMethod] public void Constructor_DoesNotSetBaseAddress_WhenInEfDesignTime() { // Arrange var originalEfVar = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_EF"); Environment.SetEnvironmentVariable("DOTNET_RUNNING_IN_EF", "true"); var config = new MistralConfig { ApiKey = "test-api-key", BaseUrl = "https://api.mistral.ai/v1/" }; try { // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert // During EF migrations, BaseAddress should NOT be set Assert.IsNull(_httpClient.BaseAddress); } finally { // Restore original value if (originalEfVar == null) Environment.SetEnvironmentVariable("DOTNET_RUNNING_IN_EF", null); else Environment.SetEnvironmentVariable("DOTNET_RUNNING_IN_EF", originalEfVar); } } [TestMethod] public void Constructor_SetsJsonOptions_Always() { // Arrange var config = new MistralConfig(); // Act var connector = new MistralConnector(_httpClient, config, _loggerMock.Object, _cache); // Assert - this tests internal state through reflection if needed // For now, we can verify indirectly that methods work // This is a basic sanity check Assert.IsNotNull(connector); } }