mirror of https://github.com/Squidex/squidex.git
5 changed files with 415 additions and 14 deletions
@ -0,0 +1,300 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using FluentAssertions; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.TestHelpers; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.Commands |
|||
{ |
|||
public class MultiSnapshotDomainObjectGrainTests |
|||
{ |
|||
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>(); |
|||
private readonly ISnapshotStore<MyDomainState, Guid> snapshotStore = A.Fake<ISnapshotStore<MyDomainState, Guid>>(); |
|||
private readonly IPersistence persistence = A.Fake<IPersistence>(); |
|||
private readonly Guid id = Guid.NewGuid(); |
|||
private readonly MyDomainObject sut; |
|||
|
|||
public sealed class ValueChanged : IEvent |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class CreateAuto : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class CreateCustom : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class UpdateAuto : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class UpdateCustom : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class MyDomainObject : MultiSnapshotDomainObjectGrain<MyDomainState> |
|||
{ |
|||
public MyDomainObject(IStore<Guid> store) |
|||
: base(store, A.Dummy<ISemanticLog>()) |
|||
{ |
|||
} |
|||
|
|||
protected override Task<object> ExecuteAsync(IAggregateCommand command) |
|||
{ |
|||
switch (command) |
|||
{ |
|||
case CreateAuto createAuto: |
|||
return CreateAsync(createAuto, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
}); |
|||
|
|||
case CreateCustom createCustom: |
|||
return CreateReturnAsync(createCustom, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
|
|||
return "CREATED"; |
|||
}); |
|||
|
|||
case UpdateAuto updateAuto: |
|||
return UpdateAsync(updateAuto, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
}); |
|||
|
|||
case UpdateCustom updateCustom: |
|||
return UpdateReturnAsync(updateCustom, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
|
|||
return "UPDATED"; |
|||
}); |
|||
} |
|||
|
|||
return Task.FromResult<object>(null); |
|||
} |
|||
|
|||
protected override MyDomainState OnEvent(Envelope<IEvent> @event) |
|||
{ |
|||
return new MyDomainState { Value = ((ValueChanged)@event.Payload).Value }; |
|||
} |
|||
} |
|||
|
|||
public MultiSnapshotDomainObjectGrainTests() |
|||
{ |
|||
A.CallTo(() => store.WithEventSourcing(typeof(MyDomainObject), id, A<Func<Envelope<IEvent>, Task>>.Ignored)) |
|||
.Returns(persistence); |
|||
|
|||
A.CallTo(() => store.GetSnapshotStore<MyDomainState>()) |
|||
.Returns(snapshotStore); |
|||
|
|||
sut = new MyDomainObject(store); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_latestet_version_when_requesting_state_with_any() |
|||
{ |
|||
await SetupUpdatedAsync(); |
|||
|
|||
var result = sut.GetSnapshot(EtagVersion.Any); |
|||
|
|||
result.Should().BeEquivalentTo(new MyDomainState { Value = 8, Version = 1 }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_empty_version_when_requesting_state_with_empty_version() |
|||
{ |
|||
await SetupUpdatedAsync(); |
|||
|
|||
var result = sut.GetSnapshot(EtagVersion.Empty); |
|||
|
|||
result.Should().BeEquivalentTo(new MyDomainState { Value = 0, Version = -1 }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_specific_version_when_requesting_state_with_specific_version() |
|||
{ |
|||
await SetupUpdatedAsync(); |
|||
|
|||
sut.GetSnapshot(0).Should().BeEquivalentTo(new MyDomainState { Value = 4, Version = 0 }); |
|||
sut.GetSnapshot(1).Should().BeEquivalentTo(new MyDomainState { Value = 8, Version = 1 }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_null_state_when_requesting_state_with_invalid_version() |
|||
{ |
|||
await SetupUpdatedAsync(); |
|||
|
|||
Assert.Null(sut.GetSnapshot(-3)); |
|||
Assert.Null(sut.GetSnapshot(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_instantiate() |
|||
{ |
|||
Assert.Equal(EtagVersion.Empty, sut.Version); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_write_state_and_events_when_created() |
|||
{ |
|||
await SetupEmptyAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(C(new CreateAuto { Value = 4 })); |
|||
|
|||
A.CallTo(() => snapshotStore.WriteAsync(id, A<MyDomainState>.That.Matches(x => x.Value == 4), -1, 0)) |
|||
.MustHaveHappened(); |
|||
A.CallTo(() => persistence.WriteEventsAsync(A<IEnumerable<Envelope<IEvent>>>.That.Matches(x => x.Count() == 1))) |
|||
.MustHaveHappened(); |
|||
|
|||
Assert.True(result.Value is EntityCreatedResult<Guid>); |
|||
|
|||
Assert.Empty(sut.GetUncomittedEvents()); |
|||
|
|||
Assert.Equal(4, sut.Snapshot.Value); |
|||
Assert.Equal(0, sut.Snapshot.Version); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_write_state_and_events_when_updated() |
|||
{ |
|||
await SetupCreatedAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(C(new UpdateAuto { Value = 8 })); |
|||
|
|||
A.CallTo(() => snapshotStore.WriteAsync(id, A<MyDomainState>.That.Matches(x => x.Value == 8), 0, 1)) |
|||
.MustHaveHappened(); |
|||
A.CallTo(() => persistence.WriteEventsAsync(A<IEnumerable<Envelope<IEvent>>>.That.Matches(x => x.Count() == 1))) |
|||
.MustHaveHappened(); |
|||
|
|||
Assert.True(result.Value is EntitySavedResult); |
|||
|
|||
Assert.Empty(sut.GetUncomittedEvents()); |
|||
|
|||
Assert.Equal(8, sut.Snapshot.Value); |
|||
Assert.Equal(1, sut.Snapshot.Version); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_throw_exception_when_already_created() |
|||
{ |
|||
await SetupCreatedAsync(); |
|||
|
|||
await Assert.ThrowsAsync<DomainException>(() => sut.ExecuteAsync(C(new CreateAuto()))); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_throw_exception_when_not_created() |
|||
{ |
|||
await SetupEmptyAsync(); |
|||
|
|||
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.ExecuteAsync(C(new UpdateAuto()))); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_custom_result_on_create() |
|||
{ |
|||
await SetupEmptyAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(C(new CreateCustom())); |
|||
|
|||
Assert.Equal("CREATED", result.Value); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_return_custom_result_on_update() |
|||
{ |
|||
await SetupCreatedAsync(); |
|||
|
|||
var result = await sut.ExecuteAsync(C(new UpdateCustom())); |
|||
|
|||
Assert.Equal("UPDATED", result.Value); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_throw_exception_when_other_verison_expected() |
|||
{ |
|||
await SetupCreatedAsync(); |
|||
|
|||
await Assert.ThrowsAsync<DomainObjectVersionException>(() => sut.ExecuteAsync(C(new UpdateCustom { ExpectedVersion = 3 }))); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_reset_state_when_writing_snapshot_for_create_failed() |
|||
{ |
|||
await SetupEmptyAsync(); |
|||
|
|||
A.CallTo(() => snapshotStore.WriteAsync(A<Guid>.Ignored, A<MyDomainState>.Ignored, -1, 0)) |
|||
.Throws(new InvalidOperationException()); |
|||
|
|||
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.ExecuteAsync(C(new CreateAuto()))); |
|||
|
|||
Assert.Empty(sut.GetUncomittedEvents()); |
|||
|
|||
Assert.Equal(0, sut.Snapshot.Value); |
|||
Assert.Equal(-1, sut.Snapshot.Version); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_reset_state_when_writing_snapshot_for_update_failed() |
|||
{ |
|||
await SetupCreatedAsync(); |
|||
|
|||
A.CallTo(() => snapshotStore.WriteAsync(A<Guid>.Ignored, A<MyDomainState>.Ignored, 0, 1)) |
|||
.Throws(new InvalidOperationException()); |
|||
|
|||
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.ExecuteAsync(C(new UpdateAuto()))); |
|||
|
|||
Assert.Empty(sut.GetUncomittedEvents()); |
|||
|
|||
Assert.Equal(4, sut.Snapshot.Value); |
|||
Assert.Equal(0, sut.Snapshot.Version); |
|||
} |
|||
|
|||
private async Task SetupCreatedAsync() |
|||
{ |
|||
await sut.OnActivateAsync(id); |
|||
|
|||
await sut.ExecuteAsync(C(new CreateAuto { Value = 4 })); |
|||
} |
|||
|
|||
private async Task SetupUpdatedAsync() |
|||
{ |
|||
await SetupCreatedAsync(); |
|||
|
|||
await sut.ExecuteAsync(C(new UpdateAuto { Value = 8 })); |
|||
} |
|||
|
|||
private async Task SetupEmptyAsync() |
|||
{ |
|||
await sut.OnActivateAsync(id); |
|||
} |
|||
|
|||
private static J<IAggregateCommand> C(IAggregateCommand command) |
|||
{ |
|||
return command.AsJ(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Infrastructure.TestHelpers |
|||
{ |
|||
public sealed class MyDomainObject : DomainObjectGrain<MyDomainState> |
|||
{ |
|||
public sealed class ValueChanged : IEvent |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class CreateAuto : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class CreateCustom : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class UpdateAuto : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public sealed class UpdateCustom : MyCommand |
|||
{ |
|||
public int Value { get; set; } |
|||
} |
|||
|
|||
public MyDomainObject(IStore<Guid> store) |
|||
: base(store, A.Dummy<ISemanticLog>()) |
|||
{ |
|||
} |
|||
|
|||
protected override Task<object> ExecuteAsync(IAggregateCommand command) |
|||
{ |
|||
switch (command) |
|||
{ |
|||
case CreateAuto createAuto: |
|||
return CreateAsync(createAuto, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
}); |
|||
|
|||
case CreateCustom createCustom: |
|||
return CreateReturnAsync(createCustom, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
|
|||
return "CREATED"; |
|||
}); |
|||
|
|||
case UpdateAuto updateAuto: |
|||
return UpdateAsync(updateAuto, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
}); |
|||
|
|||
case UpdateCustom updateCustom: |
|||
return UpdateReturnAsync(updateCustom, c => |
|||
{ |
|||
RaiseEvent(new ValueChanged { Value = c.Value }); |
|||
|
|||
return "UPDATED"; |
|||
}); |
|||
} |
|||
|
|||
return Task.FromResult<object>(null); |
|||
} |
|||
|
|||
protected override MyDomainState OnEvent(Envelope<IEvent> @event) |
|||
{ |
|||
return new MyDomainState { Value = ((ValueChanged)@event.Payload).Value }; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue