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);
}
}