public async Task SaveEvents_throws_DuplicateCorrelationException_if_correlation_duplicate()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created         = new FakeUserCreated();
            var usernameChanged = new FakeUsernameChanged();

            new DomainEvent[] { created, usernameChanged }.Raise(userId);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            var correlationId = Guid.NewGuid();

            await sut.SaveEvents <FakeUser>(new[] { created }, correlationId : correlationId);

            // Act
            Func <Task> action = () =>
                                 sut.SaveEvents <FakeUser>(new[] { usernameChanged }, correlationId: correlationId);

            // Assert
            action.ShouldThrow <DuplicateCorrelationException>().Where(
                x =>
                x.SourceType == typeof(FakeUser) &&
                x.SourceId == userId &&
                x.CorrelationId == correlationId &&
                x.InnerException is DbUpdateException);
        }
        public async Task SaveEvents_does_not_insert_UniqueIndexedProperty_if_property_value_is_null()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created = new FakeUserCreated {
                Username = null
            };

            created.Raise(userId);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            await sut.SaveEvents <FakeUser>(new DomainEvent[] { created });

            // Assert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                UniqueIndexedProperty actual = await db
                                               .UniqueIndexedProperties
                                               .Where(
                    p =>
                    p.AggregateType == typeof(FakeUser).FullName &&
                    p.PropertyName == nameof(FakeUserCreated.Username) &&
                    p.PropertyValue == created.Username)
                                               .SingleOrDefaultAsync();

                actual.Should().BeNull();
            }
        }
        public async Task SaveEvents_inserts_Correlation_entity_correctly()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created       = new FakeUserCreated();
            var correlationId = Guid.NewGuid();

            created.Raise(userId);
            DateTime now = DateTime.UtcNow;

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            await sut.SaveEvents <FakeUser>(new[] { created }, correlationId : correlationId);

            // Assert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                Correlation correlation = await db
                                          .Correlations
                                          .Where(
                    c =>
                    c.AggregateType == typeof(FakeUser).FullName &&
                    c.AggregateId == userId &&
                    c.CorrelationId == correlationId)
                                          .SingleOrDefaultAsync();

                correlation.Should().NotBeNull();
                correlation.HandledAt.Should().BeCloseTo(now, precision: 100);
            }
        }
        public void SaveEvents_fails_if_events_not_have_same_source_id()
        {
            // Arrange
            var userId  = Guid.NewGuid();
            var created = new FakeUserCreated
            {
                SourceId = userId,
                Version  = 1,
                RaisedAt = DateTime.UtcNow,
            };

            var usernameChanged = new FakeUsernameChanged
            {
                SourceId = Guid.NewGuid(),
                Version  = 2,
                RaisedAt = DateTime.UtcNow,
            };

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(new DomainEvent[] { created, usernameChanged });

            // Assert
            action.ShouldThrow <ArgumentException>().Where(x => x.ParamName == "events");
        }
        public async Task SaveEvents_fails_if_version_of_first_event_not_follows_aggregate()
        {
            // Arrange
            var userId = Guid.NewGuid();

            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                var aggregate = new Aggregate
                {
                    AggregateId   = userId,
                    AggregateType = typeof(FakeUser).FullName,
                    Version       = 1,
                };
                db.Aggregates.Add(aggregate);
                await db.SaveChangesAsync();
            }

            var usernameChanged = new FakeUsernameChanged();

            usernameChanged.Raise(userId, 2);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(new[] { usernameChanged });

            // Assert
            action.ShouldThrow <ArgumentException>().Where(x => x.ParamName == "events");
        }
        public void SaveEvents_fails_if_versions_not_sequential()
        {
            // Arrange
            var events = new DomainEvent[]
            {
                new FakeUserCreated {
                    Version = 1
                },
                new FakeUsernameChanged {
                    Version = 2
                },
                new FakeUsernameChanged {
                    Version = 4
                },
            };

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(events);

            // Assert
            action.ShouldThrow <ArgumentException>().Where(x => x.ParamName == "events");
        }
        public async Task SaveEvents_inserts_Aggregate_correctly_for_new_aggregate_id()
        {
            // Arrange
            var userId  = Guid.NewGuid();
            var created = new FakeUserCreated();
            var events  = new DomainEvent[] { created };

            events.Raise(userId);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            await sut.SaveEvents <FakeUser>(events);

            // Assert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                Aggregate actual = await db
                                   .Aggregates
                                   .Where(a => a.AggregateId == userId)
                                   .SingleOrDefaultAsync();

                actual.Should().NotBeNull();
                actual.AggregateType.Should().Be(typeof(FakeUser).FullName);
                actual.Version.Should().Be(created.Version);
            }
        }
        public async Task PublishAllPendingEvents_deletes_all_pending_events()
        {
            // Arrange
            IEnumerable <FakeUserCreated> createdEvents = fixture
                                                          .Build <FakeUserCreated>()
                                                          .With(e => e.Version, 1)
                                                          .CreateMany();
            var eventStore = new SqlEventStore(() => new DataContext(), serializer);

            foreach (FakeUserCreated createdEvent in createdEvents)
            {
                await eventStore.SaveEvents <FakeUser>(new[] { createdEvent });
            }

            // Act
            await sut.PublishAllPendingEvents(CancellationToken.None);

            // Assert
            using (var db = new DataContext())
            {
                foreach (FakeUserCreated createdEvent in createdEvents)
                {
                    Guid userId = createdEvent.SourceId;
                    IQueryable <PendingEvent> query = from e in db.PendingEvents
                                                      where e.AggregateId == userId
                                                      select e;
                    (await query.AnyAsync()).Should().BeFalse();
                }
            }
        }
        public async Task SaveEvents_inserts_UniqueIndexedProperty_with_value_of_latest_indexed_event()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created         = new FakeUserCreated();
            var usernameChanged = new FakeUsernameChanged();
            var events          = new DomainEvent[] { created, usernameChanged };

            events.Raise(userId);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            await sut.SaveEvents <FakeUser>(events);

            // Assert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                UniqueIndexedProperty actual = await db
                                               .UniqueIndexedProperties
                                               .Where(
                    p =>
                    p.AggregateId == userId &&
                    p.PropertyName == nameof(FakeUserCreated.Username))
                                               .SingleOrDefaultAsync();

                actual.PropertyValue.Should().Be(usernameChanged.Username);
                actual.Version.Should().Be(usernameChanged.Version);
            }
        }
        public async Task SaveEvents_saves_pending_events_correctly()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created         = new FakeUserCreated();
            var usernameChanged = new FakeUsernameChanged();
            var events          = new DomainEvent[] { created, usernameChanged };

            events.Raise(userId);

            var serializer = new JsonMessageSerializer();

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                serializer);

            string operationId   = Guid.NewGuid().ToString();
            var    correlationId = Guid.NewGuid();
            string contributor   = Guid.NewGuid().ToString();

            // Act
            await sut.SaveEvents <FakeUser>(events, operationId, correlationId, contributor);

            // Asseert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                var pendingEvents = db
                                    .PendingEvents
                                    .Where(e => e.AggregateId == userId)
                                    .OrderBy(e => e.Version)
                                    .ToList();

                foreach (var t in pendingEvents.Zip(events, (pending, source) =>
                                                    new { Pending = pending, Source = source }))
                {
                    var actual = new
                    {
                        t.Pending.Version,
                        t.Pending.CorrelationId,
                        t.Pending.Contributor,
                        Message = serializer.Deserialize(t.Pending.EventJson),
                    };
                    actual.ShouldBeEquivalentTo(new
                    {
                        t.Source.Version,
                        OperationId   = operationId,
                        CorrelationId = correlationId,
                        Contributor   = contributor,
                        Message       = t.Source,
                    },
                                                opts => opts.RespectingRuntimeTypes());
                }
            }
        }
Example #11
0
        public async Task SaveEvents_does_not_commit_for_empty_events()
        {
            var sut = new SqlEventStore(
                () => mockDbContext,
                new JsonMessageSerializer());

            await sut.SaveEvents <FakeUser>(Enumerable.Empty <IDomainEvent>());

            Mock.Get(mockDbContext)
            .Verify(x => x.SaveChangesAsync(), Times.Never());
        }
        public async Task SaveEvents_does_not_commit_for_empty_events()
        {
            var context = new DbContextSpy(_dbContextOptions);
            var sut     = new SqlEventStore(
                () => context,
                new JsonMessageSerializer());

            await sut.SaveEvents <FakeUser>(Enumerable.Empty <IDomainEvent>());

            context.CommitCount.Should().Be(0);
        }
        public async Task SaveEvents_saves_events_correctly()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created         = new FakeUserCreated();
            var usernameChanged = new FakeUsernameChanged();
            var events          = new DomainEvent[] { created, usernameChanged };

            events.Raise(userId);

            var serializer = new JsonMessageSerializer();

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                serializer);

            // Act
            await sut.SaveEvents <FakeUser>(events);

            // Asseert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                IEnumerable <object> actual = db
                                              .PersistentEvents
                                              .Where(e => e.AggregateId == userId)
                                              .OrderBy(e => e.Version)
                                              .AsEnumerable()
                                              .Select(e => new
                {
                    e.AggregateType,
                    e.Version,
                    e.EventType,
                    Payload = serializer.Deserialize(e.EventJson),
                })
                                              .ToList();

                actual.Should().HaveCount(events.Length);

                IEnumerable <object> expected = events.Select(e => new
                {
                    AggregateType = typeof(FakeUser).FullName,
                    e.Version,
                    EventType = e.GetType().FullName,
                    Payload   = e,
                });

                actual.ShouldAllBeEquivalentTo(expected);
            }
        }
        public async Task SaveEvents_fails_if_unique_indexed_property_duplicate()
        {
            // Arrange
            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            string duplicateUserName = Guid.NewGuid().ToString();

            var macId      = Guid.NewGuid();
            var macCreated = new FakeUserCreated {
                Username = duplicateUserName
            };

            macCreated.Raise(macId);
            await sut.SaveEvents <FakeUser>(new[] { macCreated });

            // Act
            var toshId      = Guid.NewGuid();
            var toshCreated = new FakeUserCreated {
                Username = duplicateUserName
            };

            toshCreated.Raise(toshId);
            Func <Task> action = () => sut.SaveEvents <FakeUser>(new[] { toshCreated });

            // Assert
            action.ShouldThrow <Exception>();
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                IQueryable <PersistentEvent> query =
                    from e in db.PersistentEvents
                    where e.AggregateId == toshId
                    select e;
                (await query.AnyAsync()).Should().BeFalse();
            }
        }
        public async Task FindIdByUniqueIndexedProperty_returns_aggregate_id_if_property_found()
        {
            var userId  = Guid.NewGuid();
            var created = new FakeUserCreated();

            created.Raise(userId);
            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());
            await sut.SaveEvents <FakeUser>(new[] { created });

            Guid?actual = await sut.FindIdByUniqueIndexedProperty <FakeUser>("Username", created.Username);

            actual.Should().Be(userId);
        }
