public void Command_cannot_be_issued_earlier_than_24h_before_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, "Wolne Miasto Gdańsk", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, id, 1 ); var event2 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, CandidateAcceptedTrainingInvitation>( new CandidateAcceptedTrainingInvitation(Guid.NewGuid(), CommunicationChannel.OutgoingEmail, 1, string.Empty), new Metadata(), DateTimeOffset.Now, id, 2 ); var enrollment = new EnrollmentAggregate(id); enrollment.ApplyEvents(new IDomainEvent[] { event1, event2 }); var command = new SendTrainingReminder.Command() { EnrollmentId = id.GetGuid(), TrainingId = 1 }; var training = CreateTrainingWithIdAndOffset(1, Duration.FromDays(3)); // Act var result = enrollment.CanSendTrainingReminder(command, training, SystemClock.Instance.GetCurrentInstant()); // Assert result.IsSuccess.Should().BeFalse(); var error = result.Error.Should().BeOfType <Error.DomainError>().Subject; error.Message.Should().Be(SendTrainingReminder_Messages.Reminder_cannot_be_sent_earlier_than_24h_before_training); }
public Result <Nothing, Error> CanSendTrainingReminder(SendTrainingReminder.Command command, Training training, NodaTime.Instant now) { Guard.Against.Null(command, nameof(command)); Guard.Against.Null(training, nameof(training)); ValidateIdMatchOrThrow(command.EnrollmentId); if (SelectedTrainingID != null && training.ID != SelectedTrainingID) { return(Result.Failure <Nothing, Error>(new Error.DomainError(SendTrainingReminder_Messages.Reminder_cannot_be_sent_if_the_candidate_is_not_invited_to_training))); } if (HasResignedEffectively(training.StartDateTime.ToInstant()) || HasResignedEffectively(now)) { return(Result.Failure <Nothing, Error>(new Error.DomainError(SendTrainingReminder_Messages.Reminder_cannot_be_sent_if_the_candidate_resigned))); } if (SelectedTrainingID == null) { return(Result.Failure <Nothing, Error>(new Error.DomainError(SendTrainingReminder_Messages.Reminder_cannot_be_sent_if_the_candidate_is_not_invited_to_training))); } if (training.StartDateTime.ToInstant().Minus(now) > Duration.FromHours(24)) { return(Result.Failure <Nothing, Error>(new Error.DomainError(SendTrainingReminder_Messages.Reminder_cannot_be_sent_earlier_than_24h_before_training))); } if (training.StartDateTime.ToInstant() < now) { return(Result.Failure <Nothing, Error>(new Error.DomainError(SendTrainingReminder_Messages.Reminder_cannot_be_sent_after_training_start))); } return(Result.Success <Nothing, Error>(Nothing.Value)); }
public void With_valid_command__CanSendTrainingReminder_returns_success() { // 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, "Wolne Miasto Gdańsk", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, id, 1 ); var event2 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, CandidateAcceptedTrainingInvitation>( new CandidateAcceptedTrainingInvitation(Guid.NewGuid(), CommunicationChannel.OutgoingEmail, 1, string.Empty), new Metadata(), DateTimeOffset.Now, id, 2 ); var enrollment = new EnrollmentAggregate(id); enrollment.ApplyEvents(new IDomainEvent[] { event1, event2 }); var command = new SendTrainingReminder.Command() { EnrollmentId = id.GetGuid(), TrainingId = 1 }; var training = CreateTrainingWithIdAndOffset(1, Duration.FromHours(12)); // Act var result = enrollment.CanSendTrainingReminder(command, training, SystemClock.Instance.GetCurrentInstant()); // Assert result.IsSuccess.Should().BeTrue(); }
public async Task After_command_is_executed__if_email_service_encounters_error__aggregate_contains_EmailSendingFailed_event() { // Arrange var enrollmentId = 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, "Wolne Miasto Gdańsk", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, enrollmentId, 1 ); var event2 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, CandidateAcceptedTrainingInvitation>( new CandidateAcceptedTrainingInvitation(Guid.NewGuid(), CommunicationChannel.OutgoingEmail, 1, string.Empty), new Metadata(), DateTimeOffset.Now, enrollmentId, 2 ); var enrollment = new EnrollmentAggregate(enrollmentId); enrollment.ApplyEvents(new IDomainEvent[] { event1, event2 }); var aggregateStore = new MockAggregateStore <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId>(enrollment); var emailService = new FailingEmailService(); var options = Mock.Of <IOptions <Config> >(mock => mock.Value == new Config() { TrainingReminderEmail = new Config.EmailMessageConfig() { Subject = "przypomnienie o szkoleniu", Body = "przypominamy o szkoleniu" } }); // Act var training = CreateTrainingWithIdAndOffset(1, Duration.FromHours(12)); var trainingRepo = Mock.Of <ITrainingRepository>(repo => repo.GetById(1) == Task.FromResult(Maybe <Training> .From(training))); var handler = new SendTrainingReminderHandler(SystemClock.Instance, aggregateStore, trainingRepo, emailService, options, new FluidTemplateRenderer()); var command = new SendTrainingReminder.Command() { EnrollmentId = enrollmentId.GetGuid(), TrainingId = 1 }; var result = await handler.Handle(command, CancellationToken.None); // Assert result.IsSuccess.Should().BeTrue(); enrollment.UncommittedEvents.Should().ContainSingle().Which.AggregateEvent.Should().BeOfType <EmailSendingFailed>(); var newEvent = enrollment.UncommittedEvents.Single().AggregateEvent as EmailSendingFailed; newEvent.Subject.Should().Be("przypomnienie o szkoleniu"); newEvent.Body.Should().Be("przypominamy o szkoleniu"); emailService.FailedMessages.Should().ContainSingle(); var emailMessage = emailService.FailedMessages.Single(); emailMessage.Subject.Should().Be("przypomnienie o szkoleniu"); emailMessage.Body.Should().Be("przypominamy o szkoleniu"); emailMessage.To.Should().ContainSingle().Which.Should().Be(EmailAddress.Parse("*****@*****.**")); }
public void Command_is_invalid_if_candidate_was_not_invited() { // 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, "Wolne Miasto Gdańsk", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, id, 1 ); var enrollment = new EnrollmentAggregate(id); enrollment.ApplyEvents(new IDomainEvent[] { event1 }); var command = new SendTrainingReminder.Command() { EnrollmentId = id.GetGuid(), TrainingId = 1 }; var training = CreateTrainingWithIdAndOffset(1, Duration.FromHours(12)); // Act var result = enrollment.CanSendTrainingReminder(command, training, SystemClock.Instance.GetCurrentInstant()); // Assert result.IsSuccess.Should().BeFalse(); var error = result.Error.Should().BeOfType <Error.DomainError>().Subject; error.Message.Should().Be(SendTrainingReminder_Messages.Reminder_cannot_be_sent_if_the_candidate_is_not_invited_to_training); }
public async Task <Result <Nothing, Error> > Handle(SendTrainingReminder.Command command, CancellationToken cancellationToken) { var training = await _repo.GetById(command.TrainingId); if (training.HasNoValue) { return(Result.Failure <Nothing, Error>(new Error.ResourceNotFound(SendTrainingReminder_Messages.Training_not_found))); } var options = _options.Value.TrainingReminderEmail; var enrollmentId = EnrollmentAggregate.EnrollmentId.With(command.EnrollmentId); var result = await _aggregateStore.Update <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, Result <Nothing, Error> >( enrollmentId, EventFlow.Core.SourceId.New, async (aggregate, token) => { return(await aggregate.CanSendTrainingReminder(command, training.Value, _clock.GetCurrentInstant()) .Tap(async _ => { var body = await BuildMessageBody(options, aggregate, training.Value); var message = _emailService.CreateMessage(aggregate.Email, options.Subject, body, isBodyHtml: options.IsBodyHtml); await _emailService.Send(message, token) .Tap(() => aggregate.RecordEmailSent(_clock.GetCurrentInstant(), message)) .OnFailure(() => aggregate.RecordEmailSendingFailed(_clock.GetCurrentInstant(), message)); })); }, cancellationToken); return(result.Unwrap()); }
public async Task After_command_is_executed__email_is_sent_and_aggregate_contains_EmailSent_event() { // Arrange var enrollmentId = 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, "Wolne Miasto Gdańsk", new[] { "Wadowice" }, new[] { 1 }, true), new Metadata(), DateTimeOffset.Now, enrollmentId, 1 ); var event2 = new DomainEvent <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId, CandidateAcceptedTrainingInvitation>( new CandidateAcceptedTrainingInvitation(Guid.NewGuid(), CommunicationChannel.OutgoingEmail, 1, string.Empty), new Metadata(), DateTimeOffset.Now, enrollmentId, 2 ); var enrollment = new EnrollmentAggregate(enrollmentId); enrollment.ApplyEvents(new IDomainEvent[] { event1, event2 }); var aggregateStore = new MockAggregateStore <EnrollmentAggregate, EnrollmentAggregate.EnrollmentId>(enrollment); var training = CreateTrainingWithIdAndOffset(1, Duration.FromHours(12)); var trainingRepo = Mock.Of <ITrainingRepository>(repo => repo.GetById(1) == Task.FromResult(Maybe <Training> .From(training))); var emailService = new SucceedingEmailService(); var options = Mock.Of <IOptions <Config> >(mock => mock.Value == new Config() { TrainingReminderEmail = new Config.EmailMessageConfig() { Subject = "przypomnienie o szkoleniu", Body = "{{ Candidate.FullName }} przypominamy o szkoleniu w {{ Training.City }} w dniu {{ Training.StartDate }}" + " o godzinie {{ Training.StartTime }} trwającym {{ Training.Duration }} godzin!" } }); // Act var handler = new SendTrainingReminderHandler(SystemClock.Instance, aggregateStore, trainingRepo, emailService, options, new FluidTemplateRenderer()); var command = new SendTrainingReminder.Command() { EnrollmentId = enrollmentId.GetGuid(), TrainingId = 1 }; var result = await handler.Handle(command, CancellationToken.None); // Assert result.IsSuccess.Should().BeTrue(); enrollment.UncommittedEvents.Should().ContainSingle(); enrollment.UncommittedEvents.Single().AggregateEvent.Should().BeOfType <EmailSent>(); emailService.SentMessages.Should().ContainSingle(); emailService.SentMessages.Single().Should().BeEquivalentTo(new { Subject = "przypomnienie o szkoleniu", Body = $"Andrzej Strzelba przypominamy o szkoleniu w Wadowice w dniu {training.StartDateTime.Date}" + $" o godzinie {training.StartDateTime.TimeOfDay} trwającym {training.Duration.ToString("HH':'mm", null)} godzin!" }); }