academia/tests/Integration.Tests/EnrollmentFlowTests.cs

215 lines
7.4 KiB
C#
Raw Normal View History

namespace Integration.Tests;
using Adapters.Driven.Persistence.Context;
using Adapters.Driven.Persistence.Repositories;
using Application.Enrollments.Commands;
using Application.Students.Commands;
using Domain.Entities;
using Domain.Exceptions;
using Domain.Services;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
public class EnrollmentFlowTests : IDisposable
{
private readonly AppDbContext _context;
private readonly StudentRepository _studentRepository;
private readonly SubjectRepository _subjectRepository;
private readonly EnrollmentRepository _enrollmentRepository;
private readonly UnitOfWork _unitOfWork;
public EnrollmentFlowTests()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new AppDbContext(options);
_studentRepository = new StudentRepository(_context);
_subjectRepository = new SubjectRepository(_context);
_enrollmentRepository = new EnrollmentRepository(_context);
_unitOfWork = new UnitOfWork(_context);
SeedData();
}
private void SeedData()
{
var prof1 = new Professor("Dr. Smith");
var prof2 = new Professor("Dr. Jones");
_context.Professors.AddRange(prof1, prof2);
_context.SaveChanges();
var subjects = new[]
{
new Subject("Mathematics", prof1.Id),
new Subject("Physics", prof1.Id),
new Subject("Chemistry", prof2.Id),
new Subject("Biology", prof2.Id)
};
_context.Subjects.AddRange(subjects);
_context.SaveChanges();
}
[Fact]
public async Task CreateStudent_ShouldPersistToDatabase()
{
// Arrange
var handler = new CreateStudentHandler(_studentRepository, _unitOfWork);
var command = new CreateStudentCommand("John Doe", "john@example.com");
// Act
var result = await handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Name.Should().Be("John Doe");
var savedStudent = await _context.Students.FirstOrDefaultAsync(s => s.Email.Value == "john@example.com");
savedStudent.Should().NotBeNull();
}
[Fact]
public async Task EnrollStudent_ShouldCreateEnrollment()
{
// Arrange
var student = new Student("Jane Doe", Domain.ValueObjects.Email.Create("jane@example.com"));
_context.Students.Add(student);
await _context.SaveChangesAsync();
var subject = await _context.Subjects.FirstAsync();
var enrollmentService = new EnrollmentDomainService();
var handler = new EnrollStudentHandler(
_studentRepository, _subjectRepository, _enrollmentRepository,
enrollmentService, _unitOfWork);
var command = new EnrollStudentCommand(student.Id, subject.Id);
// Act
var result = await handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
var enrollment = await _context.Enrollments
.FirstOrDefaultAsync(e => e.StudentId == student.Id && e.SubjectId == subject.Id);
enrollment.Should().NotBeNull();
}
[Fact]
public async Task EnrollStudent_WhenMaxEnrollments_ShouldThrow()
{
// Arrange
var student = new Student("Max Student", Domain.ValueObjects.Email.Create("max@example.com"));
_context.Students.Add(student);
await _context.SaveChangesAsync();
var subjects = await _context.Subjects.Take(4).ToListAsync();
var enrollmentService = new EnrollmentDomainService();
// Enroll in 3 subjects (max allowed) with different professors
var subjectsWithDiffProfs = subjects.GroupBy(s => s.ProfessorId).Select(g => g.First()).Take(3).ToList();
if (subjectsWithDiffProfs.Count < 3)
{
// If we don't have 3 different professors, just use first 3 subjects
subjectsWithDiffProfs = subjects.Take(3).ToList();
}
foreach (var subject in subjectsWithDiffProfs.Take(2))
{
_context.Enrollments.Add(new Enrollment(student.Id, subject.Id));
}
await _context.SaveChangesAsync();
// Need to reload student with enrollments
_context.Entry(student).State = EntityState.Detached;
var handler = new EnrollStudentHandler(
_studentRepository, _subjectRepository, _enrollmentRepository,
enrollmentService, _unitOfWork);
// Add third enrollment
var thirdSubject = subjectsWithDiffProfs.Skip(2).First();
var thirdCommand = new EnrollStudentCommand(student.Id, thirdSubject.Id);
await handler.Handle(thirdCommand, CancellationToken.None);
// Try fourth enrollment - should fail
var fourthSubject = subjects.First(s => !subjectsWithDiffProfs.Contains(s));
var fourthCommand = new EnrollStudentCommand(student.Id, fourthSubject.Id);
// Act
var act = () => handler.Handle(fourthCommand, CancellationToken.None);
// Assert
await act.Should().ThrowAsync<MaxEnrollmentsExceededException>();
}
[Fact]
public async Task EnrollStudent_WhenSameProfessor_ShouldThrow()
{
// Arrange
var student = new Student("Prof Test", Domain.ValueObjects.Email.Create("prof@example.com"));
_context.Students.Add(student);
await _context.SaveChangesAsync();
// Get all subjects and find two with same professor in memory
var allSubjects = await _context.Subjects.ToListAsync();
var subjectsSameProf = allSubjects
.GroupBy(s => s.ProfessorId)
.Where(g => g.Count() >= 2)
.SelectMany(g => g.Take(2))
.ToList();
if (subjectsSameProf.Count < 2)
{
// Skip test if we don't have subjects with same professor
return;
}
var enrollmentService = new EnrollmentDomainService();
var handler = new EnrollStudentHandler(
_studentRepository, _subjectRepository, _enrollmentRepository,
enrollmentService, _unitOfWork);
// Enroll first subject
var firstCommand = new EnrollStudentCommand(student.Id, subjectsSameProf[0].Id);
await handler.Handle(firstCommand, CancellationToken.None);
// Try second subject with same professor
var secondCommand = new EnrollStudentCommand(student.Id, subjectsSameProf[1].Id);
// Act
var act = () => handler.Handle(secondCommand, CancellationToken.None);
// Assert
await act.Should().ThrowAsync<SameProfessorConstraintException>();
}
[Fact]
public async Task DeleteStudent_ShouldRemoveFromDatabase()
{
// Arrange
var student = new Student("Delete Me", Domain.ValueObjects.Email.Create("delete@example.com"));
_context.Students.Add(student);
await _context.SaveChangesAsync();
var studentId = student.Id;
var handler = new DeleteStudentHandler(_studentRepository, _unitOfWork);
var command = new DeleteStudentCommand(studentId);
// Act
var result = await handler.Handle(command, CancellationToken.None);
// Assert
result.Should().BeTrue();
var deletedStudent = await _context.Students.FindAsync(studentId);
deletedStudent.Should().BeNull();
}
public void Dispose()
{
_context.Dispose();
}
}