225 lines
7.9 KiB
C#
225 lines
7.9 KiB
C#
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<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 passwordService = new PasswordService();
|
|
var configuration = Substitute.For<IConfiguration>();
|
|
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<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();
|
|
}
|
|
}
|