academia/tests/Application.Tests/Subjects/SubjectQueriesTests.cs

279 lines
9.4 KiB
C#

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<ISubjectRepository>();
_handler = new GetSubjectsHandler(_subjectRepository);
}
[Fact]
public async Task Handle_ShouldReturnAllSubjects()
{
// Arrange
var subjects = new List<SubjectDto>
{
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<Expression<Func<Domain.Entities.Subject, SubjectDto>>>(),
Arg.Any<CancellationToken>())
.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<Expression<Func<Domain.Entities.Subject, SubjectDto>>>(),
Arg.Any<CancellationToken>())
.Returns(new List<SubjectDto>());
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<SubjectDto>
{
new(1, "Math", 3, 1, "Prof A")
};
_subjectRepository.GetAllProjectedAsync(
Arg.Any<Expression<Func<Domain.Entities.Subject, SubjectDto>>>(),
Arg.Any<CancellationToken>())
.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<IStudentRepository>();
_subjectRepository = Substitute.For<ISubjectRepository>();
_handler = new GetAvailableSubjectsHandler(_studentRepository, _subjectRepository);
}
[Fact]
public async Task Handle_WhenStudentNotFound_ShouldThrow()
{
// Arrange
_studentRepository.GetByIdWithEnrollmentsAsync(999, Arg.Any<CancellationToken>())
.Returns((Domain.Entities.Student?)null);
var query = new GetAvailableSubjectsQuery(999);
// Act
var act = () => _handler.Handle(query, CancellationToken.None);
// Assert
await act.Should().ThrowAsync<Domain.Exceptions.StudentNotFoundException>();
}
[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<Domain.Entities.Subject>
{
CreateSubject(1, "Math", 1, professor),
CreateSubject(2, "Physics", 1, professor)
};
_studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
.Returns(student);
_subjectRepository.GetAllWithProfessorsAsync(Arg.Any<CancellationToken>())
.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<CancellationToken>())
.Returns(student);
_subjectRepository.GetAllWithProfessorsAsync(Arg.Any<CancellationToken>())
.Returns(new List<Domain.Entities.Subject> { 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<CancellationToken>())
.Returns(student);
_subjectRepository.GetAllWithProfessorsAsync(Arg.Any<CancellationToken>())
.Returns(new List<Domain.Entities.Subject> { 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<CancellationToken>())
.Returns(student);
_subjectRepository.GetAllWithProfessorsAsync(Arg.Any<CancellationToken>())
.Returns(new List<Domain.Entities.Subject> { 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>(T entity, int id) where T : class
{
typeof(T).GetProperty("Id")?.SetValue(entity, id);
}
private static void SetNavigationProperty<T>(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;
}
}