Browse Source

Http support.

pull/495/head
Sebastian 7 years ago
parent
commit
8824d08a9b
  1. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs
  2. 96
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintHelpers.cs
  3. 98
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintHttp.cs
  4. 206
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  5. 53
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContextExtensions.cs
  6. 48
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs
  7. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  8. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  9. 260
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs
  10. 1
      backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  11. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs
  12. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs

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

@ -7,7 +7,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting
{
@ -19,6 +21,8 @@ namespace Squidex.Domain.Apps.Core.Scripting
NamedContentData Transform(ScriptContext context, string script);
Task<IJsonValue> GetAsync(ScriptContext context, string script);
bool Evaluate(string name, object context, string script);
string? Interpolate(string name, object context, string script, Dictionary<string, Func<string>>? customFormatters = null);

96
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintHelpers.cs

@ -0,0 +1,96 @@
// ==========================================================================
// 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.Globalization;
using Jint;
using Jint.Native;
using Jint.Native.Date;
using Jint.Runtime;
using Jint.Runtime.Interop;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting
{
internal static class JintHelpers
{
public static Engine AddHelpers(this Engine engine)
{
engine.SetValue("slugify", new ClrFunctionInstance(engine, "slugify", Slugify));
engine.SetValue("formatTime", new ClrFunctionInstance(engine, "formatTime", FormatDate));
engine.SetValue("formatDate", new ClrFunctionInstance(engine, "formatDate", FormatDate));
return engine;
}
public static Engine AddFormatters(this Engine engine, Dictionary<string, Func<string>>? customFormatters = null)
{
if (customFormatters != null)
{
foreach (var (key, value) in customFormatters)
{
engine.SetValue(key, Safe(value));
}
}
engine.AddHelpers();
return engine;
}
private static Func<string> Safe(Func<string> func)
{
return () =>
{
try
{
return func();
}
catch
{
return "null";
}
};
}
private static JsValue Slugify(JsValue thisObject, JsValue[] arguments)
{
try
{
var stringInput = TypeConverter.ToString(arguments.At(0));
var single = false;
if (arguments.Length > 1)
{
single = TypeConverter.ToBoolean(arguments.At(1));
}
return stringInput.Slugify(null, single);
}
catch
{
return JsValue.Undefined;
}
}
private static JsValue FormatDate(JsValue thisObject, JsValue[] arguments)
{
try
{
var dateValue = ((DateInstance)arguments.At(0)).ToDateTime();
var dateFormat = TypeConverter.ToString(arguments.At(1));
return dateValue.ToString(dateFormat, CultureInfo.InvariantCulture);
}
catch
{
return JsValue.Undefined;
}
}
}
}

98
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintHttp.cs

@ -0,0 +1,98 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jint;
using Jint.Native;
using Jint.Native.Json;
using Jint.Runtime;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Scripting
{
internal sealed class JintHttp
{
private delegate void GetJsonDelegate(string url, Action<JsValue> callback, JsValue? headers = null);
private readonly IHttpClientFactory httpClientFactory;
private readonly Action<Exception> exceptionHandler;
private readonly CancellationToken cancellationToken;
private JsonParser parser;
public JintHttp(IHttpClientFactory httpClientFactory, CancellationToken cancellationToken, Action<Exception> exceptionHandler)
{
this.httpClientFactory = httpClientFactory;
this.exceptionHandler = exceptionHandler;
this.cancellationToken = cancellationToken;
}
public Engine Add(Engine engine)
{
parser = new JsonParser(engine);
engine.SetValue("getJSON", new GetJsonDelegate(GetJson));
return engine;
}
private void GetJson(string url, Action<JsValue> callback, JsValue? headers)
{
GetJSONAsync(url, callback, headers).Forget();
}
private async Task GetJSONAsync(string url, Action<JsValue> callback, JsValue? headers)
{
try
{
using (var httpClient = httpClientFactory.CreateClient())
{
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
throw new ArgumentException("Url must be an absolute URL");
}
var request = new HttpRequestMessage(HttpMethod.Get, uri);
if (headers != null && headers.Type == Types.Object)
{
var obj = headers.AsObject();
foreach (var (key, property) in obj.GetOwnProperties())
{
var value = TypeConverter.ToString(property.Value);
if (!string.IsNullOrWhiteSpace(key))
{
request.Headers.TryAddWithoutValidation(key, value ?? string.Empty);
}
}
}
var response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
cancellationToken.ThrowIfCancellationRequested();
var responseString = await response.Content.ReadAsStringAsync();
cancellationToken.ThrowIfCancellationRequested();
var responseJson = parser.Parse(responseString);
callback(responseJson);
}
}
catch (Exception ex)
{
exceptionHandler(ex);
}
}
}
}

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

