using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using GermanApp.Application.Services; using GermanApp.Domain.Interfaces; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace GermanApp.Tests.Unit.Application.Services; [TestClass] public class AiFallbackServiceTests { private Mock _mockMistralService; private Mock _mockVoskService; private Mock _mockTtsService; private Mock> _mockLogger; private AiFallbackService _service; private readonly byte[] _sampleAudio = new byte[100]; [TestInitialize] public void Setup() { _mockMistralService = new Mock(); _mockVoskService = new Mock(); _mockTtsService = new Mock(); _mockLogger = new Mock>(); _service = new AiFallbackService( _mockMistralService.Object, _mockVoskService.Object, _mockTtsService.Object, _mockLogger.Object); // Initialize sample audio for (int i = 0; i < _sampleAudio.Length; i++) { _sampleAudio[i] = (byte)(i % 256); } } [TestMethod] public async Task GenerateStoryWithFallbackAsync_WhenPrimaryWorks_ReturnsPrimaryStory() { // Arrange var expectedStory = "Generated by AI..."; var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn", "Reise" }; var length = 200; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, topic, vocabularyWords, length, cancellationToken)) .ReturnsAsync(expectedStory); // Act var result = await _service.GenerateStoryWithFallbackAsync( level, topic, vocabularyWords, length, cancellationToken); // Assert Assert.AreEqual(expectedStory, result); _mockMistralService.Verify(s => s.GenerateStoryAsync( level, topic, vocabularyWords, length, cancellationToken), Times.Once); } [TestMethod] public async Task GenerateStoryWithFallbackAsync_WhenPrimaryFails_ReturnsFallback() { // Arrange var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn", "Reise" }; var length = 200; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, topic, vocabularyWords, length, cancellationToken)) .ThrowsAsync(new Exception("AI Error")); // Act var result = await _service.GenerateStoryWithFallbackAsync( level, topic, vocabularyWords, length, cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contains("A1")); Assert.IsTrue(result.Contains("Travel")); Assert.IsTrue(result.Contains("einfacher")); // Level description } [TestMethod] public async Task GenerateStoryWithFallbackAsync_WithNoMistralService_ReturnsFallback() { // Arrange var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn" }; var length = 200; var cancellationToken = CancellationToken.None; // Create service with null MistralService var service = new AiFallbackService( null, _mockVoskService.Object, _mockTtsService.Object, _mockLogger.Object); // Act var result = await service.GenerateStoryWithFallbackAsync( level, topic, vocabularyWords, length, cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contains("A1")); } [TestMethod] public async Task ProvideFeedbackWithFallbackAsync_WhenPrimaryWorks_ReturnsPrimaryFeedback() { // Arrange var expectedFeedback = "Great job!"; var userText = "Ich heisse Anna."; var level = "A1"; var customPrompt = "Focus on grammar"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, customPrompt, cancellationToken)) .ReturnsAsync(expectedFeedback); // Act var result = await _service.ProvideFeedbackWithFallbackAsync( userText, level, customPrompt, cancellationToken); // Assert Assert.AreEqual(expectedFeedback, result); } [TestMethod] public async Task ProvideFeedbackWithFallbackAsync_WhenPrimaryFails_ReturnsFallback() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, It.IsAny(), cancellationToken)) .ThrowsAsync(new Exception("AI Error")); // Act var result = await _service.ProvideFeedbackWithFallbackAsync( userText, level, null, cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contains("A1")); Assert.IsTrue(result.Contains("feedback") || result.Contains("Feedback")); } [TestMethod] public async Task RecognizeSpeechWithFallbackAsync_WhenPrimaryWorks_ReturnsPrimaryText() { // Arrange var expectedText = "Hallo Welt"; var cancellationToken = CancellationToken.None; _mockVoskService.Setup(s => s.RecognizeSpeechAsync( _sampleAudio, 16000, It.IsAny(), cancellationToken)) .ReturnsAsync(expectedText); // Act var result = await _service.RecognizeSpeechWithFallbackAsync( _sampleAudio, cancellationToken); // Assert Assert.AreEqual(expectedText, result); } [TestMethod] public async Task RecognizeSpeechWithFallbackAsync_WhenPrimaryFails_ReturnsFallback() { // Arrange var cancellationToken = CancellationToken.None; _mockVoskService.Setup(s => s.RecognizeSpeechAsync( It.IsAny(), 16000, It.IsAny(), cancellationToken)) .ThrowsAsync(new Exception("Recognition Error")); // Act var result = await _service.RecognizeSpeechWithFallbackAsync( _sampleAudio, cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contains("unavailable") || result.Contains("Unavailable")); } [TestMethod] public async Task GenerateAudioWithFallbackAsync_WhenPrimaryWorks_ReturnsPrimaryAudio() { // Arrange var text = "Hallo Welt"; var language = "de"; var cancellationToken = CancellationToken.None; _mockTtsService.Setup(s => s.GenerateAudioAsync( text, It.IsAny(), language, cancellationToken)) .ReturnsAsync(_sampleAudio); // Act var result = await _service.GenerateAudioWithFallbackAsync( text, language, cancellationToken); // Assert Assert.IsNotNull(result); Assert.AreEqual(_sampleAudio.Length, result.Length); } [TestMethod] public async Task GenerateAudioWithFallbackAsync_WhenPrimaryFails_ReturnsEmpty() { // Arrange var text = "Hallo Welt"; var language = "de"; var cancellationToken = CancellationToken.None; _mockTtsService.Setup(s => s.GenerateAudioAsync( text, It.IsAny(), language, cancellationToken)) .ThrowsAsync(new Exception("TTS Error")); // Act var result = await _service.GenerateAudioWithFallbackAsync( text, language, cancellationToken); // Assert Assert.IsNotNull(result); Assert.AreEqual(0, result.Length); } [TestMethod] public async Task CheckServiceHealthAsync_WithAllServicesHealthy_ReturnsAllTrue() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.TestConnectionAsync(It.IsAny())) .ReturnsAsync(true); _mockVoskService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(true); _mockTtsService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(true); // Act var result = await _service.CheckServiceHealthAsync(cancellationToken); // Assert Assert.IsNotNull(result); Assert.AreEqual(3, result.Count); Assert.IsTrue(result["Mistral"]); Assert.IsTrue(result["Vosk"]); Assert.IsTrue(result["CoquiTTS"]); } [TestMethod] public async Task CheckServiceHealthAsync_WithSomeServicesUnhealthy_ReturnsMixed() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.TestConnectionAsync(It.IsAny())) .ReturnsAsync(true); _mockVoskService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(false); _mockTtsService.Setup(s => s.TestModelAsync(It.IsAny())) .ThrowsAsync(new Exception("Error")); // Act var result = await _service.CheckServiceHealthAsync(cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result["Mistral"]); Assert.IsFalse(result["Vosk"]); Assert.IsFalse(result["CoquiTTS"]); } [TestMethod] public async Task CheckServiceHealthAsync_WithNullServices_ReturnsAllFalse() { // Arrange var cancellationToken = CancellationToken.None; var service = new AiFallbackService( null, null, null, _mockLogger.Object); // Act var result = await service.CheckServiceHealthAsync(cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsFalse(result["Mistral"]); Assert.IsFalse(result["Vosk"]); Assert.IsFalse(result["CoquiTTS"]); } [TestMethod] public async Task GetServiceStatusMessageAsync_WithAllHealthy_ReturnsAvailableMessage() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.TestConnectionAsync(It.IsAny())) .ReturnsAsync(true); _mockVoskService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(true); _mockTtsService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(true); // Act var result = await _service.GetServiceStatusMessageAsync(cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contains("Available")); Assert.IsTrue(result.Contains("Mistral")); Assert.IsTrue(result.Contains("Vosk")); Assert.IsTrue(result.Contains("CoquiTTS")); Assert.IsFalse(result.Contains("Unavailable")); } [TestMethod] public async Task GetServiceStatusMessageAsync_WithSomeUnhealthy_ReturnsMixedMessage() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.TestConnectionAsync(It.IsAny())) .ReturnsAsync(true); _mockVoskService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(false); _mockTtsService.Setup(s => s.TestModelAsync(It.IsAny())) .ThrowsAsync(new Exception("Error")); // Act var result = await _service.GetServiceStatusMessageAsync(cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Contains("Available")); Assert.IsTrue(result.Contains("Unavailable")); } [TestMethod] public async Task TestServiceAsync_WithWorkingServices_ReturnsTrue() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.TestConnectionAsync(It.IsAny())) .ReturnsAsync(true); _mockVoskService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(true); _mockTtsService.Setup(s => s.TestModelAsync(It.IsAny())) .ReturnsAsync(true); // Act var result = await _service.TestServiceAsync(cancellationToken); // Assert Assert.IsTrue(result); } [TestMethod] public async Task TestServiceAsync_WhenAllFail_ReturnsFalse() { // Arrange var cancellationToken = CancellationToken.None; var service = new AiFallbackService(null, null, null, _mockLogger.Object); // Act var result = await service.TestServiceAsync(cancellationToken); // Assert Assert.IsFalse(result); } }