Browse Source

Simplified. (#538)

* Scripting simplified.
pull/539/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
175ce6a36f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  2. 50
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ExecutionContext.cs
  3. 21
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/DateTimeJintExtension.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs
  5. 37
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs
  6. 22
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs
  7. 176
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  8. 32
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs
  9. 23
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs
  10. 56
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs
  11. 11
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  12. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs
  13. 2
      backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs
  14. 1
      backend/src/Squidex.Infrastructure/Queries/Optimizer.cs
  15. 1
      backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs
  16. 40
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs
  17. 61
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs
  18. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs
  19. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs
  20. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs
  21. 43
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs
  22. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Counter/CounterJintExtensionTests.cs
  23. 9
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs
  24. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs
  25. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs

10
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using NodaTime.Text; using NodaTime.Text;
@ -116,7 +117,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
["event"] = @event ["event"] = @event
}; };
return scriptEngine.Interpolate(vars, script); var result = scriptEngine.Execute(vars, script).ToString();
if (result == "undefined")
{
return GlobalFallback;
}
return result;
} }
var parts = BuildParts(text, @event); var parts = BuildParts(text, @event);

50
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ExecutionContext.cs

@ -9,6 +9,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Jint; using Jint;
using Jint.Native;
using Jint.Native.Object;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
{ {
@ -22,6 +25,8 @@ namespace Squidex.Domain.Apps.Core.Scripting
public CancellationToken CancellationToken { get; } public CancellationToken CancellationToken { get; }
public bool IsAsync { get; private set; }
internal ExecutionContext(Engine engine, CancellationToken cancellationToken, ExceptionHandler? exceptionHandler = null) internal ExecutionContext(Engine engine, CancellationToken cancellationToken, ExceptionHandler? exceptionHandler = null)
: base(StringComparer.OrdinalIgnoreCase) : base(StringComparer.OrdinalIgnoreCase)
{ {
@ -32,16 +37,55 @@ namespace Squidex.Domain.Apps.Core.Scripting
this.exceptionHandler = exceptionHandler; this.exceptionHandler = exceptionHandler;
} }
public void MarkAsync()
{
IsAsync = true;
}
public void Fail(Exception exception) public void Fail(Exception exception)
{ {
exceptionHandler?.Invoke(exception); exceptionHandler?.Invoke(exception);
} }
public ExecutionContext SetValue(string key, object value) public void AddVariables(ScriptVars vars, ScriptOptions options)
{
var engine = Engine;
if (options.AsContext)
{ {
this[key] = value; var contextInstance = new ObjectInstance(engine);
foreach (var (key, value) in vars)
{
var property = key.ToCamelCase();
if (value != null)
{
contextInstance.FastAddProperty(property, JsValue.FromObject(engine, value), true, true, true);
this[property] = value;
}
}
engine.SetValue("ctx", contextInstance);
engine.SetValue("context", contextInstance);
}
else
{
foreach (var (key, value) in vars)
{
var property = key.ToCamelCase();
if (value != null)
{
engine.SetValue(property, value);
this[property] = value;
}
}
}
return this; engine.SetValue("async", true);
} }
} }
} }

21
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/DateTimeJintExtension.cs

