namespace Application.Tests.Enrollments; using Application.Enrollments.Queries; using Domain.Entities; using Domain.Exceptions; using Domain.Ports.Repositories; using Domain.ValueObjects; using FluentAssertions; using NSubstitute; using Xunit; public class GetClassmatesQueryTests { private readonly IStudentRepository _studentRepository; private readonly IEnrollmentRepository _enrollmentRepository; private readonly GetClassmatesHandler _handler; public GetClassmatesQueryTests() { _studentRepository = Substitute.For(); _enrollmentRepository = Substitute.For(); _handler = new GetClassmatesHandler(_studentRepository, _enrollmentRepository); } [Fact] public async Task Handle_WhenStudentNotFound_ShouldThrow() { // Arrange _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns((Student?)null); var query = new GetClassmatesQuery(1); // Act var act = () => _handler.Handle(query, CancellationToken.None); // Assert await act.Should().ThrowAsync(); } [Fact] public async Task Handle_WhenStudentHasNoEnrollments_ShouldReturnEmptyList() { // Arrange var student = new Student("John Doe", Email.Create("john@test.com")); _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); var query = new GetClassmatesQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().BeEmpty(); await _enrollmentRepository.DidNotReceive() .GetClassmatesBatchAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Fact] public async Task Handle_WhenStudentHasEnrollments_ShouldUseBatchQuery() { // Arrange var student = new Student("John Doe", Email.Create("john@test.com")); SetEntityId(student, 1); // Create enrollments with IDs var enrollment1 = new Enrollment(1, 1); var enrollment2 = new Enrollment(1, 2); SetEntityId(enrollment1, 1); SetEntityId(enrollment2, 2); // Use reflection to set Subject property for name access var subject1 = new Subject("Math", 1); var subject2 = new Subject("Physics", 2); SetEntityId(subject1, 1); SetEntityId(subject2, 2); SetNavigationProperty(enrollment1, "Subject", subject1); SetNavigationProperty(enrollment2, "Subject", subject2); student.AddEnrollment(enrollment1); student.AddEnrollment(enrollment2); _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); var classmate1 = new Student("Jane Doe", Email.Create("jane@test.com")); var classmate2 = new Student("Bob Smith", Email.Create("bob@test.com")); SetEntityId(classmate1, 2); SetEntityId(classmate2, 3); var batchResult = new Dictionary> { [1] = [classmate1], [2] = [classmate2] }; _enrollmentRepository.GetClassmatesBatchAsync(1, Arg.Any>(), Arg.Any()) .Returns(batchResult); var query = new GetClassmatesQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().HaveCount(2); result.Should().Contain(r => r.SubjectId == 1 && r.Classmates.Any(c => c.Name == "Jane Doe")); result.Should().Contain(r => r.SubjectId == 2 && r.Classmates.Any(c => c.Name == "Bob Smith")); await _enrollmentRepository.Received(1) .GetClassmatesBatchAsync(1, Arg.Any>(), Arg.Any()); } [Fact] public async Task Handle_WhenNoClassmatesFound_ShouldReturnEmptyClassmatesLists() { // Arrange var student = new Student("John Doe", Email.Create("john@test.com")); SetEntityId(student, 1); var enrollment = new Enrollment(1, 1); SetEntityId(enrollment, 1); var subject = new Subject("Math", 1); SetEntityId(subject, 1); SetNavigationProperty(enrollment, "Subject", subject); student.AddEnrollment(enrollment); _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); var emptyBatchResult = new Dictionary>(); _enrollmentRepository.GetClassmatesBatchAsync(1, Arg.Any>(), Arg.Any()) .Returns(emptyBatchResult); var query = new GetClassmatesQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().HaveCount(1); result[0].Classmates.Should().BeEmpty(); } private static void SetEntityId(T entity, int id) where T : class { var prop = typeof(T).GetProperty("Id"); prop?.SetValue(entity, id); } private static void SetNavigationProperty(T entity, string propertyName, object value) where T : class { var prop = typeof(T).GetProperty(propertyName); prop?.SetValue(entity, value); } }