Example #16
0
        public async Task SaveEvents_commits_once(
            FakeUserCreated created,
            FakeUsernameChanged usernameChanged)
        {
            var events = new DomainEvent[] { created, usernameChanged };

            RaiseEvents(userId, events);
            var sut = new SqlEventStore(
                () => mockDbContext,
                new JsonMessageSerializer());

            await sut.SaveEvents <FakeUser>(events);

            Mock.Get(mockDbContext).Verify(
                x => x.SaveChangesAsync(CancellationToken.None), Times.Once());
        }
Example #17
0
        public async Task FlushPendingEvents_absorbs_exception_caused_by_that_some_pending_event_already_deleted_since_loaded()
        {
            // Arrange
            var         completionSource = new TaskCompletionSource <bool>();
            IMessageBus messageBus       = new AwaitingMessageBus(completionSource.Task);
            var         fixture          = new Fixture();
            FakeUser    user             = fixture.Create <FakeUser>();

            user.ChangeUsername(fixture.Create(nameof(user.Username)));
            var eventStore = new SqlEventStore(CreateDbContext, _serializer);
            await eventStore.SaveEvents <FakeUser>(user.FlushPendingEvents());

            var sut = new SqlEventPublisher(CreateDbContext, _serializer, messageBus);

            // Act
            Func <Task> action = async() =>
            {
                Task flushTask = sut.FlushPendingEvents(user.Id, CancellationToken.None);

                using (EventStoreDbContext db = CreateDbContext())
                {
                    List <PendingEvent> pendingEvents = await db
                                                        .PendingEvents
                                                        .Where(e => e.AggregateId == user.Id)
                                                        .OrderBy(e => e.Version)
                                                        .Take(1)
                                                        .ToListAsync();

                    db.PendingEvents.RemoveRange(pendingEvents);
                    await db.SaveChangesAsync();
                }

                completionSource.SetResult(true);
                await flushTask;
            };

            // Assert
            action.ShouldNotThrow();
            using (EventStoreDbContext db = CreateDbContext())
            {
                (await db.PendingEvents.AnyAsync(e => e.AggregateId == user.Id))
                .Should().BeFalse("all pending events should be deleted");
            }
        }
        public void SaveEvents_fails_if_events_contains_null()
        {
            // Arrange
            var userId  = Guid.NewGuid();
            var created = new FakeUserCreated();

            created.Raise(userId);
            var events = new DomainEvent[] { created, null };

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(events);

            // Assert
            action.ShouldThrow <ArgumentException>().Where(x => x.ParamName == "events");
        }
        public async Task SaveEvents_commits_once()
        {
            var userId = Guid.NewGuid();
            var events = new DomainEvent[]
            {
                new FakeUserCreated(),
                new FakeUsernameChanged(),
            };

            events.Raise(userId);
            var context = new DbContextSpy(_dbContextOptions);
            var sut     = new SqlEventStore(
                () => context,
                new JsonMessageSerializer());

            await sut.SaveEvents <FakeUser>(events);

            context.CommitCount.Should().Be(1);
        }
        public async Task SaveEvents_sets_message_properties_correctly()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created         = new FakeUserCreated();
            var usernameChanged = new FakeUsernameChanged();
            var events          = new DomainEvent[] { created, usernameChanged };

            events.Raise(userId);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            await sut.SaveEvents <FakeUser>(
                events,
                correlationId : Guid.NewGuid(),
                contributor : Guid.NewGuid().ToString());

            // Asseert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                IEnumerable <object> expected = db
                                                .PendingEvents
                                                .Where(e => e.AggregateId == userId)
                                                .OrderBy(e => e.Version)
                                                .AsEnumerable()
                                                .Select(e => new { e.MessageId, e.CorrelationId, e.Contributor })
                                                .ToList();

                IEnumerable <object> actual = db
                                              .PersistentEvents
                                              .Where(e => e.AggregateId == userId)
                                              .OrderBy(e => e.Version)
                                              .AsEnumerable()
                                              .Select(e => new { e.MessageId, e.CorrelationId, e.Contributor })
                                              .ToList();

                actual.ShouldAllBeEquivalentTo(expected);
            }
        }
