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);
        }
Beispiel #2
0
        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);
        }
Beispiel #4
0
        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);
        }