193 lines
8.4 KiB
C#
193 lines
8.4 KiB
C#
using Baya.Application.Contracts.Common;
|
|
using Baya.Application.Contracts.Configuration;
|
|
using Baya.Application.Contracts.Persistence;
|
|
using Baya.Application.Features.Booking.Commands.CreateBookingRequest;
|
|
using Baya.Application.Models.Booking;
|
|
using Baya.Application.Models.Identity;
|
|
using Baya.Domain.Entities.Booking;
|
|
using Baya.Domain.Entities.Catalog;
|
|
using Baya.Domain.Entities.Identity;
|
|
using Baya.Domain.Entities.User;
|
|
using NSubstitute;
|
|
|
|
namespace Baya.Test.Foundation.Booking;
|
|
|
|
public class CreateBookingRequestHandlerTests
|
|
{
|
|
private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
|
|
private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
|
|
private readonly IPlatformConfig _config = Substitute.For<IPlatformConfig>();
|
|
private readonly IDateTimeProvider _clock = Substitute.For<IDateTimeProvider>();
|
|
private readonly INotificationDispatcher _notifications = Substitute.For<INotificationDispatcher>();
|
|
|
|
private readonly ICustomerProfileRepository _customers = Substitute.For<ICustomerProfileRepository>();
|
|
private readonly IPatientRepository _patients = Substitute.For<IPatientRepository>();
|
|
private readonly ICustomerAddressRepository _addresses = Substitute.For<ICustomerAddressRepository>();
|
|
private readonly INurseServiceVariantRepository _variants = Substitute.For<INurseServiceVariantRepository>();
|
|
private readonly INurseProfileRepository _nurses = Substitute.For<INurseProfileRepository>();
|
|
private readonly IBookingRequestRepository _requests = Substitute.For<IBookingRequestRepository>();
|
|
|
|
private const int CustomerUserId = 7;
|
|
private const long CustomerId = 100;
|
|
private const long NurseId = 42;
|
|
private const int NurseUserId = 9;
|
|
private const long PatientId = 5;
|
|
private const long AddressId = 6;
|
|
private const long VariantId = 8;
|
|
private static readonly DateTimeOffset Now = new(2026, 7, 6, 10, 0, 0, TimeSpan.Zero);
|
|
|
|
public CreateBookingRequestHandlerTests()
|
|
{
|
|
_currentUser.UserId.Returns(CustomerUserId);
|
|
_currentUser.Roles.Returns([RoleNames.Customer]);
|
|
_clock.UtcNow.Returns(Now);
|
|
|
|
_unitOfWork.CustomerProfileRepository.Returns(_customers);
|
|
_unitOfWork.PatientRepository.Returns(_patients);
|
|
_unitOfWork.CustomerAddressRepository.Returns(_addresses);
|
|
_unitOfWork.NurseServiceVariantRepository.Returns(_variants);
|
|
_unitOfWork.NurseProfileRepository.Returns(_nurses);
|
|
_unitOfWork.BookingRequestRepository.Returns(_requests);
|
|
|
|
_customers.GetProfileIdByUserIdAsync(CustomerUserId, Arg.Any<CancellationToken>()).Returns(CustomerId);
|
|
_patients.GetOwnedAsync(PatientId, CustomerId, Arg.Any<CancellationToken>())
|
|
.Returns(new Patient { CustomerId = CustomerId, DisplayName = "پدر", Gender = "male" });
|
|
_addresses.GetOwnedAsync(AddressId, CustomerId, Arg.Any<CancellationToken>())
|
|
.Returns(new CustomerAddress { CustomerId = CustomerId });
|
|
_variants.GetOwnedAsync(VariantId, NurseId, Arg.Any<CancellationToken>())
|
|
.Returns(new NurseServiceVariant { NurseId = NurseId, IsActive = true });
|
|
_nurses.GetBookingContextByIdAsync(NurseId, Arg.Any<CancellationToken>())
|
|
.Returns(new NurseBookingContext(NurseUserId, "female", IsVerified: true, IsAcceptingBookings: true));
|
|
_config.GetConfig<int>("nurse_response_deadline_hours", Arg.Any<CancellationToken>()).Returns(24);
|
|
_requests.GetDetailAsync(Arg.Any<long>(), Arg.Any<CancellationToken>()).Returns(DetailStub());
|
|
}
|
|
|
|
private CreateBookingRequestCommandHandler Handler()
|
|
=> new(_currentUser, _unitOfWork, _config, _clock, _notifications);
|
|
|
|
private static CreateBookingRequestCommand Command(string gender = "female")
|
|
=> new(NurseId, VariantId, PatientId, AddressId,
|
|
new DateOnly(2026, 8, 1), new TimeOnly(9, 0), new TimeOnly(13, 0), gender, "Careful with the IV.");
|
|
|
|
[Fact]
|
|
public async Task Create_Valid_FreezesResponseDeadlineFromConfigAndNotifiesNurse()
|
|
{
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.True(result.IsSuccess);
|
|
await _requests.Received(1).AddAsync(
|
|
Arg.Is<BookingRequest>(r =>
|
|
r.CustomerId == CustomerId && r.NurseId == NurseId && r.PatientId == PatientId
|
|
&& r.Status == BookingRequestStatus.PendingNurseResponse
|
|
&& r.PaymentDeadlineAt == null
|
|
&& r.NurseResponseDeadlineAt == Now.AddHours(24).UtcDateTime),
|
|
Arg.Any<CancellationToken>());
|
|
await _unitOfWork.Received(1).CommitAsync();
|
|
await _notifications.Received(1).DispatchAsync(
|
|
Arg.Is<Notification>(n => n.RecipientUserId == NurseUserId && n.Type == "booking_request_received"),
|
|
Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_AnyGender_MatchesRegardlessOfNurseGender()
|
|
{
|
|
var result = await Handler().Handle(Command("any"), CancellationToken.None);
|
|
|
|
Assert.True(result.IsSuccess);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_SameGenderMismatch_FailsNamingTheConflict()
|
|
{
|
|
// Nurse is female; a male requirement must not match.
|
|
var result = await Handler().Handle(Command("male"), CancellationToken.None);
|
|
|
|
Assert.False(result.IsSuccess);
|
|
Assert.False(result.IsNotFound);
|
|
await _requests.DidNotReceive().AddAsync(Arg.Any<BookingRequest>(), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_CrossCustomerPatient_IsNotFoundAndCreatesNothing()
|
|
{
|
|
_patients.GetOwnedAsync(PatientId, CustomerId, Arg.Any<CancellationToken>()).Returns((Patient)null);
|
|
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.True(result.IsNotFound);
|
|
await _requests.DidNotReceive().AddAsync(Arg.Any<BookingRequest>(), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_AddressNotOwned_IsNotFound()
|
|
{
|
|
_addresses.GetOwnedAsync(AddressId, CustomerId, Arg.Any<CancellationToken>()).Returns((CustomerAddress)null);
|
|
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.True(result.IsNotFound);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_VariantNotThisNurses_IsNotFound()
|
|
{
|
|
_variants.GetOwnedAsync(VariantId, NurseId, Arg.Any<CancellationToken>()).Returns((NurseServiceVariant)null);
|
|
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.True(result.IsNotFound);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_InactiveVariant_Fails()
|
|
{
|
|
_variants.GetOwnedAsync(VariantId, NurseId, Arg.Any<CancellationToken>())
|
|
.Returns(new NurseServiceVariant { NurseId = NurseId, IsActive = false });
|
|
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.False(result.IsSuccess);
|
|
await _requests.DidNotReceive().AddAsync(Arg.Any<BookingRequest>(), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_NurseNotAcceptingOrUnverified_Fails()
|
|
{
|
|
_nurses.GetBookingContextByIdAsync(NurseId, Arg.Any<CancellationToken>())
|
|
.Returns(new NurseBookingContext(NurseUserId, "female", IsVerified: true, IsAcceptingBookings: false));
|
|
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.False(result.IsSuccess);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_PastDate_Fails()
|
|
{
|
|
var command = Command() with { RequestedDate = new DateOnly(2026, 7, 5) };
|
|
|
|
var result = await Handler().Handle(command, CancellationToken.None);
|
|
|
|
Assert.False(result.IsSuccess);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_NonCustomer_IsForbidden()
|
|
{
|
|
_currentUser.Roles.Returns([RoleNames.Nurse]);
|
|
|
|
var result = await Handler().Handle(Command(), CancellationToken.None);
|
|
|
|
Assert.True(result.IsForbidden);
|
|
}
|
|
|
|
private static BookingRequestDetailProjection DetailStub()
|
|
=> new(
|
|
1, BookingRequestStatus.PendingNurseResponse, CustomerId, NurseId,
|
|
"پرستار", 0m, 0, PatientId, "پدر", VariantId, "خدمت", "per_day",
|
|
AddressId, "خانه", 1, "تهران", "Tehran", null, null, null,
|
|
"line", "postal", "recipient", "phone",
|
|
"female", new DateOnly(2026, 8, 1), new TimeOnly(9, 0), new TimeOnly(13, 0),
|
|
"notes", Now.AddHours(24).UtcDateTime, null, null, Now);
|
|
}
|