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