/src/NServiceBus.Core.Tests/Sagas/TypeBasedSagaMetaModelTests.cs

http://github.com/NServiceBus/NServiceBus · C# · 483 lines · 393 code · 90 blank · 0 comment · 1 complexity · a9f1710a0b77a35d394ce0d109af2f8b MD5 · raw file

  1. namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using NServiceBus.Saga;
  7. using NUnit.Framework;
  8. using Conventions = NServiceBus.Conventions;
  9. [TestFixture]
  10. public class TypeBasedSagaMetaModelTests
  11. {
  12. [Test]
  13. public void Throws_when_does_not_implement_generic_saga()
  14. {
  15. Assert.Throws<Exception>(() => TypeBasedSagaMetaModel.Create(typeof(MyNonGenericSaga)));
  16. }
  17. class MyNonGenericSaga : Saga
  18. {
  19. protected internal override void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration)
  20. {
  21. }
  22. }
  23. [Test]
  24. public void GetEntityClrType()
  25. {
  26. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySaga));
  27. Assert.AreEqual(typeof(MySaga.MyEntity), metadata.SagaEntityType);
  28. }
  29. [Test]
  30. public void GetSagaClrType()
  31. {
  32. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySaga));
  33. Assert.AreEqual(typeof(MySaga), metadata.SagaType);
  34. }
  35. [Test]
  36. public void DetectUniquePropertiesByAttribute()
  37. {
  38. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySaga));
  39. Assert.AreEqual("UniqueProperty", metadata.CorrelationProperties.Single().Name);
  40. }
  41. class MySaga : Saga<MySaga.MyEntity>
  42. {
  43. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<MyEntity> mapper)
  44. {
  45. mapper.ConfigureMapping<M1>(m => m.UniqueProperty).ToSaga(s => s.UniqueProperty);
  46. }
  47. internal class MyEntity : ContainSagaData
  48. {
  49. public int UniqueProperty { get; set; }
  50. }
  51. }
  52. class M1
  53. {
  54. public int UniqueProperty { get; set; }
  55. }
  56. [Test]
  57. public void When_finder_for_non_message()
  58. {
  59. var availableTypes = new List<Type>
  60. {
  61. typeof(SagaWithNonMessageFinder.Finder)
  62. };
  63. var exception = Assert.Throws<Exception>(() =>
  64. {
  65. TypeBasedSagaMetaModel.Create(typeof(SagaWithNonMessageFinder), availableTypes, new Conventions());
  66. });
  67. Assert.AreEqual("A custom IFindSagas must target a valid message type as defined by the message conventions. Please change 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.TypeBasedSagaMetaModelTests+SagaWithNonMessageFinder+StartSagaMessage' to a valid message type or add it to the message conventions. Finder name 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.TypeBasedSagaMetaModelTests+SagaWithNonMessageFinder+Finder'.", exception.Message);
  68. }
  69. public class SagaWithNonMessageFinder : Saga<SagaWithNonMessageFinder.SagaData>,
  70. IAmStartedByMessages<SagaWithNonMessageFinder.StartSagaMessage>
  71. {
  72. public void Handle(StartSagaMessage message)
  73. {
  74. }
  75. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  76. {
  77. }
  78. public class SagaData : ContainSagaData
  79. {
  80. public string Property { get; set; }
  81. }
  82. public class Finder : IFindSagas<SagaData>.Using<StartSagaMessage>
  83. {
  84. public SagaData FindBy(StartSagaMessage message)
  85. {
  86. return null;
  87. }
  88. }
  89. public class StartSagaMessage
  90. {
  91. public string Property { get; set; }
  92. }
  93. }
  94. [Test]
  95. public void When_a_finder_and_a_mapping_exists_for_same_property()
  96. {
  97. var availableTypes = new List<Type>
  98. {
  99. typeof(SagaWithMappingAndFinder.Finder)
  100. };
  101. var exception = Assert.Throws<Exception>(() =>
  102. {
  103. TypeBasedSagaMetaModel.Create(typeof(SagaWithMappingAndFinder), availableTypes, new Conventions());
  104. });
  105. Assert.AreEqual("A custom IFindSagas and an existing mapping where found for message 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.TypeBasedSagaMetaModelTests+SagaWithMappingAndFinder+StartSagaMessage'. Please either remove the message mapping for remove the finder. Finder name 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.TypeBasedSagaMetaModelTests+SagaWithMappingAndFinder+Finder'.", exception.Message);
  106. }
  107. public class SagaWithMappingAndFinder : Saga<SagaWithMappingAndFinder.SagaData>,
  108. IAmStartedByMessages<SagaWithMappingAndFinder.StartSagaMessage>
  109. {
  110. public void Handle(StartSagaMessage message)
  111. {
  112. }
  113. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  114. {
  115. mapper.ConfigureMapping<StartSagaMessage>(m => m.Property)
  116. .ToSaga(s => s.Property);
  117. }
  118. public class SagaData : ContainSagaData
  119. {
  120. public string Property { get; set; }
  121. }
  122. public class Finder : IFindSagas<SagaData>.Using<StartSagaMessage>
  123. {
  124. public SagaData FindBy(StartSagaMessage message)
  125. {
  126. return null;
  127. }
  128. }
  129. public class StartSagaMessage : IMessage
  130. {
  131. public string Property { get; set; }
  132. }
  133. }
  134. [Test]
  135. public void HandleBothUniqueAttributeAndMapping()
  136. {
  137. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithMappedAndUniqueProperty));
  138. Assert.AreEqual("UniqueProperty", metadata.CorrelationProperties.Single().Name);
  139. }
  140. class MySagaWithMappedAndUniqueProperty : Saga<MySagaWithMappedAndUniqueProperty.SagaData>
  141. {
  142. public class SagaData : ContainSagaData
  143. {
  144. public int UniqueProperty { get; set; }
  145. }
  146. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  147. {
  148. mapper.ConfigureMapping<SomeMessage>(m => m.SomeProperty)
  149. .ToSaga(s => s.UniqueProperty);
  150. }
  151. }
  152. [Test]
  153. public void AutomaticallyAddUniqueForMappedProperties()
  154. {
  155. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithMappedProperty));
  156. Assert.AreEqual("UniqueProperty", metadata.CorrelationProperties.Single().Name);
  157. }
  158. class MySagaWithMappedProperty : Saga<MySagaWithMappedProperty.SagaData>
  159. {
  160. public class SagaData : ContainSagaData
  161. {
  162. public int UniqueProperty { get; set; }
  163. }
  164. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  165. {
  166. mapper.ConfigureMapping<SomeMessage>(m => m.SomeProperty)
  167. .ToSaga(s => s.UniqueProperty);
  168. }
  169. }
  170. [Test, Ignore("Not sure we should enforce this yet")]
  171. public void RequireFinderForMessagesStartingTheSaga()
  172. {
  173. var ex = Assert.Throws<Exception>(() => TypeBasedSagaMetaModel.Create(typeof(MySagaWithUnmappedStartProperty)));
  174. Assert.True(ex.Message.Contains(typeof(MySagaWithUnmappedStartProperty.MessageThatStartsTheSaga).FullName));
  175. }
  176. [Test]
  177. public void HandleNonExistingFinders()
  178. {
  179. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithUnmappedStartProperty));
  180. SagaFinderDefinition finder;
  181. Assert.False(metadata.TryGetFinder(typeof(MySagaWithUnmappedStartProperty.MessageThatStartsTheSaga).FullName, out finder));
  182. }
  183. class MySagaWithUnmappedStartProperty : Saga<MySagaWithUnmappedStartProperty.SagaData>,
  184. IAmStartedByMessages<MySagaWithUnmappedStartProperty.MessageThatStartsTheSaga>,
  185. IHandleMessages<MySagaWithUnmappedStartProperty.MessageThatDoesntStartTheSaga>
  186. {
  187. public class MessageThatStartsTheSaga : IMessage
  188. {
  189. public int SomeProperty { get; set; }
  190. }
  191. public class MessageThatDoesntStartTheSaga : IMessage
  192. {
  193. public int SomeProperty { get; set; }
  194. }
  195. public class SagaData : ContainSagaData
  196. {
  197. }
  198. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  199. {
  200. }
  201. public void Handle(MessageThatStartsTheSaga message)
  202. {
  203. }
  204. public void Handle(MessageThatDoesntStartTheSaga message)
  205. {
  206. }
  207. }
  208. [Test]
  209. public void DetectMessagesStartingTheSaga()
  210. {
  211. var metadata = TypeBasedSagaMetaModel.Create(typeof(SagaWith2StartersAnd1Handler));
  212. var messages = metadata.AssociatedMessages;
  213. Assert.AreEqual(4, messages.Count());
  214. Assert.True(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.StartMessage1).FullName));
  215. Assert.True(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.StartMessage2).FullName));
  216. Assert.False(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.Message3).FullName));
  217. Assert.False(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.MyTimeout).FullName));
  218. }
  219. class SagaWith2StartersAnd1Handler : Saga<SagaWith2StartersAnd1Handler.SagaData>,
  220. IAmStartedByMessages<SagaWith2StartersAnd1Handler.StartMessage1>,
  221. IAmStartedByMessages<SagaWith2StartersAnd1Handler.StartMessage2>,
  222. IHandleMessages<SagaWith2StartersAnd1Handler.Message3>,
  223. IHandleTimeouts<SagaWith2StartersAnd1Handler.MyTimeout>
  224. {
  225. public class StartMessage1 : IMessage
  226. {
  227. public string SomeId { get; set; }
  228. }
  229. public class StartMessage2 : IMessage
  230. {
  231. public string SomeId { get; set; }
  232. }
  233. public class Message3 : IMessage
  234. {
  235. }
  236. public class SagaData : ContainSagaData
  237. {
  238. public string SomeId { get; set; }
  239. }
  240. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  241. {
  242. mapper.ConfigureMapping<StartMessage1>(m => m.SomeId)
  243. .ToSaga(s => s.SomeId);
  244. mapper.ConfigureMapping<StartMessage2>(m => m.SomeId)
  245. .ToSaga(s => s.SomeId);
  246. }
  247. public void Handle(StartMessage1 message)
  248. {
  249. throw new NotImplementedException();
  250. }
  251. public void Handle(StartMessage2 message)
  252. {
  253. throw new NotImplementedException();
  254. }
  255. public void Handle(Message3 message)
  256. {
  257. throw new NotImplementedException();
  258. }
  259. public class MyTimeout
  260. {
  261. }
  262. public void Timeout(MyTimeout state)
  263. {
  264. }
  265. }
  266. [Test]
  267. public void DetectAndRegisterPropertyFinders()
  268. {
  269. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithMappedProperty));
  270. var finder = GetFinder(metadata, typeof(SomeMessage).FullName);
  271. Assert.AreEqual(typeof(PropertySagaFinder<MySagaWithMappedProperty.SagaData>), finder.Type);
  272. Assert.NotNull(finder.Properties["property-accessor"]);
  273. Assert.AreEqual("UniqueProperty", finder.Properties["saga-property-name"]);
  274. }
  275. [Test]
  276. public void ValidateThatMappingOnSagaIdHasTypeGuidForMessageProps()
  277. {
  278. var ex = Assert.Throws<Exception>(() => TypeBasedSagaMetaModel.Create(typeof(SagaWithIdMappedToNonGuidMessageProperty)));
  279. Assert.True(ex.Message.Contains(typeof(SomeMessage).Name));
  280. }
  281. class SagaWithIdMappedToNonGuidMessageProperty : Saga<SagaWithIdMappedToNonGuidMessageProperty.SagaData>,
  282. IAmStartedByMessages<SomeMessage>
  283. {
  284. public class SagaData : ContainSagaData
  285. {
  286. }
  287. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  288. {
  289. mapper.ConfigureMapping<SomeMessage>(m => m.SomeProperty)
  290. .ToSaga(s => s.Id);
  291. }
  292. public void Handle(SomeMessage message)
  293. {
  294. }
  295. }
  296. [Test]
  297. public void ValidateThatMappingOnSagaIdHasTypeGuidForMessageFields()
  298. {
  299. var ex = Assert.Throws<Exception>(() => TypeBasedSagaMetaModel.Create(typeof(SagaWithIdMappedToNonGuidMessageField)));
  300. Assert.True(ex.Message.Contains(typeof(SomeMessage).Name));
  301. }
  302. class SagaWithIdMappedToNonGuidMessageField : Saga<SagaWithIdMappedToNonGuidMessageField.SagaData>,
  303. IAmStartedByMessages<SomeMessageWithField>
  304. {
  305. public class SagaData : ContainSagaData
  306. {
  307. }
  308. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  309. {
  310. mapper.ConfigureMapping<SomeMessageWithField>(m => m.SomeProperty)
  311. .ToSaga(s => s.Id);
  312. }
  313. public void Handle(SomeMessageWithField message)
  314. {
  315. }
  316. }
  317. [Test]
  318. public void DetectAndRegisterCustomFindersUsingScanning()
  319. {
  320. var availableTypes = new List<Type>
  321. {
  322. typeof(MySagaWithScannedFinder.CustomFinder)
  323. };
  324. var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithScannedFinder), availableTypes, new Conventions());
  325. var finder = GetFinder(metadata, typeof(SomeMessage).FullName);
  326. Assert.AreEqual(typeof(CustomFinderAdapter<MySagaWithScannedFinder.SagaData, SomeMessage>), finder.Type);
  327. Assert.AreEqual(typeof(MySagaWithScannedFinder.CustomFinder), finder.Properties["custom-finder-clr-type"]);
  328. }
  329. class MySagaWithScannedFinder : Saga<MySagaWithScannedFinder.SagaData>, IAmStartedByMessages<SomeMessage>
  330. {
  331. public class SagaData : ContainSagaData
  332. {
  333. }
  334. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
  335. {
  336. }
  337. internal class CustomFinder : IFindSagas<SagaData>.Using<SomeMessage>
  338. {
  339. public SagaData FindBy(SomeMessage message)
  340. {
  341. return null;
  342. }
  343. }
  344. public void Handle(SomeMessage message)
  345. {
  346. }
  347. }
  348. [Test]
  349. public void GetEntityClrTypeFromInheritanceChain()
  350. {
  351. var metadata = TypeBasedSagaMetaModel.Create(typeof(SagaWithInheritanceChain));
  352. Assert.AreEqual(typeof(SagaWithInheritanceChain.SagaData), metadata.SagaEntityType);
  353. }
  354. class SagaWithInheritanceChain : SagaWithInheritanceChainBase<SagaWithInheritanceChain.SagaData, SagaWithInheritanceChain.SomeOtherData>
  355. {
  356. public class SagaData : ContainSagaData
  357. {
  358. public string SomeId { get; set; }
  359. }
  360. public class SomeOtherData
  361. {
  362. public string SomeData { get; set; }
  363. }
  364. }
  365. class SagaWithInheritanceChainBase<T, O> : Saga<T>
  366. where T : IContainSagaData, new()
  367. where O : class
  368. {
  369. protected override void ConfigureHowToFindSaga(SagaPropertyMapper<T> mapper)
  370. {
  371. }
  372. }
  373. SagaFinderDefinition GetFinder(SagaMetadata metadata, string messageType)
  374. {
  375. SagaFinderDefinition finder;
  376. if (!metadata.TryGetFinder(messageType, out finder))
  377. {
  378. throw new Exception("Finder not found");
  379. }
  380. return finder;
  381. }
  382. }
  383. class SomeMessageWithField : IMessage
  384. {
  385. #pragma warning disable 649
  386. public int SomeProperty;
  387. #pragma warning restore 649
  388. }
  389. class SomeMessage : IMessage
  390. {
  391. public int SomeProperty { get; set; }
  392. }
  393. }