Last active
December 22, 2015 14:52
-
-
Save yreynhout/702005922c6dff4f9f69 to your computer and use it in GitHub Desktop.
Revisions
-
yreynhout revised this gist
Dec 22, 2015 . 1 changed file with 1 addition and 12 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -109,7 +109,7 @@ public class DonationDataModel } //Projection public class DonationProjection : ConnectedProjection<MemoryCache> { public DonationProjection(IAdditionalDataProvider additionalDataProvider) { @@ -177,17 +177,6 @@ public Task<string> GetAsync(Guid donationId) } //Infrastructure public static class MemoryCacheProjection { -
yreynhout revised this gist
Dec 22, 2015 . No changes.There are no files selected for viewing
-
yreynhout revised this gist
Dec 22, 2015 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -14,7 +14,6 @@ namespace KickIt { //Events public class DonationCollectionStarted { public readonly Guid DonationCollectionId; -
yreynhout created this gist
Dec 22, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,303 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Caching; using System.Text; using System.Threading; using System.Threading.Tasks; using KellermanSoftware.CompareNetObjects; using Newtonsoft.Json.Linq; using NUnit.Framework; using Projac.Connector; using Projac.Connector.Testing; namespace KickIt { //Events public class DonationCollectionStarted { public readonly Guid DonationCollectionId; public readonly Guid DonationId; public readonly string ReferralCode; public readonly DateTimeOffset StartDateTime; public DonationCollectionStarted(Guid donationCollectionId, Guid donationId, string referralCode, DateTimeOffset startDateTime) { DonationCollectionId = donationCollectionId; DonationId = donationId; ReferralCode = referralCode; StartDateTime = startDateTime; } } public class DonationToSuspendedCauseAttempted { public readonly Guid DonationCollectionId; public readonly Guid DonationId; public DonationToSuspendedCauseAttempted(Guid donationCollectionId, Guid donationId) { DonationCollectionId = donationCollectionId; DonationId = donationId; } } //Specs [TestFixture] public class DonationProjectionSpecs { [Test] public Task when_a_donation_collection_started() { return MemoryCacheProjection.For(new DonationProjection(MemoryAdditionalDataProvider.Empty)). Given( new DonationCollectionStarted( new Guid("438AEA16-54A2-4C76-B82E-97563C475D5E"), new Guid("69AB3034-C7DD-4629-A1F8-97AF099C8556"), "BLA-BLA", DateTimeOffset.Parse("2015-12-15 11:14:01.088"))). Expect(new CacheItem( "69AB3034-C7DD-4629-A1F8-97AF099C8556".ToLower(), new DonationDataModel { DonationCollectionId = new Guid("438AEA16-54A2-4C76-B82E-97563C475D5E"), DonationStartedTime = DateTimeOffset.Parse("2015-12-15 11:14:01.088"), ReferralCode = "BLA-BLA", AdditionalData = null, Invalid = false, Reason = null })); } [Test] public Task when_a_donation_to_a_suspended_cause_was_attempted() { return MemoryCacheProjection.For(new DonationProjection(MemoryAdditionalDataProvider.Empty)). Given( new DonationCollectionStarted( new Guid("438AEA16-54A2-4C76-B82E-97563C475D5E"), new Guid("69AB3034-C7DD-4629-A1F8-97AF099C8556"), "BLA-BLA", DateTimeOffset.Parse("2015-12-15 11:14:01.088")), new DonationToSuspendedCauseAttempted( new Guid("438AEA16-54A2-4C76-B82E-97563C475D5E"), new Guid("69AB3034-C7DD-4629-A1F8-97AF099C8556"))). Expect(new CacheItem( "69AB3034-C7DD-4629-A1F8-97AF099C8556".ToLower(), new DonationDataModel { DonationCollectionId = new Guid("438AEA16-54A2-4C76-B82E-97563C475D5E"), DonationStartedTime = DateTimeOffset.Parse("2015-12-15 11:14:01.088"), ReferralCode = "BLA-BLA", AdditionalData = null, Invalid = true, Reason = "Can't donate to a suspended cause" })); } } //Data Model public class DonationDataModel { public Guid DonationCollectionId { get; set; } public string ReferralCode { get; set; } public DateTimeOffset DonationStartedTime { get; set; } public string AdditionalData { get; set; } public bool Invalid { get; set; } public string Reason { get; set; } } //Projection public class DonationProjection : SyncConnectedProjection<MemoryCache> { public DonationProjection(IAdditionalDataProvider additionalDataProvider) { When<DonationCollectionStarted>( async (cache, message) => { var additionalData = await additionalDataProvider.GetAsync(message.DonationCollectionId); cache.Add( new CacheItem( message.DonationId.ToString(), new DonationDataModel { DonationCollectionId = message.DonationCollectionId, DonationStartedTime = message.StartDateTime, ReferralCode = message.ReferralCode, AdditionalData = additionalData, Invalid = false, Reason = null }), new CacheItemPolicy { AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration }); }); WhenSync<DonationToSuspendedCauseAttempted>( (cache, message) => { var item = cache.GetCacheItem(message.DonationId.ToString()); if (item != null) { var model = (DonationDataModel)item.Value; model.Invalid = true; model.Reason = "Can't donate to a suspended cause"; } }); } } //Services public interface IAdditionalDataProvider { Task<string> GetAsync(Guid donationId); } public class MemoryAdditionalDataProvider : IAdditionalDataProvider { public static readonly IAdditionalDataProvider Empty = new MemoryAdditionalDataProvider(new Dictionary<Guid, string>()); private readonly IReadOnlyDictionary<Guid, string> _store; public MemoryAdditionalDataProvider(IReadOnlyDictionary<Guid, string> store) { if (store == null) throw new ArgumentNullException("store"); _store = store; } public Task<string> GetAsync(Guid donationId) { string data; return Task.FromResult(_store.TryGetValue(donationId, out data) ? data : null); } } //Infrastructure public abstract class SyncConnectedProjection<TConnection> : ConnectedProjection<TConnection> { protected void WhenSync<TMessage>(Action<TConnection, TMessage> handler) { When<TMessage>((connection, message) => { handler(connection, message); return Task.FromResult<object>(null); }); } } public static class MemoryCacheProjection { public static ConnectedProjectionScenario<MemoryCache> For(ConnectedProjectionHandler<MemoryCache>[] handlers) { return new ConnectedProjectionScenario<MemoryCache>( ConcurrentResolve.WhenEqualToHandlerMessageType(handlers) ); } public static Task ExpectNone(this ConnectedProjectionScenario<MemoryCache> scenario) { return scenario. Verify(cache => { if (cache.GetCount() != 0) { return Task.FromResult( VerificationResult.Fail( string.Format("Expected no cache items, but found {0} cache item(s) ({1}).", cache.GetCount(), string.Join(",", cache.Select(pair => pair.Key))))); } return Task.FromResult(VerificationResult.Pass()); }). Assert(); } public static async Task Assert(this ConnectedProjectionTestSpecification<MemoryCache> specification) { if (specification == null) throw new ArgumentNullException("specification"); using (var cache = new MemoryCache(new Random().Next().ToString())) { await new ConnectedProjector<MemoryCache>(specification.Resolver). ProjectAsync(cache, specification.Messages); var result = await specification.Verification(cache, CancellationToken.None); if (result.Failed) { throw new AssertionException(result.Message); } } } public static Task Expect(this ConnectedProjectionScenario<MemoryCache> scenario, params CacheItem[] items) { if (items == null) throw new ArgumentNullException("items"); if (items.Length == 0) { return scenario.ExpectNone(); } return scenario. Verify(cache => { if (cache.GetCount() != items.Length) { if (cache.GetCount() == 0) { return Task.FromResult( VerificationResult.Fail( string.Format("Expected {0} cache item(s), but found 0 cache items.", items.Length))); } return Task.FromResult( VerificationResult.Fail( string.Format("Expected {0} cache item(s), but found {1} cache item(s) ({2}).", items.Length, cache.GetCount(), string.Join(",", cache.Select(pair => pair.Key))))); } if (!cache.Select(pair => cache.GetCacheItem(pair.Key)).SequenceEqual(items, new CacheItemEqualityComparer())) { var builder = new StringBuilder(); builder.AppendLine("Expected the following cache items:"); foreach (var expectedItem in items) { builder.AppendLine(expectedItem.Key + ": " + JToken.FromObject(expectedItem.Value).ToString()); } builder.AppendLine(); builder.AppendLine("But found the following cache items:"); foreach (var actualItem in cache) { builder.AppendLine(actualItem.Key + ": " + JToken.FromObject(actualItem.Value).ToString()); } return Task.FromResult(VerificationResult.Fail(builder.ToString())); } return Task.FromResult(VerificationResult.Pass()); }). Assert(); } } public class CacheItemEqualityComparer : IEqualityComparer<CacheItem> { public bool Equals(CacheItem x, CacheItem y) { if (x == null && y == null) return true; if (x == null || y == null) return false; return x.Key.Equals(y.Key) && new CompareLogic().Compare(x.Value, y.Value).AreEqual; } public int GetHashCode(CacheItem obj) { throw new NotSupportedException(); } } }