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