@ -14,20 +14,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{ {
public sealed class DateTimeJintExtension : IJintExtension public sealed class DateTimeJintExtension : IJintExtension
{ {
private readonly Func<DateTime, string, JsValue> formatDate; private readonly Func<DateTime, string, JsValue> formatDate = (date, format) =>
public DateTimeJintExtension()
{
formatDate = new Func<DateTime, string, JsValue>(FormatDate);
}
public void Extend(Engine engine)
{
engine.SetValue("formatTime", formatDate);
engine.SetValue("formatDate", formatDate);
}
private static JsValue FormatDate(DateTime date, string format)
{ {
try try
{ {
@ -37,6 +24,12 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{ {
return JsValue.Undefined; return JsValue.Undefined;
} }
};
public void Extend(Engine engine)
{
engine.SetValue("formatTime", formatDate);
engine.SetValue("formatDate", formatDate);
} }
} }
} }

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

@ -45,6 +45,8 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
private async Task GetJsonAsync(ExecutionContext context, string url, Action<JsValue> callback, JsValue? headers) private async Task GetJsonAsync(ExecutionContext context, string url, Action<JsValue> callback, JsValue? headers)
{ {
context.MarkAsync();
try try
{ {
using (var httpClient = httpClientFactory.CreateClient()) using (var httpClient = httpClientFactory.CreateClient())

37
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs

@ -15,27 +15,8 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
public sealed class StringJintExtension : IJintExtension public sealed class StringJintExtension : IJintExtension
{ {
private delegate JsValue StringSlugifyDelegate(string text, bool single = false); private delegate JsValue StringSlugifyDelegate(string text, bool single = false);
private readonly StringSlugifyDelegate slugify;
private readonly Func<string, JsValue> toCamelCase;
private readonly Func<string, JsValue> toPascalCase;
public StringJintExtension() private readonly StringSlugifyDelegate slugify = (text, single) =>
{
slugify = new StringSlugifyDelegate(Slugify);
toCamelCase = new Func<string, JsValue>(ToCamelCase);
toPascalCase = new Func<string, JsValue>(ToPascalCase);
}
public void Extend(Engine engine)
{
engine.SetValue("slugify", slugify);
engine.SetValue("toCamelCase", toCamelCase);
engine.SetValue("toPascalCase", toPascalCase);
}
private static JsValue Slugify(string text, bool single = false)
{ {
try try
{ {
@ -45,9 +26,9 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{ {
return JsValue.Undefined; return JsValue.Undefined;
} }
} };
private static JsValue ToCamelCase(string text) private readonly Func<string, JsValue> toCamelCase = text =>
{ {
try try
{ {
@ -57,9 +38,9 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{ {
return JsValue.Undefined; return JsValue.Undefined;
} }
} };
private static JsValue ToPascalCase(string text) private readonly Func<string, JsValue> toPascalCase = text =>
{ {
try try
{ {
@ -69,6 +50,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{ {
return JsValue.Undefined; return JsValue.Undefined;
} }
};
public void Extend(Engine engine)
{
engine.SetValue("slugify", slugify);
engine.SetValue("toCamelCase", toCamelCase);
engine.SetValue("toPascalCase", toPascalCase);
} }
} }
} }

22
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs

@ -13,16 +13,22 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
public interface IScriptEngine public interface IScriptEngine
{ {
Task ExecuteAsync(ScriptVars vars, string script); Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default);
Task<NamedContentData> ExecuteAndTransformAsync(ScriptVars vars, string script); Task<NamedContentData> TransformAsync(ScriptVars vars, string script, ScriptOptions options = default);
Task<NamedContentData> TransformAsync(ScriptVars vars, string script); IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default);
Task<IJsonValue> GetAsync(ScriptVars vars, string script); bool Evaluate(ScriptVars vars, string script, ScriptOptions options = default)
{
bool Evaluate(ScriptVars vars, string script); try
{
string? Interpolate(ScriptVars vars, string script); return Execute(vars, script, options).Equals(JsonValue.True);
}
catch
{
return false;
}
}
} }
} }

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

@ -40,85 +40,29 @@ namespace Squidex.Domain.Apps.Core.Scripting
this.extensions = extensions?.ToArray() ?? Array.Empty<IJintExtension>(); this.extensions = extensions?.ToArray() ?? Array.Empty<IJintExtension>();
} }
public async Task ExecuteAsync(ScriptVars vars, string script) public async Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default)
{ {
Guard.NotNull(vars, nameof(vars)); Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script)); Guard.NotNullOrEmpty(script, nameof(script));
using (var cts = new CancellationTokenSource(ExecutionTimeout)) using (var cts = new CancellationTokenSource(ExecutionTimeout))
{ {
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<IJsonValue>();
using (cts.Token.Register(() => tcs.TrySetCanceled()))
{
var engine =
CreateEngine(vars, true, cts.Token, tcs.TrySetException, true)
.AddDisallow()
.AddReject();
engine.SetValue("complete", new Action<JsValue?>(value =>
{
tcs.TrySetResult(true);
}));
Execute(engine, script);
if (engine.GetValue("async") != true)
{
tcs.TrySetResult(true);
}
await tcs.Task;
}
}
}
public async Task<NamedContentData> ExecuteAndTransformAsync(ScriptVars vars, string script)
{
Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script));
using (var cts = new CancellationTokenSource(ExecutionTimeout))
{
var tcs = new TaskCompletionSource<NamedContentData>();
using (cts.Token.Register(() => tcs.TrySetCanceled())) using (cts.Token.Register(() => tcs.TrySetCanceled()))
{ {
var engine = var context = CreateEngine(vars, options, cts.Token, tcs.TrySetException, true);
CreateEngine(vars, true, cts.Token, tcs.TrySetException, true)
.AddDisallow()
.AddReject();
engine.SetValue("complete", new Action<JsValue?>(value => context.Engine.SetValue("complete", new Action<JsValue?>(value =>
{ {
tcs.TrySetResult(vars.Data!); tcs.TrySetResult(JsonMapper.Map(value));
}));
engine.SetValue("replace", new Action(() =>
{
var dataInstance = engine.GetValue("ctx").AsObject().Get("data");
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data)
{
if (!tcs.Task.IsCompleted)
{
if (data.TryUpdate(out var modified))
{
tcs.TrySetResult(modified);
}
else
{
tcs.TrySetResult(vars.Data!);
}
}
}
})); }));
Execute(engine, script); Execute(context.Engine, script);
if (engine.GetValue("async") != true) if (!context.IsAsync)
{ {
tcs.TrySetResult(vars.Data!); tcs.TrySetResult(JsonMapper.Map(context.Engine.GetCompletionValue()));
} }
return await tcs.Task; return await tcs.Task;
@ -126,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
} }
public async Task<NamedContentData> TransformAsync(ScriptVars vars, string script) public async Task<NamedContentData> TransformAsync(ScriptVars vars, string script, ScriptOptions options = default)
{ {
Guard.NotNull(vars, nameof(vars)); Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script)); Guard.NotNullOrEmpty(script, nameof(script));
@ -137,16 +81,16 @@ namespace Squidex.Domain.Apps.Core.Scripting
using (cts.Token.Register(() => tcs.TrySetCanceled())) using (cts.Token.Register(() => tcs.TrySetCanceled()))
{ {
var engine = CreateEngine(vars, true, cts.Token, tcs.TrySetException, true); var context = CreateEngine(vars, options, cts.Token, tcs.TrySetException, true);
engine.SetValue("complete", new Action<JsValue?>(value => context.Engine.SetValue("complete", new Action<JsValue?>(value =>
{ {
tcs.TrySetResult(vars.Data!); tcs.TrySetResult(vars.Data!);
})); }));
engine.SetValue("replace", new Action(() => context.Engine.SetValue("replace", new Action(() =>
{ {
var dataInstance = engine.GetValue("ctx").AsObject().Get("data"); var dataInstance = context.Engine.GetValue("ctx").AsObject().Get("data");
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data) if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data)
{ {
@ -164,9 +108,9 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
})); }));
Execute(engine, script); Execute(context.Engine, script);
if (engine.GetValue("async") != true) if (!context.IsAsync)
{ {
tcs.TrySetResult(vars.Data!); tcs.TrySetResult(vars.Data!);
} }
@ -176,82 +120,19 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
} }
public bool Evaluate(ScriptVars vars, string script) public IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default)
{
Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script));
try
{
var engine = CreateEngine(vars, false);
Execute(engine, script);
var converted = Equals(engine.GetCompletionValue().ToObject(), true);
return converted;
}
catch
{
return false;
}
}
public string? Interpolate(ScriptVars vars, string script)
{ {
Guard.NotNull(vars, nameof(vars)); Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script)); Guard.NotNullOrEmpty(script, nameof(script));
try var context = CreateEngine(vars, options);
{
var engine = CreateEngine(vars, false);
Execute(engine, script);
var converted = engine.GetCompletionValue().ToObject()?.ToString() ?? "null"; Execute(context.Engine, script);
return converted == "undefined" ? "null" : converted; return JsonMapper.Map(context.Engine.GetCompletionValue());
}
catch (Exception ex)
{
return ex.Message;
}
} }
public Task<IJsonValue> GetAsync(ScriptVars vars, string script) private ExecutionContext CreateEngine(ScriptVars vars, ScriptOptions options, CancellationToken cancellationToken = default, ExceptionHandler? exceptionHandler = null, bool async = false)
{
Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script));
using (var cts = new CancellationTokenSource(ExecutionTimeout))
{
var tcs = new TaskCompletionSource<IJsonValue>();
using (cts.Token.Register(() =>
{
tcs.TrySetCanceled();
}))
{
var engine = CreateEngine(vars, true, cts.Token, ex => tcs.TrySetException(ex), true);
engine.SetValue("complete", new Action<JsValue?>(value =>
{
tcs.TrySetResult(JsonMapper.Map(value));
}));
engine.Execute(script);
if (engine.GetValue("async") != true)
{
tcs.TrySetResult(JsonMapper.Map(engine.GetCompletionValue()));
}
}
return tcs.Task;
}
}
private Engine CreateEngine(ScriptVars vars, bool nested, CancellationToken cancellationToken = default, ExceptionHandler? exceptionHandler = null, bool async = false)
{ {
var engine = new Engine(options => var engine = new Engine(options =>
{ {
@ -261,9 +142,14 @@ namespace Squidex.Domain.Apps.Core.Scripting
options.TimeoutInterval(Timeout); options.TimeoutInterval(Timeout);
}); });
if (async) if (options.CanDisallow)
{
engine.AddDisallow();
}
if (options.CanReject)
{ {
engine.SetValue("async", false); engine.AddReject();
} }
foreach (var extension in extensions) foreach (var extension in extensions)
@ -271,16 +157,16 @@ namespace Squidex.Domain.Apps.Core.Scripting
extension.Extend(engine); extension.Extend(engine);
} }
var executionvars = new ExecutionContext(engine, cancellationToken, exceptionHandler); var context = new ExecutionContext(engine, cancellationToken, exceptionHandler);
vars.Add(executionvars, nested); context.AddVariables(vars, options);
foreach (var extension in extensions) foreach (var extension in extensions)
{ {
extension.Extend(executionvars, async); extension.Extend(context, async);
} }
return executionvars.Engine; return context;
} }
private void Execute(Engine engine, string script) private void Execute(Engine engine, string script)