@ -7,35 +7,44 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Esprima;
using Jint;
using Jint.Native;
using Jint.Native.Date;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Interop;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class JintScriptEngine : IScriptEngine
{
private readonly IHttpClientFactory? httpClientFactory;
public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(200);
public JintScriptEngine(IHttpClientFactory? httpClientFactory = null)
{
this.httpClientFactory = httpClientFactory;
}
public void Execute(ScriptContext context, string script)
{
Guard.NotNull(context);
if (!string.IsNullOrWhiteSpace(script))
{
var engine = CreateScriptEngine(context);
EnableDisallow(engine);
EnableReject(engine);
var engine =
CreateScriptEngine()
.AddContext(context)
.AddDisallow()
.AddReject();
Execute(engine, script);
}
@ -49,20 +58,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
if (!string.IsNullOrWhiteSpace(script))
{
var engine = CreateScriptEngine(context);
EnableDisallow(engine);
EnableReject(engine);
engine.SetValue("operation", new Action(() =>
{
var dataInstance = engine.GetValue("ctx").AsObject().Get("data");
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data)
{
data.TryUpdate(out result);
}
}));
var engine =
CreateScriptEngine()
.AddContext(context)
.AddDisallow()
.AddReject();
engine.SetValue("replace", new Action(() =>
{
@ -90,7 +90,9 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
try
{
var engine = CreateScriptEngine(context);
var engine =
CreateScriptEngine()
.AddContext(context);
engine.SetValue("replace", new Action(() =>
{
@ -133,46 +135,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
}
}
private Engine CreateScriptEngine(ScriptContext context)
{
var engine = CreateScriptEngine();
var contextInstance = new ObjectInstance(engine);
if (context.Data != null)
{
contextInstance.FastAddProperty("data", new ContentDataObject(engine, context.Data), true, true, true);
}
if (context.DataOld != null)
{
contextInstance.FastAddProperty("oldData", new ContentDataObject(engine, context.DataOld), true, true, true);
}
if (context.User != null)
{
contextInstance.FastAddProperty("user", JintUser.Create(engine, context.User), false, true, false);
}
if (!string.IsNullOrWhiteSpace(context.Operation))
{
contextInstance.FastAddProperty("operation", context.Operation, false, false, false);
}
contextInstance.FastAddProperty("status", context.Status.ToString(), false, false, false);
if (context.StatusOld != default)
{
contextInstance.FastAddProperty("oldStatus", context.StatusOld.ToString(), false, false, false);
}
engine.SetValue("ctx", contextInstance);
engine.SetValue("context", contextInstance);
return engine;
}
private Engine CreateScriptEngine(IReferenceResolver? resolver = null, Dictionary<string, Func<string>>? customFormatters = null)
private Engine CreateScriptEngine(IReferenceResolver? resolver = null)
{
var engine = new Engine(options =>
{
@ -184,91 +147,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
options.TimeoutInterval(Timeout).Strict().AddObjectConverter(DefaultConverter.Instance);
});
if (customFormatters != null)
{
foreach (var (key, value) in customFormatters)
{
engine.SetValue(key, Safe(value));
}
}
engine.SetValue("slugify", new ClrFunctionInstance(engine, "slugify", Slugify));
engine.SetValue("formatTime", new ClrFunctionInstance(engine, "formatTime", FormatDate));
engine.SetValue("formatDate", new ClrFunctionInstance(engine, "formatDate", FormatDate));
engine.AddHelpers();
return engine;
}
private static Func<string> Safe(Func<string> func)
{
return () =>
{
try
{
return func();
}
catch
{
return "null";
}
};
}
private static JsValue Slugify(JsValue thisObject, JsValue[] arguments)
{
try
{
var stringInput = TypeConverter.ToString(arguments.At(0));
var single = false;
if (arguments.Length > 1)
{
single = TypeConverter.ToBoolean(arguments.At(1));
}
return stringInput.Slugify(null, single);
}
catch
{
return JsValue.Undefined;
}
}
private static JsValue FormatDate(JsValue thisObject, JsValue[] arguments)
{
try
{
var dateValue = ((DateInstance)arguments.At(0)).ToDateTime();
var dateFormat = TypeConverter.ToString(arguments.At(1));
return dateValue.ToString(dateFormat, CultureInfo.InvariantCulture);
}
catch
{
return JsValue.Undefined;
}
}
private static void EnableDisallow(Engine engine)
{
engine.SetValue("disallow", new Action<string>(message =>
{
var exMessage = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed";
throw new DomainForbiddenException(exMessage);
}));
}
private static void EnableReject(Engine engine)
{
engine.SetValue("reject", new Action<string>(message =>
{
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null;
throw new ValidationException("Script rejected the operation.", errors);
}));
}
public bool Evaluate(string name, object context, string script)
{
try
@ -293,7 +176,8 @@ namespace Squidex.Domain.Apps.Core.Scripting
try
{
var result =
CreateScriptEngine(NullPropagation.Instance, customFormatters)
CreateScriptEngine(NullPropagation.Instance)
.AddFormatters(customFormatters)
.SetValue(name, context)
.Execute(script)
.GetCompletionValue()
@ -308,5 +192,39 @@ namespace Squidex.Domain.Apps.Core.Scripting
return ex.Message;
}
}
public Task<IJsonValue> GetAsync(ScriptContext context, string script)
{
using (var cts = new CancellationTokenSource(Timeout))
{
var tcs = new TaskCompletionSource<IJsonValue>();
using (cts.Token.Register(() =>
{
tcs.SetCanceled();
}))
{
var engine =
CreateScriptEngine()
.AddContext(context);
if (httpClientFactory != null)
{
var http = new JintHttp(httpClientFactory, cts.Token, tcs.SetException);
http.Add(engine);
}
engine.SetValue("complete", new Action<JsValue?>(value =>
{
tcs.SetResult(JsonMapper.Map(value));
}));
engine.Execute(script);
}
return tcs.Task;
}
}
}
}

53
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContextExtensions.cs

@ -0,0 +1,53 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint;
using Jint.Native.Object;
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
namespace Squidex.Domain.Apps.Core.Scripting
{
internal static class ScriptContextExtensions
{
public static Engine AddContext(this Engine engine, ScriptContext context)
{
var contextInstance = new ObjectInstance(engine);
if (context.Data != null)
{
contextInstance.FastAddProperty("data", new ContentDataObject(engine, context.Data), true, true, true);
}
if (context.DataOld != null)
{
contextInstance.FastAddProperty("oldData", new ContentDataObject(engine, context.DataOld), true, true, true);
}
if (context.User != null)
{
contextInstance.FastAddProperty("user", JintUser.Create(engine, context.User), false, true, false);
}
if (!string.IsNullOrWhiteSpace(context.Operation))
{
contextInstance.FastAddProperty("operation", context.Operation, false, false, false);
}
contextInstance.FastAddProperty("status", context.Status.ToString(), false, false, false);
if (context.StatusOld != default)
{
contextInstance.FastAddProperty("oldStatus", context.StatusOld.ToString(), false, false, false);
}
engine.SetValue("ctx", contextInstance);
engine.SetValue("context", contextInstance);
return engine;
}
}
}

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

@ -0,0 +1,48 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Core.Scripting
{
internal static class ScriptOperations
{
public static Engine AddDisallow(this Engine engine)
{
engine.SetValue("disallow", new DisallowDelegate(Disallow));
return engine;
}
private delegate void DisallowDelegate(string? message);
private static void Disallow(string? message = null)
{
message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed";
throw new DomainForbiddenException(message);
}
public static Engine AddReject(this Engine engine)
{
engine.SetValue("reject", new RejectDelegate(Reject));
return engine;
}
private delegate void RejectDelegate(string? message);
private static void Reject(string? message = null)
{
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null;
throw new ValidationException("Script rejected the operation.", errors);
}
}
}

1
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -17,6 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-1580" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.6.3" />
<PackageReference Include="NJsonSchema" Version="10.1.5" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => urlGenerator.ContentUI(appId, schemaId, contentId))
.Returns("content-url");
sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator, new JintScriptEngine());
sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator, new JintScriptEngine(null));
}
[Fact]

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

@ -6,10 +6,17 @@
// ==========================================================================
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security;
using Squidex.Infrastructure.Validation;
using Xunit;
@ -18,38 +25,93 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
{
public class JintScriptEngineTests
{
private readonly JintScriptEngine sut = new JintScriptEngine { Timeout = TimeSpan.FromSeconds(1) };
private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>();
private readonly JintScriptEngine sut;
public JintScriptEngineTests()
{
sut = new JintScriptEngine(httpClientFactory)
{
Timeout = TimeSpan.FromSeconds(1)
};
}
[Fact]
public void Should_throw_validation_exception_when_calling_reject()
{
Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), "reject()"));
Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), "reject('Not valid')"));
const string script = @"
reject()
";
var ex = Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), script));
Assert.Empty(ex.Errors);
}
[Fact]
public void Should_throw_validation_exception_when_calling_reject_with_message()
{
const string script = @"
reject('Not valid')
";
var ex = Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), script));
Assert.Equal("Not valid", ex.Errors.Single().Message);
}
[Fact]
public void Should_throw_security_exception_when_calling_reject()
{
Assert.Throws<DomainForbiddenException>(() => sut.Execute(new ScriptContext(), "disallow()"));
Assert.Throws<DomainForbiddenException>(() => sut.Execute(new ScriptContext(), "disallow('Not allowed')"));
const string script = @"
disallow()
";
var ex = Assert.Throws<DomainForbiddenException>(() => sut.Execute(new ScriptContext(), script));
Assert.Equal("Not allowed", ex.Message);
}
[Fact]
public void Should_throw_security_exception_when_calling_reject_with_message()
{
const string script = @"
disallow('Operation not allowed')
";
var ex = Assert.Throws<DomainForbiddenException>(() => sut.Execute(new ScriptContext(), script));
Assert.Equal("Operation not allowed", ex.Message);
}
[Fact]
public void Should_catch_script_syntax_errors()
{
Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), "invalid()"));
const string script = @"
invalid()
";
Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), script));
}
[Fact]
public void Should_catch_script_runtime_errors()
{
Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), "throw 'Error';"));
const string script = @"
throw 'Error';
";
Assert.Throws<ValidationException>(() => sut.Execute(new ScriptContext(), script));
}
[Fact]
public void Should_catch_script_runtime_errors_on_execute_and_transform()
{
Assert.Throws<ValidationException>(() => sut.ExecuteAndTransform(new ScriptContext(), "throw 'Error';"));
const string script = @"
throw 'Error';
";
Assert.Throws<ValidationException>(() => sut.ExecuteAndTransform(new ScriptContext(), script));
}
[Fact]
@ -58,7 +120,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var content = new NamedContentData();
var context = new ScriptContext { Data = content };
var result = sut.Transform(context, "x => x");
const string script = @"
x => x
";
var result = sut.Transform(context, script);
Assert.Same(content, result);
}
@ -69,7 +135,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var content = new NamedContentData();
var context = new ScriptContext { Data = content };
Assert.Throws<ValidationException>(() => sut.ExecuteAndTransform(context, "invalid()"));
const string script = @"
invalid();
";
Assert.Throws<ValidationException>(() => sut.ExecuteAndTransform(context, script));
}
[Fact]
@ -78,7 +148,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var content = new NamedContentData();
var context = new ScriptContext { Data = content };
var result = sut.ExecuteAndTransform(context, "var x = 0;");
const string script = @"
var x = 0;
";
var result = sut.ExecuteAndTransform(context, script);
Assert.Same(content, result);
}
@ -96,12 +170,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var context = new ScriptContext { Data = content, Operation = "MyOperation" };
var result = sut.ExecuteAndTransform(context, @"
const string script = @"
var data = ctx.data;
data.operation = { iv: ctx.operation };
replace(data)");
replace(data);
";
var result = sut.ExecuteAndTransform(context, script);
Assert.Equal(expected, result);
}
@ -128,7 +205,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var context = new ScriptContext { Data = content };
var result = sut.Transform(context, @"
const string script = @"
var data = ctx.data;
delete data.number0;
@ -136,7 +213,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
data.number1.iv = data.number1.iv + 1;
data.number2 = { 'iv': 10 };
replace(data);");
replace(data);
";
var result = sut.Transform(context, script);
Assert.Equal(expected, result);
}
@ -161,12 +241,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var context = new ScriptContext { Data = content };
var result = sut.Transform(context, @"
const string script = @"
var data = ctx.data;
data.slug = { iv: slugify(data.title.iv) };
replace(data);");
replace(data);
";
var result = sut.Transform(context, script);
Assert.Equal(expected, result);
}
@ -191,12 +274,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var context = new ScriptContext { Data = content };
var result = sut.Transform(context, @"
const string script = @"
var data = ctx.data;
data.slug = { iv: slugify(data.title.iv, true) };
replace(data);");
replace(data);
";
var result = sut.Transform(context, script);
Assert.Equal(expected, result);
}
@ -223,7 +309,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var context = new ScriptContext { Data = content };
var result = sut.ExecuteAndTransform(context, @"
const string script = @"
var data = ctx.data;
delete data.number0;
@ -231,7 +317,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
data.number1.iv = data.number1.iv + 1;
data.number2 = { 'iv': 10 };
replace(data);");
replace(data);
";
var result = sut.ExecuteAndTransform(context, script);
Assert.Equal(expected, result);
}
@ -264,10 +353,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
var context = new ScriptContext { Data = content, DataOld = oldContent, User = userPrincipal };
var result = sut.ExecuteAndTransform(context, @"
const string script = @"
ctx.data.number0.iv = ctx.data.number0.iv + ctx.oldData.number0.iv * parseInt(ctx.user.id, 10);
replace(ctx.data);");
replace(ctx.data);
";
var result = sut.ExecuteAndTransform(context, script);
Assert.Equal(expected, result);
}
@ -275,7 +367,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
[Fact]
public void Should_evaluate_to_true_when_expression_match()
{
var result = sut.Evaluate("value", new { i = 2 }, "value.i == 2");
const string script = @"
value.i == 2
";
var result = sut.Evaluate("value", new { i = 2 }, script);
Assert.True(result);
}
@ -283,7 +379,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
[Fact]
public void Should_evaluate_to_true_when_status_match()
{
var result = sut.Evaluate("value", new { status = Status.Published }, "value.status == 'Published'");
const string script = @"
value.status == 'Published'
";
var result = sut.Evaluate("value", new { status = Status.Published }, script);
Assert.True(result);
}
@ -291,7 +391,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
[Fact]
public void Should_evaluate_to_false_when_expression_match()
{
var result = sut.Evaluate("value", new { i = 2 }, "value.i == 3");
const string script = @"
value.i == 3
";
var result = sut.Evaluate("value", new { i = 2 }, script);
Assert.False(result);
}
@ -299,9 +403,111 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
[Fact]
public void Should_evaluate_to_false_when_script_is_invalid()
{
var result = sut.Evaluate("value", new { i = 2 }, "function()");
const string script = @"
function();
";
var result = sut.Evaluate("value", new { i = 2 }, script);
Assert.False(result);
}
[Fact]
public async Task Should_make_json_request()
{
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{ \"key\": 42 }")
};
var httpHandler = new MockupHander(httpResponse);
A.CallTo(() => httpClientFactory.CreateClient(A<string>._))
.Returns(new HttpClient(httpHandler));
const string script = @"
getJSON('http://squidex.io', function(result) {
complete(result);
});
";
var result = await sut.GetAsync(new ScriptContext(), script);
httpHandler.ShouldBeMethod(HttpMethod.Get);
httpHandler.ShouldBeUrl("http://squidex.io/");
var expectedResult = JsonValue.Object().Add("key", 42);
Assert.Equal(expectedResult, result);
}
[Fact]
public async Task Should_make_json_request_with_headers()
{
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{ \"key\": 42 }")
};
var httpHandler = new MockupHander(httpResponse);
A.CallTo(() => httpClientFactory.CreateClient(A<string>._))
.Returns(new HttpClient(httpHandler));
const string script = @"
var headers = {
'X-Header1': 1,
'X-Header2': '2'
};
getJSON('http://squidex.io', function(result) {
complete(result);
}, headers);
";
var result = await sut.GetAsync(new ScriptContext(), script);
httpHandler.ShouldBeMethod(HttpMethod.Get);
httpHandler.ShouldBeUrl("http://squidex.io/");
httpHandler.ShouldBeHeader("X-Header1", "1");
httpHandler.ShouldBeHeader("X-Header2", "2");
var expectedResult = JsonValue.Object().Add("key", 42);
Assert.Equal(expectedResult, result);
}
private sealed class MockupHander : HttpMessageHandler
{
private readonly HttpResponseMessage response;
private HttpRequestMessage madeRequest;
public void ShouldBeMethod(HttpMethod method)
{
Assert.Equal(method, madeRequest.Method);
}
public void ShouldBeUrl(string url)
{
Assert.Equal(url, madeRequest.RequestUri.ToString());
}
public void ShouldBeHeader(string key, string value)
{
Assert.Equal(value, madeRequest.Headers.GetValues(key).FirstOrDefault());
}
public MockupHander(HttpResponseMessage response)
{
this.response = response;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
madeRequest = request;
return Task.FromResult(response);
}
}
}
}

1
backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -14,6 +14,7 @@
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="6.0.0" />
<PackageReference Include="FluentAssertions" Version="5.10.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

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

@ -267,7 +267,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
{
var trigger = new CommentTrigger { Condition = condition };
var handler = new CommentTriggerHandler(new JintScriptEngine(), userResolver);
var handler = new CommentTriggerHandler(new JintScriptEngine(null), userResolver);
action(handler, trigger);
}

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs

@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => app.Workflows)
.Returns(workflows);
sut = new DynamicContentWorkflow(new JintScriptEngine(), appProvider);
sut = new DynamicContentWorkflow(new JintScriptEngine(null), appProvider);
}
[Fact]

Loading…
Cancel
Save