DeutschLernen/Tests/Integration/Presentation/Controllers/StoryControllerTests.cs
Lasse Rune Hansen 33ed27abd8
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix(backend): resolve admin redirect, null reference, and Docker build issues
- Added Role to AuthResponse DTO and all auth endpoints
- Fixed null config handling in MistralConnector, TtsService, VoskService, MistralService
- Fixed BaseAddress setup in MistralConnector to work without API key
- Reverted seed data to use hardcoded bcrypt hashes (compatible with PasswordHasher)
- Added integration tests for StoryController
- Added unit tests for MistralConnector
- Updated frontend AuthResponse type to include role

Fixes admin redirect to /, story generation null reference, and Docker build failures.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-16 17:35:50 +02:00

205 lines
7.2 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 GermanApp.Presentation.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace GermanApp.Tests.Integration.Presentation.Controllers;
/// <summary>
/// Integration tests for StoryController.
/// Tests the full controller -> service -> repository flow.
/// </summary>
[TestClass]
public class StoryControllerTests
{
private Mock<ILessonRepository> _lessonRepositoryMock;
private Mock<IStoryRepository> _storyRepositoryMock;
private Mock<StoryGenerationService> _generationServiceMock;
private Mock<StoryService> _storyServiceMock;
private Mock<StoryUnlockService> _unlockServiceMock;
private Mock<ILevelRepository> _levelRepositoryMock;
private Mock<ILogger<StoryController>> _loggerMock;
private StoryController _controller;
[TestInitialize]
public void TestInitialize()
{
_lessonRepositoryMock = new Mock<ILessonRepository>();
_storyRepositoryMock = new Mock<IStoryRepository>();
_generationServiceMock = new Mock<StoryGenerationService>();
_storyServiceMock = new Mock<StoryService>();
_unlockServiceMock = new Mock<StoryUnlockService>();
_levelRepositoryMock = new Mock<ILevelRepository>();
_loggerMock = new Mock<ILogger<StoryController>>();
_controller = new StoryController(
_storyServiceMock.Object,
_generationServiceMock.Object,
_unlockServiceMock.Object,
_levelRepositoryMock.Object,
_lessonRepositoryMock.Object,
_loggerMock.Object);
}
[TestCleanup]
public void TestCleanup()
{
_controller?.Dispose();
}
// ============================================
// Story Generation Tests
// ============================================
[TestMethod]
public async Task GenerateStoryAsync_ReturnsSuccess_WhenLessonsExistAndGenerationSucceeds()
{
// Arrange
int levelId = 1;
string theme = "Adventure";
var request = new StoryGenerationRequestDto(theme);
var lessons = new List<Lesson>
{
Lesson.Create(1, "Greetings", 1, "Greetings", "Basic greetings"),
Lesson.Create(1, "Numbers", 2, "Numbers", "German numbers 1-100")
};
var expectedResponse = new StoryGenerationResponseDto(
levelId,
theme,
2,
"Full story text here",
new List<StorySegmentDto>());
_lessonRepositoryMock.Setup(r => r.GetByLevelAsync(levelId, It.IsAny<CancellationToken>()))
.ReturnsAsync(lessons);
_generationServiceMock.Setup(s => s.GenerateStoryAsync(
levelId,
theme,
lessons,
It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedResponse);
// Act
var result = await _controller.GenerateStoryAsync(levelId, request, CancellationToken.None);
// Assert
Assert.IsInstanceOfType(result.Result, typeof(OkObjectResult));
var okResult = result.Result as OkObjectResult;
Assert.IsNotNull(okResult);
Assert.IsInstanceOfType(okResult.Value, typeof(StoryGenerationResponseDto));
var response = okResult.Value as StoryGenerationResponseDto;
Assert.AreEqual(levelId, response.LevelId);
Assert.AreEqual(theme, response.Theme);
Assert.AreEqual(2, response.SegmentCount);
}
[TestMethod]
public async Task GenerateStoryAsync_ReturnsBadRequest_WhenNoLessonsFound()
{
// Arrange
int levelId = 999; // Non-existent level
string theme = "Adventure";
var request = new StoryGenerationRequestDto(theme);
_lessonRepositoryMock.Setup(r => r.GetByLevelAsync(levelId, It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Lesson>()); // Empty list
// Act
var result = await _controller.GenerateStoryAsync(levelId, request, CancellationToken.None);
// Assert
Assert.IsInstanceOfType(result.Result, typeof(BadRequestObjectResult));
}
// ============================================
// Story Segment Audio Generation Tests
// ============================================
[TestMethod]
public async Task GenerateSegmentAudioAsync_ReturnsSuccess_WhenSegmentExists()
{
// Arrange
int segmentId = 1;
var expectedDto = new StorySegmentDto
{
Id = segmentId,
AudioUrl = "/audio/story/level1-segment1.wav"
};
_storyServiceMock.Setup(s => s.GenerateAudioAsync(segmentId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedDto);
// Act
var result = await _controller.GenerateSegmentAudioAsync(segmentId, CancellationToken.None);
// Assert
Assert.IsInstanceOfType(result.Result, typeof(OkObjectResult));
var okResult = result.Result as OkObjectResult;
Assert.IsNotNull(okResult);
Assert.IsInstanceOfType(okResult.Value, typeof(StorySegmentDto));
var response = okResult.Value as StorySegmentDto;
Assert.AreEqual(segmentId, response.Id);
Assert.AreEqual(expectedDto.AudioUrl, response.AudioUrl);
}
[TestMethod]
public async Task GenerateSegmentAudioAsync_ReturnsNotFound_WhenSegmentDoesNotExist()
{
// Arrange
int segmentId = 999; // Non-existent segment
_storyServiceMock.Setup(s => s.GenerateAudioAsync(segmentId, It.IsAny<CancellationToken>()))
.ReturnsAsync((StorySegmentDto?)null);
// Act
var result = await _controller.GenerateSegmentAudioAsync(segmentId, CancellationToken.None);
// Assert
Assert.IsInstanceOfType(result.Result, typeof(NotFoundResult));
}
// ============================================
// Get Stories by Level Tests
// ============================================
[TestMethod]
public async Task GetByLevelAsync_ReturnsSuccess_WhenSegmentsExist()
{
// Arrange
int levelId = 1;
var expectedSegments = new List<StorySegmentDto>
{
new StorySegmentDto { Id = 1, LevelId = levelId, Order = 1, Title = "Part 1" },
new StorySegmentDto { Id = 2, LevelId = levelId, Order = 2, Title = "Part 2" }
};
_storyServiceMock.Setup(s => s.GetByLevelAsync(levelId, false, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedSegments);
// Act
var result = await _controller.GetByLevelAsync(levelId, CancellationToken.None);
// Assert
Assert.IsInstanceOfType(result.Result, typeof(OkObjectResult));
var okResult = result.Result as OkObjectResult;
Assert.IsNotNull(okResult);
Assert.IsInstanceOfType(okResult.Value, typeof(List<StorySegmentDto>));
var response = okResult.Value as List<StorySegmentDto>;
Assert.AreEqual(2, response.Count);
}
}