mirror of https://github.com/Squidex/squidex.git
Browse Source
* Custom indexes. * Adjust test. * New script extensions. * New script extensions.pull/1137/head
committed by
GitHub
12 changed files with 367 additions and 23 deletions
@ -0,0 +1,118 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Security.Claims; |
|||
using Jint; |
|||
using Jint.Native; |
|||
using Jint.Native.Object; |
|||
using Jint.Runtime; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Core.Scripting.Internal; |
|||
using Squidex.Domain.Apps.Entities.Properties; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents; |
|||
|
|||
public sealed class ContentsJintExtension : IJintExtension, IScriptDescriptor |
|||
{ |
|||
private delegate void GetContentsDelegate(string schema, JsValue query, Action<JsValue> callback); |
|||
private readonly IServiceProvider serviceProvider; |
|||
|
|||
public ContentsJintExtension(IServiceProvider serviceProvider) |
|||
{ |
|||
this.serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public void ExtendAsync(ScriptExecutionContext context) |
|||
{ |
|||
if (!context.TryGetValueIfExists<DomainId>("appId", out var appId)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!context.TryGetValueIfExists<ClaimsPrincipal>("user", out var user)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var getContents = new GetContentsDelegate((schemas, query, callback) => |
|||
{ |
|||
GetContents(context, appId, user, schemas, query, callback); |
|||
}); |
|||
|
|||
context.Engine.SetValue("getContents", getContents); |
|||
} |
|||
|
|||
private void GetContents(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, |
|||
string schema, JsValue query, Action<JsValue> callback) |
|||
{ |
|||
if (callback == null) |
|||
{ |
|||
throw new JavaScriptException("Callback is not defined."); |
|||
} |
|||
|
|||
context.Schedule(async (scheduler, ct) => |
|||
{ |
|||
var app = await GetAppAsync(appId); |
|||
|
|||
if (app == null) |
|||
{ |
|||
scheduler.Run(callback, new JsArray(context.Engine)); |
|||
return; |
|||
} |
|||
|
|||
var contentQuery = serviceProvider.GetRequiredService<IContentQueryService>(); |
|||
|
|||
var requestContext = |
|||
new Context(user, app).Clone(b => b |
|||
.WithFields(null) |
|||
.WithNoEnrichment() |
|||
.WithUnpublished() |
|||
.WithNoTotal()); |
|||
|
|||
var q = Q.Empty; |
|||
if (query is ObjectInstance obj) |
|||
{ |
|||
if (obj.TryGetValue("query", out var t) && t is JsString oDataQuery) |
|||
{ |
|||
q = q.WithODataQuery(oDataQuery.AsString()); |
|||
} |
|||
} |
|||
else if (query is JsString oDataQuery) |
|||
{ |
|||
q = q.WithODataQuery(oDataQuery.AsString()); |
|||
} |
|||
|
|||
var contents = await contentQuery.QueryAsync(requestContext, schema, q, ct); |
|||
|
|||
scheduler.Run(callback, JsValue.FromObject(context.Engine, contents.ToArray())); |
|||
}); |
|||
} |
|||
|
|||
private async Task<App> GetAppAsync(DomainId appId) |
|||
{ |
|||
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); |
|||
|
|||
var app = await appProvider.GetAppAsync(appId) ?? |
|||
throw new JavaScriptException("App does not exist."); |
|||
|
|||
return app; |
|||
} |
|||
|
|||
public void Describe(AddDescription describe, ScriptScope scope) |
|||
{ |
|||
if (!scope.HasFlag(ScriptScope.Async)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
describe(JsonType.Function, "getContents(schema, query, callback)", |
|||
Resources.ScriptingGetContents); |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Security.Claims; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Core.TestHelpers; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents; |
|||
|
|||
public class ContentsJintExtensionTests : GivenContext, IClassFixture<TranslationsFixture> |
|||
{ |
|||
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); |
|||
private readonly JintScriptEngine sut; |
|||
|
|||
public ContentsJintExtensionTests() |
|||
{ |
|||
var serviceProvider = |
|||
new ServiceCollection() |
|||
.AddSingleton(AppProvider) |
|||
.AddSingleton(contentQuery) |
|||
.BuildServiceProvider(); |
|||
|
|||
var extensions = new IJintExtension[] |
|||
{ |
|||
new ContentsJintExtension(serviceProvider) |
|||
}; |
|||
|
|||
sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), |
|||
Options.Create(new JintScriptOptions |
|||
{ |
|||
TimeoutScript = TimeSpan.FromSeconds(2), |
|||
TimeoutExecution = TimeSpan.FromSeconds(10) |
|||
}), |
|||
extensions); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_throw_exception_if_callback_is_null() |
|||
{ |
|||
var (vars, _) = SetupQueryVars("my-schema", "$filter=data/field/iv eq 42", 2); |
|||
|
|||
var script = @"getContents('my-schema', '$filter=data/field/iv eq 42')"; |
|||
|
|||
await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(vars, script, ct: CancellationToken)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_contents() |
|||
{ |
|||
var (vars, _) = SetupQueryVars("my-schema", "$filter=data/field/iv eq 42", 2); |
|||
|
|||
var expected = @"
|
|||
Text: Hello 1 World 1 |
|||
";
|
|||
|
|||
var script = @"
|
|||
getContents('my-schema', { query: '$filter=data/field/iv eq 42' }, function (references) { |
|||
var actual1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`; |
|||
|
|||
complete(`${actual1}`); |
|||
})";
|
|||
|
|||
var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString(); |
|||
|
|||
Assert.Equal(Cleanup(expected), Cleanup(actual)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_contents_with_string() |
|||
{ |
|||
var (vars, _) = SetupQueryVars("my-schema", "$filter=data/field/iv eq 42", 2); |
|||
|
|||
var expected = @"
|
|||
Text: Hello 1 World 1 |
|||
";
|
|||
|
|||
var script = @"
|
|||
getContents('my-schema', '$filter=data/field/iv eq 42', function (references) { |
|||
var actual1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`; |
|||
|
|||
complete(`${actual1}`); |
|||
})";
|
|||
|
|||
var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString(); |
|||
|
|||
Assert.Equal(Cleanup(expected), Cleanup(actual)); |
|||
} |
|||
|
|||
private (ScriptVars, EnrichedContent[]) SetupQueryVars(string schema, string filter, int count) |
|||
{ |
|||
var references = Enumerable.Range(0, count).Select((x, i) => CreateContent(i + 1)).ToArray(); |
|||
var referenceIds = references.Select(x => x.Id); |
|||
|
|||
var user = new ClaimsPrincipal(); |
|||
|
|||
A.CallTo(() => contentQuery.QueryAsync( |
|||
A<Context>.That.Matches(x => x.App == App && x.UserPrincipal == user), |
|||
schema, |
|||
A<Q>.That.Matches(x => x.QueryAsOdata == filter), |
|||
A<CancellationToken>._)) |
|||
.Returns(ResultList.CreateFrom(2, [CreateContent(1)])); |
|||
|
|||
var vars = new ScriptVars |
|||
{ |
|||
["appId"] = AppId.Id, |
|||
["appName"] = AppId.Name, |
|||
["user"] = user |
|||
}; |
|||
|
|||
return (vars, references); |
|||
} |
|||
|
|||
private EnrichedContent CreateContent(int index) |
|||
{ |
|||
return CreateContent() with |
|||
{ |
|||
Data = |
|||
new ContentData() |
|||
.AddField("field1", |
|||
new ContentFieldData() |
|||
.AddInvariant(JsonValue.Create($"Hello {index}"))) |
|||
.AddField("field2", |
|||
new ContentFieldData() |
|||
.AddInvariant(JsonValue.Create($"World {index}"))) |
|||
}; |
|||
} |
|||
|
|||
private static string Cleanup(string text) |
|||
{ |
|||
return text |
|||
.Replace("\r", string.Empty, StringComparison.Ordinal) |
|||
.Replace("\n", string.Empty, StringComparison.Ordinal) |
|||
.Replace(" ", string.Empty, StringComparison.Ordinal); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue