mirror of https://github.com/Squidex/squidex.git
Browse Source
* Fix surrogate keys. * Fallback handling. * Script and template extensions for references and assets. * Bugfix. * Lazy references.pull/641/head
committed by
GitHub
30 changed files with 1431 additions and 315 deletions
@ -0,0 +1,37 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public class ScriptContext : Dictionary<string, object?> |
||||
|
{ |
||||
|
public ScriptContext() |
||||
|
: base(StringComparer.OrdinalIgnoreCase) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value) |
||||
|
{ |
||||
|
Guard.NotNull(key, nameof(key)); |
||||
|
|
||||
|
value = default!; |
||||
|
|
||||
|
if (TryGetValue(key, out var temp) && temp is T typed) |
||||
|
{ |
||||
|
value = typed; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Text.Encodings.Web; |
||||
|
using System.Threading.Tasks; |
||||
|
using Fluid; |
||||
|
using Fluid.Ast; |
||||
|
using Fluid.Tags; |
||||
|
using GraphQL.Utilities; |
||||
|
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; |
||||
|
using Squidex.Domain.Apps.Core.Templates; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public sealed class AssetsFluidExtension : IFluidExtension |
||||
|
{ |
||||
|
private readonly IServiceProvider serviceProvider; |
||||
|
|
||||
|
private sealed class AssetTag : ArgumentsTag |
||||
|
{ |
||||
|
private readonly IServiceProvider serviceProvider; |
||||
|
|
||||
|
public AssetTag(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
this.serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context, FilterArgument[] arguments) |
||||
|
{ |
||||
|
if (arguments.Length == 2 && context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent) |
||||
|
{ |
||||
|
var app = await GetAppAsync(enrichedEvent); |
||||
|
|
||||
|
if (app == null) |
||||
|
{ |
||||
|
return Completion.Normal; |
||||
|
} |
||||
|
|
||||
|
var requestContext = |
||||
|
Context.Admin(app).Clone(b => b |
||||
|
.WithoutTotal()); |
||||
|
|
||||
|
var id = (await arguments[1].Expression.EvaluateAsync(context)).ToStringValue(); |
||||
|
|
||||
|
var assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>(); |
||||
|
|
||||
|
var asset = await assetQuery.FindAsync(requestContext, DomainId.Create(id)); |
||||
|
|
||||
|
if (asset != null) |
||||
|
{ |
||||
|
var name = (await arguments[0].Expression.EvaluateAsync(context)).ToStringValue(); |
||||
|
|
||||
|
context.SetValue(name, asset); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Completion.Normal; |
||||
|
} |
||||
|
|
||||
|
private Task<IAppEntity?> GetAppAsync(EnrichedEvent enrichedEvent) |
||||
|
{ |
||||
|
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); |
||||
|
|
||||
|
return appProvider.GetAppAsync(enrichedEvent.AppId.Id, false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public AssetsFluidExtension(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Guard.NotNull(serviceProvider, nameof(serviceProvider)); |
||||
|
|
||||
|
this.serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy) |
||||
|
{ |
||||
|
memberAccessStrategy.Register<IAssetEntity>(); |
||||
|
memberAccessStrategy.Register<IAssetInfo>(); |
||||
|
memberAccessStrategy.Register<IEntity>(); |
||||
|
memberAccessStrategy.Register<IEntityWithCreatedBy>(); |
||||
|
memberAccessStrategy.Register<IEntityWithLastModifiedBy>(); |
||||
|
memberAccessStrategy.Register<IEntityWithVersion>(); |
||||
|
memberAccessStrategy.Register<IEnrichedAssetEntity>(); |
||||
|
} |
||||
|
|
||||
|
public void RegisterLanguageExtensions(FluidParserFactory factory) |
||||
|
{ |
||||
|
factory.RegisterTag("asset", new AssetTag(serviceProvider)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,123 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using GraphQL.Utilities; |
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime; |
||||
|
using Squidex.Domain.Apps.Core.Scripting; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public sealed class AssetsJintExtension : IJintExtension |
||||
|
{ |
||||
|
private delegate void GetAssetsDelegate(JsValue references, Action<JsValue> callback); |
||||
|
private readonly IServiceProvider serviceProvider; |
||||
|
|
||||
|
public AssetsJintExtension(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Guard.NotNull(serviceProvider, nameof(serviceProvider)); |
||||
|
|
||||
|
this.serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public void ExtendAsync(ExecutionContext context) |
||||
|
{ |
||||
|
if (!context.TryGetValue<DomainId>(nameof(ScriptVars.AppId), out var appId)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!context.TryGetValue<ClaimsPrincipal>(nameof(ScriptVars.User), out var user)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var action = new GetAssetsDelegate((references, callback) => GetReferences(context, appId, user, references, callback)); |
||||
|
|
||||
|
context.Engine.SetValue("getAsset", action); |
||||
|
context.Engine.SetValue("getAssets", action); |
||||
|
} |
||||
|
|
||||
|
private void GetReferences(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) |
||||
|
{ |
||||
|
GetReferencesAsync(context, appId, user, references, callback).Forget(); |
||||
|
} |
||||
|
|
||||
|
private async Task GetReferencesAsync(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) |
||||
|
{ |
||||
|
Guard.NotNull(callback, nameof(callback)); |
||||
|
|
||||
|
var ids = new List<DomainId>(); |
||||
|
|
||||
|
if (references.IsString()) |
||||
|
{ |
||||
|
ids.Add(DomainId.Create(references.ToString())); |
||||
|
} |
||||
|
else if (references.IsArray()) |
||||
|
{ |
||||
|
foreach (var value in references.AsArray()) |
||||
|
{ |
||||
|
if (value.IsString()) |
||||
|
{ |
||||
|
ids.Add(DomainId.Create(value.ToString())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (ids.Count == 0) |
||||
|
{ |
||||
|
var emptyAssets = Array.Empty<IEnrichedAssetEntity>(); |
||||
|
|
||||
|
callback(JsValue.FromObject(context.Engine, emptyAssets)); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
context.MarkAsync(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var app = await GetAppAsync(appId); |
||||
|
|
||||
|
var requestContext = |
||||
|
new Context(user, app).Clone(b => b |
||||
|
.WithoutTotal()); |
||||
|
|
||||
|
var assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>(); |
||||
|
|
||||
|
var assets = await assetQuery.QueryAsync(requestContext, null, Q.Empty.WithIds(ids)); |
||||
|
|
||||
|
callback(JsValue.FromObject(context.Engine, assets.ToArray())); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
context.Fail(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<IAppEntity> GetAppAsync(DomainId appId) |
||||
|
{ |
||||
|
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); |
||||
|
|
||||
|
var app = await appProvider.GetAppAsync(appId); |
||||
|
|
||||
|
if (app == null) |
||||
|
{ |
||||
|
throw new JavaScriptException("App does not exist."); |
||||
|
} |
||||
|
|
||||
|
return app; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Squidex.Domain.Apps.Core.Scripting; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class ReferencesJintExtension : IJintExtension |
||||
|
{ |
||||
|
private delegate void GetReferencesDelegate(JsValue references, Action<JsValue> callback); |
||||
|
private readonly IServiceProvider serviceProvider; |
||||
|
|
||||
|
public ReferencesJintExtension(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Guard.NotNull(serviceProvider, nameof(serviceProvider)); |
||||
|
|
||||
|
this.serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public void ExtendAsync(ExecutionContext context) |
||||
|
{ |
||||
|
if (!context.TryGetValue<DomainId>(nameof(ScriptVars.AppId), out var appId)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!context.TryGetValue<ClaimsPrincipal>(nameof(ScriptVars.User), out var user)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var action = new GetReferencesDelegate((references, callback) => GetReferences(context, appId, user, references, callback)); |
||||
|
|
||||
|
context.Engine.SetValue("getReference", action); |
||||
|
context.Engine.SetValue("getReferences", action); |
||||
|
} |
||||
|
|
||||
|
private void GetReferences(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) |
||||
|
{ |
||||
|
GetReferencesAsync(context, appId, user, references, callback).Forget(); |
||||
|
} |
||||
|
|
||||
|
private async Task GetReferencesAsync(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) |
||||
|
{ |
||||
|
Guard.NotNull(callback, nameof(callback)); |
||||
|
|
||||
|
var ids = new List<DomainId>(); |
||||
|
|
||||
|
if (references.IsString()) |
||||
|
{ |
||||
|
ids.Add(DomainId.Create(references.ToString())); |
||||
|
} |
||||
|
else if (references.IsArray()) |
||||
|
{ |
||||
|
foreach (var value in references.AsArray()) |
||||
|
{ |
||||
|
if (value.IsString()) |
||||
|
{ |
||||
|
ids.Add(DomainId.Create(value.ToString())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (ids.Count == 0) |
||||
|
{ |
||||
|
var emptyContents = Array.Empty<IEnrichedContentEntity>(); |
||||
|
|
||||
|
callback(JsValue.FromObject(context.Engine, emptyContents)); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
context.MarkAsync(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var app = await GetAppAsync(appId); |
||||
|
|
||||
|
var requestContext = |
||||
|
new Context(user, app).Clone(b => b |
||||
|
.WithoutContentEnrichment() |
||||
|
.WithUnpublished() |
||||
|
.WithoutTotal()); |
||||
|
|
||||
|
var contentQuery = serviceProvider.GetRequiredService<IContentQueryService>(); |
||||
|
|
||||
|
var contents = await contentQuery.QueryAsync(requestContext, Q.Empty.WithIds(ids)); |
||||
|
|
||||
|
callback(JsValue.FromObject(context.Engine, contents.ToArray())); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
context.Fail(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<IAppEntity> GetAppAsync(DomainId appId) |
||||
|
{ |
||||
|
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); |
||||
|
|
||||
|
var app = await appProvider.GetAppAsync(appId); |
||||
|
|
||||
|
if (app == null) |
||||
|
{ |
||||
|
throw new JavaScriptException("App does not exist."); |
||||
|
} |
||||
|
|
||||
|
return app; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; |
||||
|
using Squidex.Domain.Apps.Core.Templates; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetsFluidExtensionTests |
||||
|
{ |
||||
|
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); |
||||
|
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); |
||||
|
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); |
||||
|
private readonly FluidTemplateEngine sut; |
||||
|
|
||||
|
public AssetsFluidExtensionTests() |
||||
|
{ |
||||
|
var services = |
||||
|
new ServiceCollection() |
||||
|
.AddSingleton(appProvider) |
||||
|
.AddSingleton(assetQuery) |
||||
|
.BuildServiceProvider(); |
||||
|
|
||||
|
var extensions = new IFluidExtension[] |
||||
|
{ |
||||
|
new AssetsFluidExtension(services) |
||||
|
}; |
||||
|
|
||||
|
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false)) |
||||
|
.Returns(Mocks.App(appId)); |
||||
|
|
||||
|
sut = new FluidTemplateEngine(extensions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_resolve_assets_in_loop() |
||||
|
{ |
||||
|
var assetId1 = DomainId.NewGuid(); |
||||
|
var asset1 = CreateAsset(assetId1, 1); |
||||
|
var assetId2 = DomainId.NewGuid(); |
||||
|
var asset2 = CreateAsset(assetId2, 2); |
||||
|
|
||||
|
var @event = new EnrichedContentEvent |
||||
|
{ |
||||
|
Data = |
||||
|
new ContentData() |
||||
|
.AddField("assets", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(assetId1, assetId2))), |
||||
|
AppId = appId |
||||
|
}; |
||||
|
|
||||
|
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId1, EtagVersion.Any)) |
||||
|
.Returns(asset1); |
||||
|
|
||||
|
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId2, EtagVersion.Any)) |
||||
|
.Returns(asset2); |
||||
|
|
||||
|
var vars = new TemplateVars |
||||
|
{ |
||||
|
["event"] = @event |
||||
|
}; |
||||
|
|
||||
|
var template = @"
|
||||
|
{% for id in event.data.assets.iv %} |
||||
|
{% asset 'ref', id %} |
||||
|
Text: {{ ref.fileName }} {{ ref.id }} |
||||
|
{% endfor %} |
||||
|
";
|
||||
|
|
||||
|
var expected = $@"
|
||||
|
Text: file1.jpg {assetId1} |
||||
|
Text: file2.jpg {assetId2} |
||||
|
";
|
||||
|
|
||||
|
var result = await sut.RenderAsync(template, vars); |
||||
|
|
||||
|
Assert.Equal(Cleanup(expected), Cleanup(result)); |
||||
|
} |
||||
|
|
||||
|
private static IEnrichedAssetEntity CreateAsset(DomainId assetId, int index) |
||||
|
{ |
||||
|
return new AssetEntity { FileName = $"file{index}.jpg", Id = assetId }; |
||||
|
} |
||||
|
|
||||
|
private static string Cleanup(string text) |
||||
|
{ |
||||
|
return text |
||||
|
.Replace("\r", string.Empty) |
||||
|
.Replace("\n", string.Empty) |
||||
|
.Replace(" ", string.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,139 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
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 Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture> |
||||
|
{ |
||||
|
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); |
||||
|
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); |
||||
|
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); |
||||
|
private readonly JintScriptEngine sut; |
||||
|
|
||||
|
public AssetsJintExtensionTests() |
||||
|
{ |
||||
|
var services = |
||||
|
new ServiceCollection() |
||||
|
.AddSingleton(appProvider) |
||||
|
.AddSingleton(assetQuery) |
||||
|
.BuildServiceProvider(); |
||||
|
|
||||
|
var extensions = new IJintExtension[] |
||||
|
{ |
||||
|
new AssetsJintExtension(services) |
||||
|
}; |
||||
|
|
||||
|
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false)) |
||||
|
.Returns(Mocks.App(appId)); |
||||
|
|
||||
|
sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), extensions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_resolve_asset() |
||||
|
{ |
||||
|
var assetId1 = DomainId.NewGuid(); |
||||
|
var asset1 = CreateAsset(assetId1, 1); |
||||
|
|
||||
|
var user = new ClaimsPrincipal(); |
||||
|
|
||||
|
var data = |
||||
|
new ContentData() |
||||
|
.AddField("assets", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(assetId1))); |
||||
|
|
||||
|
A.CallTo(() => assetQuery.QueryAsync( |
||||
|
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.User == user), null, A<Q>.That.HasIds(assetId1))) |
||||
|
.Returns(ResultList.CreateFrom(1, asset1)); |
||||
|
|
||||
|
var vars = new ScriptVars { Data = data, AppId = appId.Id, User = user }; |
||||
|
|
||||
|
var script = @"
|
||||
|
getAsset(data.assets.iv[0], function (assets) { |
||||
|
var result1 = `Text: ${assets[0].fileName}`; |
||||
|
|
||||
|
complete(`${result1}`); |
||||
|
})";
|
||||
|
|
||||
|
var expected = @"
|
||||
|
Text: file1.jpg |
||||
|
";
|
||||
|
|
||||
|
var result = (await sut.ExecuteAsync(vars, script)).ToString(); |
||||
|
|
||||
|
Assert.Equal(Cleanup(expected), Cleanup(result)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_resolve_assets() |
||||
|
{ |
||||
|
var assetId1 = DomainId.NewGuid(); |
||||
|
var asset1 = CreateAsset(assetId1, 1); |
||||
|
var assetId2 = DomainId.NewGuid(); |
||||
|
var asset2 = CreateAsset(assetId1, 2); |
||||
|
|
||||
|
var user = new ClaimsPrincipal(); |
||||
|
|
||||
|
var data = |
||||
|
new ContentData() |
||||
|
.AddField("assets", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(assetId1, assetId2))); |
||||
|
|
||||
|
A.CallTo(() => assetQuery.QueryAsync( |
||||
|
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.User == user), null, A<Q>.That.HasIds(assetId1, assetId2))) |
||||
|
.Returns(ResultList.CreateFrom(2, asset1, asset2)); |
||||
|
|
||||
|
var vars = new ScriptVars { Data = data, AppId = appId.Id, User = user }; |
||||
|
|
||||
|
var script = @"
|
||||
|
getAssets(data.assets.iv, function (assets) { |
||||
|
var result1 = `Text: ${assets[0].fileName}`; |
||||
|
var result2 = `Text: ${assets[1].fileName}`; |
||||
|
|
||||
|
complete(`${result1}\n${result2}`); |
||||
|
})";
|
||||
|
|
||||
|
var expected = @"
|
||||
|
Text: file1.jpg |
||||
|
Text: file2.jpg |
||||
|
";
|
||||
|
|
||||
|
var result = (await sut.ExecuteAsync(vars, script)).ToString(); |
||||
|
|
||||
|
Assert.Equal(Cleanup(expected), Cleanup(result)); |
||||
|
} |
||||
|
|
||||
|
private static IEnrichedAssetEntity CreateAsset(DomainId assetId, int index) |
||||
|
{ |
||||
|
return new AssetEntity { FileName = $"file{index}.jpg", Id = assetId }; |
||||
|
} |
||||
|
|
||||
|
private static string Cleanup(string text) |
||||
|
{ |
||||
|
return text |
||||
|
.Replace("\r", string.Empty) |
||||
|
.Replace("\n", string.Empty) |
||||
|
.Replace(" ", string.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,150 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
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 Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public class ReferencesJintExtensionTests : IClassFixture<TranslationsFixture> |
||||
|
{ |
||||
|
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); |
||||
|
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); |
||||
|
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); |
||||
|
private readonly JintScriptEngine sut; |
||||
|
|
||||
|
public ReferencesJintExtensionTests() |
||||
|
{ |
||||
|
var services = |
||||
|
new ServiceCollection() |
||||
|
.AddSingleton(appProvider) |
||||
|
.AddSingleton(contentQuery) |
||||
|
.BuildServiceProvider(); |
||||
|
|
||||
|
var extensions = new IJintExtension[] |
||||
|
{ |
||||
|
new ReferencesJintExtension(services) |
||||
|
}; |
||||
|
|
||||
|
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false)) |
||||
|
.Returns(Mocks.App(appId)); |
||||
|
|
||||
|
sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), extensions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_resolve_reference() |
||||
|
{ |
||||
|
var referenceId1 = DomainId.NewGuid(); |
||||
|
var reference1 = CreateReference(referenceId1, 1); |
||||
|
|
||||
|
var user = new ClaimsPrincipal(); |
||||
|
|
||||
|
var data = |
||||
|
new ContentData() |
||||
|
.AddField("references", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(referenceId1))); |
||||
|
|
||||
|
A.CallTo(() => contentQuery.QueryAsync( |
||||
|
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.User == user), A<Q>.That.HasIds(referenceId1))) |
||||
|
.Returns(ResultList.CreateFrom(1, reference1)); |
||||
|
|
||||
|
var vars = new ScriptVars { Data = data, AppId = appId.Id, User = user }; |
||||
|
|
||||
|
var script = @"
|
||||
|
getReference(data.references.iv[0], function (references) { |
||||
|
var result1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`; |
||||
|
|
||||
|
complete(`${result1}`); |
||||
|
})";
|
||||
|
|
||||
|
var expected = @"
|
||||
|
Text: Hello 1 World 1 |
||||
|
";
|
||||
|
|
||||
|
var result = (await sut.ExecuteAsync(vars, script)).ToString(); |
||||
|
|
||||
|
Assert.Equal(Cleanup(expected), Cleanup(result)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_resolve_references() |
||||
|
{ |
||||
|
var referenceId1 = DomainId.NewGuid(); |
||||
|
var reference1 = CreateReference(referenceId1, 1); |
||||
|
var referenceId2 = DomainId.NewGuid(); |
||||
|
var reference2 = CreateReference(referenceId1, 2); |
||||
|
|
||||
|
var user = new ClaimsPrincipal(); |
||||
|
|
||||
|
var data = |
||||
|
new ContentData() |
||||
|
.AddField("references", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(referenceId1, referenceId2))); |
||||
|
|
||||
|
A.CallTo(() => contentQuery.QueryAsync( |
||||
|
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.User == user), A<Q>.That.HasIds(referenceId1, referenceId2))) |
||||
|
.Returns(ResultList.CreateFrom(2, reference1, reference2)); |
||||
|
|
||||
|
var vars = new ScriptVars { Data = data, AppId = appId.Id, User = user }; |
||||
|
|
||||
|
var script = @"
|
||||
|
getReferences(data.references.iv, function (references) { |
||||
|
var result1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`; |
||||
|
var result2 = `Text: ${references[1].data.field1.iv} ${references[1].data.field2.iv}`; |
||||
|
|
||||
|
complete(`${result1}\n${result2}`); |
||||
|
})";
|
||||
|
|
||||
|
var expected = @"
|
||||
|
Text: Hello 1 World 1 |
||||
|
Text: Hello 2 World 2 |
||||
|
";
|
||||
|
|
||||
|
var result = (await sut.ExecuteAsync(vars, script)).ToString(); |
||||
|
|
||||
|
Assert.Equal(Cleanup(expected), Cleanup(result)); |
||||
|
} |
||||
|
|
||||
|
private static IEnrichedContentEntity CreateReference(DomainId referenceId, int index) |
||||
|
{ |
||||
|
return new ContentEntity |
||||
|
{ |
||||
|
Data = |
||||
|
new ContentData() |
||||
|
.AddField("field1", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Create($"Hello {index}"))) |
||||
|
.AddField("field2", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Create($"World {index}"))), |
||||
|
Id = referenceId |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static string Cleanup(string text) |
||||
|
{ |
||||
|
return text |
||||
|
.Replace("\r", string.Empty) |
||||
|
.Replace("\n", string.Empty) |
||||
|
.Replace(" ", string.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue