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 StoryGenerationServiceTests { private Mock _mockMistralService; private Mock> _mockLogger; private StoryGenerationService _service; [TestInitialize] public void Setup() { _mockMistralService = new Mock(); _mockLogger = new Mock>(); _service = new StoryGenerationService( _mockMistralService.Object, _mockLogger.Object); } [TestMethod] public async Task GenerateStoryAsync_WithValidParameters_ReturnsStory() { // Arrange var expectedStory = "Once upon a time in Germany..."; var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn", "Reise", "Stadt" }; var length = 200; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, topic, vocabularyWords, length, cancellationToken)) .ReturnsAsync(expectedStory); // Act var result = await _service.GenerateStoryAsync( 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 GenerateStoryAsync_WithEmptyStory_ThrowsAiServiceException() { // Arrange var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn" }; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, topic, vocabularyWords, 200, cancellationToken)) .ReturnsAsync(string.Empty); // Act & Assert try { await _service.GenerateStoryAsync(level, topic, vocabularyWords, 200, cancellationToken); Assert.Fail("Expected AiServiceException was not thrown"); } catch (AiServiceException) { // Expected - StoryGenerationService wraps validation exceptions in AiServiceException } } [TestMethod] public async Task GenerateStoryAsync_WithNullStory_ThrowsAiServiceException() { // Arrange var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn" }; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, topic, vocabularyWords, 200, cancellationToken)) .ReturnsAsync((string?)null); // Act & Assert try { await _service.GenerateStoryAsync(level, topic, vocabularyWords, 200, cancellationToken); Assert.Fail("Expected AiServiceException was not thrown"); } catch (AiServiceException) { // Expected - StoryGenerationService wraps validation exceptions in AiServiceException } } [TestMethod] public async Task GenerateStoryAsync_WhenMistralThrows_ThrowsAiServiceException() { // Arrange var level = "A1"; var topic = "Travel"; var vocabularyWords = new List { "Bahn" }; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, topic, vocabularyWords, 200, cancellationToken)) .ThrowsAsync(new Exception("API Error")); // Act & Assert try { await _service.GenerateStoryAsync(level, topic, vocabularyWords, 200, cancellationToken); Assert.Fail("Expected AiServiceException was not thrown"); } catch (AiServiceException) { // Expected } } [TestMethod] public async Task GenerateLessonStoryAsync_WithValidParameters_CallsGenerateStory() { // Arrange var expectedStory = "Lesson story..."; var lessonTitle = "Greetings"; var level = "A1"; var vocabularyWords = new List { "Hallo", "Tschüss" }; var length = 250; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, lessonTitle, vocabularyWords, length, cancellationToken)) .ReturnsAsync(expectedStory); // Act var result = await _service.GenerateLessonStoryAsync( lessonTitle, level, vocabularyWords, length, cancellationToken); // Assert Assert.AreEqual(expectedStory, result); _mockMistralService.Verify(s => s.GenerateStoryAsync( level, lessonTitle, vocabularyWords, length, cancellationToken), Times.Once); } [TestMethod] public async Task GenerateLessonStoryAsync_WithDefaultLength_Uses250() { // Arrange var expectedStory = "Lesson story..."; var lessonTitle = "Greetings"; var level = "A1"; var vocabularyWords = new List { "Hallo" }; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( level, lessonTitle, vocabularyWords, 250, cancellationToken)) .ReturnsAsync(expectedStory); // Act var result = await _service.GenerateLessonStoryAsync( lessonTitle, level, vocabularyWords, cancellationToken: cancellationToken); // Assert Assert.AreEqual(expectedStory, result); } [TestMethod] public async Task GenerateStoriesByLevelAsync_WithMultipleLevels_ReturnsAllStories() { // Arrange var topic = "Food"; var vocabularyByLevel = new Dictionary> { ["A1"] = new List { "Apfel", "Banane" }, ["A2"] = new List { "Restaurant", "Bestrellung" } }; var lengthByLevel = new Dictionary { ["A1"] = 100, ["A2"] = 150 }; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync("A1", topic, vocabularyByLevel["A1"], 100, cancellationToken)) .ReturnsAsync("A1 story"); _mockMistralService.Setup(s => s.GenerateStoryAsync("A2", topic, vocabularyByLevel["A2"], 150, cancellationToken)) .ReturnsAsync("A2 story"); // Act var result = await _service.GenerateStoriesByLevelAsync( topic, vocabularyByLevel, lengthByLevel, cancellationToken); // Assert Assert.AreEqual(2, result.Count); Assert.AreEqual("A1 story", result["A1"]); Assert.AreEqual("A2 story", result["A2"]); } [TestMethod] public async Task GenerateStoriesByLevelAsync_WithMissingLength_UsesDefault() { // Arrange var topic = "Food"; var vocabularyByLevel = new Dictionary> { ["A1"] = new List { "Apfel" } }; // No lengthByLevel provided var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync("A1", topic, vocabularyByLevel["A1"], 200, cancellationToken)) .ReturnsAsync("A1 story"); // Act var result = await _service.GenerateStoriesByLevelAsync( topic, vocabularyByLevel, null, cancellationToken); // Assert Assert.AreEqual(1, result.Count); Assert.AreEqual("A1 story", result["A1"]); } [TestMethod] public async Task GenerateStoriesByLevelAsync_WhenOneLevelFails_ReturnsEmptyForThatLevel() { // Arrange var topic = "Food"; var vocabularyByLevel = new Dictionary> { ["A1"] = new List { "Apfel" }, ["A2"] = new List { "Restaurant" } }; var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync("A1", topic, vocabularyByLevel["A1"], 200, cancellationToken)) .ReturnsAsync("A1 story"); _mockMistralService.Setup(s => s.GenerateStoryAsync("A2", topic, vocabularyByLevel["A2"], 200, cancellationToken)) .ThrowsAsync(new Exception("Error")); // Act var result = await _service.GenerateStoriesByLevelAsync( topic, vocabularyByLevel, null, cancellationToken); // Assert Assert.AreEqual(2, result.Count); Assert.AreEqual("A1 story", result["A1"]); Assert.AreEqual(string.Empty, result["A2"]); } [TestMethod] public async Task TestServiceAsync_WithWorkingService_ReturnsTrue() { // Arrange var cancellationToken = CancellationToken.None; _mockMistralService.Setup(s => s.GenerateStoryAsync( "A1", "Test", It.IsAny>(), 50, cancellationToken)) .ReturnsAsync("Test story"); // 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.GenerateStoryAsync( "A1", "Test", It.IsAny>(), 50, 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.GenerateStoryAsync( "A1", "Test", It.IsAny>(), 50, cancellationToken)) .ReturnsAsync(string.Empty); // Act var result = await _service.TestServiceAsync(cancellationToken); // Assert Assert.IsFalse(result); } }