using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using GermanApp.Application.Models; using GermanApp.Infrastructure.Configuration; using GermanApp.Infrastructure.Services; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace GermanApp.Tests.Unit.Infrastructure.Services; [TestClass] public class MistralConnectorTests { private HttpClient _httpClient; private MistralConfig _config; private MistralConnector _connector; [TestInitialize] public void Setup() { _config = new MistralConfig { ApiKey = "test-api-key", BaseUrl = "https://api.mistral.ai/v1/", DefaultModel = "mistral-medium", TimeoutSeconds = 30, MaxRetries = 3, RateLimitPerMinute = 10, EnableCaching = true, CacheTTLMinutes = 60, CircuitBreakerFailureThreshold = 5, CircuitBreakerResetMinutes = 1 }; _httpClient = new HttpClient(); _connector = new MistralConnector(_httpClient, _config, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance, new Microsoft.Extensions.Caching.Memory.MemoryCache( Microsoft.Extensions.Options.Options.Create(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions()))); } [TestCleanup] public void Cleanup() { _httpClient?.Dispose(); } [TestMethod] public void Constructor_WithValidParameters_CreatesConnector() { Assert.IsNotNull(_connector); } [TestMethod] public void Constructor_WithEmptyApiKey_ThrowsArgumentException() { var invalidConfig = new MistralConfig { ApiKey = "", BaseUrl = "https://api.mistral.ai/v1/" }; try { new MistralConnector(_httpClient, invalidConfig, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance, new Microsoft.Extensions.Caching.Memory.MemoryCache( Microsoft.Extensions.Options.Options.Create(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions()))); Assert.Fail("Expected ArgumentException was not thrown"); } catch (ArgumentException) { // Expected } } [TestMethod] public void Constructor_WithInvalidBaseUrl_ThrowsArgumentException() { var invalidConfig = new MistralConfig { ApiKey = "key", BaseUrl = "not-a-url" }; try { new MistralConnector(_httpClient, invalidConfig, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance, new Microsoft.Extensions.Caching.Memory.MemoryCache( Microsoft.Extensions.Options.Options.Create(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions()))); Assert.Fail("Expected ArgumentException was not thrown"); } catch (ArgumentException) { // Expected } } [TestMethod] public void Constructor_ConfiguresHttpClient() { Assert.AreEqual(new Uri("https://api.mistral.ai/v1/"), _httpClient.BaseAddress); Assert.AreEqual(TimeSpan.FromSeconds(30), _httpClient.Timeout); Assert.AreEqual("Bearer test-api-key", _httpClient.DefaultRequestHeaders.Authorization?.ToString()); Assert.IsTrue(_httpClient.DefaultRequestHeaders.Accept.Any(h => h.MediaType == "application/json")); } [TestMethod] public void MistralConfig_Validate_WithValidConfig_Passes() { var config = new MistralConfig { ApiKey = "valid-key", BaseUrl = "https://api.mistral.ai/v1/" }; // Should not throw config.Validate(); } [TestMethod] public void MistralConfig_Validate_WithEmptyApiKey_Throws() { var config = new MistralConfig { ApiKey = "", BaseUrl = "https://api.mistral.ai/v1/" }; try { config.Validate(); Assert.Fail("Expected ArgumentException was not thrown"); } catch (ArgumentException) { // Expected } } [TestMethod] public void MistralConfig_Validate_WithInvalidTimeout_Throws() { var config = new MistralConfig { ApiKey = "key", BaseUrl = "https://api.mistral.ai/v1/", TimeoutSeconds = 0 }; try { config.Validate(); Assert.Fail("Expected ArgumentException was not thrown"); } catch (ArgumentException) { // Expected } } [TestMethod] public void MistralConfig_Validate_WithNegativeMaxRetries_Throws() { var config = new MistralConfig { ApiKey = "key", BaseUrl = "https://api.mistral.ai/v1/", MaxRetries = -1 }; try { config.Validate(); Assert.Fail("Expected ArgumentException was not thrown"); } catch (ArgumentException) { // Expected } } [TestMethod] public void MistralRequest_CreateCompletion_CreatesRequest() { var request = MistralRequest.CreateCompletion("Test prompt", "mistral-medium", 512); Assert.AreEqual("Test prompt", request.Prompt); Assert.AreEqual("mistral-medium", request.Model); Assert.AreEqual(512, request.MaxTokens); Assert.AreEqual(0.7, request.Temperature); } [TestMethod] public void MistralChatRequest_CreateChat_CreatesRequest() { var messages = new List { MistralMessage.User("Hello") }; var request = MistralChatRequest.CreateChat(messages, "mistral-medium", 512); Assert.AreEqual(1, request.Messages.Count); Assert.AreEqual("user", request.Messages[0].Role); Assert.AreEqual("Hello", request.Messages[0].Content); } [TestMethod] public void MistralMessage_FactoryMethods_CreateMessages() { var userMsg = MistralMessage.User("User message"); var assistantMsg = MistralMessage.Assistant("Assistant message"); var systemMsg = MistralMessage.System("System message"); Assert.AreEqual("user", userMsg.Role); Assert.AreEqual("User message", userMsg.Content); Assert.AreEqual("assistant", assistantMsg.Role); Assert.AreEqual("Assistant message", assistantMsg.Content); Assert.AreEqual("system", systemMsg.Role); Assert.AreEqual("System message", systemMsg.Content); } [TestMethod] public void MistralResponse_HasValidChoices_WithEmptyChoices_ReturnsFalse() { var response = new MistralResponse { Model = "mistral-medium", Choices = new List(), Usage = new MistralUsage() }; Assert.IsFalse(response.HasValidChoices); } [TestMethod] public void MistralResponse_HasValidChoices_WithText_ReturnsTrue() { var choices = new List { new MistralChoice { Text = "Test completion", Index = 0 } }; var response = new MistralResponse { Model = "mistral-medium", Choices = choices, Usage = new MistralUsage() }; Assert.IsTrue(response.HasValidChoices); Assert.AreEqual("Test completion", response.FirstChoiceText); } [TestMethod] public void MistralUsage_EstimatedCost_CalculatesCorrectly() { var usage = new MistralUsage { PromptTokens = 100, CompletionTokens = 50, TotalTokens = 150 }; // Approx $0.0000025 per token for mistral-medium decimal expectedCost = 150 * 0.0000025m; Assert.AreEqual(expectedCost, usage.EstimatedCost); } [TestMethod] public void MistralRateLimiter_TryAcquire_WithinLimit_ReturnsTrue() { var limiter = new MistralRateLimiter(10); for (int i = 0; i < 10; i++) { Assert.IsTrue(limiter.TryAcquire("test")); } } [TestMethod] public void MistralRateLimiter_TryAcquire_ExceedsLimit_ReturnsFalse() { var limiter = new MistralRateLimiter(2); Assert.IsTrue(limiter.TryAcquire("test")); Assert.IsTrue(limiter.TryAcquire("test")); Assert.IsFalse(limiter.TryAcquire("test")); } [TestMethod] public void MistralCircuitBreaker_IsClosed_Initially_ReturnsTrue() { var breaker = new MistralCircuitBreaker(5, TimeSpan.FromMinutes(1)); Assert.IsTrue(breaker.IsClosed); } [TestMethod] public void MistralCircuitBreaker_AfterFailures_Opens() { var breaker = new MistralCircuitBreaker(2, TimeSpan.FromMinutes(1)); breaker.RecordFailure(); breaker.RecordFailure(); Assert.IsFalse(breaker.IsClosed); } [TestMethod] public void MistralCircuitBreaker_AfterSuccess_Resets() { var breaker = new MistralCircuitBreaker(2, TimeSpan.FromMinutes(1)); breaker.RecordFailure(); breaker.RecordSuccess(); Assert.IsTrue(breaker.IsClosed); } [TestMethod] public void MistralCircuitBreaker_AfterResetTimeout_ResetsAutomatically() { var breaker = new MistralCircuitBreaker(1, TimeSpan.FromMilliseconds(10)); breaker.RecordFailure(); Assert.IsFalse(breaker.IsClosed); // Wait for reset System.Threading.Tasks.Task.Delay(20).Wait(); Assert.IsTrue(breaker.IsClosed); } }