PageRenderTime 29ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

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

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