public void Constructor_commands_cannot_be_used_on_aggregates_that_have_prior_events()
        {
            var order = new Order();
            order.Apply(new AddItem
            {
                Price = 1,
                ProductName = Any.CamelCaseName()
            });

            Action apply = () =>
                           order.Apply(new CreateOrder(Any.CamelCaseName()));

            apply.ShouldThrow<ConcurrencyException>();
        }
Example #2
0
        public void The_clock_set_in_the_CommandContext_is_used_by_resulting_events()
        {
            var created = DateTimeOffset.Parse("2014-05-15 00:00:00");

            var addItem = new AddItem
            {
                ProductName = "Widget",
                Price = 3.99m
            };

            Order order;
            using (CommandContext.Establish(addItem, Clock.Create(() => created)))
            {
                order = new Order(new CreateOrder(Any.FullName()));
                order.Apply(addItem);
            }

            order.Events()
                 .Count()
                 .Should()
                 .Be(3);
            order.Events()
                 .Should()
                 .OnlyContain(e => e.Timestamp == created);
        }
Example #3
0
        public void Command_applicability_can_be_validated_by_the_command_class()
        {
            var cancel = new Cancel();

            var order = new Order();

            order.IsValidTo(cancel).Should().Be(true);

            order.Apply(new Deliver());

            order.IsValidTo(cancel).Should().Be(false);
        }
