diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index f1a0af6bd..fbfecd219 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -357,7 +357,6 @@ public sealed class RuleService : IRuleService var actionWatch = ValueStopwatch.StartNew(); Result result; - try { var actionType = typeRegistry.GetType(actionName); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs index 9a49685e5..669efd510 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs @@ -53,7 +53,7 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor describe(JsonType.Function, "patchJSON(url, body, callback, headers?)", Resources.ScriptingPatchJson); - describe(JsonType.Function, "deleteJSON(url, body, callback, headers?)", + describe(JsonType.Function, "deleteJSON(url, callback, headers?)", Resources.ScriptingDeleteJson); } @@ -81,11 +81,6 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor { context.Schedule(async (scheduler, ct) => { - if (callback == null) - { - throw new JavaScriptException("Callback cannot be null."); - } - if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) { throw new JavaScriptException("URL is not valid."); @@ -100,7 +95,10 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor var responseObject = await ParseResponseasync(context, response, ct); - scheduler.Run(callback, responseObject); + if (callback != null) + { + scheduler.Run(callback, responseObject); + } }); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs index 74ea573c7..eac3d77f9 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs @@ -140,23 +140,29 @@ public sealed class ScriptExecutionContext : ScriptExecutionContext, ISchedul void IScheduler.Run(Action? action) { - if (IsCompleted || action == null) + lock (Engine) { - return; - } + if (IsCompleted || action == null) + { + return; + } - Engine.ResetConstraints(); - action(); + Engine.ResetConstraints(); + action(); + } } void IScheduler.Run(Action? action, TArg argument) { - if (IsCompleted || action == null) + lock (Engine) { - return; - } + if (IsCompleted || action == null) + { + return; + } - Engine.ResetConstraints(); - action(argument); + Engine.ResetConstraints(); + action(argument); + } } } diff --git a/backend/src/Squidex/Config/Domain/SerializationServices.cs b/backend/src/Squidex/Config/Domain/SerializationServices.cs index d2cb722fe..242986521 100644 --- a/backend/src/Squidex/Config/Domain/SerializationServices.cs +++ b/backend/src/Squidex/Config/Domain/SerializationServices.cs @@ -20,6 +20,7 @@ using Squidex.Domain.Apps.Core.Apps.Json; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents.Json; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Json; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; @@ -47,6 +48,7 @@ public static class SerializationServices options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); options.Converters.Add(new GeoJsonConverterFactory()); options.Converters.Add(new PolymorphicConverter(typeRegistry)); + options.Converters.Add(new PolymorphicConverter(typeRegistry)); options.Converters.Add(new PolymorphicConverter(typeRegistry)); options.Converters.Add(new PolymorphicConverter(typeRegistry)); options.Converters.Add(new PolymorphicConverter(typeRegistry)); @@ -95,6 +97,9 @@ public static class SerializationServices services.AddSingleton( new AssemblyTypeProvider("triggerType")); + services.AddSingleton( + new AssemblyTypeProvider()); + services.AddSingletonAs() .As(); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs index 5320ad325..d9ae4da6e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Security.Claims; +using Jint.Native; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Contents; @@ -39,7 +40,8 @@ public class JintScriptEngineTests : IClassFixture new DateTimeJintExtension(), new HttpJintExtension(httpClientFactory), new StringJintExtension(), - new StringWordsJintExtension() + new StringWordsJintExtension(), + new AsyncExtension(), }; var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) @@ -61,6 +63,23 @@ public class JintScriptEngineTests : IClassFixture extensions); } + private sealed class AsyncExtension : IJintExtension + { + private delegate void Delay(Action callback); + + public void ExtendAsync(ScriptExecutionContext context) + { + context.Engine.SetValue("setTimeout", new Delay(callback => + { + context.Schedule(async (scheduler, ct) => + { + await Task.Delay(5, ct); + scheduler.Run(callback); + }); + })); + } + } + [Fact] public async Task ExecuteAsync_should_catch_script_syntax_errors() { @@ -598,4 +617,27 @@ public class JintScriptEngineTests : IClassFixture Assert.Equal(JsonValue.Create(28), actual["test"]!["iv"]); } + + [Fact] + public async Task Should_not_run_callbacks_in_parallel() + { + var vars = new DataScriptVars + { + ["value"] = 13 + }; + + const string script1 = @" + var x = ctx.value; + for (var i = 0; i < 100; i++) { + setTimeout(function () { + x++; + ctx.shared = x; + }); + } + "; + + await sut.ExecuteAsync(vars, script1, new ScriptOptions { AsContext = true }); + + Assert.Equal(113.0, vars["shared"]); + } } diff --git a/tools/TestSuite/TestSuite.ApiTests/RuleRunnerTests.cs b/tools/TestSuite/TestSuite.ApiTests/RuleRunnerTests.cs index 24aa152da..dbe23417f 100644 --- a/tools/TestSuite/TestSuite.ApiTests/RuleRunnerTests.cs +++ b/tools/TestSuite/TestSuite.ApiTests/RuleRunnerTests.cs @@ -83,6 +83,53 @@ public class RuleRunnerTests : IClassFixture, IClassFixture x.Method == "POST" && x.Content.Contains(schemaName, StringComparison.OrdinalIgnoreCase)); + + Assert.NotNull(request); + + + // STEP 4: Get events + var eventsAll = await _.Rules.GetEventsAsync(appName, rule.Id); + var eventsRule = await _.Rules.GetEventsAsync(appName); + + Assert.Single(eventsAll.Items); + Assert.Single(eventsRule.Items); + } + [Fact] public async Task Should_run_rules_on_asset_change() {