Browse Source

Update (#1083)

* Update dependencies.

* Use new cache settings.

* Fix tests.

* Fix domain object cache.-

* Test subscriptions again.

* Disable messaging cache.

* Use random name for cluster instances.

* Bind settings properly.

* Update operation.
pull/1084/head
Sebastian Stehle 2 years ago
committed by GitHub
parent
commit
43b895ba8a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 56
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/UpdateValues.cs
  2. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  3. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JsonMapper.cs
  5. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  6. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs
  7. 1
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  8. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  9. 12
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs
  10. 2
      backend/src/Squidex/wwwroot/editor/squidex-editor.css
  11. 446
      backend/src/Squidex/wwwroot/editor/squidex-editor.js
  12. 142
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/UpdateConverterTests.cs
  13. 45
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs
  14. 53
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/ContentDomainObjectTests.Path_should_invoke_scripts.verified.txt
  15. 53
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/ContentDomainObjectTests.Update_should_invoke_scripts.verified.txt
  16. 63
      tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs

56
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/UpdateValues.cs

@ -0,0 +1,56 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent;
public sealed class UpdateValues : IContentValueConverter
{
private readonly ContentData existingData;
private readonly IScriptEngine scriptEngine;
private ScriptVars? vars;
public UpdateValues(ContentData existingData, IScriptEngine scriptEngine)
{
this.existingData = existingData;
this.scriptEngine = scriptEngine;
}
public (bool Remove, JsonValue) ConvertValue(IField field, JsonValue source, IField? parent)
{
if (source.Value is not JsonObject jsonObject)
{
return (false, source);
}
if (jsonObject.TryGetValue("$unset", out var value1) && !Equals(value1.Value, false))
{
return (true, source);
}
if (!jsonObject.TryGetValue("$update", out var value2) || value2.Value is not string update)
{
return (false, source);
}
var options = new ScriptOptions { Readonly = true };
vars ??= new ScriptVars
{
["$data"] = existingData,
["$self"] = jsonObject
};
var result = scriptEngine.Execute(vars, update, options);
return (false, result);
}
}

1
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -7,6 +7,7 @@
using System.Diagnostics;
using Jint.Native;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper;

4
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs

@ -14,6 +14,7 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.Scripting.Internal;
@ -44,6 +45,9 @@ public sealed class JintObjectConverter : IObjectConverter
case ClaimsPrincipal principal:
result = JintUser.Create(engine, principal);
return true;
case JsonValue jsonValue:
result = JsonMapper.Map(jsonValue, engine);
return true;
case DomainId domainId:
result = domainId.ToString();
return true;

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs → backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JsonMapper.cs

@ -14,7 +14,7 @@ using Jint.Runtime.Interop;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
namespace Squidex.Domain.Apps.Core.Scripting.Internal;
public static class JsonMapper
{

3
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -152,8 +152,9 @@ public sealed class JintScriptEngine : IScriptEngine, IScriptDescriptor
var engine = new Engine(engineOptions =>
{
engineOptions.SetTypeConverter(engine => new CustomClrConverter(engine));
engineOptions.AddObjectConverter(JintObjectConverter.Instance);
engineOptions.AllowClrWrite(!options.Readonly);
engineOptions.SetTypeConverter(engine => new CustomClrConverter(engine));
engineOptions.SetReferencesResolver(NullPropagation.Instance);
engineOptions.Strict();

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs

@ -14,4 +14,6 @@ public record struct ScriptOptions
public bool CanDisallow { get; set; }
public bool AsContext { get; set; }
public bool Readonly { get; set; }
}

1
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs

@ -17,7 +17,6 @@ using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Properties;

8
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs

@ -322,9 +322,11 @@ public partial class ContentDomainObject : DomainObject<WriteContent>
operation.MustHavePermission(PermissionIds.AppContentsUpdate);
operation.MustHaveData(c.Data);
var newData = operation.InvokeUpdates(c.Data, Snapshot.EditingData);
if (!c.DoNotValidate)
{
await operation.ValidateInputAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished, ct);
await operation.ValidateInputAsync(newData, c.OptimizeValidation, Snapshot.IsPublished, ct);
}
if (!c.DoNotValidateWorkflow)
@ -332,8 +334,6 @@ public partial class ContentDomainObject : DomainObject<WriteContent>
await operation.CheckUpdateAsync();
}
var newData = c.Data;
if (c.EnrichDefaults)
{
newData = operation.GenerateDefaultValues(newData);
@ -363,6 +363,8 @@ public partial class ContentDomainObject : DomainObject<WriteContent>
operation.MustHavePermission(PermissionIds.AppContentsUpdate);
operation.MustHaveData(c.Data);
c.Data = operation.InvokeUpdates(c.Data, Snapshot.EditingData);
if (!c.DoNotValidate)
{
await operation.ValidateInputPartialAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished, ct);

12
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs

@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Infrastructure;
@ -97,6 +98,17 @@ public static class ValidationExtensions
return converter.Convert(data);
}
public static ContentData InvokeUpdates(this ContentOperation operation, ContentData data, ContentData currentData)
{
var converter =
new ContentConverter(
operation.Components,
operation.Schema);
converter.Add(new UpdateValues(currentData, operation.Resolve<IScriptEngine>()));
return converter.Convert(data);
}
public static async Task CheckReferrersAsync(this ContentOperation operation,
CancellationToken ct)
{

2
backend/src/Squidex/wwwroot/editor/squidex-editor.css

@ -1 +1 @@
.remirror-theme{width:100%}.remirror-theme{position:relative}.remirror-theme *{box-sizing:border-box}.remirror-theme .ProseMirror p{margin-bottom:1rem!important}.remirror-editor{min-height:300px!important;max-height:500px}.MuiStack-root>.MuiBox-root{margin-right:5px}.MuiBox-root>.MuiButtonBase-root{border-color:#dedfe3!important;border-radius:0}.MuiBox-root>.MuiButtonBase-root{border-left-width:0!important}.MuiBox-root>.MuiBox-root:first-child>.MuiButtonBase-root{border-left-width:1px!important}.MuiStack-root>.MuiBox-root>.MuiButtonBase-root:first-child{border-left-width:1px!important}.MuiButtonBase-root.Mui-selected:hover{background-color:#3284f4!important}.remirror-editor-wrapper{padding-top:0!important}.custom-icon path{fill:#0000008a}.remirror-theme div.ProseMirror{border:1px solid #dedfe3!important;border-radius:0!important;box-shadow:none!important}.MuiTooltip-popper>div{background-color:#1a2129;border-radius:0;font-size:85%;font-weight:400;padding:.5rem}.MuiMenu-paper{transform:none!important}.squidex-editor-disabled{pointer-events:none}.squidex-editor-menu{border:1px solid #dedfe3;border-bottom:0;padding:5px}.squidex-editor-menu fieldset{border-radius:0;border:0;display:flex;outline:0;padding:0}.squidex-editor-menu fieldset:disabled,.squidex-editor-menu fieldset.disabled{pointer-events:none}.squidex-editor-menu fieldset:disabled,.squidex-editor-menu fieldset.disabled{opacity:.5}.squidex-editor-counter{border:1px solid #dedfe3;border-top:0;font-size:85%;font-weight:400;opacity:.8;padding:4px 10px 4px 4px;text-align:right}.squidex-editor-image-view{border:1px solid #dedfe3;border-radius:0;display:inline-block;margin-top:15px;margin-bottom:15px;overflow:hidden}.squidex-editor-image-buttons{bottom:10px;left:10px;position:absolute}.squidex-editor-image-element{display:block;max-width:400px;max-height:400px}.squidex-editor-image-info{left:10px;top:10px;position:absolute;background-color:#3284f4;color:#fff;font-size:85%;font-weight:400;padding:2px 6px}.squidex-editor-main{position:relative}.squidex-editor-main .ace_editor{bottom:0;left:0;right:0;top:0;position:absolute;border:1px solid #dedfe3}.squidex-editor-content-link{align-items:center;border-radius:2px;border:1px solid #dedfe3;padding:10px;display:flex;flex-direction:row;flex-wrap:nowrap;margin-top:10px;margin-bottom:10px}.squidex-editor-content-schema{display:block;overflow-x:hidden;overflow-y:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;width:auto;border-right:1px solid #c2c4cc;color:#8b8f9d;flex-shrink:0;padding-left:10px;padding-right:10px;width:200px}.squidex-editor-content-name{display:block;overflow-x:hidden;overflow-y:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;width:auto;padding-left:10px;padding-right:0}.squidex-editor-html{margin-bottom:10px;margin-top:10px;position:relative}.squidex-editor-html-label{left:6px;top:0;position:absolute;color:#8b8f9d;font-size:85%;font-weight:400}.squidex-editor-html textarea{font-family:monospace;padding:30px 20px 20px}.squidex-editor-button{align-items:center;background-color:#fff;border-radius:0;border:1px solid #dedfe3;bottom:10px;display:inline-flex;font-size:85%;font-weight:400;margin-right:5px;padding:6px 12px}.squidex-editor-button:hover{background-color:#f5f5f5}.squidex-editor-input{border:1px solid #dedfe3;border-radius:0;box-sizing:border-box;height:30px;margin-left:0;margin-right:5px;outline:none;padding:6px 12px}.squidex-editor-input:active{border-color:#3284f4}.squidex-editor-floating{border:1px solid #dedfe3}.squidex-editor-floating .MuiBox-root{margin-right:0!important}.squidex-editor-floating .MuiButtonBase-root{height:30px}.squidex-editor-modal-wrapper,.squidex-editor-modal-backdrop{bottom:0;left:0;right:0;top:0;position:absolute}.squidex-editor-modal-backdrop{background-color:#00000003}.squidex-editor-modal-window{left:50%;top:50%;background-color:#fff;border:0;border-radius:.25rem;box-sizing:border-box;box-shadow:0 3px 16px #0003;margin-top:-50px;margin-left:-150px;position:absolute;width:350px}.squidex-editor-modal-body{display:flex;flex-direction:row;flex-grow:1;padding-top:0!important}.squidex-editor-modal-body,.squidex-editor-modal-title{padding:15px}.squidex-editor-modal-title{font-size:85%}.squidex-editor-modal-window input{flex-grow:1}.b:before{content:"<a>";font-family:monospace;font-size:90%}.b:after{content:"</a>";font-family:monospace;font-size:90%}
.remirror-theme{width:100%}.remirror-theme{position:relative}.remirror-theme *{box-sizing:border-box}.remirror-theme .ProseMirror p{margin-bottom:1rem!important}.remirror-editor{min-height:300px!important;max-height:500px}.MuiStack-root>.MuiBox-root{margin-right:5px}.MuiBox-root>.MuiButtonBase-root{border-color:#dedfe3!important;border-radius:0}.MuiBox-root>.MuiButtonBase-root{border-left-width:0!important}.MuiBox-root>.MuiBox-root:first-child>.MuiButtonBase-root{border-left-width:1px!important}.MuiStack-root>.MuiBox-root>.MuiButtonBase-root:first-child{border-left-width:1px!important}.MuiButtonBase-root.Mui-selected:hover{background-color:#3284f4!important}.remirror-editor-wrapper{padding-top:0!important}.custom-icon path{fill:#0000008a}.remirror-theme div.ProseMirror{border:1px solid #dedfe3!important;border-radius:0!important;box-shadow:none!important}.MuiTooltip-popper>div{background-color:#1a2129;border-radius:0;font-size:85%;font-weight:400;padding:.5rem}.MuiMenu-paper{transform:none!important}.squidex-editor-disabled{pointer-events:none}.squidex-editor-menu{border:1px solid #dedfe3;border-bottom:0;padding:5px}.squidex-editor-menu fieldset{border-radius:0;border:0;display:flex;outline:0;padding:0}.squidex-editor-menu fieldset:disabled,.squidex-editor-menu fieldset.disabled{pointer-events:none}.squidex-editor-menu fieldset:disabled,.squidex-editor-menu fieldset.disabled{opacity:.5}.squidex-editor-counter{border:1px solid #dedfe3;border-top:0;font-size:85%;font-weight:400;opacity:.8;padding:4px 10px 4px 4px;text-align:right}.squidex-editor-image-view{border:1px solid #dedfe3;border-radius:0;display:inline-block;margin-top:15px;margin-bottom:15px;overflow:hidden}.squidex-editor-image-buttons{bottom:10px;left:10px;position:absolute}.squidex-editor-image-element{display:block;max-width:400px;max-height:400px}.squidex-editor-image-info{left:10px;top:10px;position:absolute;background-color:#3284f4;color:#fff;font-size:85%;font-weight:400;padding:2px 6px}.squidex-editor-main{position:relative}.squidex-editor-main .ace_editor{bottom:0;left:0;right:0;top:0;position:absolute;border:1px solid #dedfe3}.squidex-editor-content-link{align-items:center;border-radius:2px;border:1px solid #dedfe3;padding:10px;display:flex;flex-direction:row;flex-wrap:nowrap;margin-top:10px;margin-bottom:10px}.squidex-editor-content-schema{display:block;overflow-x:hidden;overflow-y:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;width:auto;border-right:1px solid #c2c4cc;color:#8b8f9d;flex-shrink:0;padding-left:10px;padding-right:10px;width:200px}.squidex-editor-content-name{display:block;overflow-x:hidden;overflow-y:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0;width:auto;padding-left:10px;padding-right:0}.squidex-editor-html{margin-bottom:10px;margin-top:10px;position:relative}.squidex-editor-html-label{left:6px;top:0;position:absolute;color:#8b8f9d;font-size:85%;font-weight:400}.squidex-editor-html textarea{border:1px solid #dedfe3;font-family:monospace;font-size:inherit;height:100px;outline:none;overflow-x:hidden;overflow-y:auto;padding:30px 14px 14px;resize:vertical;width:100%}.squidex-editor-button{align-items:center;background-color:#fff;border-radius:0;border:1px solid #dedfe3;bottom:10px;display:inline-flex;font-size:85%;font-weight:400;margin-right:5px;padding:6px 12px}.squidex-editor-button:hover{background-color:#f5f5f5}.squidex-editor-input{border:1px solid #dedfe3;border-radius:0;box-sizing:border-box;height:30px;margin-left:0;margin-right:5px;outline:none;padding:6px 12px}.squidex-editor-input:active{border-color:#3284f4}.squidex-editor-floating{border:1px solid #dedfe3}.squidex-editor-floating .MuiBox-root{margin-right:0!important}.squidex-editor-floating .MuiButtonBase-root{height:30px}.squidex-editor-modal-wrapper,.squidex-editor-modal-backdrop{bottom:0;left:0;right:0;top:0;position:absolute}.squidex-editor-modal-backdrop{background-color:#00000003}.squidex-editor-modal-window{left:50%;top:50%;background-color:#fff;border:0;border-radius:.25rem;box-sizing:border-box;box-shadow:0 3px 16px #0003;margin-top:-50px;margin-left:-150px;position:absolute;width:350px}.squidex-editor-modal-body{display:flex;flex-direction:row;flex-grow:1;padding-top:0!important}.squidex-editor-modal-body,.squidex-editor-modal-title{padding:15px}.squidex-editor-modal-title{font-size:85%}.squidex-editor-modal-window input{flex-grow:1}.b:before{content:"<a>";font-family:monospace;font-size:90%}.b:after{content:"</a>";font-family:monospace;font-size:90%}

446
backend/src/Squidex/wwwroot/editor/squidex-editor.js

File diff suppressed because one or more lines are too long

142
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/UpdateConverterTests.cs

@ -0,0 +1,142 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Operations.ConvertContent;
public class UpdateConverterTests
{
private readonly IScriptEngine scriptEngine;
public UpdateConverterTests()
{
scriptEngine = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())),
Options.Create(new JintScriptOptions
{
TimeoutScript = TimeSpan.FromSeconds(2),
TimeoutExecution = TimeSpan.FromSeconds(10)
}));
}
[Fact]
public void Should_update()
{
var field1 = Fields.Number(1, "number1", Partitioning.Invariant);
var schema =
new Schema { Name = "my-schema" }
.AddField(field1);
var source =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en",
JsonValue.Object()
.Add("$update", "$data.number1.en + 1")));
var existing =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en", 42));
var actual =
new ContentConverter(ResolvedComponents.Empty, schema)
.Add(new UpdateValues(existing, scriptEngine))
.Convert(source);
var expected =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en", 43));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_update_with_existing_value()
{
var field1 = Fields.Number(1, "number1", Partitioning.Invariant);
var schema =
new Schema { Name = "my-schema" }
.AddField(field1);
var source =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en",
JsonValue.Object()
.Add("$update", "$data.number1.en + $self.increment")
.Add("increment", 7)));
var existing =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en", 42));
var actual =
new ContentConverter(ResolvedComponents.Empty, schema)
.Add(new UpdateValues(existing, scriptEngine))
.Convert(source);
var expected =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en", 49));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_unset_value()
{
var field1 = Fields.Number(1, "number1", Partitioning.Invariant);
var schema =
new Schema { Name = "my-schema" }
.AddField(field1);
var source =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en",
JsonValue.Object()
.Add("$unset", true)));
var existing =
new ContentData()
.AddField(field1.Name,
new ContentFieldData()
.AddLocalized("en", 42));
var actual =
new ContentConverter(ResolvedComponents.Empty, schema)
.Add(new UpdateValues(existing, scriptEngine))
.Convert(source);
var expected =
new ContentData()
.AddField(field1.Name,
new ContentFieldData());
Assert.Equal(expected, actual);
}
}

45
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs

@ -45,6 +45,18 @@ public class ContentDomainObjectTests : HandlerTestBase<WriteContent>
.AddField("my-field1",
new ContentFieldData()
.AddInvariant(1));
private readonly ContentData update =
new ContentData()
.AddField("my-field1",
new ContentFieldData()
.AddInvariant(
JsonValue.Object()
.Add("$update", "$data['my-field1'].iv + 42")));
private readonly ContentData updated =
new ContentData()
.AddField("my-field1",
new ContentFieldData()
.AddInvariant(43));
private readonly ContentData patch =
new ContentData()
.AddField("my-field2",
@ -85,6 +97,9 @@ public class ContentDomainObjectTests : HandlerTestBase<WriteContent>
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), CancellationToken))
.ReturnsLazily(x => Task.FromResult(x.GetArgument<DataScriptVars>(0)!.Data!));
A.CallTo(() => scriptEngine.Execute(A<ScriptVars>._, A<string>._, A<ScriptOptions>._))
.Returns(JsonValue.Create(43));
A.CallTo(() => contentWorkflow.GetInitialStatusAsync(Schema))
.Returns(Status.Draft);
@ -352,6 +367,21 @@ public class ContentDomainObjectTests : HandlerTestBase<WriteContent>
.MustHaveHappened();
}
[Fact]
public async Task Update_should_invoke_scripts()
{
var command = new UpdateContent { Data = update };
await ExecuteCreateAsync();
var actual = await PublishAsync(command);
await VerifySutAsync(actual);
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(updated, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened();
}
[Fact]
public async Task Update_should_create_events_and_update_new_version_if_draft_available()
{
@ -409,6 +439,21 @@ public class ContentDomainObjectTests : HandlerTestBase<WriteContent>
.MustHaveHappened();
}
[Fact]
public async Task Path_should_invoke_scripts()
{
var command = new PatchContent { Data = update };
await ExecuteCreateAsync();
var actual = await PublishAsync(command);
await VerifySutAsync(actual);
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(updated, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened();
}
[Fact]
public async Task Patch_should_create_events_and_update_new_version_if_draft_available()
{

53
backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/ContentDomainObjectTests.Path_should_invoke_scripts.verified.txt

@ -0,0 +1,53 @@
{
sut: {
UniqueId: Guid_1--Guid_2,
Snapshot: {
SchemaId: Guid_3,my-schema,
CurrentVersion: {
Status: Draft,
Data: {
my-field1: {
iv: 43.0
}
}
},
EditingData: {
my-field1: {
iv: 43.0
}
},
EditingStatus: Draft,
IsPublished: false,
AppId: Guid_1,my-app,
IsDeleted: false,
UniqueId: Guid_1--Guid_2,
Id: Guid_2,
CreatedBy: subject:me,
LastModifiedBy: subject:me,
Version: 1
},
Version: 1,
ObjectState: Created
},
events: [
{
Headers: {
AggregateId: Guid_1--Guid_2,
EventId: Guid_4
},
Payload: {
Data: {
my-field1: {
iv: 43.0
}
},
NewVersion: false,
ContentId: Guid_2,
SchemaId: Guid_3,my-schema,
AppId: Guid_1,my-app,
Actor: subject:me,
FromRule: false
}
}
]
}

53
backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/ContentDomainObjectTests.Update_should_invoke_scripts.verified.txt

@ -0,0 +1,53 @@
{
sut: {
UniqueId: Guid_1--Guid_2,
Snapshot: {
SchemaId: Guid_3,my-schema,
CurrentVersion: {
Status: Draft,
Data: {
my-field1: {
iv: 43.0
}
}
},
EditingData: {
my-field1: {
iv: 43.0
}
},
EditingStatus: Draft,
IsPublished: false,
AppId: Guid_1,my-app,
IsDeleted: false,
UniqueId: Guid_1--Guid_2,
Id: Guid_2,
CreatedBy: subject:me,
LastModifiedBy: subject:me,
Version: 1
},
Version: 1,
ObjectState: Created
},
events: [
{
Headers: {
AggregateId: Guid_1--Guid_2,
EventId: Guid_4
},
Payload: {
Data: {
my-field1: {
iv: 43.0
}
},
NewVersion: false,
ContentId: Guid_2,
SchemaId: Guid_3,my-schema,
AppId: Guid_1,my-app,
Actor: subject:me,
FromRule: false
}
}
]
}

63
tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs

@ -9,7 +9,6 @@ using FluentAssertions;
using Newtonsoft.Json.Linq;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.EnrichedEvents;
using System.Runtime.InteropServices;
using TestSuite.Model;
#pragma warning disable CS0618 // Type or member is obsolete
@ -383,6 +382,37 @@ public class ContentUpdateTests : IClassFixture<ContentFixture>
Assert.Null(updated.Data.String);
}
[Fact]
public async Task Should_update_content_with_script()
{
// STEP 1: Create a new item.
var content = await _.Contents.CreateAsync(new TestEntityData
{
Number = 100
}, ContentCreateOptions.AsPublish);
// STEP 2: Update content with script.
await _.Client.DynamicContents(_.SchemaName).UpdateAsync(content.Id,
new DynamicData
{
[TestEntityData.NumberField] = new JObject
{
["iv"] = new JObject
{
["$update"] = "$data.number.iv + 42"
}
}
});
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(142, updated.Data.Number);
// Other data fields are overwritten.
Assert.Null(updated.Data.String);
}
[Theory]
[InlineData(ContentStrategies.Update.Normal)]
[InlineData(ContentStrategies.Update.Upsert)]
@ -440,6 +470,37 @@ public class ContentUpdateTests : IClassFixture<ContentFixture>
Assert.Equal("initial", updated.Data.String);
}
[Fact]
public async Task Should_patch_content_with_script()
{
// STEP 1: Create a new item.
var content = await _.Contents.CreateAsync(new TestEntityData
{
Number = 100
}, ContentCreateOptions.AsPublish);
// STEP 2: Patch content with script.
await _.Client.DynamicContents(_.SchemaName).PatchAsync(content.Id,
new DynamicData
{
[TestEntityData.NumberField] = new JObject
{
["iv"] = new JObject
{
["$update"] = "$data.number.iv + 42"
}
}
});
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(142, updated.Data.Number);
// Other data fields are overwritten.
Assert.Null(updated.Data.String);
}
[Theory]
[InlineData(ContentStrategies.Patch.Normal)]
[InlineData(ContentStrategies.Patch.Upsert)]

Loading…
Cancel
Save