using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using GermanApp.Application.DTOs; using GermanApp.Application.Services; using GermanApp.Domain.Entities; using GermanApp.Domain.Interfaces; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace GermanApp.Tests.Unit.Application.Services; [TestClass] public class StoryServiceTests { private Mock _mockStoryRepository; private Mock _mockProgressRepository; private Mock> _mockLogger; private StoryService _service; [TestInitialize] public void Setup() { _mockStoryRepository = new Mock(); _mockProgressRepository = new Mock(); _mockLogger = new Mock>(); _service = new StoryService( _mockStoryRepository.Object, _mockProgressRepository.Object, _mockLogger.Object); } [TestMethod] public async Task GetByIdAsync_WithExistingId_ReturnsSegment() { // Arrange var segment = StorySegment.Create(1, 1, "Test content", 1, "Test Title", "Test Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment, 1); _mockStoryRepository.Setup(s => s.GetByIdAsync(1, It.IsAny())) .ReturnsAsync(segment); // Act var result = await _service.GetByIdAsync(1); // Assert Assert.IsNotNull(result); Assert.AreEqual(1, result.Id); Assert.AreEqual("Test content", result.Content); Assert.AreEqual("Test Title", result.Title); } [TestMethod] public async Task GetByIdAsync_WithNonExistingId_ReturnsNull() { // Arrange _mockStoryRepository.Setup(s => s.GetByIdAsync(999, It.IsAny())) .ReturnsAsync((StorySegment?)null); // Act var result = await _service.GetByIdAsync(999); // Assert Assert.IsNull(result); } [TestMethod] public async Task GetByLevelAsync_WithExistingLevel_ReturnsSegments() { // Arrange var segment1 = StorySegment.Create(1, 1, "Content 1", 1, "Title 1", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment1, 1); var segment2 = StorySegment.Create(1, 2, "Content 2", 2, "Title 2", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment2, 2); _mockStoryRepository.Setup(s => s.GetByLevelAsync(1, false, It.IsAny())) .ReturnsAsync(new List { segment1, segment2 }); // Act var result = await _service.GetByLevelAsync(1); // Assert Assert.IsNotNull(result); Assert.AreEqual(2, result.Count); Assert.AreEqual("Content 1", result[0].Content); Assert.AreEqual("Content 2", result[1].Content); } [TestMethod] public async Task GetByLevelAsync_WithNoSegments_ReturnsEmptyList() { // Arrange _mockStoryRepository.Setup(s => s.GetByLevelAsync(1, false, It.IsAny())) .ReturnsAsync(new List()); // Act var result = await _service.GetByLevelAsync(1); // Assert Assert.IsNotNull(result); Assert.AreEqual(0, result.Count); } [TestMethod] public async Task GetByLessonAsync_WithExistingLesson_ReturnsSegments() { // Arrange var segment = StorySegment.Create(1, 1, "Test content", 1, "Test Title", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment, 1); _mockStoryRepository.Setup(s => s.GetByLessonAsync(1, It.IsAny())) .ReturnsAsync(new List { segment }); // Act var result = await _service.GetByLessonAsync(1); // Assert Assert.IsNotNull(result); Assert.AreEqual(1, result.Count); Assert.AreEqual("Test content", result[0].Content); } [TestMethod] public async Task CreateAsync_WithValidData_CreatesSegment() { // Arrange var dto = new CreateStorySegmentDto(1, 1, "New content", 1, "New Title", "New Theme"); var createdSegment = StorySegment.Create(1, 1, "New content", 1, "New Title", "New Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(createdSegment, 10); _mockStoryRepository.Setup(s => s.GetByOrderRangeAsync(1, 1, 1, It.IsAny())) .ReturnsAsync(new List()); // No existing segment _mockStoryRepository.Setup(s => s.AddAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(createdSegment); // Act var result = await _service.CreateAsync(dto); // Assert Assert.IsNotNull(result); Assert.AreEqual(10, result.Id); Assert.AreEqual("New content", result.Content); _mockStoryRepository.Verify(s => s.AddAsync(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] public async Task CreateAsync_WithDuplicateOrder_ThrowsException() { // Arrange var dto = new CreateStorySegmentDto(1, 1, "New content", 1, "New Title", "New Theme"); var existingSegment = StorySegment.Create(1, 1, "Existing", 1, "Existing", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(existingSegment, 1); _mockStoryRepository.Setup(s => s.GetByOrderRangeAsync(1, 1, 1, It.IsAny())) .ReturnsAsync(new List { existingSegment }); // Act & Assert try { await _service.CreateAsync(dto); Assert.Fail("Expected InvalidOperationException was not thrown"); } catch (InvalidOperationException ex) { Assert.IsTrue(ex.Message.Contains("already exists")); } } [TestMethod] public async Task UpdateAsync_WithExistingId_UpdatesSegment() { // Arrange var existingSegment = StorySegment.Create(1, 1, "Old content", 1, "Old Title", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(existingSegment, 1); var dto = new UpdateStorySegmentDto(Content: "New content", Title: "New Title"); _mockStoryRepository.Setup(s => s.GetByIdAsync(1, It.IsAny())) .ReturnsAsync(existingSegment); _mockStoryRepository.Setup(s => s.UpdateAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act var result = await _service.UpdateAsync(1, dto); // Assert Assert.IsNotNull(result); Assert.AreEqual("New content", result.Content); Assert.AreEqual("New Title", result.Title); _mockStoryRepository.Verify(s => s.UpdateAsync(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] public async Task UpdateAsync_WithNonExistingId_ReturnsNull() { // Arrange var dto = new UpdateStorySegmentDto(Content: "New content"); _mockStoryRepository.Setup(s => s.GetByIdAsync(999, It.IsAny())) .ReturnsAsync((StorySegment?)null); // Act var result = await _service.UpdateAsync(999, dto); // Assert Assert.IsNull(result); } [TestMethod] public async Task DeleteAsync_WithExistingId_DeletesSegment() { // Arrange _mockStoryRepository.Setup(s => s.ExistsAsync(1, It.IsAny())) .ReturnsAsync(true); _mockStoryRepository.Setup(s => s.DeleteAsync(1, It.IsAny())) .Returns(Task.CompletedTask); // Act var result = await _service.DeleteAsync(1); // Assert Assert.IsTrue(result); _mockStoryRepository.Verify(s => s.DeleteAsync(1, It.IsAny()), Times.Once); } [TestMethod] public async Task DeleteAsync_WithNonExistingId_ReturnsFalse() { // Arrange _mockStoryRepository.Setup(s => s.ExistsAsync(999, It.IsAny())) .ReturnsAsync(false); // Act var result = await _service.DeleteAsync(999); // Assert Assert.IsFalse(result); } [TestMethod] public async Task GetNextSegmentToUnlockAsync_WithValidLesson_ReturnsSegment() { // Arrange var segment = StorySegment.Create(1, 2, "Next content", 2, "Next Title", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment, 2); _mockStoryRepository.Setup(s => s.GetNextSegmentToUnlockAsync(1, 1, It.IsAny())) .ReturnsAsync(segment); // Act var result = await _service.GetNextSegmentToUnlockAsync(1, 1); // Assert Assert.IsNotNull(result); Assert.AreEqual(2, result.Id); Assert.AreEqual("Next content", result.Content); } [TestMethod] public async Task GetNextSegmentToUnlockAsync_WithNoSegment_ReturnsNull() { // Arrange _mockStoryRepository.Setup(s => s.GetNextSegmentToUnlockAsync(1, 10, It.IsAny())) .ReturnsAsync((StorySegment?)null); // Act var result = await _service.GetNextSegmentToUnlockAsync(1, 10); // Assert Assert.IsNull(result); } [TestMethod] public async Task UpdateAudioUrlAsync_WithExistingSegment_UpdatesUrl() { // Arrange var segment = StorySegment.Create(1, 1, "Content", 1, "Title", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment, 1); _mockStoryRepository.Setup(s => s.GetByIdAsync(1, It.IsAny())) .ReturnsAsync(segment); _mockStoryRepository.Setup(s => s.UpdateAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act var result = await _service.UpdateAudioUrlAsync(1, "/audio/test.wav"); // Assert Assert.IsNotNull(result); Assert.AreEqual("/audio/test.wav", result.AudioUrl); } [TestMethod] public async Task UpdateAudioUrlAsync_WithNonExistingSegment_ReturnsNull() { // Arrange _mockStoryRepository.Setup(s => s.GetByIdAsync(999, It.IsAny())) .ReturnsAsync((StorySegment?)null); // Act var result = await _service.UpdateAudioUrlAsync(999, "/audio/test.wav"); // Assert Assert.IsNull(result); } [TestMethod] public async Task MarkSegmentAsCompletedAsync_WithExistingProgress_MarksAsCompleted() { // Arrange var progress = StoryProgress.Create(1, 1, 1); typeof(StoryProgress).GetProperty("Id")?.SetValue(progress, 1); _mockProgressRepository.Setup(p => p.GetByUserAndSegmentAsync(1, 1, It.IsAny())) .ReturnsAsync(progress); _mockProgressRepository.Setup(p => p.UpdateAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act var result = await _service.MarkSegmentAsCompletedAsync(1, 1); // Assert Assert.IsTrue(result); Assert.IsTrue(progress.IsCompleted); Assert.IsNotNull(progress.CompletedAt); } [TestMethod] public async Task MarkSegmentAsCompletedAsync_WithNonExistingProgress_ReturnsFalse() { // Arrange _mockProgressRepository.Setup(p => p.GetByUserAndSegmentAsync(1, 999, It.IsAny())) .ReturnsAsync((StoryProgress?)null); // Act var result = await _service.MarkSegmentAsCompletedAsync(1, 999); // Assert Assert.IsFalse(result); } [TestMethod] public async Task MarkSegmentAsCompletedAsync_WithAlreadyCompleted_ReturnsFalse() { // Arrange var progress = StoryProgress.Create(1, 1, 1); typeof(StoryProgress).GetProperty("Id")?.SetValue(progress, 1); progress.MarkAsCompleted(); _mockProgressRepository.Setup(p => p.GetByUserAndSegmentAsync(1, 1, It.IsAny())) .ReturnsAsync(progress); // Act var result = await _service.MarkSegmentAsCompletedAsync(1, 1); // Assert Assert.IsFalse(result); } [TestMethod] public async Task UnlockNextSegmentAsync_WithValidLesson_UnlocksSegment() { // Arrange var segment = StorySegment.Create(1, 2, "Next content", 2, "Next Title", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment, 2); _mockStoryRepository.Setup(s => s.GetNextSegmentToUnlockAsync(1, 1, It.IsAny())) .ReturnsAsync(segment); _mockProgressRepository.Setup(p => p.IsSegmentUnlockedAsync(1, 2, It.IsAny())) .ReturnsAsync(false); _mockProgressRepository.Setup(p => p.AddAsync(It.IsAny(), It.IsAny())) .ReturnsAsync((StoryProgress p, CancellationToken ct) => p); // Act var result = await _service.UnlockNextSegmentAsync(1, 1, 1); // Assert Assert.IsNotNull(result); Assert.AreEqual(2, result.Id); _mockProgressRepository.Verify(p => p.AddAsync(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] public async Task UnlockNextSegmentAsync_WithAlreadyUnlocked_ReturnsSegmentWithoutAdding() { // Arrange var segment = StorySegment.Create(1, 2, "Next content", 2, "Next Title", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment, 2); _mockStoryRepository.Setup(s => s.GetNextSegmentToUnlockAsync(1, 1, It.IsAny())) .ReturnsAsync(segment); _mockProgressRepository.Setup(p => p.IsSegmentUnlockedAsync(1, 2, It.IsAny())) .ReturnsAsync(true); // Act var result = await _service.UnlockNextSegmentAsync(1, 1, 1); // Assert Assert.IsNotNull(result); Assert.AreEqual(2, result.Id); _mockProgressRepository.Verify(p => p.AddAsync(It.IsAny(), It.IsAny()), Times.Never); } [TestMethod] public async Task UnlockNextSegmentAsync_WithNoSegment_ReturnsNull() { // Arrange _mockStoryRepository.Setup(s => s.GetNextSegmentToUnlockAsync(1, 10, It.IsAny())) .ReturnsAsync((StorySegment?)null); // Act var result = await _service.UnlockNextSegmentAsync(1, 1, 10); // Assert Assert.IsNull(result); } [TestMethod] public async Task GetUserProgressAsync_WithSegments_ReturnsProgress() { // Arrange var segment1 = StorySegment.Create(1, 1, "Content 1", 1, "Title 1", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment1, 1); var segment2 = StorySegment.Create(1, 2, "Content 2", 2, "Title 2", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment2, 2); var progress1 = StoryProgress.Create(1, 1, 1); typeof(StoryProgress).GetProperty("Id")?.SetValue(progress1, 1); progress1.MarkAsCompleted(); _mockStoryRepository.Setup(s => s.GetByLevelAsync(1, false, It.IsAny())) .ReturnsAsync(new List { segment1, segment2 }); _mockProgressRepository.Setup(p => p.GetByUserAndLevelAsync(1, 1, It.IsAny())) .ReturnsAsync(new List { progress1 }); _mockProgressRepository.Setup(p => p.IsSegmentUnlockedAsync(1, 1, It.IsAny())) .ReturnsAsync(true); _mockProgressRepository.Setup(p => p.IsSegmentUnlockedAsync(1, 2, It.IsAny())) .ReturnsAsync(false); _mockProgressRepository.Setup(p => p.IsSegmentCompletedAsync(1, 1, It.IsAny())) .ReturnsAsync(true); _mockProgressRepository.Setup(p => p.IsSegmentCompletedAsync(1, 2, It.IsAny())) .ReturnsAsync(false); _mockProgressRepository.Setup(p => p.GetHighestUnlockedOrderAsync(1, 1, It.IsAny())) .ReturnsAsync(1); // Act var result = await _service.GetUserProgressAsync(1, 1); // Assert Assert.IsNotNull(result); Assert.AreEqual(1, result.LevelId); Assert.AreEqual(2, result.TotalSegments); Assert.AreEqual(1, result.UnlockedSegments); Assert.AreEqual(2, result.Segments.Count); } [TestMethod] public async Task GetSegmentsNeedingAudioAsync_WithSegmentsNeedingAudio_ReturnsSegments() { // Arrange var segment1 = StorySegment.Create(1, 1, "Content 1", 1, "Title 1", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment1, 1); var segment2 = StorySegment.Create(1, 2, "Content 2", 2, "Title 2", "Theme"); typeof(StorySegment).GetProperty("Id")?.SetValue(segment2, 2); segment2.UpdateAudioUrl("/audio/existing.wav"); _mockStoryRepository.Setup(s => s.GetSegmentsNeedingAudioAsync(It.IsAny())) .ReturnsAsync(new List { segment1 }); // Act var result = await _service.GetSegmentsNeedingAudioAsync(); // Assert Assert.IsNotNull(result); Assert.AreEqual(1, result.Count); Assert.IsNull(result[0].AudioUrl); } [TestMethod] public async Task IsSegmentUnlockedAsync_WithUnlockedSegment_ReturnsTrue() { // Arrange _mockProgressRepository.Setup(p => p.IsSegmentUnlockedAsync(1, 1, It.IsAny())) .ReturnsAsync(true); // Act var result = await _service.IsSegmentUnlockedAsync(1, 1); // Assert Assert.IsTrue(result); } [TestMethod] public async Task IsSegmentUnlockedAsync_WithLockedSegment_ReturnsFalse() { // Arrange _mockProgressRepository.Setup(p => p.IsSegmentUnlockedAsync(1, 999, It.IsAny())) .ReturnsAsync(false); // Act var result = await _service.IsSegmentUnlockedAsync(1, 999); // Assert Assert.IsFalse(result); } [TestMethod] public async Task IsSegmentCompletedAsync_WithCompletedSegment_ReturnsTrue() { // Arrange _mockProgressRepository.Setup(p => p.IsSegmentCompletedAsync(1, 1, It.IsAny())) .ReturnsAsync(true); // Act var result = await _service.IsSegmentCompletedAsync(1, 1); // Assert Assert.IsTrue(result); } [TestMethod] public async Task IsSegmentCompletedAsync_WithIncompleteSegment_ReturnsFalse() { // Arrange _mockProgressRepository.Setup(p => p.IsSegmentCompletedAsync(1, 999, It.IsAny())) .ReturnsAsync(false); // Act var result = await _service.IsSegmentCompletedAsync(1, 999); // Assert Assert.IsFalse(result); } }