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 WritingFeedbackServiceTests { private Mock _mockMistralService; private Mock> _mockLogger; private WritingFeedbackService _service; [TestInitialize] public void Setup() { _mockMistralService = new Mock(); _mockLogger = new Mock>(); _service = new WritingFeedbackService( _mockMistralService.Object, _mockLogger.Object); } [TestMethod] public async Task ProvideFeedbackAsync_WithValidText_ReturnsFeedback() { // Arrange var userText = "Ich heisse Anna und wohne in Berlin."; var level = "A1"; var expectedFeedback = "Good job! Your sentence structure is correct."; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ReturnsAsync(expectedFeedback); // Act var result = await _service.ProvideFeedbackAsync( userText, level, null, cancellationToken); // Assert Assert.AreEqual(expectedFeedback, result); _mockMistralService.Verify(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken), Times.Once); } [TestMethod] public async Task ProvideFeedbackAsync_WithCustomPrompt_IncludesPrompt() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var customPrompt = "Focus on grammar and vocabulary."; var expectedFeedback = "Feedback with custom prompt..."; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, customPrompt, cancellationToken)) .ReturnsAsync(expectedFeedback); // Act var result = await _service.ProvideFeedbackAsync( userText, level, customPrompt, cancellationToken); // Assert Assert.AreEqual(expectedFeedback, result); } [TestMethod] public async Task ProvideFeedbackAsync_WithEmptyFeedback_ThrowsInvalidOperationException() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ReturnsAsync(string.Empty); // Act & Assert try { await _service.ProvideFeedbackAsync(userText, level, null, cancellationToken); Assert.Fail("Expected InvalidOperationException was not thrown"); } catch (InvalidOperationException) { // Expected } } [TestMethod] public async Task ProvideFeedbackAsync_WithNullFeedback_ThrowsInvalidOperationException() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ReturnsAsync((string?)null); // Act & Assert try { await _service.ProvideFeedbackAsync(userText, level, null, cancellationToken); Assert.Fail("Expected InvalidOperationException was not thrown"); } catch (InvalidOperationException) { // Expected } } [TestMethod] public async Task ProvideFeedbackAsync_WhenMistralThrows_ThrowsAiServiceException() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ThrowsAsync(new Exception("API Error")); // Act & Assert try { await _service.ProvideFeedbackAsync(userText, level, null, cancellationToken); Assert.Fail("Expected AiServiceException was not thrown"); } catch (AiServiceException) { // Expected } } [TestMethod] public async Task ProvideStructuredFeedbackAsync_WithValidText_ReturnsStructuredFeedback() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var feedbackText = "Good job!\nGrammar: Correct\nSuggestion: Use more vocabulary"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ReturnsAsync(feedbackText); // Act var result = await _service.ProvideStructuredFeedbackAsync( userText, level, cancellationToken); // Assert Assert.IsNotNull(result); Assert.AreEqual(userText, result.OriginalText); Assert.AreEqual(feedbackText, result.FeedbackText); Assert.IsNotNull(result.GrammarCorrections); Assert.IsNotNull(result.ImprovementSuggestions); Assert.IsNotNull(result.Encouragement); } [TestMethod] public async Task CheckGrammarAsync_WithValidText_ReturnsCorrections() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var feedbackText = "Grammar: Incorrect -> Correct: Ich heiße Anna"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ReturnsAsync(feedbackText); // Act var result = await _service.CheckGrammarAsync( userText, level, cancellationToken); // Assert Assert.IsNotNull(result); Assert.AreEqual(1, result.Count); } [TestMethod] public async Task SuggestImprovementsAsync_WithValidText_ReturnsSuggestions() { // Arrange var userText = "Ich heisse Anna."; var level = "A1"; var feedbackText = "Suggestion: Add more details about yourself"; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( userText, level, null, cancellationToken)) .ReturnsAsync(feedbackText); // Act var result = await _service.SuggestImprovementsAsync( userText, level, cancellationToken); // Assert Assert.IsNotNull(result); Assert.IsTrue(result.Count > 0); } [TestMethod] public async Task TestServiceAsync_WithWorkingService_ReturnsTrue() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( "Ich heisse Anna.", "A1", null, cancellationToken)) .ReturnsAsync("Test feedback"); // Act var result = await _service.TestServiceAsync(cancellationToken); // Assert Assert.IsTrue(result); } [TestMethod] public async Task TestServiceAsync_WhenServiceFails_ReturnsFalse() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( "Ich heisse Anna.", "A1", null, cancellationToken)) .ThrowsAsync(new Exception("Error")); // Act var result = await _service.TestServiceAsync(cancellationToken); // Assert Assert.IsFalse(result); } [TestMethod] public async Task TestServiceAsync_WithEmptyResult_ReturnsFalse() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateWritingFeedbackAsync( "Ich heisse Anna.", "A1", null, cancellationToken)) .ReturnsAsync(string.Empty); // Act var result = await _service.TestServiceAsync(cancellationToken); // Assert Assert.IsFalse(result); } }