mirror of https://github.com/Squidex/squidex.git
2 changed files with 168 additions and 10 deletions
@ -0,0 +1,162 @@ |
|||
// ==========================================================================
|
|||
// StatesTests.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 Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.States |
|||
{ |
|||
public class StatesTests |
|||
{ |
|||
private class MyStatefulObject : StatefulObject<int> |
|||
{ |
|||
public void SetState(int value) |
|||
{ |
|||
State = value; |
|||
} |
|||
} |
|||
|
|||
private readonly string key = Guid.NewGuid().ToString(); |
|||
private readonly MyStatefulObject state = new MyStatefulObject(); |
|||
private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); |
|||
private readonly IPubSub pubSub = new InMemoryPubSub(); |
|||
private readonly IServiceProvider services = A.Fake<IServiceProvider>(); |
|||
private readonly IStateStore store = A.Fake<IStateStore>(); |
|||
private readonly StateFactory sut; |
|||
|
|||
public StatesTests() |
|||
{ |
|||
A.CallTo(() => services.GetService(typeof(MyStatefulObject))) |
|||
.Returns(state); |
|||
|
|||
sut = new StateFactory(pubSub, services, store, cache); |
|||
sut.Connect(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_provide_object_from_cache() |
|||
{ |
|||
cache.Set(key, state); |
|||
|
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
Assert.Same(state, actual); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_read_from_store() |
|||
{ |
|||
A.CallTo(() => store.ReadAsync<int>(key)) |
|||
.Returns((123, Guid.NewGuid().ToString())); |
|||
|
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
Assert.Same(state, actual); |
|||
Assert.Same(state, cache.Get<MyStatefulObject>(key)); |
|||
|
|||
Assert.Equal(123, state.State); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_provide_state_from_services_and_add_to_cache() |
|||
{ |
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
Assert.Same(state, actual); |
|||
Assert.Same(state, cache.Get<MyStatefulObject>(key)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_serve_next_request_from_cache() |
|||
{ |
|||
var actual1 = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
Assert.Same(state, actual1); |
|||
Assert.Same(state, cache.Get<MyStatefulObject>(key)); |
|||
|
|||
var actual2 = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
Assert.Same(state, actual2); |
|||
Assert.Same(state, cache.Get<MyStatefulObject>(key)); |
|||
|
|||
A.CallTo(() => services.GetService(typeof(MyStatefulObject))) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_write_to_store_with_previous_etag() |
|||
{ |
|||
var etag = Guid.NewGuid().ToString(); |
|||
|
|||
A.CallTo(() => store.ReadAsync<int>(key)) |
|||
.Returns((123, etag)); |
|||
|
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
Assert.Same(state, actual); |
|||
Assert.Same(state, cache.Get<MyStatefulObject>(key)); |
|||
|
|||
Assert.Equal(123, state.State); |
|||
|
|||
state.SetState(456); |
|||
|
|||
await state.WriteStateAsync(); |
|||
|
|||
A.CallTo(() => store.WriteAsync(key, 456, etag, A<string>.That.Matches(x => x != null))) |
|||
.MustHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_from_cache_when_message_sent() |
|||
{ |
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
await InvalidateCacheAsync(); |
|||
|
|||
Assert.True(actual.IsDisposed); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_dispose_states_if_exired() |
|||
{ |
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
await RemoveFromCacheAsync(); |
|||
|
|||
Assert.True(actual.IsDisposed); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_dispose_states_if_disposed() |
|||
{ |
|||
var actual = await sut.GetAsync<MyStatefulObject, int>(key); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
Assert.True(actual.IsDisposed); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue