DeutschLernen/Tests/Unit/Application/Services/StoryUnlockServiceTests.cs
Mistral Vibe 01f6a1fd30 test(backend): add Phase 3 unit tests for Story Integration
- StoryServiceTests.cs: 536 lines, comprehensive tests for StoryService
- StoryGenerationServiceTests.cs: 469 lines, tests for AI story generation
- StoryUnlockServiceTests.cs: 292 lines, tests for story unlock logic
- StoryRepositoryTests.cs: 120 lines, tests for EF Core repository
- StoryProgressRepositoryTests.cs: 74 lines, tests for progress repository
- AppDbContext.cs: Made DbSet properties virtual for Moq compatibility
- Fixed return types in StoryRepository and StoryProgressRepository
- Updated docs/features/story-integration.md for Phase 3 completion
- All 324 unit tests pass

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-13 14:23:54 +02:00

292 lines
11 KiB
C#

using System;
using System.Collections.Generic;
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 StoryUnlockServiceTests
{
private Mock<StoryService> _mockStoryService;
private Mock<IUserProgressRepository> _mockUserProgressRepository;
private Mock<ILessonRepository> _mockLessonRepository;
private Mock<ILogger<StoryUnlockService>> _mockLogger;
private StoryUnlockService _service;
[TestInitialize]
public void Setup()
{
_mockStoryService = new Mock<StoryService>(
new Mock<IStoryRepository>().Object,
new Mock<IStoryProgressRepository>().Object,
new Mock<ILogger<StoryService>>().Object);
_mockUserProgressRepository = new Mock<IUserProgressRepository>();
_mockLessonRepository = new Mock<ILessonRepository>();
_mockLogger = new Mock<ILogger<StoryUnlockService>>();
_service = new StoryUnlockService(
_mockStoryService.Object,
_mockUserProgressRepository.Object,
_mockLessonRepository.Object,
_mockLogger.Object);
}
private Lesson CreateTestLesson(int id, int levelId, int order, string title = "Test", string topic = "Test")
{
var lesson = Lesson.Create(levelId, title, order, topic);
typeof(Lesson).GetProperty("Id")?.SetValue(lesson, id);
return lesson;
}
private StorySegment CreateTestSegment(int id, int levelId, int? lessonId, int order, string content = "Content", string title = "Title", string theme = "Theme")
{
var segment = StorySegment.Create(levelId, lessonId, content, order, title, theme);
typeof(StorySegment).GetProperty("Id")?.SetValue(segment, id);
return segment;
}
#region HandleLessonCompletionAsync Tests
[TestMethod]
public async Task HandleLessonCompletionAsync_WithValidLesson_UnlocksSegment()
{
// Arrange
var userId = 1;
var lessonId = 1;
var lesson = CreateTestLesson(lessonId, 1, 1);
var segment = CreateTestSegment(100, 1, 2, 2, "Next segment", "Next Title", "Theme");
_mockLessonRepository.Setup(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockUserProgressRepository.Setup(u => u.HasUserCompletedLessonAsync(userId, lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
_mockStoryService.Setup(s => s.GetNextSegmentToUnlockAsync(lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()))
.ReturnsAsync(StorySegmentDto.FromEntity(segment));
_mockStoryService.Setup(s => s.IsSegmentUnlockedAsync(userId, segment.Id, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
_mockStoryService.Setup(s => s.UnlockNextSegmentAsync(userId, lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()))
.ReturnsAsync(StorySegmentDto.FromEntity(segment));
// Act
var result = await _service.HandleLessonCompletionAsync(userId, lessonId);
// Assert
Assert.IsTrue(result);
_mockLessonRepository.Verify(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()), Times.Once);
_mockUserProgressRepository.Verify(u => u.HasUserCompletedLessonAsync(userId, lessonId, It.IsAny<CancellationToken>()), Times.Once);
_mockStoryService.Verify(s => s.UnlockNextSegmentAsync(userId, lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()), Times.Once);
}
[TestMethod]
public async Task HandleLessonCompletionAsync_WithNonExistingLesson_ReturnsFalse()
{
// Arrange
var userId = 1;
var lessonId = 999;
_mockLessonRepository.Setup(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync((Lesson?)null);
// Act
var result = await _service.HandleLessonCompletionAsync(userId, lessonId);
// Assert
Assert.IsFalse(result);
}
[TestMethod]
public async Task HandleLessonCompletionAsync_WithLessonNotCompleted_ReturnsFalse()
{
// Arrange
var userId = 1;
var lessonId = 1;
var lesson = CreateTestLesson(lessonId, 1, 1);
_mockLessonRepository.Setup(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockUserProgressRepository.Setup(u => u.HasUserCompletedLessonAsync(userId, lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _service.HandleLessonCompletionAsync(userId, lessonId);
// Assert
Assert.IsFalse(result);
}
[TestMethod]
public async Task HandleLessonCompletionAsync_WithNoSegmentToUnlock_ReturnsFalse()
{
// Arrange
var userId = 1;
var lessonId = 1;
var lesson = CreateTestLesson(lessonId, 1, 10); // High order, no next segment
_mockLessonRepository.Setup(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockUserProgressRepository.Setup(u => u.HasUserCompletedLessonAsync(userId, lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
_mockStoryService.Setup(s => s.GetNextSegmentToUnlockAsync(lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()))
.ReturnsAsync((StorySegmentDto?)null);
// Act
var result = await _service.HandleLessonCompletionAsync(userId, lessonId);
// Assert
Assert.IsFalse(result);
}
[TestMethod]
public async Task HandleLessonCompletionAsync_WithSegmentAlreadyUnlocked_ReturnsFalse()
{
// Arrange
var userId = 1;
var lessonId = 1;
var lesson = CreateTestLesson(lessonId, 1, 1);
var segment = CreateTestSegment(100, 1, 2, 2, "Next segment", "Next Title", "Theme");
_mockLessonRepository.Setup(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockUserProgressRepository.Setup(u => u.HasUserCompletedLessonAsync(userId, lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
_mockStoryService.Setup(s => s.GetNextSegmentToUnlockAsync(lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()))
.ReturnsAsync(StorySegmentDto.FromEntity(segment));
_mockStoryService.Setup(s => s.IsSegmentUnlockedAsync(userId, segment.Id, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); // Already unlocked
// Act
var result = await _service.HandleLessonCompletionAsync(userId, lessonId);
// Assert
Assert.IsFalse(result);
_mockStoryService.Verify(s => s.UnlockNextSegmentAsync(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Never);
}
[TestMethod]
public async Task HandleLessonCompletionAsync_WithUnlockFailure_ReturnsFalse()
{
// Arrange
var userId = 1;
var lessonId = 1;
var lesson = CreateTestLesson(lessonId, 1, 1);
var segment = CreateTestSegment(100, 1, 2, 2, "Next segment", "Next Title", "Theme");
_mockLessonRepository.Setup(l => l.GetByIdAsync(lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(lesson);
_mockUserProgressRepository.Setup(u => u.HasUserCompletedLessonAsync(userId, lessonId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
_mockStoryService.Setup(s => s.GetNextSegmentToUnlockAsync(lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()))
.ReturnsAsync(StorySegmentDto.FromEntity(segment));
_mockStoryService.Setup(s => s.IsSegmentUnlockedAsync(userId, segment.Id, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
_mockStoryService.Setup(s => s.UnlockNextSegmentAsync(userId, lesson.LevelId, lesson.Order, It.IsAny<CancellationToken>()))
.ReturnsAsync((StorySegmentDto?)null); // Unlock failed
// Act
var result = await _service.HandleLessonCompletionAsync(userId, lessonId);
// Assert
Assert.IsFalse(result);
}
#endregion
#region Delegation Tests
[TestMethod]
public async Task IsSegmentUnlockedAsync_DelegatesToStoryService()
{
// Arrange
var userId = 1;
var segmentId = 100;
_mockStoryService.Setup(s => s.IsSegmentUnlockedAsync(userId, segmentId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _service.IsSegmentUnlockedAsync(userId, segmentId);
// Assert
Assert.IsTrue(result);
_mockStoryService.Verify(s => s.IsSegmentUnlockedAsync(userId, segmentId, It.IsAny<CancellationToken>()), Times.Once);
}
[TestMethod]
public async Task IsSegmentCompletedAsync_DelegatesToStoryService()
{
// Arrange
var userId = 1;
var segmentId = 100;
_mockStoryService.Setup(s => s.IsSegmentCompletedAsync(userId, segmentId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _service.IsSegmentCompletedAsync(userId, segmentId);
// Assert
Assert.IsTrue(result);
_mockStoryService.Verify(s => s.IsSegmentCompletedAsync(userId, segmentId, It.IsAny<CancellationToken>()), Times.Once);
}
[TestMethod]
public async Task MarkSegmentAsCompletedAsync_DelegatesToStoryService()
{
// Arrange
var userId = 1;
var segmentId = 100;
_mockStoryService.Setup(s => s.MarkSegmentAsCompletedAsync(userId, segmentId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _service.MarkSegmentAsCompletedAsync(userId, segmentId);
// Assert
Assert.IsTrue(result);
_mockStoryService.Verify(s => s.MarkSegmentAsCompletedAsync(userId, segmentId, It.IsAny<CancellationToken>()), Times.Once);
}
[TestMethod]
public async Task GetUserProgressAsync_DelegatesToStoryService()
{
// Arrange
var userId = 1;
var levelId = 1;
var expectedProgress = new StoryProgressDto(1, "A1", 5, 3, 2, new List<StorySegmentProgressDto>());
_mockStoryService.Setup(s => s.GetUserProgressAsync(userId, levelId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedProgress);
// Act
var result = await _service.GetUserProgressAsync(userId, levelId);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(levelId, result.LevelId);
Assert.AreEqual("A1", result.LevelName);
_mockStoryService.Verify(s => s.GetUserProgressAsync(userId, levelId, It.IsAny<CancellationToken>()), Times.Once);
}
#endregion
}