Example #4
0
        public void The_event_Actor_is_set_from_the_Command_Principal()
        {
            // arrange
            var customer = new Customer(Any.FullName());
            var serviceRepresentative = new CustomerServiceRepresentative
            {
                Name = Any.FullName()
            };
            var command = new SpecifyShippingInfo
            {
                Principal = serviceRepresentative
            };

            var order = new Order(new CreateOrder(customer.Name)
            {
                Principal = customer
            });

            // act
            order.Apply(command);

            // assert
            order.Events()
                 .OfType<Order.Created>()
                 .Single()
                 .Actor()
                 .Should()
                 .Be(customer.Name);

            order.Events()
                 .OfType<Order.ShippingMethodSelected>()
                 .Single()
                 .Actor()
                 .Should()
                 .Be(serviceRepresentative.Name);
        }
        public async Task When_a_command_is_scheduled_but_the_aggregate_it_applies_to_is_not_found_then_the_command_is_retried()
        {
            // create and cancel an order for a nonexistent customer account 
            var customerId = Any.Guid();
            Console.WriteLine(new { customerId });
            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = customerId
            });

            order.Apply(new Cancel());
            await orderRepository.Save(order);

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == customerId)
                  .Should()
                  .ContainSingle(c => c.AppliedTime == null);
            }

            // act
            // now save the customer and advance the clock
            await accountRepository.Save(new CustomerAccount(customerId).Apply(new ChangeEmailAddress(Any.Email())));

            await clockTrigger.AdvanceClock(clockName, TimeSpan.FromMinutes(2));

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == customerId)
                  .Should()
                  .ContainSingle(c => c.AppliedTime != null);

                var customer = await accountRepository.GetLatest(customerId);
                customer.Events()
                        .Last()
                        .Should().BeOfType<CustomerAccount.OrderCancelationConfirmationEmailSent>();
            }
        }
        public async Task When_a_command_is_scheduled_but_the_event_that_triggered_it_was_not_successfully_written_then_the_command_is_not_applied()
        {
            VirtualClock.Start();

            // create a customer account
            var customer = new CustomerAccount(Any.Guid())
                .Apply(new ChangeEmailAddress(Any.Email()));
            await accountRepository.Save(customer);

            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = customer.Id
            });

            // act
            // cancel the order but don't save it
            order.Apply(new Cancel());

            // assert
            // verify that the customer did not receive the scheduled NotifyOrderCanceled command
            customer = await accountRepository.GetLatest(customer.Id);
            customer.Events().Last().Should().BeOfType<CustomerAccount.EmailAddressChanged>();

            using (var db = new CommandSchedulerDbContext())
            {
                db.ScheduledCommands
                  .Where(c => c.AggregateId == customer.Id)
                  .Should()
                  .ContainSingle(c => c.AppliedTime == null &&
                                      c.DueTime == null);
            }
        }
        public async Task When_a_command_is_scheduled_but_an_exception_is_thrown_in_a_handler_then_an_error_is_recorded()
        {
            Configuration.Current.UseDependency(_ => new CustomerAccount.OrderEmailConfirmer
            {
                SendOrderConfirmationEmail = x => { throw new Exception("drat!"); }
            });

            // create a customer account
            var customer = new CustomerAccount(Any.Guid())
                .Apply(new ChangeEmailAddress(Any.Email()));
            await accountRepository.Save(customer);

            var order = new Order(new CreateOrder(Any.FullName())
            {
                CustomerId = customer.Id
            });
            await orderRepository.Save(order);

            // act
            order.Apply(new Cancel());
            await orderRepository.Save(order);

            await clockTrigger.AdvanceClock(clockName, TimeSpan.FromMinutes(1.2));

            using (var db = new CommandSchedulerDbContext())
            {
                db.Errors
                  .Where(e => e.ScheduledCommand.AggregateId == customer.Id)
                  .Should()
                  .Contain(e => e.Error.Contains("drat!"));
            }
        }
        public void When_a_command_is_applied_its_updates_are_applied_to_the_state_of_the_aggregate()
        {
            var order = new Order();

            order.IsCancelled.Should().Be(false);

            order.Apply(new Cancel());

            order.IsCancelled.Should().Be(true);
        }
        public void Aggregates_can_be_re_sourced_in_memory_to_older_versions()
        {
            var originalName = Any.FullName();
            var order = new Order(new CreateOrder(originalName));
            order.Apply(new ChangeCustomerInfo
            {
                CustomerName = Any.FullName()
            });

            var orderAtOlderVersion = order.AsOfVersion(1);

            orderAtOlderVersion.CustomerName.Should().Be(originalName);
        }
        public void When_there_are_gaps_in_the_event_sequence_then_new_events_have_the_correct_sequence_numbers_after_save()
        {
            // arrange
            var id = Guid.NewGuid();
            var order = new Order(id,
                new Order.Created { AggregateId = id, SequenceNumber = 1, CustomerId = Any.Guid() },
                new Order.ItemAdded { AggregateId = id, SequenceNumber = 4, ProductName = "foo", Price = 1 },
                new Order.ItemAdded { AggregateId = id, SequenceNumber = 8, ProductName = "foo", Price = 1 },
                new Order.ItemAdded { AggregateId = id, SequenceNumber = 103, ProductName = "foo", Price = 1 }
                );

            // act
            order.Apply(new Cancel());
            order.ConfirmSave();

            // assert
            order.EventHistory.Last().Should().BeOfType<Order.Cancelled>();
            order.EventHistory.Last().SequenceNumber.Should().Be(104);
        }
        public void When_there_are_gaps_in_the_event_sequence_then_new_events_have_the_correct_sequence_numbers_prior_to_save()
        {
            // arrange
            var id = Guid.NewGuid();
            var order = new Order(id,
                new Order.Created { AggregateId = id, SequenceNumber = 1, CustomerId = Any.Guid() },
                new Order.ItemAdded { AggregateId = id, SequenceNumber = 4, ProductName = "foo", Price = 1 },
                new Order.ItemAdded { AggregateId = id, SequenceNumber = 8, ProductName = "foo", Price = 1 },
                new Order.ItemAdded { AggregateId = id, SequenceNumber = 103, ProductName = "foo", Price = 1 }
                );

            // act
            order.Apply(new Cancel());

            // assert
            order.Version.Should().Be(104);
            order.PendingEvents.Last().SequenceNumber.Should().Be(104);
        }