PageRenderTime 50ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/Framework/src/Ncqrs.Tests/Eventing/Storage/SQL/MsSqlServerEventStoreTests.cs

http://github.com/ncqrs/ncqrs
C# | 380 lines | 233 code | 81 blank | 66 comment | 5 complexity | e70fba0248bba926afc3e74b77c00c43 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using System.Collections.Generic;
  6. using FluentAssertions;
  7. using Ncqrs.Eventing;
  8. using Ncqrs.Eventing.Sourcing;
  9. using Ncqrs.Eventing.Sourcing.Snapshotting;
  10. using Ncqrs.Eventing.Storage.SQL;
  11. using Ncqrs.Spec;
  12. using Rhino.Mocks;
  13. using System.Data.SqlClient;
  14. using Ncqrs.Eventing.Storage;
  15. using System.Configuration;
  16. using Xunit;
  17. namespace Ncqrs.Tests.Eventing.Storage.SQL
  18. {
  19. public class MsSqlServerEventStoreTests
  20. {
  21. [Serializable]
  22. public class CustomerCreatedEvent
  23. {
  24. protected CustomerCreatedEvent()
  25. {
  26. }
  27. public CustomerCreatedEvent(string name, int age)
  28. {
  29. Name = name;
  30. Age = age;
  31. }
  32. public string Name { get; set; }
  33. public int Age
  34. {
  35. get;
  36. set;
  37. }
  38. }
  39. [Serializable]
  40. public class CustomerNameChanged
  41. {
  42. public Guid CustomerId { get; set; }
  43. public string NewName { get; set; }
  44. protected CustomerNameChanged()
  45. {
  46. }
  47. public CustomerNameChanged(string newName)
  48. {
  49. NewName = newName;
  50. }
  51. }
  52. [Serializable]
  53. public class AccountNameChangedEvent : IEntitySourcedEvent
  54. {
  55. public Guid CustomerId { get; set; }
  56. public Guid AccountId { get; set; }
  57. public string NewAccountName { get; set; }
  58. public AccountNameChangedEvent()
  59. {
  60. }
  61. public AccountNameChangedEvent(Guid accountId, string newAccountName)
  62. {
  63. NewAccountName = newAccountName;
  64. AccountId = accountId;
  65. }
  66. public Guid EntityId
  67. {
  68. get { return AccountId; }
  69. }
  70. public Guid AggregateId
  71. {
  72. get { return CustomerId; }
  73. }
  74. }
  75. [Serializable]
  76. public class MySnapshot : Snapshot
  77. {
  78. }
  79. private const string DEFAULT_CONNECTIONSTRING_KEY = "EventStore";
  80. private readonly string connectionString;
  81. public MsSqlServerEventStoreTests()
  82. {
  83. connectionString = ConfigurationManager.ConnectionStrings[DEFAULT_CONNECTIONSTRING_KEY].ConnectionString;
  84. var connection = new SqlConnection(connectionString);
  85. try
  86. {
  87. connection.Open();
  88. var cmd = connection.CreateCommand();
  89. cmd.CommandText = "IF EXISTS(SELECT * FROM sysobjects WHERE name='PipelineState' AND xtype = 'U') DELETE FROM [PipelineState]";
  90. cmd.ExecuteNonQuery();
  91. cmd.CommandText = "IF EXISTS(SELECT * FROM sysobjects WHERE name='Snapshots' AND xtype = 'U') DELETE FROM [Snapshots]";
  92. cmd.ExecuteNonQuery();
  93. cmd.CommandText = "IF EXISTS(SELECT * FROM sysobjects WHERE name='Events' AND xtype = 'U') DELETE FROM [Events]";
  94. cmd.ExecuteNonQuery();
  95. cmd.CommandText = "IF EXISTS(SELECT * FROM sysobjects WHERE name='EventSources' AND xtype = 'U') DELETE FROM [EventSources]";
  96. cmd.ExecuteNonQuery();
  97. }
  98. catch (SqlException caught)
  99. {
  100. Skip.If(true, "No connection could be made with SQL server: " + caught.Message);
  101. }
  102. finally
  103. {
  104. connection.Dispose();
  105. }
  106. }
  107. [Fact]
  108. public void Retrieving_table_creation_queries_should_return_dll()
  109. {
  110. var dllQueries = MsSqlServerEventStore.GetTableCreationQueries();
  111. dllQueries.Should().NotBeNull().And.NotBeEmpty();
  112. }
  113. [Fact]
  114. public void Storing_event_source_should_succeed()
  115. {
  116. var targetStore = new MsSqlServerEventStore(connectionString);
  117. var theEventSourceId = Guid.NewGuid();
  118. var theCommitId = Guid.NewGuid();
  119. var eventStream = Prepare.Events(
  120. new CustomerCreatedEvent("Foo", 35),
  121. new CustomerNameChanged("Name" + 2),
  122. new CustomerNameChanged("Name" + 3),
  123. new CustomerNameChanged("Name" + 4))
  124. .ForSourceUncomitted(theEventSourceId, theCommitId);
  125. targetStore.Store(eventStream);
  126. var eventsFromStore = targetStore.ReadFrom(theEventSourceId, long.MinValue, long.MaxValue);
  127. eventsFromStore.Count().Should().Be(eventStream.Count());
  128. for (int i = 0; i < eventsFromStore.Count(); i++)
  129. {
  130. var uncommittedEvent = eventStream.ElementAt(i);
  131. var committedEvent = eventsFromStore.ElementAt(i);
  132. committedEvent.EventSourceId.Should().Be(uncommittedEvent.EventSourceId);
  133. committedEvent.EventIdentifier.Should().Be(uncommittedEvent.EventIdentifier);
  134. committedEvent.EventSequence.Should().Be(uncommittedEvent.EventSequence);
  135. committedEvent.Payload.GetType().Should().Be(uncommittedEvent.Payload.GetType());
  136. }
  137. }
  138. [Fact]
  139. public void Storing_entity_sourced_event_should_preserve_entity_id()
  140. {
  141. var targetStore = new MsSqlServerEventStore(connectionString);
  142. var theEventSourceId = Guid.NewGuid();
  143. var theCommitId = Guid.NewGuid();
  144. var theEntityId = Guid.NewGuid();
  145. var eventStream = Prepare.Events(new AccountNameChangedEvent(theEntityId, "NewName"))
  146. .ForSourceUncomitted(theEventSourceId, theCommitId);
  147. targetStore.Store(eventStream);
  148. var restoredEvent = targetStore.ReadFrom(theEventSourceId, long.MinValue, long.MaxValue).Single();
  149. var payload = (AccountNameChangedEvent)restoredEvent.Payload;
  150. payload.EntityId.Should().Be(theEntityId);
  151. }
  152. [Fact]
  153. public void Saving_with_concurrent_event_edits_should_be_subject_to_concurrency_checks()
  154. {
  155. // test created in response to an issue with concurrent edits happening within the window between
  156. // reading the current version number of the aggregate and the event source record being updated with
  157. // the new version number. this would leave the event stream for an event source out of sequence and
  158. // the aggregate in a state in which it could not be retrieved :o
  159. var concurrencyExceptionThrown = false;
  160. var targetStore = new MsSqlServerEventStore(connectionString);
  161. var theEventSourceId = Guid.NewGuid();
  162. var theCommitId = Guid.NewGuid();
  163. // make sure that the event source for the aggregate is created
  164. var creationEvent = Prepare.Events(new CustomerCreatedEvent("Foo", 35))
  165. .ForSourceUncomitted(theEventSourceId, theCommitId);
  166. targetStore.Store(creationEvent);
  167. var tasks = new Task[130];
  168. // now simulate concurreny updates coming in on the same aggregate
  169. for (int idx = 0; idx < tasks.Length; idx++)
  170. {
  171. tasks[idx] = Task.Factory.StartNew(() =>
  172. {
  173. var changeEvent = new CustomerNameChanged(DateTime.Now.Ticks.ToString()) { CustomerId = theEventSourceId };
  174. var eventStream = Prepare.Events(changeEvent)
  175. .ForSourceUncomitted(theEventSourceId, Guid.NewGuid(), 2);
  176. try
  177. {
  178. targetStore.Store(eventStream);
  179. }
  180. catch (ConcurrencyException)
  181. {
  182. concurrencyExceptionThrown = true;
  183. }
  184. targetStore.ReadFrom(theEventSourceId, long.MinValue, long.MaxValue);
  185. });
  186. }
  187. Task.WaitAll(tasks);
  188. if (concurrencyExceptionThrown == false)
  189. {
  190. Assert.True(false, "We're expecting concurrency exceptions!");
  191. }
  192. }
  193. [Fact]
  194. public void Saving_with_concurrent_event_adds_should_not_be_causing_deadlocks()
  195. {
  196. // test created in response to an issue with high frequency adds causing deadlocks on the EventSource table.
  197. // I reworked the sequencing of reads/updates to the EventSource table to reduce the amount
  198. // of time to any locks will be held. But this wasn't strictly required as the problem resided
  199. // in the fact that there was no index on the event source table result in full table scans occuring.
  200. // I therefore also changed the DDL to incude an non clustered index on EventSource.Id which resulted
  201. // in a nice performance boost during informal testing.
  202. var targetStore = new MsSqlServerEventStore(connectionString);
  203. var tasks = new Task[30]; // this number require to reproduce the issue might vary depending on hardware
  204. for (int idx = 0; idx < tasks.Length; idx++)
  205. {
  206. tasks[idx] = Task.Factory.StartNew(() =>
  207. {
  208. var theEventSourceId = Guid.NewGuid();
  209. var theCommitId = Guid.NewGuid();
  210. var eventStream = Prepare.Events(new CustomerCreatedEvent(Task.CurrentId.ToString(), 35))
  211. .ForSourceUncomitted(theEventSourceId, theCommitId);
  212. // should not be receiving a deadlock
  213. targetStore.Store(eventStream);
  214. });
  215. }
  216. Task.WaitAll(tasks);
  217. }
  218. //[Fact]
  219. //public void Saving_event_source_while_there_is_a_newer_event_source_should_throw_concurency_exception()
  220. //{
  221. // var targetStore = new MsSqlServerEventStore(connectionString);
  222. // var id = Guid.NewGuid();
  223. // int sequenceCounter = 0;
  224. // var events = new SourcedEvent[]
  225. // {
  226. // new CustomerCreatedEvent(Guid.NewGuid(), id, sequenceCounter++, DateTime.UtcNow, "Foo",
  227. // 35),
  228. // new CustomerNameChanged(Guid.NewGuid(), id, sequenceCounter++, DateTime.UtcNow,
  229. // "Name" + sequenceCounter)
  230. // };
  231. // var eventSource = MockRepository.GenerateMock<IEventSource>();
  232. // eventSource.Stub(e => e.EventSourceId).Return(id).Repeat.Any();
  233. // eventSource.Stub(e => e.InitialVersion).Return(0).Repeat.Any();
  234. // eventSource.Stub(e => e.Version).Return(events.Length).Repeat.Any();
  235. // eventSource.Stub(e => e.GetUncommittedEvents()).Return(events).Repeat.Any();
  236. // targetStore.Save(eventSource);
  237. // Action act = () => targetStore.Save(eventSource);
  238. // act.ShouldThrow<ConcurrencyException>();
  239. //}
  240. //[Fact]
  241. //public void Retrieving_all_events_should_return_the_same_as_added()
  242. //{
  243. // var targetStore = new MsSqlServerEventStore(connectionString);
  244. // var aggregateId = Guid.NewGuid();
  245. // var entityId = Guid.NewGuid();
  246. // int sequenceCounter = 1;
  247. // var events = new SourcedEvent[]
  248. // {
  249. // new CustomerCreatedEvent(Guid.NewGuid(), aggregateId, sequenceCounter++, DateTime.UtcNow, "Foo",
  250. // 35),
  251. // new CustomerNameChanged(Guid.NewGuid(), aggregateId, sequenceCounter++, DateTime.UtcNow,
  252. // "Name" + sequenceCounter),
  253. // new CustomerNameChanged(Guid.NewGuid(), aggregateId, sequenceCounter++, DateTime.UtcNow,
  254. // "Name" + sequenceCounter),
  255. // new CustomerNameChanged(Guid.NewGuid(), aggregateId, sequenceCounter++, DateTime.UtcNow,
  256. // "Name" + sequenceCounter),
  257. // new AccountNameChangedEvent(Guid.NewGuid(), aggregateId, entityId, sequenceCounter++, DateTime.UtcNow, "New Account Title" + sequenceCounter)
  258. // };
  259. // var eventSource = MockRepository.GenerateMock<IEventSource>();
  260. // eventSource.Stub(e => e.EventSourceId).Return(aggregateId);
  261. // eventSource.Stub(e => e.InitialVersion).Return(0);
  262. // eventSource.Stub(e => e.Version).Return(events.Length);
  263. // eventSource.Stub(e => e.GetUncommittedEvents()).Return(events);
  264. // targetStore.Save(eventSource);
  265. // var result = targetStore.GetAllEvents(aggregateId);
  266. // result.Count().Should().Be(events.Length);
  267. // result.First().EventIdentifier.Should().Be(events.First().EventIdentifier);
  268. // var foundEntityId = result.ElementAt(4).As<SourcedEntityEvent>().EntityId;
  269. // foundEntityId.Should().Be(entityId);
  270. //}
  271. [Fact]
  272. public void Saving_snapshot_should_not_throw_an_exception_when_snapshot_is_valid()
  273. {
  274. var targetStore = new MsSqlServerEventStore(connectionString);
  275. var anId = Guid.NewGuid();
  276. var aCommitId = Guid.NewGuid();
  277. var aVersion = 12;
  278. var eventStream = Prepare.Events(new object())
  279. .ForSourceUncomitted(anId, aCommitId);
  280. var snapshot = new Snapshot(anId, aVersion, new MySnapshot());
  281. targetStore.Store(eventStream);
  282. targetStore.SaveSnapshot(snapshot);
  283. var savedSnapshot = targetStore.GetSnapshot(anId, long.MaxValue);
  284. savedSnapshot.EventSourceId.Should().Be(anId);
  285. savedSnapshot.Version.Should().Be(aVersion);
  286. }
  287. [Fact]
  288. public void Storing_empty_event_stream_should_not_throw()
  289. {
  290. var targetStore = new MsSqlServerEventStore(connectionString);
  291. var theEventSourceId = Guid.NewGuid();
  292. var theCommitId = Guid.NewGuid();
  293. var eventStream = Prepare.Events(new object[0])
  294. .ForSourceUncomitted(theEventSourceId, theCommitId);
  295. targetStore.Store(eventStream);
  296. Assert.True(true);
  297. }
  298. }
  299. }