- Add QuizQuestion and QuizOption domain entities with QuestionType enum - Add IQuizQuestionRepository and IQuizOptionRepository interfaces - Add QuizQuestionRepository and QuizOptionRepository EF Core implementations - Add QuizQuestionService with CRUD, quiz submission, and statistics - Add QuizQuestionsController with comprehensive REST API endpoints - Add QuizQuestionDto and related DTOs for API communication - Add EF Core migration (20260612050652_AddQuizQuestionTables) for QuizQuestion and QuizOption - Add seed data with sample quiz questions for Greetings, Numbers, Grammar lessons - Update AppDbContext with DbSets and entity configurations - Update Program.cs with migration retry logic for Docker - Update docker-compose.yml with SeedDatabase configuration - Add unit tests for QuizQuestionService Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
321 lines
12 KiB
C#
321 lines
12 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.VisualStudio.TestTools.UnitTesting;
|
|
using Moq;
|
|
|
|
namespace GermanApp.Tests.Unit.Application.Services;
|
|
|
|
[TestClass]
|
|
public class QuizQuestionServiceTests
|
|
{
|
|
private Mock<IQuizQuestionRepository> _mockQuizQuestionRepo;
|
|
private Mock<IQuizOptionRepository> _mockQuizOptionRepo;
|
|
private Mock<ILessonRepository> _mockLessonRepo;
|
|
private QuizQuestionService _service;
|
|
|
|
[TestInitialize]
|
|
public void Setup()
|
|
{
|
|
_mockQuizQuestionRepo = new Mock<IQuizQuestionRepository>();
|
|
_mockQuizOptionRepo = new Mock<IQuizOptionRepository>();
|
|
_mockLessonRepo = new Mock<ILessonRepository>();
|
|
_service = new QuizQuestionService(
|
|
_mockQuizQuestionRepo.Object,
|
|
_mockQuizOptionRepo.Object,
|
|
_mockLessonRepo.Object);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetAllQuizQuestionsAsync_ReturnsMappedDtoList()
|
|
{
|
|
var lesson = Lesson.Create(1, "Test Lesson", 1, "Test Topic", "Test Description");
|
|
var questions = new List<QuizQuestion>
|
|
{
|
|
QuizQuestion.Create(1, "Question 1", QuestionType.MultipleChoice, "Answer 1", 3, 1)
|
|
};
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetAllOrderedAsync(It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(questions);
|
|
|
|
var result = await _service.GetAllQuizQuestionsAsync();
|
|
|
|
Assert.AreEqual(1, result.Count);
|
|
Assert.AreEqual("Question 1", result[0].QuestionText);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetQuizQuestionByIdAsync_WithExistingId_ReturnsQuestion()
|
|
{
|
|
var question = QuizQuestion.Create(1, "Test Question", QuestionType.MultipleChoice, "Test Answer", 3, 1);
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(question);
|
|
|
|
var result = await _service.GetQuizQuestionByIdAsync(1);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("Test Question", result.QuestionText);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetQuizQuestionByIdAsync_WithNonExistingId_ReturnsNull()
|
|
{
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(999, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync((QuizQuestion?)null);
|
|
|
|
var result = await _service.GetQuizQuestionByIdAsync(999);
|
|
|
|
Assert.IsNull(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetQuizQuestionsByLessonAsync_ReturnsFilteredQuestions()
|
|
{
|
|
var questions = new List<QuizQuestion>
|
|
{
|
|
QuizQuestion.Create(1, "Q1", QuestionType.MultipleChoice, "A1", 3, 1)
|
|
};
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByLessonAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(questions);
|
|
|
|
var result = await _service.GetQuizQuestionsByLessonAsync(1);
|
|
|
|
Assert.AreEqual(1, result.Count);
|
|
Assert.AreEqual("Q1", result[0].QuestionText);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetActiveQuizQuestionsByLessonAsync_ReturnsOnlyActive()
|
|
{
|
|
var activeQuestion = QuizQuestion.Create(1, "Active", QuestionType.MultipleChoice, "A1", 3, 1);
|
|
var inactiveQuestion = QuizQuestion.Create(1, "Inactive", QuestionType.MultipleChoice, "A2", 3, 2);
|
|
inactiveQuestion.Deactivate();
|
|
|
|
var questions = new List<QuizQuestion> { activeQuestion, inactiveQuestion };
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetActiveByLessonAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new List<QuizQuestion> { activeQuestion });
|
|
|
|
var result = await _service.GetActiveQuizQuestionsByLessonAsync(1);
|
|
|
|
Assert.AreEqual(1, result.Count);
|
|
Assert.AreEqual("Active", result[0].QuestionText);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task CreateQuizQuestionAsync_WithValidDtoAndValidLesson_CreatesQuestion()
|
|
{
|
|
var lesson = Lesson.Create(1, "Test Lesson", 1, "Test Topic", "Test Description");
|
|
var options = new List<CreateQuizOptionDto>
|
|
{
|
|
new CreateQuizOptionDto("Option 1", true, 1),
|
|
new CreateQuizOptionDto("Option 2", false, 2)
|
|
};
|
|
var dto = new CreateQuizQuestionDto(
|
|
1, "New Question", QuestionType.MultipleChoice, "Answer 1", 3, 1, options);
|
|
|
|
var createdQuestion = QuizQuestion.Create(1, "New Question", QuestionType.MultipleChoice, "Answer 1", 3, 1);
|
|
|
|
_mockLessonRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(lesson);
|
|
_mockQuizQuestionRepo.Setup(r => r.OrderExistsInLessonAsync(1, 1, null, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(false);
|
|
_mockQuizQuestionRepo.Setup(r => r.AddAsync(It.IsAny<QuizQuestion>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(createdQuestion);
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(createdQuestion.Id, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(createdQuestion);
|
|
|
|
var result = await _service.CreateQuizQuestionAsync(dto);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("New Question", result.QuestionText);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task UpdateQuizQuestionAsync_WithExistingQuestion_UpdatesQuestion()
|
|
{
|
|
var lesson = Lesson.Create(1, "Test Lesson", 1, "Test Topic", "Test Description");
|
|
var existing = QuizQuestion.Create(1, "Old Question", QuestionType.MultipleChoice, "Old Answer", 3, 1);
|
|
var options = new List<UpdateQuizOptionDto>
|
|
{
|
|
new UpdateQuizOptionDto(1, "Updated Option", true, 1)
|
|
};
|
|
var dto = new UpdateQuizQuestionDto(1, "Updated Question", QuestionType.TrueFalse, "New Answer", 4, 1, true, options);
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(existing);
|
|
_mockLessonRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(lesson);
|
|
_mockQuizQuestionRepo.Setup(r => r.OrderExistsInLessonAsync(1, 1, 1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(false);
|
|
_mockQuizQuestionRepo.Setup(r => r.UpdateAsync(existing, It.IsAny<CancellationToken>()))
|
|
.Returns(Task.CompletedTask);
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(existing);
|
|
_mockQuizOptionRepo.Setup(r => r.DeleteByQuestionAsync(1, It.IsAny<CancellationToken>()))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var result = await _service.UpdateQuizQuestionAsync(1, dto);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("Updated Question", result.QuestionText);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task DeleteQuizQuestionAsync_WithExistingQuestion_ReturnsTrue()
|
|
{
|
|
var question = QuizQuestion.Create(1, "Test Question", QuestionType.MultipleChoice, "Answer", 3, 1);
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(question);
|
|
_mockQuizOptionRepo.Setup(r => r.DeleteByQuestionAsync(1, It.IsAny<CancellationToken>()))
|
|
.Returns(Task.CompletedTask);
|
|
_mockQuizQuestionRepo.Setup(r => r.DeleteAsync(question, It.IsAny<CancellationToken>()))
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var result = await _service.DeleteQuizQuestionAsync(1);
|
|
|
|
Assert.IsTrue(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetQuizOptionsByQuestionAsync_ReturnsOptions()
|
|
{
|
|
var options = new List<QuizOption>
|
|
{
|
|
QuizOption.Create(1, "Option 1", true, 1),
|
|
QuizOption.Create(1, "Option 2", false, 2)
|
|
};
|
|
|
|
_mockQuizOptionRepo.Setup(r => r.GetByQuestionAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(options);
|
|
|
|
var result = await _service.GetQuizOptionsByQuestionAsync(1);
|
|
|
|
Assert.AreEqual(2, result.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetCorrectOptionAsync_ReturnsCorrectOption()
|
|
{
|
|
var option = QuizOption.Create(1, "Correct Option", true, 1);
|
|
|
|
_mockQuizOptionRepo.Setup(r => r.GetCorrectOptionAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(option);
|
|
|
|
var result = await _service.GetCorrectOptionAsync(1);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.IsTrue(result.IsCorrect);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task CreateQuizOptionAsync_WithValidData_CreatesOption()
|
|
{
|
|
var question = QuizQuestion.Create(1, "Test Question", QuestionType.MultipleChoice, "Answer", 3, 1);
|
|
var dto = new CreateQuizOptionDto("New Option", true, 1);
|
|
var createdOption = QuizOption.Create(1, "New Option", true, 1);
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByIdAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(question);
|
|
_mockQuizOptionRepo.Setup(r => r.OrderExistsForQuestionAsync(1, 1, null, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(false);
|
|
_mockQuizOptionRepo.Setup(r => r.AddAsync(It.IsAny<QuizOption>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(createdOption);
|
|
|
|
var result = await _service.CreateQuizOptionAsync(1, dto);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("New Option", result.Text);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task SubmitQuizAnswersAsync_WithCorrectAnswers_ReturnsPassedResult()
|
|
{
|
|
var question = QuizQuestion.Create(1, "What is 2+2?", QuestionType.FillInTheBlank, "4", 3, 1);
|
|
var questions = new List<QuizQuestion> { question };
|
|
var answers = new List<QuizAnswerDto>
|
|
{
|
|
new QuizAnswerDto(1, "4", null)
|
|
};
|
|
var dto = new SubmitQuizAnswersDto(1, answers);
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByLessonAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(questions);
|
|
|
|
var result = await _service.SubmitQuizAnswersAsync(1, dto);
|
|
|
|
Assert.AreEqual(1, result.TotalQuestions);
|
|
Assert.AreEqual(1, result.CorrectAnswers);
|
|
Assert.AreEqual(100, result.ScorePercentage);
|
|
Assert.IsTrue(result.Passed);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task SubmitQuizAnswersAsync_WithIncorrectAnswers_ReturnsFailedResult()
|
|
{
|
|
var question = QuizQuestion.Create(1, "What is 2+2?", QuestionType.FillInTheBlank, "4", 3, 1);
|
|
var questions = new List<QuizQuestion> { question };
|
|
var answers = new List<QuizAnswerDto>
|
|
{
|
|
new QuizAnswerDto(1, "5", null) // Wrong answer
|
|
};
|
|
var dto = new SubmitQuizAnswersDto(1, answers);
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetByLessonAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(questions);
|
|
|
|
var result = await _service.SubmitQuizAnswersAsync(1, dto);
|
|
|
|
Assert.AreEqual(1, result.TotalQuestions);
|
|
Assert.AreEqual(0, result.CorrectAnswers);
|
|
Assert.AreEqual(0, result.ScorePercentage);
|
|
Assert.IsFalse(result.Passed);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetQuizQuestionCountForLessonAsync_ReturnsCount()
|
|
{
|
|
var questions = new List<QuizQuestion>
|
|
{
|
|
QuizQuestion.Create(1, "Q1", QuestionType.MultipleChoice, "A1", 3, 1),
|
|
QuizQuestion.Create(1, "Q2", QuestionType.MultipleChoice, "A2", 3, 2)
|
|
};
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetActiveByLessonAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(questions);
|
|
|
|
var result = await _service.GetQuizQuestionCountForLessonAsync(1);
|
|
|
|
Assert.AreEqual(2, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task GetDifficultyDistributionAsync_ReturnsDistribution()
|
|
{
|
|
var questions = new List<QuizQuestion>
|
|
{
|
|
QuizQuestion.Create(1, "Easy", QuestionType.MultipleChoice, "A1", 1, 1),
|
|
QuizQuestion.Create(1, "Medium", QuestionType.MultipleChoice, "A2", 3, 2),
|
|
QuizQuestion.Create(1, "Hard", QuestionType.MultipleChoice, "A3", 5, 3)
|
|
};
|
|
|
|
_mockQuizQuestionRepo.Setup(r => r.GetActiveByLessonAsync(1, It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(questions);
|
|
|
|
var result = await _service.GetDifficultyDistributionAsync(1);
|
|
|
|
Assert.AreEqual(1, result[1]); // Easy
|
|
Assert.AreEqual(0, result[2]); // No questions with difficulty 2
|
|
Assert.AreEqual(1, result[3]); // Medium
|
|
Assert.AreEqual(0, result[4]); // No questions with difficulty 4
|
|
Assert.AreEqual(1, result[5]); // Hard
|
|
}
|
|
|
|
}
|