32
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs

@ -13,36 +13,34 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
internal static class ScriptOperations internal static class ScriptOperations
{ {
public static Engine AddDisallow(this Engine engine) private delegate void MessageDelegate(string? message);
{
engine.SetValue("disallow", new DisallowDelegate(Disallow));
return engine;
}
private delegate void DisallowDelegate(string? message); private static readonly MessageDelegate Disallow = new MessageDelegate(message =>
private static void Disallow(string? message = null)
{ {
message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed"; message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed";
throw new DomainForbiddenException(message); throw new DomainForbiddenException(message);
} });
public static Engine AddReject(this Engine engine) private static readonly MessageDelegate Reject = new MessageDelegate(message =>
{ {
engine.SetValue("reject", new RejectDelegate(Reject)); var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null;
throw new ValidationException("Script rejected the operation.", errors);
});
public static Engine AddDisallow(this Engine engine)
{
engine.SetValue("disallow", Disallow);
return engine; return engine;
} }
private delegate void RejectDelegate(string? message); public static Engine AddReject(this Engine engine)
private static void Reject(string? message = null)
{ {
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null; engine.SetValue("reject", Reject);
throw new ValidationException("Script rejected the operation.", errors); return engine;
} }
} }
} }

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

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Scripting
{
public struct ScriptOptions
{
public bool CanReject { get; set; }
public bool CanDisallow { get; set; }
public bool AsContext { get; set; }
public override string ToString()
{
return $"CanReject={CanReject}, CanDisallow={CanDisallow}, AsContext={AsContext}";
}
}
}

56
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs

@ -9,10 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Claims; using System.Security.Claims;
using Jint.Native;
using Jint.Native.Object;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
{ {
@ -41,12 +38,6 @@ namespace Squidex.Domain.Apps.Core.Scripting
set => SetValue(value); set => SetValue(value);
} }
public NamedContentData? Data
{
get => GetValue<NamedContentData?>();
set => SetValue(value);
}
public Status Status public Status Status
{ {
get => GetValue<Status>(); get => GetValue<Status>();
@ -65,6 +56,12 @@ namespace Squidex.Domain.Apps.Core.Scripting
set => SetValue(value); set => SetValue(value);
} }
public NamedContentData? Data
{
get => GetValue<NamedContentData?>();
set => SetValue(value);
}
public NamedContentData? DataOld public NamedContentData? DataOld
{ {
get => GetValue<NamedContentData?>(); get => GetValue<NamedContentData?>();
@ -85,7 +82,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
} }
public void SetValue(object? value, [CallerMemberNameAttribute] string? key = null) public void SetValue(object? value, [CallerMemberName] string? key = null)
{ {
if (key != null) if (key != null)
{ {
@ -93,7 +90,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
} }
public T GetValue<T>([CallerMemberNameAttribute] string? key = null) public T GetValue<T>([CallerMemberName] string? key = null)
{ {
if (key != null && TryGetValue(key, out var temp) && temp is T result) if (key != null && TryGetValue(key, out var temp) && temp is T result)
{ {
@ -102,42 +99,5 @@ namespace Squidex.Domain.Apps.Core.Scripting
return default!; return default!;
} }
internal void Add(ExecutionContext context, bool nested)
{
var engine = context.Engine;
if (nested)
{
var contextInstance = new ObjectInstance(engine);
foreach (var (key, value) in this)
{
var property = key.ToCamelCase();
if (value != null)
{
contextInstance.FastAddProperty(property, JsValue.FromObject(engine, value), true, true, true);
context[property] = value;
}
}
engine.SetValue("ctx", contextInstance);
engine.SetValue("context", contextInstance);
}
else
{
foreach (var (key, value) in this)
{
var property = key.ToCamelCase();
if (value != null)
{
engine.SetValue(property, value);
context[property] = value;
}
}
}
}
} }
} }

11
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs

@ -26,6 +26,13 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ContentOperationContext public sealed class ContentOperationContext
{ {
private static readonly ScriptOptions ScriptOptions = new ScriptOptions
{
AsContext = true,
CanDisallow = true,
CanReject = true
};
private readonly IScriptEngine scriptEngine; private readonly IScriptEngine scriptEngine;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly IEnumerable<IValidatorsFactory> factories; private readonly IEnumerable<IValidatorsFactory> factories;
@ -122,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return context.Data!; return context.Data!;
} }
return await scriptEngine.ExecuteAndTransformAsync(context, actualScript); return await scriptEngine.TransformAsync(context, actualScript, ScriptOptions);
} }
public async Task ExecuteScriptAsync(Func<SchemaScripts, string> script, ScriptVars context) public async Task ExecuteScriptAsync(Func<SchemaScripts, string> script, ScriptVars context)
@ -136,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return; return;
} }
await scriptEngine.ExecuteAsync(context, GetScript(script)); await scriptEngine.ExecuteAsync(context, GetScript(script), ScriptOptions);
} }
private void Enrich(ScriptVars context) private void Enrich(ScriptVars context)

1
backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure; using Squidex.Infrastructure;

2
backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs

@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Search
var tasks = searchSources.Select(x => SearchAsync(x, query, context)); var tasks = searchSources.Select(x => SearchAsync(x, query, context));
var results = await Task.WhenAll<SearchResults>(tasks); var results = await Task.WhenAll(tasks);
return new SearchResults(results.SelectMany(x => x)); return new SearchResults(results.SelectMany(x => x));
} }

1
backend/src/Squidex.Infrastructure/Queries/Optimizer.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {

1
backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;

40
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs

@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = "Hello World" ["value"] = "Hello World"
}; };
var result = sut.Interpolate(vars, script); var result = sut.Execute(vars, script).ToString();
Assert.Equal("helloWorld", result); Assert.Equal("helloWorld", result);
} }
@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = "Hello World" ["value"] = "Hello World"
}; };
var result = sut.Interpolate(vars, script); var result = sut.Execute(vars, script).ToString();
Assert.Equal("HelloWorld", result); Assert.Equal("HelloWorld", result);
} }
@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = "4 Häuser" ["value"] = "4 Häuser"
}; };
var result = sut.Interpolate(vars, script); var result = sut.Execute(vars, script).ToString();
Assert.Equal("4-haeuser", result); Assert.Equal("4-haeuser", result);
} }
@ -107,7 +107,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = "4 Häuser" ["value"] = "4 Häuser"
}; };
var result = sut.Interpolate(vars, script); var result = sut.Execute(vars, script).ToString();
Assert.Equal("4-hauser", result); Assert.Equal("4-hauser", result);
} }
@ -119,7 +119,12 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
reject() reject()
"; ";
var ex = await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(new ScriptVars(), script)); var options = new ScriptOptions
{
CanReject = true
};
var ex = await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(new ScriptVars(), script, options));
Assert.Empty(ex.Errors); Assert.Empty(ex.Errors);
} }
@ -131,7 +136,12 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
reject('Not valid') reject('Not valid')
"; ";
var ex = await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(new ScriptVars(), script)); var options = new ScriptOptions
{
CanReject = true
};
var ex = await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(new ScriptVars(), script, options));
Assert.Equal("Not valid", ex.Errors.Single().Message); Assert.Equal("Not valid", ex.Errors.Single().Message);
} }
@ -143,7 +153,12 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
disallow() disallow()
"; ";
var ex = await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.ExecuteAsync(new ScriptVars(), script)); var options = new ScriptOptions
{
CanDisallow = true
};
var ex = await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.ExecuteAsync(new ScriptVars(), script, options));
Assert.Equal("Not allowed", ex.Message); Assert.Equal("Not allowed", ex.Message);
} }
@ -155,7 +170,12 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
disallow('Operation not allowed') disallow('Operation not allowed')
"; ";
var ex = await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.ExecuteAsync(new ScriptVars(), script)); var options = new ScriptOptions
{
CanDisallow = true
};
var ex = await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.ExecuteAsync(new ScriptVars(), script, options));
Assert.Equal("Operation not allowed", ex.Message); Assert.Equal("Operation not allowed", ex.Message);
} }
@ -173,7 +193,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
}); });
"; ";
var result = await sut.GetAsync(new ScriptVars(), script); var result = await sut.ExecuteAsync(new ScriptVars(), script);
httpHandler.ShouldBeMethod(HttpMethod.Get); httpHandler.ShouldBeMethod(HttpMethod.Get);
httpHandler.ShouldBeUrl("http://squidex.io/"); httpHandler.ShouldBeUrl("http://squidex.io/");
@ -201,7 +221,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
}, headers); }, headers);
"; ";
var result = await sut.GetAsync(new ScriptVars(), script); var result = await sut.ExecuteAsync(new ScriptVars(), script);
httpHandler.ShouldBeMethod(HttpMethod.Get); httpHandler.ShouldBeMethod(HttpMethod.Get);
httpHandler.ShouldBeUrl("http://squidex.io/"); httpHandler.ShouldBeUrl("http://squidex.io/");

61
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs

@ -24,6 +24,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
{ {
public class JintScriptEngineTests public class JintScriptEngineTests
{ {
private readonly ScriptOptions contentOptions = new ScriptOptions
{
CanReject = true,
CanDisallow = true,
AsContext = true
};
private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>(); private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>();
private readonly JintScriptEngine sut; private readonly JintScriptEngine sut;
@ -84,7 +91,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
x => x x => x
"; ";
var result = await sut.TransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Empty(result); Assert.Empty(result);
} }
@ -122,23 +129,23 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
replace(data); replace(data);
"; ";
var result = await sut.TransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_catch_javascript_error() public async Task TransformAsync_should_catch_javascript_error()
{ {
const string script = @" const string script = @"
throw 'Error'; throw 'Error';
"; ";
await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAndTransformAsync(new ScriptVars(), script)); await Assert.ThrowsAsync<ValidationException>(() => sut.TransformAsync(new ScriptVars(), script));
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_throw_when_script_failed() public async Task TransformAsync_should_throw_when_script_failed()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
var context = new ScriptVars { Data = content }; var context = new ScriptVars { Data = content };
@ -147,11 +154,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
invalid(); invalid();
"; ";
await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAndTransformAsync(context, script)); await Assert.ThrowsAsync<ValidationException>(() => sut.TransformAsync(context, script, contentOptions));
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_return_original_content_when_not_replaced() public async Task TransformAsync_should_return_original_content_when_not_replaced()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
var context = new ScriptVars { Data = content }; var context = new ScriptVars { Data = content };
@ -160,13 +167,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var x = 0; var x = 0;
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Empty(result); Assert.Empty(result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_return_original_content_when_not_replaced_async() public async Task TransformAsync_should_return_original_content_when_not_replaced_async()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
var context = new ScriptVars { Data = content }; var context = new ScriptVars { Data = content };
@ -181,13 +188,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
}); });
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Empty(result); Assert.Empty(result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_transform_object() public async Task TransformAsync_should_transform_object()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
@ -207,13 +214,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
replace(data); replace(data);
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_transform_object_async() public async Task TransformAsync_should_transform_object_async()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
@ -238,13 +245,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_ignore_transformation_when_async_not_set() public async Task TransformAsync_should_not_ignore_transformation_when_async_not_set()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
var context = new ScriptVars { Data = content, Operation = "MyOperation" }; var context = new ScriptVars { Data = content, Operation = "MyOperation" };
@ -260,13 +267,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Empty( result); Assert.NotEmpty(result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_timeout_when_replace_never_called() public async Task TransformAsync_should_timeout_when_replace_never_called()
{ {
var content = new NamedContentData(); var content = new NamedContentData();
var context = new ScriptVars { Data = content, Operation = "MyOperation" }; var context = new ScriptVars { Data = content, Operation = "MyOperation" };
@ -282,11 +289,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
"; ";
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => sut.ExecuteAndTransformAsync(context, script)); await Assert.ThrowsAnyAsync<OperationCanceledException>(() => sut.TransformAsync(context, script, contentOptions));
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_transform_content_and_return_with_execute_transform() public async Task TransformAsync_should_transform_content_and_return_with_execute_transform()
{ {
var content = var content =
new NamedContentData() new NamedContentData()
@ -318,13 +325,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
replace(data); replace(data);
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
[Fact] [Fact]
public async Task ExecuteAndTransformAsync_should_transform_content_with_old_content() public async Task TransformAsync_should_transform_content_with_old_content()
{ {
var content = var content =
new NamedContentData() new NamedContentData()
@ -357,7 +364,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
replace(ctx.data); replace(ctx.data);
"; ";
var result = await sut.ExecuteAndTransformAsync(context, script); var result = await sut.TransformAsync(context, script, contentOptions);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -374,7 +381,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = new { i = 2 } ["value"] = new { i = 2 }
}; };
var result = sut.Evaluate(context, script); var result = ((IScriptEngine)sut).Evaluate(context, script);
Assert.True(result); Assert.True(result);
} }
@ -391,7 +398,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = new { status = Status.Published } ["value"] = new { status = Status.Published }
}; };
var result = sut.Evaluate(context, script); var result = ((IScriptEngine)sut).Evaluate(context, script);
Assert.True(result); Assert.True(result);
} }
@ -408,7 +415,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = new { i = 2 } ["value"] = new { i = 2 }
}; };
var result = sut.Evaluate(context, script); var result = ((IScriptEngine)sut).Evaluate(context, script);
Assert.False(result); Assert.False(result);
} }
@ -425,7 +432,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
["value"] = new { i = 2 } ["value"] = new { i = 2 }
}; };
var result = sut.Evaluate(context, script); var result = ((IScriptEngine)sut).Evaluate(context, script);
Assert.False(result); Assert.False(result);
} }

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs

@ -32,10 +32,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
public AssetChangedTriggerHandlerTests() public AssetChangedTriggerHandlerTests()
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true); .Returns(true);
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false", default))
.Returns(false); .Returns(false);
sut = new AssetChangedTriggerHandler(scriptEngine, assetLoader); sut = new AssetChangedTriggerHandler(scriptEngine, assetLoader);
@ -149,12 +149,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
if (string.IsNullOrWhiteSpace(condition)) if (string.IsNullOrWhiteSpace(condition))
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
else else
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs

@ -33,10 +33,10 @@ namespace Squidex.Domain.Apps.Entities.Comments
public CommentTriggerHandlerTests() public CommentTriggerHandlerTests()
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true); .Returns(true);
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false", default))
.Returns(false); .Returns(false);
sut = new CommentTriggerHandler(scriptEngine, userResolver); sut = new CommentTriggerHandler(scriptEngine, userResolver);
@ -290,12 +290,12 @@ namespace Squidex.Domain.Apps.Entities.Comments
if (string.IsNullOrWhiteSpace(condition)) if (string.IsNullOrWhiteSpace(condition))
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
else else
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs

@ -41,10 +41,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentChangedTriggerHandlerTests() public ContentChangedTriggerHandlerTests()
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true); .Returns(true);
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false", default))
.Returns(false); .Returns(false);
sut = new ContentChangedTriggerHandler(scriptEngine, contentLoader); sut = new ContentChangedTriggerHandler(scriptEngine, contentLoader);
@ -251,12 +251,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (string.IsNullOrWhiteSpace(condition)) if (string.IsNullOrWhiteSpace(condition))
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, A<string>._)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, A<string>._, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
else else
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

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

@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)) A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId))
.Returns((app, schema)); .Returns((app, schema));
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(A<ScriptVars>._, A<string>._)) A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, A<string>._, ScriptOptions()))
.ReturnsLazily(x => Task.FromResult(x.GetArgument<ScriptVars>(0)!.Data!)); .ReturnsLazily(x => Task.FromResult(x.GetArgument<ScriptVars>(0)!.Data!));
patched = patch.MergeInto(data); patched = patch.MergeInto(data);
@ -137,9 +137,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(ScriptContext(data, null, Status.Draft), "<create-script>")) A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft), "<create-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>", ScriptOptions()))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -160,9 +160,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published }) CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(ScriptContext(data, null, Status.Draft), "<create-script>")) A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft), "<create-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Published), "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Published), "<change-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -192,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentUpdated { Data = otherData }) CreateContentEvent(new ContentUpdated { Data = otherData })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(ScriptContext(otherData, data, Status.Draft), "<update-script>")) A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(otherData, data, Status.Draft), "<update-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -216,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentUpdated { Data = otherData }) CreateContentEvent(new ContentUpdated { Data = otherData })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(ScriptContext(otherData, data, Status.Draft), "<update-script>")) A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(otherData, data, Status.Draft), "<update-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -233,7 +233,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Single(LastEvents); Assert.Single(LastEvents);
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(A<ScriptVars>._, "<update-script>")) A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, "<update-script>", ScriptOptions()))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -265,7 +265,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentUpdated { Data = patched }) CreateContentEvent(new ContentUpdated { Data = patched })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(ScriptContext(patched, data, Status.Draft), "<update-script>")) A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(patched, data, Status.Draft), "<update-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -289,7 +289,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentUpdated { Data = patched }) CreateContentEvent(new ContentUpdated { Data = patched })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(ScriptContext(patched, data, Status.Draft), "<update-script>")) A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(patched, data, Status.Draft), "<update-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -306,7 +306,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Single(LastEvents); Assert.Single(LastEvents);
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(A<ScriptVars>._, "<update-script>")) A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, "<update-script>", ScriptOptions()))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -328,7 +328,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published }) CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Published, Status.Draft), "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Published, Status.Draft), "<change-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -350,7 +350,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -373,7 +373,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Draft, Status.Published), "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -397,7 +397,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Change, Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Change, Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -424,7 +424,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>", ScriptOptions()))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -452,7 +452,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -480,7 +480,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentSchedulingCancelled()) CreateContentEvent(new ContentSchedulingCancelled())
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<change-script>", ScriptOptions()))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -502,7 +502,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CreateContentEvent(new ContentDeleted()) CreateContentEvent(new ContentDeleted())
); );
A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Draft), "<delete-script>")) A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Draft), "<delete-script>", ScriptOptions()))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -587,6 +587,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
return A<ScriptVars>.That.Matches(x => M(x, newData, oldData, newStatus, oldStatus)); return A<ScriptVars>.That.Matches(x => M(x, newData, oldData, newStatus, oldStatus));
} }
private ScriptOptions ScriptOptions()
{
return A<ScriptOptions>.That.Matches(x => x.CanDisallow && x.CanReject && x.AsContext);
}
private bool M(ScriptVars x, NamedContentData? newData, NamedContentData? oldData, Status newStatus, Status oldStatus) private bool M(ScriptVars x, NamedContentData? newData, NamedContentData? oldData, Status newStatus, Status oldStatus)
{ {
return return

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Counter/CounterJintExtensionTests.cs

@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter
["appId"] = appId ["appId"] = appId
}; };
var result = sut.Interpolate(context, script); var result = sut.Execute(context, script).ToString();
Assert.Equal("3", result); Assert.Equal("3", result);
} }
@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter
["appId"] = appId ["appId"] = appId
}; };
var result = sut.Interpolate(context, script); var result = sut.Execute(context, script).ToString();
Assert.Equal("3", result); Assert.Equal("3", result);
} }

9
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs

@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
await sut.EnrichAsync(ctx, new[] { content }, schemaProvider); await sut.EnrichAsync(ctx, new[] { content }, schemaProvider);
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(A<ScriptVars>._, A<string>._)) A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, A<string>._, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
await sut.EnrichAsync(ctx, new[] { content }, schemaProvider); await sut.EnrichAsync(ctx, new[] { content }, schemaProvider);
A.CallTo(() => scriptEngine.ExecuteAndTransformAsync(A<ScriptVars>._, A<string>._)) A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, A<string>._, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var content = new ContentEntity { SchemaId = schemaWithScriptId, Data = oldData }; var content = new ContentEntity { SchemaId = schemaWithScriptId, Data = oldData };
A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, "my-query")) A.CallTo(() => scriptEngine.TransformAsync(A<ScriptVars>._, "my-query", default))
.Returns(new NamedContentData()); .Returns(new NamedContentData());
await sut.EnrichAsync(ctx, new[] { content }, schemaProvider); await sut.EnrichAsync(ctx, new[] { content }, schemaProvider);
@ -105,7 +105,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
ReferenceEquals(x.User, ctx.User) && ReferenceEquals(x.User, ctx.User) &&
ReferenceEquals(x.Data, oldData) && ReferenceEquals(x.Data, oldData) &&
x.ContentId == content.Id), x.ContentId == content.Id),
"my-query")) "my-query",
A<ScriptOptions>._))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs

@ -31,10 +31,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public SchemaChangedTriggerHandlerTests() public SchemaChangedTriggerHandlerTests()
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true); .Returns(true);
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false")) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "false", default))
.Returns(false); .Returns(false);
sut = new SchemaChangedTriggerHandler(scriptEngine); sut = new SchemaChangedTriggerHandler(scriptEngine);
@ -136,12 +136,12 @@ namespace Squidex.Domain.Apps.Entities.Schemas
if (string.IsNullOrWhiteSpace(condition)) if (string.IsNullOrWhiteSpace(condition))
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
else else
{ {
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, condition, default))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs

@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Search
result.Should().BeEquivalentTo(result2); result.Should().BeEquivalentTo(result2);
A.CallTo(() => log.Log<string>(A<SemanticLogLevel>._, A<string>._, A<Exception?>._, A<LogFormatter<string>>._!)) A.CallTo(() => log.Log(A<SemanticLogLevel>._, A<string>._, A<Exception?>._, A<LogFormatter<string>>._!))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

Loading…
Cancel
Save