namespace Integration.Tests; using Adapters.Driven.Persistence.Context; using Adapters.Driven.Persistence.Repositories; using Adapters.Driven.Persistence.Services; using Application.Auth; using Application.Enrollments.Commands; using Application.Students.Commands; using Domain.Entities; using Domain.Exceptions; using Domain.Services; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using NSubstitute; 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() .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 passwordService = new PasswordService(); var configuration = Substitute.For(); configuration["App:BaseUrl"].Returns("http://localhost:4200"); var handler = new CreateStudentHandler(_studentRepository, passwordService, _unitOfWork, configuration); var command = new CreateStudentCommand("John Doe", "john@example.com"); // Act var result = await handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.Student.Name.Should().Be("John Doe"); result.ActivationCode.Should().NotBeNullOrEmpty(); var savedStudent = await _context.Students.FirstOrDefaultAsync(s => s.Email.Value == "john@example.com"); savedStudent.Should().NotBeNull(); savedStudent!.ActivationCodeHash.Should().NotBeNullOrEmpty(); } [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(); } [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(); } [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(); } }