public void Command_with_invalid_CommunicationChannel_fails(CommunicationChannel communicationChannel) { // Arrange var id = EnrollmentAggregate.EnrollmentId.New; var enrollment = new EnrollmentAggregate(id); var training = CreateTrainingInFutureWithId(1); var recordingCoordinator = new Models.Users.ApplicationUser() { Id = Guid.NewGuid() }; var command = new RecordAcceptedTrainingInvitation.Command() { EnrollmentId = id.GetGuid(), CommunicationChannel = communicationChannel, SelectedTrainingID = 1, AdditionalNotes = "brak notatek" }; // Act var result = enrollment.RecordCandidateAcceptedTrainingInvitation(command, recordingCoordinator, new[] { training }, NodaTime.SystemClock.Instance.GetCurrentInstant()); // Assert Assert.False(result.IsSuccess); var error = Assert.IsType <Error.ValidationFailed>(result.Error); var failure = Assert.Single(error.Failures); Assert.Equal(nameof(command.CommunicationChannel), failure.PropertyName); Assert.Single(failure.Errors); }
public Result <Nothing, Error> RecordCandidateAcceptedTrainingInvitation( RecordAcceptedTrainingInvitation.Command command, ApplicationUser recordingCoordinator, IReadOnlyCollection <Training> availableTrainings, Instant currentInstant) { Guard.Against.Null(command, nameof(command)); Guard.Against.Null(recordingCoordinator, nameof(recordingCoordinator)); Guard.Against.Null(availableTrainings, nameof(availableTrainings)); Guard.Against.Default(currentInstant, nameof(currentInstant)); ValidateIdMatchOrThrow(command.EnrollmentId); var preferredTrainings = availableTrainings.Where(x => PreferredTrainingIds.Contains(x.ID)).ToArray(); var selectedTraining = preferredTrainings.SingleOrDefault(x => x.ID == command.SelectedTrainingID); return (Validate(new RecordAcceptedTrainingInvitation.Validator(), command) .Ensure(_ => CanAcceptTrainingInvitation(preferredTrainings, currentInstant)) .Ensure( _ => availableTrainings.Any(x => x.ID == command.SelectedTrainingID), new Error.ResourceNotFound(RecordAcceptedTrainingInvitation_ErrorMessages.TrainingNotFound)) .Ensure( _ => selectedTraining != null && PreferredTrainingIds.Contains(selectedTraining.ID), new Error.ValidationFailed(nameof(command.SelectedTrainingID), RecordAcceptedTrainingInvitation_ErrorMessages.TrainingWasNotSpecifiedAsPreferred)) .Ensure( _ => availableTrainings.Single(x => x.ID == command.SelectedTrainingID).StartDateTime.ToInstant() > currentInstant, new Error.ValidationFailed(nameof(command.SelectedTrainingID), RecordAcceptedTrainingInvitation_ErrorMessages.TrainingTimeAlreadyPassed)) .Tap(_ => Emit(new CandidateAcceptedTrainingInvitation( recordingCoordinatorID: recordingCoordinator.Id, communicationChannel: command.CommunicationChannel, selectedTrainingID: command.SelectedTrainingID, additionalNotes: command.AdditionalNotes ?? string.Empty)))); }
public void Candidate_must_be_registered_to_accept_training_invitation() { // Arrange var id = EnrollmentAggregate.EnrollmentId.New; var enrollment = new EnrollmentAggregate(id); var training = CreateTrainingInFutureWithId(1); var recordingCoordinator = new Models.Users.ApplicationUser() { Id = Guid.NewGuid() }; var command = new RecordAcceptedTrainingInvitation.Command() { EnrollmentId = id.GetGuid(), CommunicationChannel = CommunicationChannel.OutgoingEmail, SelectedTrainingID = 1, AdditionalNotes = "brak notatek" }; // Act var result = enrollment.RecordCandidateAcceptedTrainingInvitation(command, recordingCoordinator, new[] { training }, NodaTime.SystemClock.Instance.GetCurrentInstant()); // Assert Assert.True(result.IsFailure); var error = Assert.IsType <Error.ResourceNotFound>(result.Error); Assert.Equal(CommonErrorMessages.CandidateNotFound, error.Message); }
public async Task <Result <Nothing, Error> > Handle(RecordAcceptedTrainingInvitation.Command request, CancellationToken cancellationToken) { var user = await _userAccessor.GetUser(); var enrollmentId = EnrollmentId.With(request.EnrollmentId); var enrollmentReadModel = _enrollmentRepo.Query() .SingleOrDefault(x => x.Id == enrollmentId); if (enrollmentReadModel == null) { return(Result.Failure <Nothing, Error>(new Error.ResourceNotFound())); } var preferredTrainings = await _trainingRepository.GetByIds(enrollmentReadModel.PreferredTrainings.Select(x => x.ID).ToArray()); var result = await _aggregateStore.Update <EnrollmentAggregate, EnrollmentId, Result <Nothing, Error> >( enrollmentId, CommandId.New, (aggregate) => aggregate.RecordCandidateAcceptedTrainingInvitation(request, user, preferredTrainings, _clock.GetCurrentInstant()), cancellationToken); if (result.Unwrap().IsSuccess) { var selectedTraining = preferredTrainings.Single(x => x.ID == request.SelectedTrainingID); _backgroundJobClient.Schedule(() => _engine.Execute( new SendTrainingReminder.Command() { EnrollmentId = request.EnrollmentId, TrainingId = request.SelectedTrainingID }), selectedTraining.StartDateTime.Minus(NodaTime.Duration.FromHours(24)).ToDateTimeOffset()); } return(result.Unwrap()); }
public void Cannot_accept_invitation_to_training_not_selected_as_preferred_training() { // Arrange var id = EnrollmentAggregate.EnrollmentId.New; var event1 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, RecruitmentFormSubmitted>( new RecruitmentFormSubmitted( NodaTime.SystemClock.Instance.GetCurrentInstant(), "Andrzej", "Strzelba", EmailAddress.Parse("*****@*****.**"), Consts.FakePhoneNumber, "ala ma kota", 1, "małopolskie", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, id, 1); var enrollment = new EnrollmentAggregate(id); enrollment.ApplyEvents(new[] { event1 }); var training1 = CreateTrainingInFutureWithId(1); var training2 = CreateTrainingInFutureWithId(2); var recordingCoordinator = new Models.Users.ApplicationUser() { Id = Guid.NewGuid() }; var command = new RecordAcceptedTrainingInvitation.Command() { EnrollmentId = id.GetGuid(), CommunicationChannel = CommunicationChannel.OutgoingEmail, SelectedTrainingID = 2, AdditionalNotes = "brak notatek" }; // Act var result = enrollment.RecordCandidateAcceptedTrainingInvitation(command, recordingCoordinator, new[] { training1, training2 }, NodaTime.SystemClock.Instance.GetCurrentInstant()); // Assert Assert.False(result.IsSuccess); var error = Assert.IsType <Error.ValidationFailed>(result.Error); var failure = Assert.Single(error.Failures); Assert.Equal(nameof(command.SelectedTrainingID), failure.PropertyName); var errorMessage = Assert.Single(failure.Errors); Assert.Equal(RecordAcceptedTrainingInvitation_ErrorMessages.TrainingWasNotSpecifiedAsPreferred, errorMessage); }
public void Cannot_accept_invitation_to_training_earlier_than_submission_date() { // Arrange var id = EnrollmentAggregate.EnrollmentId.New; var event1 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, RecruitmentFormSubmitted>( new RecruitmentFormSubmitted( SystemClock.Instance.GetCurrentInstant(), "Andrzej", "Strzelba", EmailAddress.Parse("*****@*****.**"), Consts.FakePhoneNumber, "ala ma kota", 1, "małopolskie", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, id, 1); var enrollment = new EnrollmentAggregate(id); enrollment.ApplyEvents(new[] { event1 }); var training = new Training( "Papieska 12/37", "Wadowice", SystemClock.Instance.GetOffsetDateTime().Minus(Duration.FromDays(7)), SystemClock.Instance.GetOffsetDateTime().Minus(Duration.FromDays(6)), Guid.NewGuid()); training.GetType().GetProperty(nameof(training.ID)).SetValue(training, 1); var recordingCoordinator = new Models.Users.ApplicationUser() { Id = Guid.NewGuid() }; var command = new RecordAcceptedTrainingInvitation.Command() { EnrollmentId = id.GetGuid(), CommunicationChannel = CommunicationChannel.OutgoingEmail, SelectedTrainingID = 1, AdditionalNotes = "brak notatek" }; // Act var result = enrollment.RecordCandidateAcceptedTrainingInvitation(command, recordingCoordinator, new[] { training }, NodaTime.SystemClock.Instance.GetCurrentInstant()); // Assert Assert.False(result.IsSuccess); var error = Assert.IsType <Error.DomainError>(result.Error); Assert.Equal(RecordAcceptedTrainingInvitation_ErrorMessages.TrainingTimeAlreadyPassed, error.Message); }
public void Registered_candidate_can_accept_training_invitation() { // Arrange var id = EnrollmentAggregate.EnrollmentId.New; var event1 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, RecruitmentFormSubmitted>( new RecruitmentFormSubmitted( NodaTime.SystemClock.Instance.GetCurrentInstant(), "Andrzej", "Strzelba", EmailAddress.Parse("*****@*****.**"), Consts.FakePhoneNumber, "ala ma kota", 1, "małopolskie", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, id, 1); var enrollment = new EnrollmentAggregate(id); enrollment.ApplyEvents(new[] { event1 }); var training = CreateTrainingInFutureWithId(1); var command = new RecordAcceptedTrainingInvitation.Command() { EnrollmentId = id.GetGuid(), CommunicationChannel = CommunicationChannel.OutgoingEmail, SelectedTrainingID = 1, AdditionalNotes = "brak notatek" }; var recordingCoordinator = new Models.Users.ApplicationUser() { Id = Guid.NewGuid() }; // Act var result = enrollment.RecordCandidateAcceptedTrainingInvitation(command, recordingCoordinator, new[] { training }, NodaTime.SystemClock.Instance.GetCurrentInstant()); // Assert Assert.True(result.IsSuccess); var uncommittedEvent = Assert.Single(enrollment.UncommittedEvents); var @event = Assert.IsType <CandidateAcceptedTrainingInvitation>(uncommittedEvent.AggregateEvent); Assert.Equal(recordingCoordinator.Id, @event.RecordingCoordinatorID); Assert.Equal(CommunicationChannel.OutgoingEmail, @event.CommunicationChannel); Assert.Equal(1, @event.SelectedTrainingID); Assert.Equal("brak notatek", @event.AdditionalNotes); }
public void Command_with_mismatched_EnrollmentID_fails() { // Arrange var id = EnrollmentAggregate.EnrollmentId.New; var enrollment = new EnrollmentAggregate(id); var training = CreateTrainingInFutureWithId(1); var recordingCoordinator = new Models.Users.ApplicationUser() { Id = Guid.NewGuid() }; var command = new RecordAcceptedTrainingInvitation.Command() { CommunicationChannel = CommunicationChannel.OutgoingEmail, SelectedTrainingID = 1, AdditionalNotes = "brak notatek" }; // Act & Assert var ex = Assert.Throws <AggregateMismatchException>(() => enrollment.RecordCandidateAcceptedTrainingInvitation(command, recordingCoordinator, new[] { training }, NodaTime.SystemClock.Instance.GetCurrentInstant())); }
public async Task <IActionResult> RecordAcceptedTrainingInvitation(RecordAcceptedTrainingInvitation.Command command) { var result = await _engine.Execute(command); return(result.MatchToActionResult(success => Ok())); }
public async Task After_accepting_invitation__SendTrainingReminder_command_is_scheduled() { // Arrange var enrollmentId = Guid.NewGuid(); var aggregateUpdateResult = Mock.Of <IAggregateUpdateResult <AggregateStoreExtensions.ExecutionResultWrapper <Result <Nothing, Error> > > >( mock => mock.DomainEvents == Array.Empty <IDomainEvent>() && mock.Result == new AggregateStoreExtensions.ExecutionResultWrapper <Result <Nothing, Error> >(Result.Success <Nothing, Error>(Nothing.Value))); var aggregateStore = Mock.Of <IAggregateStore>( mock => mock.UpdateAsync( It.IsAny <EnrollmentAggregate.EnrollmentId>(), It.IsAny <EventFlow.Core.ISourceId>(), It.IsAny <Func <EnrollmentAggregate, CancellationToken, Task <AggregateStoreExtensions.ExecutionResultWrapper <Result <Nothing, Error> > > > >(), It.IsAny <CancellationToken>()) == Task.FromResult(aggregateUpdateResult), MockBehavior.Strict ); var training = CreateTrainingWithIdAndOffset(1, Duration.FromDays(3)); var trainingRepo = Mock.Of <Szlem.Recruitment.Impl.Repositories.ITrainingRepository>( mock => mock.GetByIds(It.IsAny <IReadOnlyCollection <int> >()) == Task.FromResult <IReadOnlyCollection <Training> >(new[] { training }), MockBehavior.Strict); var enrollmentRepo = Mock.Of <IEnrollmentRepository>( mock => mock.Query() == new[] { new EnrollmentReadModel() { Id = EnrollmentAggregate.EnrollmentId.With(enrollmentId), PreferredTrainings = new[] { new EnrollmentReadModel.TrainingSummary() { ID = 1 } } } }.AsQueryable(), MockBehavior.Strict); var userAccessor = Mock.Of <Szlem.Engine.Interfaces.IUserAccessor>( mock => mock.GetUser() == Task.FromResult(new Models.Users.ApplicationUser()), MockBehavior.Strict); var backgroundJobClientMock = new Mock <Hangfire.IBackgroundJobClient>(MockBehavior.Loose); var handler = new RecordAcceptedTrainingInvitationHandler( aggregateStore, trainingRepo, enrollmentRepo, NodaTime.SystemClock.Instance, userAccessor, backgroundJobClientMock.Object, Mock.Of <ISzlemEngine>()); var command = new RecordAcceptedTrainingInvitation.Command() { EnrollmentId = enrollmentId, SelectedTrainingID = 1, CommunicationChannel = CommunicationChannel.OutgoingPhone }; // Act var result = await handler.Handle(command, CancellationToken.None); // Assert result.IsSuccess.Should().BeTrue(); backgroundJobClientMock.Invocations.Should().ContainSingle(); var invocation = backgroundJobClientMock.Invocations.Single(); invocation.Method.Name.Should().Be(nameof(Hangfire.IBackgroundJobClient.Create)); invocation.Arguments.Should().HaveCount(2); var scheduledJob = invocation.Arguments[0].Should().BeOfType <Hangfire.Common.Job>().Subject; var jobState = invocation.Arguments[1].Should().BeOfType <Hangfire.States.ScheduledState>().Subject; var subCommand = scheduledJob.Args[0] as SendTrainingReminder.Command; subCommand.EnrollmentId.Should().Be(enrollmentId); subCommand.TrainingId.Should().Be(1); jobState.EnqueueAt.Should().Be(training.StartDateTime.Minus(Duration.FromHours(24)).ToDateTimeOffset().UtcDateTime); }