diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index b1f880328..6e4f995c2 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -7,6 +7,8 @@ using System; using System.Threading.Tasks; +using Orleans.Core; +using Orleans.Runtime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Repositories; @@ -24,7 +26,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Contents { - public sealed class ContentGrain : DomainObjectGrain, IContentGrain + public class ContentGrain : DomainObjectGrain, IContentGrain { private readonly IAppProvider appProvider; private readonly IAssetRepository assetRepository; @@ -37,7 +39,19 @@ namespace Squidex.Domain.Apps.Entities.Contents IAssetRepository assetRepository, IScriptEngine scriptEngine, IContentRepository contentRepository) - : base(store) + : this(store, appProvider, assetRepository, scriptEngine, contentRepository, null, null) + { + } + + protected ContentGrain( + IStore store, + IAppProvider appProvider, + IAssetRepository assetRepository, + IScriptEngine scriptEngine, + IContentRepository contentRepository, + IGrainIdentity identity, + IGrainRuntime runtime) + : base(store, identity, runtime) { Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(scriptEngine, nameof(scriptEngine)); @@ -74,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Create(c); - return EntityCreatedResult.Create(c.Data, Version); + return EntityCreatedResult.Create(c.Data, NewVersion); }); case UpdateContent updateContent: @@ -89,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Update(c); - return new ContentDataChangedResult(Snapshot.Data, Version); + return new ContentDataChangedResult(Snapshot.Data, NewVersion); }); case PatchContent patchContent: @@ -104,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Patch(c); - return new ContentDataChangedResult(Snapshot.Data, Version); + return new ContentDataChangedResult(Snapshot.Data, NewVersion); }); case ChangeContentStatus patchContent: diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs index 5f4cdef14..6a6e6028e 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs @@ -86,8 +86,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", "Berlin")) + .AddField("city", + new ContentFieldData() + .AddValue("iv", "Berlin")) }; var result = sut.FormatString("$CONTENT_DATA.country.iv", AsEnvelope(@event)); @@ -102,8 +103,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", "Berlin")) + .AddField("city", + new ContentFieldData() + .AddValue("iv", "Berlin")) }; var result = sut.FormatString("$CONTENT_DATA.city.de", AsEnvelope(@event)); @@ -118,8 +120,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", new JArray())) + .AddField("city", + new ContentFieldData() + .AddValue("iv", new JArray())) }; var result = sut.FormatString("$CONTENT_DATA.city.de.10", AsEnvelope(@event)); @@ -134,9 +137,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", new JObject( - new JProperty("name", "Berlin")))) + .AddField("city", + new ContentFieldData() + .AddValue("iv", new JObject( + new JProperty("name", "Berlin")))) }; var result = sut.FormatString("$CONTENT_DATA.city.de.Name", AsEnvelope(@event)); @@ -151,8 +155,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", "Berlin")) + .AddField("city", + new ContentFieldData() + .AddValue("iv", "Berlin")) }; var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); @@ -167,8 +172,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", JValue.CreateNull())) + .AddField("city", + new ContentFieldData() + .AddValue("iv", JValue.CreateNull())) }; var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); @@ -183,8 +189,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", JValue.CreateUndefined())) + .AddField("city", + new ContentFieldData() + .AddValue("iv", JValue.CreateUndefined())) }; var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); @@ -199,9 +206,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", new JObject( - new JProperty("name", "Berlin")))) + .AddField("city", + new ContentFieldData() + .AddValue("iv", new JObject( + new JProperty("name", "Berlin")))) }; var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); @@ -216,9 +224,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", new JArray( - "Berlin"))) + .AddField("city", + new ContentFieldData() + .AddValue("iv", new JArray( + "Berlin"))) }; var result = sut.FormatString("$CONTENT_DATA.city.iv.0", AsEnvelope(@event)); @@ -233,9 +242,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { Data = new NamedContentData() - .AddField("city", new ContentFieldData() - .AddValue("iv", new JObject( - new JProperty("name", "Berlin")))) + .AddField("city", + new ContentFieldData() + .AddValue("iv", new JObject( + new JProperty("name", "Berlin")))) }; var result = sut.FormatString("$CONTENT_DATA.city.iv.name", AsEnvelope(@event)); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs deleted file mode 100644 index 1ef316155..000000000 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs +++ /dev/null @@ -1,264 +0,0 @@ -//// ========================================================================== -//// Squidex Headless CMS -//// ========================================================================== -//// Copyright (c) Squidex UG (haftungsbeschränkt) -//// All rights reserved. Licensed under the MIT license. -//// ========================================================================== - -//using System; -//using System.Security.Claims; -//using System.Threading.Tasks; -//using FakeItEasy; -//using NodaTime; -//using Squidex.Domain.Apps.Core; -//using Squidex.Domain.Apps.Core.Apps; -//using Squidex.Domain.Apps.Core.Contents; -//using Squidex.Domain.Apps.Core.Schemas; -//using Squidex.Domain.Apps.Core.Scripting; -//using Squidex.Domain.Apps.Entities.Apps; -//using Squidex.Domain.Apps.Entities.Assets.Repositories; -//using Squidex.Domain.Apps.Entities.Contents.Commands; -//using Squidex.Domain.Apps.Entities.Contents.Repositories; -//using Squidex.Domain.Apps.Entities.Schemas; -//using Squidex.Domain.Apps.Entities.TestHelpers; -//using Squidex.Infrastructure; -//using Squidex.Infrastructure.Commands; -//using Xunit; - -//namespace Squidex.Domain.Apps.Entities.Contents -//{ -// public class ContentCommandMiddlewareTests : HandlerTestBase -// { -// private readonly ISchemaEntity schema = A.Fake(); -// private readonly IScriptEngine scriptEngine = A.Fake(); -// private readonly IAppProvider appProvider = A.Fake(); -// private readonly IAppEntity app = A.Fake(); -// private readonly ClaimsPrincipal user = new ClaimsPrincipal(); -// private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE); -// private readonly Guid contentId = Guid.NewGuid(); -// private readonly ContentDomainObject content = new ContentDomainObject(); -// private readonly ContentCommandMiddleware sut; - -// protected override Guid Id -// { -// get { return contentId; } -// } - -// private readonly NamedContentData invalidData = -// new NamedContentData() -// .AddField("my-field1", new ContentFieldData() -// .AddValue(null)) -// .AddField("my-field2", new ContentFieldData() -// .AddValue(1)); -// private readonly NamedContentData data = -// new NamedContentData() -// .AddField("my-field1", new ContentFieldData() -// .AddValue(1)) -// .AddField("my-field2", new ContentFieldData() -// .AddValue(1)); -// private readonly NamedContentData patch = -// new NamedContentData() -// .AddField("my-field1", new ContentFieldData() -// .AddValue(1)); - -// public ContentCommandMiddlewareTests() -// { -// var schemaDef = -// new Schema("my-schema") -// .AddField(new NumberField(1, "my-field1", Partitioning.Invariant, -// new NumberFieldProperties { IsRequired = true })) -// .AddField(new NumberField(2, "my-field2", Partitioning.Invariant, -// new NumberFieldProperties { IsRequired = false })); - -// sut = new ContentCommandMiddleware(Handler, appProvider, A.Dummy(), scriptEngine, A.Dummy()); - -// A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); - -// A.CallTo(() => appProvider.GetAppAsync(AppName)).Returns(app); - -// A.CallTo(() => schema.SchemaDef).Returns(schemaDef); -// A.CallTo(() => schema.ScriptCreate).Returns(""); -// A.CallTo(() => schema.ScriptChange).Returns(""); -// A.CallTo(() => schema.ScriptUpdate).Returns(""); -// A.CallTo(() => schema.ScriptDelete).Returns(""); - -// A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((app, schema)); -// } - -// [Fact] -// public async Task Create_should_throw_exception_if_data_is_not_valid() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(invalidData); - -// var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = invalidData, User = user }); - -// await TestCreate(content, async _ => -// { -// await Assert.ThrowsAsync(() => sut.HandleAsync(context)); -// }, false); -// } - -// [Fact] -// public async Task Create_should_create_content() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(data); - -// var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = data, User = user }); - -// await TestCreate(content, async _ => -// { -// await sut.HandleAsync(context); -// }); - -// Assert.Equal(data, context.Result>().IdOrValue); - -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")).MustHaveHappened(); -// A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustNotHaveHappened(); -// } - -// [Fact] -// public async Task Create_should_also_invoke_publish_script_when_publishing() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(data); - -// var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = data, User = user, Publish = true }); - -// await TestCreate(content, async _ => -// { -// await sut.HandleAsync(context); -// }); - -// Assert.Equal(data, context.Result>().IdOrValue); - -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")).MustHaveHappened(); -// A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustHaveHappened(); -// } - -// [Fact] -// public async Task Update_should_throw_exception_if_data_is_not_valid() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(invalidData); - -// CreateContent(); - -// var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = invalidData, User = user }); - -// await TestUpdate(content, async _ => -// { -// await Assert.ThrowsAsync(() => sut.HandleAsync(context)); -// }, false); -// } - -// [Fact] -// public async Task Update_should_update_domain_object() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(data); - -// CreateContent(); - -// var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = data, User = user }); - -// await TestUpdate(content, async _ => -// { -// await sut.HandleAsync(context); -// }); - -// Assert.Equal(data, context.Result().Data); - -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")).MustHaveHappened(); -// } - -// [Fact] -// public async Task Patch_should_throw_exception_if_data_is_not_valid() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(invalidData); - -// CreateContent(); - -// var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = invalidData, User = user }); - -// await TestUpdate(content, async _ => -// { -// await Assert.ThrowsAsync(() => sut.HandleAsync(context)); -// }, false); -// } - -// [Fact] -// public async Task Patch_should_update_domain_object() -// { -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) -// .Returns(data); - -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)).Returns(patch); - -// CreateContent(); - -// var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = patch, User = user }); - -// await TestUpdate(content, async _ => -// { -// await sut.HandleAsync(context); -// }); - -// Assert.NotNull(context.Result().Data); - -// A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")).MustHaveHappened(); -// } - -// [Fact] -// public async Task ChangeStatus_should_publish_domain_object() -// { -// CreateContent(); - -// var context = CreateContextForCommand(new ChangeContentStatus { ContentId = contentId, User = user, Status = Status.Published }); - -// await TestUpdate(content, async _ => -// { -// await sut.HandleAsync(context); -// }); - -// A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustHaveHappened(); -// } - -// [Fact] -// public async Task ChangeStatus_should_not_invoke_scripts_when_scheduled() -// { -// CreateContent(); - -// var context = CreateContextForCommand(new ChangeContentStatus { ContentId = contentId, User = user, Status = Status.Published, DueTime = Instant.MaxValue }); - -// await TestUpdate(content, async _ => -// { -// await sut.HandleAsync(context); -// }); - -// A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustNotHaveHappened(); -// } - -// [Fact] -// public async Task Delete_should_update_domain_object() -// { -// CreateContent(); - -// var command = CreateContextForCommand(new DeleteContent { ContentId = contentId, User = user }); - -// await TestUpdate(content, async _ => -// { -// await sut.HandleAsync(command); -// }); - -// A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustHaveHappened(); -// } - -// private void CreateContent() -// { -// content.Create(CreateCommand(new CreateContent { Data = data })); -// } -// } -//} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs deleted file mode 100644 index b6d428fdf..000000000 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ /dev/null @@ -1,304 +0,0 @@ -//// ========================================================================== -//// Squidex Headless CMS -//// ========================================================================== -//// Copyright (c) Squidex UG (haftungsbeschränkt) -//// All rights reserved. Licensed under the MIT license. -//// ========================================================================== - -//using System; -//using FakeItEasy; -//using FluentAssertions; -//using NodaTime; -//using Squidex.Domain.Apps.Core.Contents; -//using Squidex.Domain.Apps.Entities.Contents.Commands; -//using Squidex.Domain.Apps.Entities.TestHelpers; -//using Squidex.Domain.Apps.Events.Contents; -//using Squidex.Infrastructure; -//using Squidex.Infrastructure.States; -//using Xunit; - -//namespace Squidex.Domain.Apps.Entities.Contents -//{ -// public class ContentDomainObjectTests : HandlerTestBase -// { -// private readonly NamedContentData data = -// new NamedContentData() -// .AddField("field1", -// new ContentFieldData() -// .AddValue("iv", 1)); -// private readonly NamedContentData otherData = -// new NamedContentData() -// .AddField("field2", -// new ContentFieldData() -// .AddValue("iv", 2)); -// private readonly NamedContentData patched; -// private readonly Guid contentId = Guid.NewGuid(); -// private readonly ContentDomainObject sut = new ContentDomainObject(); - -// protected override Guid Id -// { -// get { return contentId; } -// } - -// public ContentDomainObjectTests() -// { -// patched = otherData.MergeInto(data); - -// sut.ActivateAsync(Id, A.Fake>()); -// } - -// [Fact] -// public void Create_should_throw_exception_if_created() -// { -// sut.Create(CreateCommand(new CreateContent { Data = data })); - -// Assert.Throws(() => -// { -// sut.Create(CreateContentCommand(new CreateContent { Data = data })); -// }); -// } - -// [Fact] -// public void Create_should_create_events() -// { -// sut.Create(CreateContentCommand(new CreateContent { Data = data })); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentCreated { Data = data }) -// ); -// } - -// [Fact] -// public void Create_should_also_publish_if_set_to_true() -// { -// sut.Create(CreateContentCommand(new CreateContent { Data = data, Publish = true })); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentCreated { Data = data }), -// CreateContentEvent(new ContentStatusChanged { Status = Status.Published }) -// ); -// } - -// [Fact] -// public void Update_should_throw_exception_if_not_created() -// { -// Assert.Throws(() => -// { -// sut.Update(CreateContentCommand(new UpdateContent { Data = data })); -// }); -// } - -// [Fact] -// public void Update_should_throw_exception_if_content_is_deleted() -// { -// CreateContent(); -// DeleteContent(); - -// Assert.Throws(() => -// { -// sut.Update(CreateContentCommand(new UpdateContent())); -// }); -// } - -// [Fact] -// public void Update_should_create_events() -// { -// CreateContent(); - -// sut.Update(CreateContentCommand(new UpdateContent { Data = otherData })); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentUpdated { Data = otherData }) -// ); -// } - -// [Fact] -// public void Update_should_not_create_event_for_same_data() -// { -// CreateContent(); -// UpdateContent(); - -// sut.Update(CreateContentCommand(new UpdateContent { Data = data })); - -// sut.GetUncomittedEvents().Should().BeEmpty(); -// } - -// [Fact] -// public void Patch_should_throw_exception_if_not_created() -// { -// Assert.Throws(() => -// { -// sut.Patch(CreateContentCommand(new PatchContent { Data = data })); -// }); -// } - -// [Fact] -// public void Patch_should_throw_exception_if_content_is_deleted() -// { -// CreateContent(); -// DeleteContent(); - -// Assert.Throws(() => -// { -// sut.Patch(CreateContentCommand(new PatchContent())); -// }); -// } - -// [Fact] -// public void Patch_should_create_events() -// { -// CreateContent(); -// UpdateContent(); - -// sut.Patch(CreateContentCommand(new PatchContent { Data = otherData })); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentUpdated { Data = patched }) -// ); -// } - -// [Fact] -// public void Patch_should_not_create_event_for_same_data() -// { -// CreateContent(); -// UpdateContent(); - -// sut.Patch(CreateContentCommand(new PatchContent { Data = data })); - -// sut.GetUncomittedEvents().Should().BeEmpty(); -// } - -// [Fact] -// public void ChangeStatus_should_throw_exception_if_not_created() -// { -// Assert.Throws(() => -// { -// sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus())); -// }); -// } - -// [Fact] -// public void ChangeStatus_should_throw_exception_if_content_is_deleted() -// { -// CreateContent(); -// DeleteContent(); - -// Assert.Throws(() => -// { -// sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus())); -// }); -// } - -// [Fact] -// public void ChangeStatus_should_refresh_properties_and_create_events() -// { -// CreateContent(); - -// sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = Status.Published })); - -// Assert.Equal(Status.Published, sut.Snapshot.Status); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentStatusChanged { Status = Status.Published }) -// ); -// } - -// [Fact] -// public void ChangeStatus_should_refresh_properties_and_create_scheduled_events_when_command_has_due_time() -// { -// CreateContent(); - -// var dueTime = Instant.MaxValue; - -// sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = Status.Published, DueTime = dueTime })); - -// Assert.Equal(Status.Draft, sut.Snapshot.Status); -// Assert.Equal(Status.Published, sut.Snapshot.ScheduledTo); -// Assert.Equal(dueTime, sut.Snapshot.ScheduledAt); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) -// ); -// } - -// [Fact] -// public void Delete_should_throw_exception_if_not_created() -// { -// Assert.Throws(() => -// { -// sut.Delete(CreateContentCommand(new DeleteContent())); -// }); -// } - -// [Fact] -// public void Delete_should_throw_exception_if_already_deleted() -// { -// CreateContent(); -// DeleteContent(); - -// Assert.Throws(() => -// { -// sut.Delete(CreateContentCommand(new DeleteContent())); -// }); -// } - -// [Fact] -// public void Delete_should_update_properties_and_create_events() -// { -// CreateContent(); - -// sut.Delete(CreateContentCommand(new DeleteContent())); - -// Assert.True(sut.Snapshot.IsDeleted); - -// sut.GetUncomittedEvents() -// .ShouldHaveSameEvents( -// CreateContentEvent(new ContentDeleted()) -// ); -// } - -// private void CreateContent() -// { -// sut.Create(CreateContentCommand(new CreateContent { Data = data })); -// sut.ClearUncommittedEvents(); -// } - -// private void UpdateContent() -// { -// sut.Update(CreateContentCommand(new UpdateContent { Data = data })); -// sut.ClearUncommittedEvents(); -// } - -// private void ChangeStatus(Status status) -// { -// sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = status })); -// sut.ClearUncommittedEvents(); -// } - -// private void DeleteContent() -// { -// sut.Delete(CreateContentCommand(new DeleteContent())); -// sut.ClearUncommittedEvents(); -// } - -// protected T CreateContentEvent(T @event) where T : ContentEvent -// { -// @event.ContentId = contentId; - -// return CreateEvent(@event); -// } - -// protected T CreateContentCommand(T command) where T : ContentCommand -// { -// command.ContentId = contentId; - -// return CreateCommand(command); -// } -// } -//} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs new file mode 100644 index 000000000..1ccd7546a --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs @@ -0,0 +1,379 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using FakeItEasy; +using NodaTime; +using Orleans.Core; +using Orleans.Runtime; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Contents.State; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.States; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public class ContentDomainObjectTests : HandlerTestBase + { + private readonly ISchemaEntity schema = A.Fake(); + private readonly IScriptEngine scriptEngine = A.Fake(); + private readonly IAppProvider appProvider = A.Fake(); + private readonly IAppEntity app = A.Fake(); + private readonly ClaimsPrincipal user = new ClaimsPrincipal(); + private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE); + + private readonly NamedContentData invalidData = + new NamedContentData() + .AddField("my-field1", + new ContentFieldData() + .AddValue(null)) + .AddField("my-field2", + new ContentFieldData() + .AddValue(1)); + private readonly NamedContentData data = + new NamedContentData() + .AddField("my-field1", + new ContentFieldData() + .AddValue("iv", 1)); + private readonly NamedContentData patch = + new NamedContentData() + .AddField("my-field2", + new ContentFieldData() + .AddValue("iv", 2)); + private readonly NamedContentData otherData = + new NamedContentData() + .AddField("my-field1", + new ContentFieldData() + .AddValue("iv", 2)) + .AddField("my-field2", + new ContentFieldData() + .AddValue("iv", 2)); + private readonly NamedContentData patched; + private readonly Guid contentId = Guid.NewGuid(); + private readonly ContentGrain sut; + + public class MyContentGrain : ContentGrain + { + public MyContentGrain( + IStore store, + IAppProvider appProvider, + IAssetRepository assetRepository, + IScriptEngine scriptEngine, + IContentRepository contentRepository, + IGrainIdentity identity, + IGrainRuntime runtime) + : base(store, appProvider, assetRepository, scriptEngine, contentRepository, identity, runtime) + { + } + } + + protected override Guid Id + { + get { return contentId; } + } + + public ContentDomainObjectTests() + { + var schemaDef = + new Schema("my-schema") + .AddField(new NumberField(1, "my-field1", Partitioning.Invariant, + new NumberFieldProperties { IsRequired = true })) + .AddField(new NumberField(2, "my-field2", Partitioning.Invariant, + new NumberFieldProperties { IsRequired = false })); + + A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); + + A.CallTo(() => appProvider.GetAppAsync(AppName)).Returns(app); + A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((app, schema)); + + A.CallTo(() => schema.SchemaDef).Returns(schemaDef); + A.CallTo(() => schema.ScriptCreate).Returns(""); + A.CallTo(() => schema.ScriptChange).Returns(""); + A.CallTo(() => schema.ScriptUpdate).Returns(""); + A.CallTo(() => schema.ScriptDelete).Returns(""); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.That.Matches(x => ReferenceEquals(x.Data, data)), A.Ignored)) + .Returns(data); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.That.Matches(x => ReferenceEquals(x.Data, invalidData)), A.Ignored)) + .Returns(invalidData); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.That.Matches(x => ReferenceEquals(x.Data, patch)), A.Ignored)) + .Returns(patch); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.That.Matches(x => ReferenceEquals(x.Data, otherData)), A.Ignored)) + .Returns(otherData); + + patched = patch.MergeInto(data); + + sut = new MyContentGrain(Store, appProvider, A.Dummy(), scriptEngine, A.Dummy(), Identity, Runtime); + sut.OnActivateAsync(); + } + + [Fact] + public async Task Command_should_throw_exception_if_content_is_deleted() + { + await ExecuteCreateAsync(); + await ExecuteDeleteAsync(); + + await Assert.ThrowsAsync(ExecuteUpdateAsync); + } + + [Fact] + public async Task Create_should_create_events_and_update_state() + { + var command = new CreateContent { Data = data }; + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(EntityCreatedResult.Create(data, 0)); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentCreated { Data = data }) + ); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")) + .MustHaveHappened(); + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Create_should_also_publish() + { + var command = new CreateContent { Data = data, Publish = true }; + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(EntityCreatedResult.Create(data, 1)); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentCreated { Data = data }), + CreateContentEvent(new ContentStatusChanged { Status = Status.Published }) + ); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")) + .MustHaveHappened(); + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")) + .MustHaveHappened(); + } + + [Fact] + public async Task Create_should_throw_when_invalid_data_is_passed() + { + var command = new CreateContent { Data = invalidData }; + + await Assert.ThrowsAsync(() => sut.ExecuteAsync(J(CreateContentCommand(command)))); + } + + [Fact] + public async Task Update_should_create_events_and_update_state() + { + var command = new UpdateContent { Data = otherData }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new ContentDataChangedResult(otherData, 1)); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentUpdated { Data = otherData }) + ); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")) + .MustHaveHappened(); + } + + [Fact] + public async Task Update_should_not_create_event_for_same_data() + { + var command = new UpdateContent { Data = data }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new ContentDataChangedResult(data, 0)); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentCreated { Data = data }) + ); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")) + .MustHaveHappened(); + } + + [Fact] + public async Task Update_should_throw_when_invalid_data_is_passed() + { + var command = new UpdateContent { Data = invalidData }; + + await ExecuteCreateAsync(); + + await Assert.ThrowsAsync(() => sut.ExecuteAsync(J(CreateContentCommand(command)))); + } + + [Fact] + public async Task Patch_should_create_events_and_update_state() + { + var command = new PatchContent { Data = patch }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new ContentDataChangedResult(otherData, 1)); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentUpdated { Data = patched }) + ); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")) + .MustHaveHappened(); + } + + [Fact] + public async Task Patch_should_not_create_event_for_same_data() + { + var command = new PatchContent { Data = data }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new ContentDataChangedResult(data, 0)); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentCreated { Data = data }) + ); + + A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, "")) + .MustHaveHappened(); + } + + [Fact] + public async Task ChangedStatus_should_create_events_and_update_state() + { + var command = new ChangeContentStatus { Status = Status.Published }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new EntitySavedResult(1)); + + Assert.Equal(Status.Published, sut.Snapshot.Status); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentStatusChanged { Status = Status.Published }) + ); + + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")) + .MustHaveHappened(); + } + + [Fact] + public async Task ChangeStatus_should_refresh_properties_and_create_scheduled_events_when_command_has_due_time() + { + var dueTime = Instant.MaxValue; + + var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTime }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new EntitySavedResult(1)); + + Assert.Equal(Status.Draft, sut.Snapshot.Status); + Assert.Equal(Status.Published, sut.Snapshot.ScheduledTo); + Assert.Equal(dueTime, sut.Snapshot.ScheduledAt); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) + ); + + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Delete_should_update_properties_and_create_events() + { + var command = new DeleteContent(); + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(J(CreateContentCommand(command))); + + result.ShouldBeEquivalent(new EntitySavedResult(1)); + + Assert.True(sut.Snapshot.IsDeleted); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentDeleted()) + ); + + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")) + .MustHaveHappened(); + } + + private Task ExecuteCreateAsync() + { + return sut.ExecuteAsync(J(CreateContentCommand(new CreateContent { Data = data }))); + } + + private Task ExecuteUpdateAsync() + { + return sut.ExecuteAsync(J(CreateContentCommand(new UpdateContent { Data = data }))); + } + + private Task ExecuteDeleteAsync() + { + return sut.ExecuteAsync(J(CreateContentCommand(new DeleteContent()))); + } + + protected T CreateContentEvent(T @event) where T : ContentEvent + { + @event.ContentId = contentId; + + return CreateEvent(@event); + } + + protected T CreateContentCommand(T command) where T : ContentCommand + { + command.ContentId = contentId; + + return CreateCommand(command); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs deleted file mode 100644 index 48f5338b0..000000000 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ /dev/null @@ -1,224 +0,0 @@ -// ========================================================================== -// 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.Security.Claims; -using System.Threading.Tasks; -using FakeItEasy; -using Microsoft.OData.UriParser; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents.Edm; -using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Security; -using Xunit; - -namespace Squidex.Domain.Apps.Entities.Contents -{ - public class ContentQueryServiceTests - { - private readonly IContentRepository contentRepository = A.Fake(); - private readonly IScriptEngine scriptEngine = A.Fake(); - private readonly ISchemaEntity schema = A.Fake(); - private readonly IContentEntity content = A.Fake(); - private readonly IAppEntity app = A.Fake(); - private readonly IAppProvider appProvider = A.Fake(); - private readonly Guid appId = Guid.NewGuid(); - private readonly Guid schemaId = Guid.NewGuid(); - private readonly Guid contentId = Guid.NewGuid(); - private readonly string appName = "my-app"; - private readonly NamedContentData contentData = new NamedContentData(); - private readonly NamedContentData contentTransformed = new NamedContentData(); - private readonly ClaimsPrincipal user; - private readonly ClaimsIdentity identity = new ClaimsIdentity(); - private readonly EdmModelBuilder modelBuilder = A.Fake(); - private readonly ContentQueryService sut; - - public ContentQueryServiceTests() - { - user = new ClaimsPrincipal(identity); - - A.CallTo(() => app.Id).Returns(appId); - A.CallTo(() => app.Name).Returns(appName); - - A.CallTo(() => content.Id).Returns(contentId); - A.CallTo(() => content.Data).Returns(contentData); - A.CallTo(() => content.Status).Returns(Status.Published); - - sut = new ContentQueryService(contentRepository, appProvider, scriptEngine, modelBuilder); - } - - [Fact] - public async Task Should_return_schema_from_id_if_string_is_guid() - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - - var result = await sut.FindSchemaAsync(app, schemaId.ToString()); - - Assert.Equal(schema, result); - } - - [Fact] - public async Task Should_return_schema_from_name_if_string_not_guid() - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) - .Returns(schema); - - var result = await sut.FindSchemaAsync(app, "my-schema"); - - Assert.Equal(schema, result); - } - - [Fact] - public async Task Should_throw_if_schema_not_found() - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) - .Returns((ISchemaEntity)null); - - await Assert.ThrowsAsync(() => sut.FindSchemaAsync(app, "my-schema")); - } - - [Fact] - public async Task Should_return_content_from_repository_and_transform() - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - A.CallTo(() => contentRepository.FindContentAsync(app, schema, contentId)) - .Returns(content); - - A.CallTo(() => schema.ScriptQuery) - .Returns(""); - - A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "")) - .Returns(contentTransformed); - - var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId); - - Assert.Equal(schema, result.Schema); - - Assert.Equal(contentTransformed, result.Content.Data); - Assert.Equal(content.Id, result.Content.Id); - } - - [Fact] - public async Task Should_throw_if_content_to_find_does_not_exist() - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - - A.CallTo(() => contentRepository.FindContentAsync(app, schema, contentId)) - .Returns((IContentEntity)null); - - await Assert.ThrowsAsync(async () => await sut.FindContentAsync(app, schemaId.ToString(), user, contentId)); - } - - [Fact] - public async Task Should_return_contents_with_ids_from_repository_and_transform() - { - await TestManyIdRequest(true, false, new HashSet { Guid.NewGuid() }, Status.Draft, Status.Published); - } - - [Fact] - public async Task Should_return_non_archived_contents_from_repository_and_transform() - { - await TestManyRequest(true, false, Status.Draft, Status.Published); - } - - [Fact] - public async Task Should_return_archived_contents_from_repository_and_transform() - { - await TestManyRequest(true, true, Status.Archived); - } - - [Fact] - public async Task Should_return_draft_contents_from_repository_and_transform() - { - await TestManyRequest(false, false, Status.Published); - } - - [Fact] - public async Task Should_return_draft_contents_from_repository_and_transform_when_requesting_archive_as_non_frontend() - { - await TestManyRequest(false, true, Status.Published); - } - - private async Task TestManyRequest(bool isFrontend, bool archive, params Status[] status) - { - SetupClaims(isFrontend); - - SetupFakeWithOdataQuery(status); - SetupFakeWithScripting(); - - var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, string.Empty); - - Assert.Equal(schema, result.Schema); - - Assert.Equal(contentData, result.Contents[0].Data); - Assert.Equal(content.Id, result.Contents[0].Id); - - Assert.Equal(123, result.Contents.Total); - } - - private async Task TestManyIdRequest(bool isFrontend, bool archive, HashSet ids, params Status[] status) - { - SetupClaims(isFrontend); - - SetupFakeWithIdQuery(status, ids); - SetupFakeWithScripting(); - - var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, ids); - - Assert.Equal(schema, result.Schema); - - Assert.Equal(contentData, result.Contents[0].Data); - Assert.Equal(content.Id, result.Contents[0].Id); - - Assert.Equal(123, result.Contents.Total); - } - - private void SetupClaims(bool isFrontend) - { - if (isFrontend) - { - identity.AddClaim(new Claim(OpenIdClaims.ClientId, "squidex-frontend")); - } - } - - private void SetupFakeWithIdQuery(Status[] status, HashSet ids) - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - - A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), ids)) - .Returns(ResultList.Create(Enumerable.Repeat(content, 1), 123)); - } - - private void SetupFakeWithOdataQuery(Status[] status) - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - - A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), A.Ignored)) - .Returns(ResultList.Create(Enumerable.Repeat(content, 1), 123)); - } - - private void SetupFakeWithScripting() - { - A.CallTo(() => schema.ScriptQuery) - .Returns(""); - - A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "")) - .Returns(contentTransformed); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index 96cfbb42c..18754535c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -97,26 +97,35 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL data = data ?? new NamedContentData() .AddField("my-string", - new ContentFieldData().AddValue("de", "value")) + new ContentFieldData() + .AddValue("de", "value")) .AddField("my-assets", - new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { assetId }))) + new ContentFieldData() + .AddValue("iv", JToken.FromObject(new[] { assetId }))) .AddField("my-number", - new ContentFieldData().AddValue("iv", 1)) + new ContentFieldData() + .AddValue("iv", 1)) .AddField("my-boolean", - new ContentFieldData().AddValue("iv", true)) + new ContentFieldData() + .AddValue("iv", true)) .AddField("my-datetime", - new ContentFieldData().AddValue("iv", now.ToDateTimeUtc())) + new ContentFieldData() + .AddValue("iv", now.ToDateTimeUtc())) .AddField("my-tags", - new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { "tag1", "tag2" }))) + new ContentFieldData() + .AddValue("iv", JToken.FromObject(new[] { "tag1", "tag2" }))) .AddField("my-references", - new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { refId }))) + new ContentFieldData() + .AddValue("iv", JToken.FromObject(new[] { refId }))) .AddField("my-geolocation", - new ContentFieldData().AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))); + new ContentFieldData() + .AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))); if (!noJson) { data.AddField("my-json", - new ContentFieldData().AddValue("iv", JToken.FromObject(new { value = 1 }))); + new ContentFieldData() + .AddValue("iv", JToken.FromObject(new { value = 1 }))); } var content = new ContentEntity