namespace Application.Tests.Subjects; using Application.Subjects.DTOs; using Application.Subjects.Queries; using Domain.Ports.Repositories; using FluentAssertions; using NSubstitute; using System.Linq.Expressions; using Xunit; public class GetSubjectsQueryTests { private readonly ISubjectRepository _subjectRepository; private readonly GetSubjectsHandler _handler; public GetSubjectsQueryTests() { _subjectRepository = Substitute.For(); _handler = new GetSubjectsHandler(_subjectRepository); } [Fact] public async Task Handle_ShouldReturnAllSubjects() { // Arrange var subjects = new List { new(1, "Math", 3, 1, "Prof A"), new(2, "Physics", 3, 1, "Prof A"), new(3, "Chemistry", 3, 2, "Prof B") }; _subjectRepository.GetAllProjectedAsync( Arg.Any>>(), Arg.Any()) .Returns(subjects); var query = new GetSubjectsQuery(); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().HaveCount(3); result[0].Name.Should().Be("Math"); result[0].ProfessorName.Should().Be("Prof A"); } [Fact] public async Task Handle_WhenNoSubjects_ShouldReturnEmptyList() { // Arrange _subjectRepository.GetAllProjectedAsync( Arg.Any>>(), Arg.Any()) .Returns(new List()); var query = new GetSubjectsQuery(); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().BeEmpty(); } [Fact] public async Task Handle_ShouldReturnCorrectCredits() { // Arrange var subjects = new List { new(1, "Math", 3, 1, "Prof A") }; _subjectRepository.GetAllProjectedAsync( Arg.Any>>(), Arg.Any()) .Returns(subjects); var query = new GetSubjectsQuery(); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result[0].Credits.Should().Be(3); } } public class GetAvailableSubjectsQueryTests { private readonly IStudentRepository _studentRepository; private readonly ISubjectRepository _subjectRepository; private readonly GetAvailableSubjectsHandler _handler; public GetAvailableSubjectsQueryTests() { _studentRepository = Substitute.For(); _subjectRepository = Substitute.For(); _handler = new GetAvailableSubjectsHandler(_studentRepository, _subjectRepository); } [Fact] public async Task Handle_WhenStudentNotFound_ShouldThrow() { // Arrange _studentRepository.GetByIdWithEnrollmentsAsync(999, Arg.Any()) .Returns((Domain.Entities.Student?)null); var query = new GetAvailableSubjectsQuery(999); // Act var act = () => _handler.Handle(query, CancellationToken.None); // Assert await act.Should().ThrowAsync(); } [Fact] public async Task Handle_WhenNoEnrollments_AllSubjectsShouldBeAvailable() { // Arrange var student = new Domain.Entities.Student("John", Domain.ValueObjects.Email.Create("john@test.com")); var professor = new Domain.Entities.Professor("Prof A"); SetEntityId(professor, 1); var subjects = new List { CreateSubject(1, "Math", 1, professor), CreateSubject(2, "Physics", 1, professor) }; _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); _subjectRepository.GetAllWithProfessorsAsync(Arg.Any()) .Returns(subjects); var query = new GetAvailableSubjectsQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result.Should().HaveCount(2); result.Should().OnlyContain(s => s.IsAvailable); } [Fact] public async Task Handle_WhenAlreadyEnrolled_SubjectShouldNotBeAvailable() { // Arrange var student = new Domain.Entities.Student("John", Domain.ValueObjects.Email.Create("john@test.com")); SetEntityId(student, 1); var professor = new Domain.Entities.Professor("Prof A"); SetEntityId(professor, 1); var subject = CreateSubject(1, "Math", 1, professor); var enrollment = new Domain.Entities.Enrollment(1, 1); SetNavigationProperty(enrollment, "Subject", subject); student.AddEnrollment(enrollment); _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); _subjectRepository.GetAllWithProfessorsAsync(Arg.Any()) .Returns(new List { subject }); var query = new GetAvailableSubjectsQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result[0].IsAvailable.Should().BeFalse(); result[0].UnavailableReason.Should().Be("Already enrolled"); } [Fact] public async Task Handle_WhenSameProfessor_SubjectShouldNotBeAvailable() { // Arrange var student = new Domain.Entities.Student("John", Domain.ValueObjects.Email.Create("john@test.com")); SetEntityId(student, 1); var professor = new Domain.Entities.Professor("Prof A"); SetEntityId(professor, 1); var subject1 = CreateSubject(1, "Math", 1, professor); var subject2 = CreateSubject(2, "Physics", 1, professor); var enrollment = new Domain.Entities.Enrollment(1, 1); SetNavigationProperty(enrollment, "Subject", subject1); student.AddEnrollment(enrollment); _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); _subjectRepository.GetAllWithProfessorsAsync(Arg.Any()) .Returns(new List { subject1, subject2 }); var query = new GetAvailableSubjectsQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert var physics = result.First(s => s.Name == "Physics"); physics.IsAvailable.Should().BeFalse(); physics.UnavailableReason.Should().Be("Already have a subject with this professor"); } [Fact] public async Task Handle_WhenMaxEnrollmentsReached_AllSubjectsShouldBeUnavailable() { // Arrange var student = new Domain.Entities.Student("John", Domain.ValueObjects.Email.Create("john@test.com")); SetEntityId(student, 1); var prof1 = new Domain.Entities.Professor("Prof A"); var prof2 = new Domain.Entities.Professor("Prof B"); var prof3 = new Domain.Entities.Professor("Prof C"); var prof4 = new Domain.Entities.Professor("Prof D"); SetEntityId(prof1, 1); SetEntityId(prof2, 2); SetEntityId(prof3, 3); SetEntityId(prof4, 4); var subject1 = CreateSubject(1, "Math", 1, prof1); var subject2 = CreateSubject(2, "Physics", 2, prof2); var subject3 = CreateSubject(3, "Chemistry", 3, prof3); var subject4 = CreateSubject(4, "Biology", 4, prof4); student.AddEnrollment(CreateEnrollmentWithSubject(1, 1, subject1)); student.AddEnrollment(CreateEnrollmentWithSubject(1, 2, subject2)); student.AddEnrollment(CreateEnrollmentWithSubject(1, 3, subject3)); _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any()) .Returns(student); _subjectRepository.GetAllWithProfessorsAsync(Arg.Any()) .Returns(new List { subject4 }); var query = new GetAvailableSubjectsQuery(1); // Act var result = await _handler.Handle(query, CancellationToken.None); // Assert result[0].IsAvailable.Should().BeFalse(); result[0].UnavailableReason.Should().Be("Maximum 3 subjects reached"); } private static void SetEntityId(T entity, int id) where T : class { typeof(T).GetProperty("Id")?.SetValue(entity, id); } private static void SetNavigationProperty(T entity, string propertyName, object value) where T : class { typeof(T).GetProperty(propertyName)?.SetValue(entity, value); } private static Domain.Entities.Subject CreateSubject(int id, string name, int professorId, Domain.Entities.Professor professor) { var subject = new Domain.Entities.Subject(name, professorId); SetEntityId(subject, id); SetNavigationProperty(subject, "Professor", professor); return subject; } private static Domain.Entities.Enrollment CreateEnrollmentWithSubject(int studentId, int subjectId, Domain.Entities.Subject subject) { var enrollment = new Domain.Entities.Enrollment(studentId, subjectId); SetNavigationProperty(enrollment, "Subject", subject); return enrollment; } }