Skip to content

Instantly share code, notes, and snippets.

@yreynhout
Last active December 22, 2015 14:52
Show Gist options
  • Select an option

  • Save yreynhout/702005922c6dff4f9f69 to your computer and use it in GitHub Desktop.

Select an option

Save yreynhout/702005922c6dff4f9f69 to your computer and use it in GitHub Desktop.

Revisions

  1. yreynhout revised this gist Dec 22, 2015. 1 changed file with 1 addition and 12 deletions.
    13 changes: 1 addition & 12 deletions KickIt.cs
    Original file line number Diff line number Diff line change
    @@ -109,7 +109,7 @@ public class DonationDataModel
    }

    //Projection
    public class DonationProjection : SyncConnectedProjection<MemoryCache>
    public class DonationProjection : ConnectedProjection<MemoryCache>
    {
    public DonationProjection(IAdditionalDataProvider additionalDataProvider)
    {
    @@ -177,17 +177,6 @@ public Task<string> GetAsync(Guid donationId)
    }

    //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
    {
  2. yreynhout revised this gist Dec 22, 2015. No changes.
  3. yreynhout revised this gist Dec 22, 2015. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion KickIt.cs
    Original file line number Diff line number Diff line change
    @@ -14,7 +14,6 @@
    namespace KickIt
    {
    //Events

    public class DonationCollectionStarted
    {
    public readonly Guid DonationCollectionId;
  4. yreynhout created this gist Dec 22, 2015.
    303 changes: 303 additions & 0 deletions KickIt.cs
    Original 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();
    }
    }
    }