mirror of https://github.com/Squidex/squidex.git
Browse Source
* Scripting extensions * Scripting improvements. * Scripting engine fixed.pull/498/head
committed by
GitHub
36 changed files with 1278 additions and 668 deletions
@ -0,0 +1,50 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint.Native; |
||||
|
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; |
||||
|
using Squidex.Domain.Apps.Core.Scripting; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Scripting |
||||
|
{ |
||||
|
public sealed class EventScriptExtension : IScriptExtension |
||||
|
{ |
||||
|
private delegate JsValue EventDelegate(); |
||||
|
private readonly IUrlGenerator urlGenerator; |
||||
|
|
||||
|
public EventScriptExtension(IUrlGenerator urlGenerator) |
||||
|
{ |
||||
|
Guard.NotNull(urlGenerator); |
||||
|
|
||||
|
this.urlGenerator = urlGenerator; |
||||
|
} |
||||
|
|
||||
|
public void Extend(ExecutionContext context, bool async) |
||||
|
{ |
||||
|
context.Engine.SetValue("contentAction", new EventDelegate(() => |
||||
|
{ |
||||
|
if (context.TryGetValue("event", out var temp) && temp is EnrichedContentEvent contentEvent) |
||||
|
{ |
||||
|
return contentEvent.Status.ToString(); |
||||
|
} |
||||
|
|
||||
|
return JsValue.Null; |
||||
|
})); |
||||
|
|
||||
|
context.Engine.SetValue("contentUrl", new EventDelegate(() => |
||||
|
{ |
||||
|
if (context.TryGetValue("event", out var temp) && temp is EnrichedContentEvent contentEvent) |
||||
|
{ |
||||
|
return urlGenerator.ContentUI(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id); |
||||
|
} |
||||
|
|
||||
|
return JsValue.Null; |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using Jint; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public delegate bool ExceptionHandler(Exception exception); |
||||
|
|
||||
|
public sealed class ExecutionContext : Dictionary<string, object> |
||||
|
{ |
||||
|
private readonly ExceptionHandler? exceptionHandler; |
||||
|
|
||||
|
public Engine Engine { get; } |
||||
|
|
||||
|
public CancellationToken CancellationToken { get; } |
||||
|
|
||||
|
internal ExecutionContext(Engine engine, CancellationToken cancellationToken, ExceptionHandler? exceptionHandler = null) |
||||
|
: base(StringComparer.OrdinalIgnoreCase) |
||||
|
{ |
||||
|
Engine = engine; |
||||
|
|
||||
|
CancellationToken = cancellationToken; |
||||
|
|
||||
|
this.exceptionHandler = exceptionHandler; |
||||
|
} |
||||
|
|
||||
|
public void Fail(Exception exception) |
||||
|
{ |
||||
|
exceptionHandler?.Invoke(exception); |
||||
|
} |
||||
|
|
||||
|
public ExecutionContext SetValue(string key, object value) |
||||
|
{ |
||||
|
this[key] = value; |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.Extensions |
||||
|
{ |
||||
|
public sealed class DateTimeScriptExtension : IScriptExtension |
||||
|
{ |
||||
|
private delegate JsValue FormatDateDelegate(DateTime date, string format); |
||||
|
|
||||
|
public void Extend(Engine engine) |
||||
|
{ |
||||
|
engine.SetValue("formatTime", new FormatDateDelegate(FormatDate)); |
||||
|
engine.SetValue("formatDate", new FormatDateDelegate(FormatDate)); |
||||
|
} |
||||
|
|
||||
|
private static JsValue FormatDate(DateTime date, string format) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return date.ToString(format, CultureInfo.InvariantCulture); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return JsValue.Undefined; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Tasks; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Json; |
||||
|
using Jint.Runtime; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.Extensions |
||||
|
{ |
||||
|
public sealed class HttpScriptExtension : IScriptExtension |
||||
|
{ |
||||
|
private delegate void GetJsonDelegate(string url, Action<JsValue> callback, JsValue? headers = null); |
||||
|
private readonly IHttpClientFactory httpClientFactory; |
||||
|
|
||||
|
public HttpScriptExtension(IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
Guard.NotNull(httpClientFactory); |
||||
|
|
||||
|
this.httpClientFactory = httpClientFactory; |
||||
|
} |
||||
|
|
||||
|
public void Extend(ExecutionContext context, bool async) |
||||
|
{ |
||||
|
if (async) |
||||
|
{ |
||||
|
var engine = context.Engine; |
||||
|
|
||||
|
engine.SetValue("getJSON", new GetJsonDelegate((url, callback, headers) => GetJson(context, url, callback, headers))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void GetJson(ExecutionContext context, string url, Action<JsValue> callback, JsValue? headers) |
||||
|
{ |
||||
|
GetJsonAsync(context, url, callback, headers).Forget(); |
||||
|
} |
||||
|
|
||||
|
private async Task GetJsonAsync(ExecutionContext context, string url, Action<JsValue> callback, JsValue? headers) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using (var httpClient = httpClientFactory.CreateClient()) |
||||
|
{ |
||||
|
var request = CreateRequest(url, headers); |
||||
|
var response = await httpClient.SendAsync(request, context.CancellationToken); |
||||
|
|
||||
|
response.EnsureSuccessStatusCode(); |
||||
|
|
||||
|
var responseObject = await ParseResponse(context, response); |
||||
|
|
||||
|
context.Engine.ResetTimeoutTicks(); |
||||
|
|
||||
|
callback(responseObject); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
context.Fail(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static HttpRequestMessage CreateRequest(string url, JsValue? headers) |
||||
|
{ |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return request; |
||||
|
} |
||||
|
|
||||
|
private async Task<JsValue> ParseResponse(ExecutionContext context, HttpResponseMessage response) |
||||
|
{ |
||||
|
var responseString = await response.Content.ReadAsStringAsync(); |
||||
|
|
||||
|
context.CancellationToken.ThrowIfCancellationRequested(); |
||||
|
|
||||
|
var jsonParser = new JsonParser(context.Engine); |
||||
|
var jsonValue = jsonParser.Parse(responseString); |
||||
|
|
||||
|
context.CancellationToken.ThrowIfCancellationRequested(); |
||||
|
|
||||
|
return jsonValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.Extensions |
||||
|
{ |
||||
|
public sealed class StringScriptExtension : IScriptExtension |
||||
|
{ |
||||
|
private delegate JsValue StringSlugifyDelegate(string text, bool single = false); |
||||
|
private delegate JsValue StringFormatDelegate(string text); |
||||
|
|
||||
|
public void Extend(Engine engine) |
||||
|
{ |
||||
|
engine.SetValue("slugify", new StringSlugifyDelegate(Slugify)); |
||||
|
|
||||
|
engine.SetValue("toCamelCase", new StringFormatDelegate(ToCamelCase)); |
||||
|
engine.SetValue("toPascalCase", new StringFormatDelegate(ToPascalCase)); |
||||
|
} |
||||
|
|
||||
|
private static JsValue Slugify(string text, bool single = false) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return text.Slugify(null, single); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return JsValue.Undefined; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static JsValue ToCamelCase(string text) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return text.ToCamelCase(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return JsValue.Undefined; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static JsValue ToPascalCase(string text) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return text.ToPascalCase(); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return JsValue.Undefined; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public interface IScriptExtension |
||||
|
{ |
||||
|
void Extend(Engine engine) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
void Extend(ExecutionContext context, bool async) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Esprima; |
||||
|
using Esprima.Ast; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.Internal |
||||
|
{ |
||||
|
internal sealed class Parser |
||||
|
{ |
||||
|
private static readonly TimeSpan Expiration = TimeSpan.FromMinutes(10); |
||||
|
private static readonly ParserOptions DefaultParserOptions = new ParserOptions |
||||
|
{ |
||||
|
AdaptRegexp = true, Tolerant = true, Loc = true |
||||
|
}; |
||||
|
|
||||
|
private readonly IMemoryCache memoryCache; |
||||
|
|
||||
|
public Parser(IMemoryCache memoryCache) |
||||
|
{ |
||||
|
Guard.NotNull(memoryCache); |
||||
|
|
||||
|
this.memoryCache = memoryCache; |
||||
|
} |
||||
|
|
||||
|
public Program Parse(string script) |
||||
|
{ |
||||
|
var key = Key(script); |
||||
|
|
||||
|
if (!memoryCache.TryGetValue<Program>(key, out var program)) |
||||
|
{ |
||||
|
var parser = new JavaScriptParser(script, DefaultParserOptions); |
||||
|
|
||||
|
program = parser.ParseProgram(); |
||||
|
|
||||
|
memoryCache.Set(key, program, Expiration); |
||||
|
} |
||||
|
|
||||
|
return program; |
||||
|
} |
||||
|
|
||||
|
private static string Key(string script) |
||||
|
{ |
||||
|
return $"SCRIPT_{script}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,96 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,98 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// 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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,53 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// 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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,231 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Squidex.Domain.Apps.Core.Scripting; |
||||
|
using Squidex.Domain.Apps.Core.Scripting.Extensions; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Squidex.Infrastructure.Validation; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Operations.Scripting |
||||
|
{ |
||||
|
public class JintScriptEngineHelperTests |
||||
|
{ |
||||
|
private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>(); |
||||
|
private readonly JintScriptEngine sut; |
||||
|
|
||||
|
public JintScriptEngineHelperTests() |
||||
|
{ |
||||
|
var extensions = new IScriptExtension[] |
||||
|
{ |
||||
|
new DateTimeScriptExtension(), |
||||
|
new HttpScriptExtension(httpClientFactory), |
||||
|
new StringScriptExtension() |
||||
|
}; |
||||
|
|
||||
|
var cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); |
||||
|
|
||||
|
sut = new JintScriptEngine(cache, extensions) |
||||
|
{ |
||||
|
Timeout = TimeSpan.FromSeconds(1) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_camel_case_value() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
return toCamelCase(value); |
||||
|
";
|
||||
|
|
||||
|
var context = new ScriptContext |
||||
|
{ |
||||
|
["value"] = "Hello World" |
||||
|
}; |
||||
|
|
||||
|
var result = sut.Interpolate(context, script); |
||||
|
|
||||
|
Assert.Equal("helloWorld", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_pascal_case_value() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
return toPascalCase(value); |
||||
|
";
|
||||
|
|
||||
|
var context = new ScriptContext |
||||
|
{ |
||||
|
["value"] = "Hello World" |
||||
|
}; |
||||
|
|
||||
|
var result = sut.Interpolate(context, script); |
||||
|
|
||||
|
Assert.Equal("HelloWorld", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_slugify_value() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
return slugify(value); |
||||
|
";
|
||||
|
|
||||
|
var context = new ScriptContext |
||||
|
{ |
||||
|
["value"] = "4 Häuser" |
||||
|
}; |
||||
|
|
||||
|
var result = sut.Interpolate(context, script); |
||||
|
|
||||
|
Assert.Equal("4-haeuser", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_slugify_value_with_single_char() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
return slugify(value, true); |
||||
|
";
|
||||
|
|
||||
|
var context = new ScriptContext |
||||
|
{ |
||||
|
["value"] = "4 Häuser" |
||||
|
}; |
||||
|
|
||||
|
var result = sut.Interpolate(context, script); |
||||
|
|
||||
|
Assert.Equal("4-hauser", result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_validation_exception_when_calling_reject() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
reject() |
||||
|
";
|
||||
|
|
||||
|
var ex = await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(new ScriptContext(), script)); |
||||
|
|
||||
|
Assert.Empty(ex.Errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_validation_exception_when_calling_reject_with_message() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
reject('Not valid') |
||||
|
";
|
||||
|
|
||||
|
var ex = await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(new ScriptContext(), script)); |
||||
|
|
||||
|
Assert.Equal("Not valid", ex.Errors.Single().Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_security_exception_when_calling_reject() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
disallow() |
||||
|
";
|
||||
|
|
||||
|
var ex = await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.ExecuteAsync(new ScriptContext(), script)); |
||||
|
|
||||
|
Assert.Equal("Not allowed", ex.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_security_exception_when_calling_reject_with_message() |
||||
|
{ |
||||
|
const string script = @"
|
||||
|
disallow('Operation not allowed') |
||||
|
";
|
||||
|
|
||||
|
var ex = await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.ExecuteAsync(new ScriptContext(), script)); |
||||
|
|
||||
|
Assert.Equal("Operation not allowed", ex.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_make_json_request() |
||||
|
{ |
||||
|
var httpHandler = SetupRequest(); |
||||
|
|
||||
|
const string script = @"
|
||||
|
async = true; |
||||
|
|
||||
|
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 httpHandler = SetupRequest(); |
||||
|
|
||||
|
const string script = @"
|
||||
|
async = true; |
||||
|
|
||||
|
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 MockupHttpHandler SetupRequest() |
||||
|
{ |
||||
|
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) |
||||
|
{ |
||||
|
Content = new StringContent("{ \"key\": 42 }") |
||||
|
}; |
||||
|
|
||||
|
var httpHandler = new MockupHttpHandler(httpResponse); |
||||
|
|
||||
|
A.CallTo(() => httpClientFactory.CreateClient(A<string>._)) |
||||
|
.Returns(new HttpClient(httpHandler)); |
||||
|
|
||||
|
return httpHandler; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Operations.Scripting |
||||
|
{ |
||||
|
internal sealed class MockupHttpHandler : 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 MockupHttpHandler(HttpResponseMessage response) |
||||
|
{ |
||||
|
this.response = response; |
||||
|
} |
||||
|
|
||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
await Task.Delay(1000); |
||||
|
|
||||
|
madeRequest = request; |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue