2026-01-08 04:00:27 +00:00
|
|
|
namespace Application.Tests.Students;
|
|
|
|
|
|
2026-01-09 12:42:05 +00:00
|
|
|
using Application.Auth;
|
2026-01-08 04:00:27 +00:00
|
|
|
using Application.Students.Commands;
|
|
|
|
|
using Application.Students.DTOs;
|
|
|
|
|
using Domain.Entities;
|
|
|
|
|
using Domain.Exceptions;
|
|
|
|
|
using Domain.Ports.Repositories;
|
|
|
|
|
using Domain.ValueObjects;
|
|
|
|
|
using FluentAssertions;
|
2026-01-09 12:42:05 +00:00
|
|
|
using Microsoft.Extensions.Configuration;
|
2026-01-08 04:00:27 +00:00
|
|
|
using NSubstitute;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
public class CreateStudentCommandTests
|
|
|
|
|
{
|
|
|
|
|
private readonly IStudentRepository _studentRepository;
|
2026-01-09 12:42:05 +00:00
|
|
|
private readonly IPasswordService _passwordService;
|
2026-01-08 04:00:27 +00:00
|
|
|
private readonly IUnitOfWork _unitOfWork;
|
2026-01-09 12:42:05 +00:00
|
|
|
private readonly IConfiguration _configuration;
|
2026-01-08 04:00:27 +00:00
|
|
|
private readonly CreateStudentHandler _handler;
|
|
|
|
|
|
|
|
|
|
public CreateStudentCommandTests()
|
|
|
|
|
{
|
|
|
|
|
_studentRepository = Substitute.For<IStudentRepository>();
|
2026-01-09 12:42:05 +00:00
|
|
|
_passwordService = Substitute.For<IPasswordService>();
|
2026-01-08 04:00:27 +00:00
|
|
|
_unitOfWork = Substitute.For<IUnitOfWork>();
|
2026-01-09 12:42:05 +00:00
|
|
|
_configuration = Substitute.For<IConfiguration>();
|
|
|
|
|
|
|
|
|
|
_passwordService.HashPassword(Arg.Any<string>()).Returns("hashed_code");
|
|
|
|
|
_configuration["App:BaseUrl"].Returns("http://localhost:4200");
|
|
|
|
|
|
|
|
|
|
_handler = new CreateStudentHandler(_studentRepository, _passwordService, _unitOfWork, _configuration);
|
2026-01-08 04:00:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WithValidData_ShouldCreateStudent()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var command = new CreateStudentCommand("John Doe", "john@example.com");
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Should().NotBeNull();
|
2026-01-09 12:42:05 +00:00
|
|
|
result.Student.Name.Should().Be("John Doe");
|
|
|
|
|
result.Student.Email.Should().Be("john@example.com");
|
|
|
|
|
result.Student.TotalCredits.Should().Be(0);
|
|
|
|
|
result.Student.Enrollments.Should().BeEmpty();
|
|
|
|
|
result.ActivationCode.Should().NotBeNullOrEmpty();
|
|
|
|
|
result.ActivationUrl.Should().Contain("/activate?code=");
|
2026-01-08 04:00:27 +00:00
|
|
|
|
|
|
|
|
_studentRepository.Received(1).Add(Arg.Any<Student>());
|
|
|
|
|
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WithInvalidEmail_ShouldThrow()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var command = new CreateStudentCommand("John Doe", "invalid-email");
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await act.Should().ThrowAsync<ArgumentException>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WithEmptyName_ShouldThrow()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var command = new CreateStudentCommand("", "john@example.com");
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await act.Should().ThrowAsync<ArgumentException>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class UpdateStudentCommandTests
|
|
|
|
|
{
|
|
|
|
|
private readonly IStudentRepository _studentRepository;
|
|
|
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
|
|
|
private readonly UpdateStudentHandler _handler;
|
|
|
|
|
|
|
|
|
|
public UpdateStudentCommandTests()
|
|
|
|
|
{
|
|
|
|
|
_studentRepository = Substitute.For<IStudentRepository>();
|
|
|
|
|
_unitOfWork = Substitute.For<IUnitOfWork>();
|
|
|
|
|
_handler = new UpdateStudentHandler(_studentRepository, _unitOfWork);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WhenStudentExists_ShouldUpdateStudent()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var student = new Student("Old Name", Email.Create("old@example.com"));
|
|
|
|
|
SetEntityId(student, 1);
|
|
|
|
|
|
|
|
|
|
_studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(student);
|
|
|
|
|
|
|
|
|
|
var command = new UpdateStudentCommand(1, "New Name", "new@example.com");
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Should().NotBeNull();
|
|
|
|
|
result.Name.Should().Be("New Name");
|
|
|
|
|
result.Email.Should().Be("new@example.com");
|
|
|
|
|
|
|
|
|
|
_studentRepository.Received(1).Update(student);
|
|
|
|
|
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WhenStudentNotFound_ShouldThrow()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
_studentRepository.GetByIdWithEnrollmentsAsync(999, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns((Student?)null);
|
|
|
|
|
|
|
|
|
|
var command = new UpdateStudentCommand(999, "Name", "email@test.com");
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await act.Should().ThrowAsync<StudentNotFoundException>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WithInvalidEmail_ShouldThrow()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var student = new Student("Name", Email.Create("old@example.com"));
|
|
|
|
|
_studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(student);
|
|
|
|
|
|
|
|
|
|
var command = new UpdateStudentCommand(1, "Name", "invalid");
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await act.Should().ThrowAsync<ArgumentException>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SetEntityId<T>(T entity, int id) where T : class
|
|
|
|
|
{
|
|
|
|
|
typeof(T).GetProperty("Id")?.SetValue(entity, id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class DeleteStudentCommandTests
|
|
|
|
|
{
|
|
|
|
|
private readonly IStudentRepository _studentRepository;
|
|
|
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
|
|
|
private readonly DeleteStudentHandler _handler;
|
|
|
|
|
|
|
|
|
|
public DeleteStudentCommandTests()
|
|
|
|
|
{
|
|
|
|
|
_studentRepository = Substitute.For<IStudentRepository>();
|
|
|
|
|
_unitOfWork = Substitute.For<IUnitOfWork>();
|
|
|
|
|
_handler = new DeleteStudentHandler(_studentRepository, _unitOfWork);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WhenStudentExists_ShouldDeleteStudent()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var student = new Student("John Doe", Email.Create("john@example.com"));
|
|
|
|
|
_studentRepository.GetByIdAsync(1, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(student);
|
|
|
|
|
|
|
|
|
|
var command = new DeleteStudentCommand(1);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Should().BeTrue();
|
|
|
|
|
_studentRepository.Received(1).Delete(student);
|
|
|
|
|
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Handle_WhenStudentNotFound_ShouldThrow()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
_studentRepository.GetByIdAsync(999, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns((Student?)null);
|
|
|
|
|
|
|
|
|
|
var command = new DeleteStudentCommand(999);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _handler.Handle(command, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await act.Should().ThrowAsync<StudentNotFoundException>();
|
|
|
|
|
}
|
|
|
|
|
}
|