Example #21
0
        public async Task FlushAllPendingEvents_sends_app_pending_events()
        {
            // Arrange
            IEnumerable <FakeUserCreated> createdEvents = _fixture
                                                          .Build <FakeUserCreated>()
                                                          .With(e => e.Version, 1)
                                                          .With(e => e.RaisedAt, DateTime.UtcNow)
                                                          .CreateMany();
            var eventStore = new SqlEventStore(() => new DataContext(), _serializer);

            foreach (FakeUserCreated createdEvent in createdEvents)
            {
                await eventStore.SaveEvents <FakeUser>(new[] { createdEvent });
            }

            var sentEvents = new List <Envelope>();

            Mock.Get(_messageBus)
            .Setup(
                x =>
                x.Send(
                    It.IsAny <IEnumerable <Envelope> >(),
                    It.IsAny <CancellationToken>()))
            .Callback <IEnumerable <Envelope>, CancellationToken>((batch, t) => sentEvents.AddRange(batch))
            .Returns(Task.FromResult(true));

            // Act
            await _sut.FlushAllPendingEvents(CancellationToken.None);

            // Assert
            foreach (FakeUserCreated createdEvent in createdEvents)
            {
                sentEvents
                .Select(e => e.Message)
                .OfType <FakeUserCreated>()
                .Where(e => e.SourceId == createdEvent.SourceId)
                .Should()
                .ContainSingle()
                .Which
                .ShouldBeEquivalentTo(createdEvent);
            }
        }
