using Baya.Application.Contracts.Common; using Baya.Application.Contracts.Persistence; using Baya.Application.Features.Verification.Commands.SubmitVerification; using Baya.Application.Models.Identity; using Baya.Application.Models.Verification; using Baya.Domain.Entities.Identity; using Baya.Domain.Entities.User; using Baya.Domain.Entities.Verification; using NSubstitute; using NSubstitute.ReturnsExtensions; using static Baya.Test.Foundation.Verification.VerificationTestSupport; namespace Baya.Test.Foundation.Verification; public class SubmitNurseVerificationHandlerTests { private readonly ICurrentUser _currentUser = Substitute.For(); private readonly IUnitOfWork _unitOfWork = Substitute.For(); private readonly IVerificationRepository _verif = Substitute.For(); private readonly INurseProfileRepository _nurses = Substitute.For(); private readonly IDateTimeProvider _clock = Substitute.For(); public SubmitNurseVerificationHandlerTests() { _currentUser.UserId.Returns(7); _currentUser.Roles.Returns([RoleNames.Nurse]); _unitOfWork.VerificationRepository.Returns(_verif); _unitOfWork.NurseProfileRepository.Returns(_nurses); _clock.UtcNow.Returns(new DateTimeOffset(2026, 7, 1, 12, 0, 0, TimeSpan.Zero)); _nurses.GetIdentityContextByUserIdAsync(7, Arg.Any()) .Returns(new NurseIdentityContext(42, "0012345678")); _nurses.GetTrackedByIdAsync(42, Arg.Any()).Returns(new NurseProfile()); _verif.GetActiveRequiredStepTypesAsync(Arg.Any()) .Returns(new List { StepType(1, VerificationStepTypeCodes.IdentityKyc, automated: true), StepType(2, VerificationStepTypeCodes.MohCompetencyLicense, automated: false) }); _verif.GetStatusForNurseAsync(42, Arg.Any()) .Returns(new VerificationStatusDto("pending", false, ["identity_kyc", "moh_competency_license"], [])); } private SubmitNurseVerificationCommandHandler Handler() => new(_currentUser, _unitOfWork, _clock); [Fact] public async Task Submit_NewVerification_SeedsOneStepPerRequiredType_WithAutomationSnapshot() { _verif.GetTrackedByNurseIdAsync(42, Arg.Any()).ReturnsNull(); var result = await Handler().Handle(new SubmitNurseVerificationCommand(), CancellationToken.None); Assert.True(result.IsSuccess); await _verif.Received(1).AddVerificationAsync( Arg.Is(v => v.NurseId == 42 && v.Status == VerificationStatus.Pending && v.Steps.Count == 2 && v.Steps.Any(s => s.StepTypeId == 1 && s.IsAutomated) && v.Steps.Any(s => s.StepTypeId == 2 && !s.IsAutomated)), Arg.Any()); await _unitOfWork.Received(1).CommitAsync(); } [Fact] public async Task Submit_ExistingVerification_IsIdempotent_OnlyAddsMissingSteps() { var existing = new NurseVerification { NurseId = 42, Status = VerificationStatus.Pending, SubmittedAt = _clock.UtcNow }; existing.Steps.Add(Step(1, StepType(1, VerificationStepTypeCodes.IdentityKyc, automated: true), VerificationStepStatus.Pending)); _verif.GetTrackedByNurseIdAsync(42, Arg.Any()).Returns(existing); var result = await Handler().Handle(new SubmitNurseVerificationCommand(), CancellationToken.None); Assert.True(result.IsSuccess); // Only the missing MoH step is added; the identity step is not duplicated. Assert.Equal(2, existing.Steps.Count); Assert.Single(existing.Steps, s => s.StepTypeId == 2); await _verif.DidNotReceive().AddVerificationAsync(Arg.Any(), Arg.Any()); } [Fact] public async Task Submit_NonNurse_IsForbidden() { _currentUser.Roles.Returns([RoleNames.Customer]); var result = await Handler().Handle(new SubmitNurseVerificationCommand(), CancellationToken.None); Assert.True(result.IsForbidden); await _unitOfWork.DidNotReceive().CommitAsync(); } }