// ========================================================================== // StateSnapshotTests.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== using System; using System.Threading.Tasks; using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Tasks; using Xunit; #pragma warning disable RECS0002 // Convert anonymous method to method group namespace Squidex.Infrastructure.States { public class StateFactoryTests : IDisposable { private class MyStatefulObject : IStatefulObject { public Task ActivateAsync(string key, IStore store) { return TaskHelper.Done; } } private readonly string key = Guid.NewGuid().ToString(); private readonly MyStatefulObject statefulObject = new MyStatefulObject(); private readonly IEventDataFormatter eventDataFormatter = A.Fake(); private readonly IEventStore eventStore = A.Fake(); private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly IPubSub pubSub = new InMemoryPubSub(true); private readonly IServiceProvider services = A.Fake(); private readonly ISnapshotStore snapshotStore = A.Fake>(); private readonly IStreamNameResolver streamNameResolver = A.Fake(); private readonly StateFactory sut; public StateFactoryTests() { A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .Returns(statefulObject); A.CallTo(() => services.GetService(typeof(ISnapshotStore))) .Returns(snapshotStore); sut = new StateFactory(pubSub, cache, eventStore, eventDataFormatter, services, streamNameResolver); sut.Initialize(); } public void Dispose() { sut.Dispose(); } [Fact] public async Task Should_provide_state_from_services_and_add_to_cache() { var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); Assert.NotNull(cache.Get(key)); } [Fact] public async Task Should_serve_next_request_from_cache() { var actualObject1 = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject1); Assert.NotNull(cache.Get(key)); var actualObject2 = await sut.GetSingleAsync(key); A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .MustHaveHappened(Repeated.Exactly.Once); } [Fact] public async Task Should_not_serve_next_request_from_cache_when_detached() { var actualObject1 = await sut.CreateAsync(key); Assert.Same(statefulObject, actualObject1); Assert.Null(cache.Get(key)); var actualObject2 = await sut.CreateAsync(key); A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .MustHaveHappened(Repeated.Exactly.Twice); } [Fact] public async Task Should_remove_from_cache_when_invalidation_message_received() { var actualObject = await sut.GetSingleAsync(key); await InvalidateCacheAsync(); Assert.False(cache.TryGetValue(key, out var t)); } [Fact] public async Task Should_remove_from_cache_when_method_called() { var actualObject = await sut.GetSingleAsync(key); sut.Remove(key); Assert.False(cache.TryGetValue(key, out var t)); } [Fact] public void Should_send_invalidation_message_on_refresh() { InvalidateMessage message = null; pubSub.Subscribe(m => { message = m; }); sut.Synchronize(key); Assert.NotNull(message); Assert.Equal(key, message.Key); } private async Task RemoveFromCacheAsync() { cache.Remove(key); await Task.Delay(400); } private async Task InvalidateCacheAsync() { pubSub.Publish(new InvalidateMessage { Key = key }, true); await Task.Delay(400); } } }