Example #22
0
        public async Task FlushAllPendingEvents_sends_all_pending_events()
        {
            // Arrange
            IEnumerable <FakeUserCreated> createdEvents = new[]
            {
                new FakeUserCreated(),
                new FakeUserCreated(),
                new FakeUserCreated(),
            };

            var eventStore = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            foreach (FakeUserCreated createdEvent in createdEvents)
            {
                createdEvent.Raise(Guid.NewGuid());
                await eventStore.SaveEvents <FakeUser>(
                    new[] { createdEvent },
                    correlationId : default,
        public void SaveEvents_fails_if_db_context_does_not_support_transaction()
        {
            // Arrange
            var created = new FakeUserCreated {
                Version = 1
            };

            string           databaseName = nameof(SaveEvents_fails_if_db_context_does_not_support_transaction);
            DbContextOptions options      = new DbContextOptionsBuilder()
                                            .UseInMemoryDatabase(databaseName)
                                            .Options;
            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(options),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(new[] { created });

            // Assert
            action.ShouldThrow <InvalidOperationException>();
        }
        public async Task SaveEvents_updates_Aggregate_correctly_for_existing_aggregate_id()
        {
            // Arrange
            var userId = Guid.NewGuid();

            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                var aggregate = new Aggregate
                {
                    AggregateId   = userId,
                    AggregateType = typeof(FakeUser).FullName,
                    Version       = 1,
                };
                db.Aggregates.Add(aggregate);
                await db.SaveChangesAsync();
            }

            var usernameChanged = new FakeUsernameChanged();

            usernameChanged.Raise(userId, 1);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            await sut.SaveEvents <FakeUser>(new[] { usernameChanged });

            // Assert
            using (var db = new FakeEventStoreDbContext(_dbContextOptions))
            {
                Aggregate actual = await db
                                   .Aggregates
                                   .Where(a => a.AggregateId == userId)
                                   .SingleOrDefaultAsync();

                actual.Version.Should().Be(usernameChanged.Version);
            }
        }
        public void SaveEvents_succeeds_if_db_context_supports_transaction()
        {
            // Arrange
            var created = new FakeUserCreated {
                Version = 1
            };

            string           databaseName = nameof(SaveEvents_succeeds_if_db_context_supports_transaction);
            DbContextOptions options      = new DbContextOptionsBuilder()
                                            .UseInMemoryDatabase(databaseName)
                                            .ConfigureWarnings(builder => builder.Ignore(InMemoryEventId.TransactionIgnoredWarning))
                                            .Options;
            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(new[] { created });

            // Assert
            action.ShouldNotThrow();
        }
        public void SaveEvents_fails_if_kind_of_event_raised_time_is_not_utc(DateTimeKind dateTimeKind)
        {
            // Arrange
            var userId = Guid.NewGuid();
            var events = new DomainEvent[]
            {
                new FakeUserCreated(),
                new FakeUsernameChanged(),
            };

            events.Raise(userId);
            events.OrderBy(e => e.GetHashCode()).First().RaisedAt = new DateTime(DateTime.Now.Ticks, dateTimeKind);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            // Act
            Func <Task> action = () => sut.SaveEvents <FakeUser>(events);

            // Assert
            action.ShouldThrow <ArgumentException>().Where(x => x.ParamName == "events");
        }
        public async Task LoadEvents_restores_events_after_specified_version_correctly()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var created         = new FakeUserCreated();
            var usernameChanged = new FakeUsernameChanged();
            var events          = new DomainEvent[] { created, usernameChanged };

            events.Raise(userId);

            var sut = new SqlEventStore(
                () => new FakeEventStoreDbContext(_dbContextOptions),
                new JsonMessageSerializer());

            await sut.SaveEvents <FakeUser>(events);

            // Act
            IEnumerable <IDomainEvent> actual = await sut.LoadEvents <FakeUser>(userId, 1);

            // Assert
            actual.ShouldAllBeEquivalentTo(events.Skip(1));
        }
Example #28
0
        public void SaveEvents_fails_if_events_contains_null(
            FakeUserCreated created)
        {
            var events = new DomainEvent[] { created, null };

            RaiseEvents(userId, created);

            Func <Task> action = () => sut.SaveEvents <FakeUser>(events);

            action.ShouldThrow <ArgumentException>()
            .Where(x => x.ParamName == "events");
        }