Browse Source

Asset tag.

pull/729/head
Sebastian 5 years ago
parent
commit
c4f999b5ca
  1. 23
      backend/src/Squidex.Domain.Apps.Entities/AppTag.cs
  2. 140
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs
  3. 85
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  4. 90
      backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs
  5. 1
      backend/src/Squidex.Web/Pipeline/AppResolver.cs
  6. 0
      backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs
  7. 219
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs
  8. 182
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs
  9. 48
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesFluidExtensionTests.cs

23
backend/src/Squidex.Domain.Apps.Entities/AppTag.cs

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Fluid.Tags;
using Microsoft.Extensions.DependencyInjection;
namespace Squidex.Domain.Apps.Entities
{
internal abstract class AppTag : ArgumentsTag
{
protected IAppProvider AppProvider { get; }
protected AppTag(IServiceProvider serviceProvider)
{
AppProvider = serviceProvider.GetRequiredService<IAppProvider>();
}
}
}

140
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs

@ -11,68 +11,52 @@ using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Fluid; using Fluid;
using Fluid.Ast; using Fluid.Ast;
using Fluid.Tags; using Fluid.Values;
using GraphQL.Utilities; using GraphQL.Utilities;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Templates; using Squidex.Domain.Apps.Core.Templates;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public sealed class AssetsFluidExtension : IFluidExtension public sealed class AssetsFluidExtension : IFluidExtension
{ {
private static readonly FluidValue ErrorNullAsset = FluidValue.Create(null);
private static readonly FluidValue ErrorNoAsset = new StringValue("NoAsset");
private static readonly FluidValue ErrorTooBig = new StringValue("ErrorTooBig");
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private sealed class AssetTag : ArgumentsTag private sealed class AssetTag : AppTag
{ {
private readonly IServiceProvider serviceProvider; private readonly IAssetQueryService assetQuery;
public AssetTag(IServiceProvider serviceProvider) public AssetTag(IServiceProvider serviceProvider)
: base(serviceProvider)
{ {
this.serviceProvider = serviceProvider; assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>();
} }
public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context, FilterArgument[] arguments) 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) if (arguments.Length == 2 && context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
{ {
var app = await GetAppAsync(enrichedEvent); var id = await arguments[1].Expression.EvaluateAsync(context);
if (app == null) var content = await ResolveAssetAsync(AppProvider, assetQuery, enrichedEvent.AppId.Id, id);
{
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>(); if (content != null)
var asset = await assetQuery.FindAsync(requestContext, DomainId.Create(id));
if (asset != null)
{ {
var name = (await arguments[0].Expression.EvaluateAsync(context)).ToStringValue(); var name = (await arguments[0].Expression.EvaluateAsync(context)).ToStringValue();
context.SetValue(name, asset); context.SetValue(name, content);
} }
} }
return Completion.Normal; 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) public AssetsFluidExtension(IServiceProvider serviceProvider)
@ -91,11 +75,109 @@ namespace Squidex.Domain.Apps.Entities.Assets
memberAccessStrategy.Register<IEntityWithLastModifiedBy>(); memberAccessStrategy.Register<IEntityWithLastModifiedBy>();
memberAccessStrategy.Register<IEntityWithVersion>(); memberAccessStrategy.Register<IEntityWithVersion>();
memberAccessStrategy.Register<IEnrichedAssetEntity>(); memberAccessStrategy.Register<IEnrichedAssetEntity>();
AddAssetFilter();
AddAssetTextFilter();
}
private void AddAssetFilter()
{
var appProvider = serviceProvider.GetRequiredService<IAppProvider>();
var assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>();
TemplateContext.GlobalFilters.AddAsyncFilter("asset", async (input, arguments, context) =>
{
if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
{
var asset = await ResolveAssetAsync(appProvider, assetQuery, enrichedEvent.AppId.Id, input);
if (asset == null)
{
return ErrorNullAsset;
}
return FluidValue.Create(asset);
}
return ErrorNullAsset;
});
}
private void AddAssetTextFilter()
{
var assetFileStore = serviceProvider.GetRequiredService<IAssetFileStore>();
TemplateContext.GlobalFilters.AddAsyncFilter("assetText", async (input, arguments, context) =>
{
if (input is not ObjectValue objectValue)
{
return ErrorNoAsset;
}
async Task<FluidValue> ResolveAssetText(DomainId appId, DomainId id, long fileSize, long fileVersion)
{
if (fileSize > 256_000)
{
return ErrorTooBig;
}
var tempStream = DefaultPools.MemoryStream.Get();
try
{
await assetFileStore!.DownloadAsync(appId, id, fileVersion, tempStream);
tempStream.Position = 0;
using (var reader = new StreamReader(tempStream, leaveOpen: true))
{
var text = reader.ReadToEnd();
return new StringValue(text);
}
}
finally
{
DefaultPools.MemoryStream.Return(tempStream);
}
}
switch (objectValue.ToObjectValue())
{
case IAssetEntity asset:
return await ResolveAssetText(asset.AppId.Id, asset.Id, asset.FileSize, asset.FileVersion);
case EnrichedAssetEvent @event:
return await ResolveAssetText(@event.AppId.Id, @event.Id, @event.FileSize, @event.FileVersion);
}
return ErrorNoAsset;
});
} }
public void RegisterLanguageExtensions(FluidParserFactory factory) public void RegisterLanguageExtensions(FluidParserFactory factory)
{ {
factory.RegisterTag("asset", new AssetTag(serviceProvider)); factory.RegisterTag("asset", new AssetTag(serviceProvider));
} }
private static async Task<IAssetEntity?> ResolveAssetAsync(IAppProvider appProvider, IAssetQueryService assetQuery, DomainId appId, FluidValue id)
{
var app = await appProvider.GetAppAsync(appId);
if (app == null)
{
return null;
}
var domainId = DomainId.Create(id.ToStringValue());
var requestContext =
Context.Admin(app).Clone(b => b
.WithoutTotal());
var asset = await assetQuery.FindAsync(requestContext, domainId);
return asset;
}
} }
} }

85
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs

@ -7,15 +7,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jint.Native; using Jint.Native;
using Jint.Runtime; using Jint.Runtime;
using Jint.Runtime.Interop;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
@ -33,6 +37,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
public void ExtendAsync(ExecutionContext context) public void ExtendAsync(ExecutionContext context)
{
AddAssetText(context);
AddAsset(context);
}
private void AddAsset(ExecutionContext context)
{ {
if (!context.TryGetValue<DomainId>(nameof(ScriptVars.AppId), out var appId)) if (!context.TryGetValue<DomainId>(nameof(ScriptVars.AppId), out var appId))
{ {
@ -50,6 +60,81 @@ namespace Squidex.Domain.Apps.Entities.Assets
context.Engine.SetValue("getAssets", action); context.Engine.SetValue("getAssets", action);
} }
private void AddAssetText(ExecutionContext context)
{
var action = new GetAssetsDelegate((references, callback) => GetAssetText(context, references, callback));
context.Engine.SetValue("getAssetText", action);
}
private void GetAssetText(ExecutionContext context, JsValue input, Action<JsValue> callback)
{
GetAssetTextCore(context, input, callback).Forget();
}
private async Task GetAssetTextCore(ExecutionContext context, JsValue input, Action<JsValue> callback)
{
Guard.NotNull(callback, nameof(callback));
if (input is not ObjectWrapper objectWrapper)
{
callback(JsValue.FromObject(context.Engine, "ErrorNoAsset"));
return;
}
async Task ResolveAssetText(DomainId appId, DomainId id, long fileSize, long fileVersion)
{
if (fileSize > 256_000)
{
callback(JsValue.FromObject(context.Engine, "ErrorTooBig"));
return;
}
context.MarkAsync();
try
{
var assetFileStore = serviceProvider.GetRequiredService<IAssetFileStore>();
var tempStream = DefaultPools.MemoryStream.Get();
try
{
await assetFileStore!.DownloadAsync(appId, id, fileVersion, tempStream, default, context.CancellationToken);
tempStream.Position = 0;
using (var reader = new StreamReader(tempStream, leaveOpen: true))
{
var text = reader.ReadToEnd();
callback(JsValue.FromObject(context.Engine, text));
}
}
finally
{
DefaultPools.MemoryStream.Return(tempStream);
}
}
catch (Exception ex)
{
context.Fail(ex);
}
}
switch (objectWrapper.Target)
{
case IAssetEntity asset:
await ResolveAssetText(asset.AppId.Id, asset.Id, asset.FileSize, asset.FileVersion);
return;
case EnrichedAssetEvent @event:
await ResolveAssetText(@event.AppId.Id, @event.Id, @event.FileSize, @event.FileVersion);
return;
}
callback(JsValue.FromObject(context.Engine, "ErrorNoAsset"));
}
private void GetAssets(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) private void GetAssets(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{ {
GetReferencesAsync(context, appId, user, references, callback).Forget(); GetReferencesAsync(context, appId, user, references, callback).Forget();

90
backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs

@ -13,11 +13,10 @@ using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Fluid; using Fluid;
using Fluid.Ast; using Fluid.Ast;
using Fluid.Tags; using Fluid.Values;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Templates; using Squidex.Domain.Apps.Core.Templates;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable CA1826 // Do not use Enumerable methods on indexable collections #pragma warning disable CA1826 // Do not use Enumerable methods on indexable collections
@ -26,43 +25,26 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ReferencesFluidExtension : IFluidExtension public sealed class ReferencesFluidExtension : IFluidExtension
{ {
private static readonly FluidValue ErrorNullReference = FluidValue.Create(null);
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private sealed class ReferenceTag : ArgumentsTag private sealed class ReferenceTag : AppTag
{ {
private readonly IServiceProvider serviceProvider; private readonly IContentQueryService contentQuery;
public ReferenceTag(IServiceProvider serviceProvider) public ReferenceTag(IServiceProvider serviceProvider)
: base(serviceProvider)
{ {
this.serviceProvider = serviceProvider; contentQuery = serviceProvider.GetRequiredService<IContentQueryService>();
} }
public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context, FilterArgument[] arguments) 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) if (arguments.Length == 2 && context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
{ {
var app = await GetAppAsync(enrichedEvent); var id = await arguments[1].Expression.EvaluateAsync(context);
if (app == null) var content = await ResolveContentAsync(AppProvider, contentQuery, enrichedEvent.AppId.Id, id);
{
return Completion.Normal;
}
var requestContext =
Context.Admin(app).Clone(b => b
.WithoutContentEnrichment()
.WithUnpublished()
.WithoutTotal());
var id = (await arguments[1].Expression.EvaluateAsync(context)).ToStringValue();
var domainId = DomainId.Create(id);
var domainIds = new List<DomainId> { domainId };
var contentQuery = serviceProvider.GetRequiredService<IContentQueryService>();
var contents = await contentQuery.QueryAsync(requestContext, Q.Empty.WithIds(domainIds));
var content = contents.FirstOrDefault();
if (content != null) if (content != null)
{ {
@ -74,13 +56,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
return Completion.Normal; return Completion.Normal;
} }
private Task<IAppEntity?> GetAppAsync(EnrichedEvent enrichedEvent)
{
var appProvider = serviceProvider.GetRequiredService<IAppProvider>();
return appProvider.GetAppAsync(enrichedEvent.AppId.Id, false);
}
} }
public ReferencesFluidExtension(IServiceProvider serviceProvider) public ReferencesFluidExtension(IServiceProvider serviceProvider)
@ -98,11 +73,60 @@ namespace Squidex.Domain.Apps.Entities.Contents
memberAccessStrategy.Register<IEntityWithLastModifiedBy>(); memberAccessStrategy.Register<IEntityWithLastModifiedBy>();
memberAccessStrategy.Register<IEntityWithVersion>(); memberAccessStrategy.Register<IEntityWithVersion>();
memberAccessStrategy.Register<IEnrichedContentEntity>(); memberAccessStrategy.Register<IEnrichedContentEntity>();
AddReferenceFilter();
}
private void AddReferenceFilter()
{
var appProvider = serviceProvider.GetRequiredService<IAppProvider>();
var contentQuery = serviceProvider.GetRequiredService<IContentQueryService>();
TemplateContext.GlobalFilters.AddAsyncFilter("reference", async (input, arguments, context) =>
{
if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
{
var content = await ResolveContentAsync(appProvider, contentQuery, enrichedEvent.AppId.Id, input);
if (content == null)
{
return ErrorNullReference;
}
return FluidValue.Create(content);
}
return ErrorNullReference;
});
} }
public void RegisterLanguageExtensions(FluidParserFactory factory) public void RegisterLanguageExtensions(FluidParserFactory factory)
{ {
factory.RegisterTag("reference", new ReferenceTag(serviceProvider)); factory.RegisterTag("reference", new ReferenceTag(serviceProvider));
} }
private static async Task<IContentEntity?> ResolveContentAsync(IAppProvider appProvider, IContentQueryService contentQuery, DomainId appId, FluidValue id)
{
var app = await appProvider.GetAppAsync(appId);
if (app == null)
{
return null;
}
var domainId = DomainId.Create(id.ToStringValue());
var domainIds = new List<DomainId> { domainId };
var requestContext =
Context.Admin(app).Clone(b => b
.WithUnpublished()
.WithoutTotal());
var contents = await contentQuery.QueryAsync(requestContext, Q.Empty.WithIds(domainIds));
var content = contents.FirstOrDefault();
return content;
}
} }
} }

1
backend/src/Squidex.Web/Pipeline/AppResolver.cs

@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;

0
backend/src/Squidex/Areas/IdentityServer/Config/TokenInitializer.cs → backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs

219
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs

@ -5,10 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.IO;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Assets;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Templates; using Squidex.Domain.Apps.Core.Templates;
@ -22,6 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public class AssetsFluidExtensionTests public class AssetsFluidExtensionTests
{ {
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly FluidTemplateEngine sut; private readonly FluidTemplateEngine sut;
@ -32,6 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(appProvider)
.AddSingleton(assetQuery) .AddSingleton(assetQuery)
.AddSingleton(assetFileStore)
.BuildServiceProvider(); .BuildServiceProvider();
var extensions = new IFluidExtension[] var extensions = new IFluidExtension[]
@ -79,7 +84,53 @@ namespace Squidex.Domain.Apps.Entities.Assets
{% asset 'ref', id %} {% asset 'ref', id %}
Text: {{ ref.fileName }} {{ ref.id }} Text: {{ ref.fileName }} {{ ref.id }}
{% endfor %} {% endfor %}
"; ";
var expected = $@"
Text: file1.jpg {assetId1}
Text: file2.jpg {assetId2}
";
var result = await sut.RenderAsync(template, vars);
Assert.Equal(Cleanup(expected), Cleanup(result));
}
[Fact]
public async Task Should_resolve_assets_in_loop_with_filter()
{
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()
.AddInvariant(JsonValue.Array(assetId1, assetId2))),
AppId = appId
};
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId1, EtagVersion.Any, A<CancellationToken>._))
.Returns(asset1);
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId2, EtagVersion.Any, A<CancellationToken>._))
.Returns(asset2);
var vars = new TemplateVars
{
["event"] = @event
};
var template = @"
{% for id in event.data.assets.iv %}
{% assign ref = id | asset %}
Text: {{ ref.fileName }} {{ ref.id }}
{% endfor %}
";
var expected = $@" var expected = $@"
Text: file1.jpg {assetId1} Text: file1.jpg {assetId1}
@ -91,9 +142,171 @@ namespace Squidex.Domain.Apps.Entities.Assets
Assert.Equal(Cleanup(expected), Cleanup(result)); Assert.Equal(Cleanup(expected), Cleanup(result));
} }
private static IEnrichedAssetEntity CreateAsset(DomainId assetId, int index) [Fact]
public async Task Should_resolve_asset_text()
{ {
return new AssetEntity { FileName = $"file{index}.jpg", Id = assetId }; var assetId = DomainId.NewGuid();
var asset = CreateAsset(assetId, 1);
var @event = new EnrichedContentEvent
{
Data =
new ContentData()
.AddField("assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId))),
AppId = appId
};
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId, EtagVersion.Any, A<CancellationToken>._))
.Returns(asset);
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, assetId, asset.FileVersion, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.Invokes(x =>
{
var stream = x.GetArgument<Stream>(3)!;
stream.Write(Encoding.UTF8.GetBytes("Hello Asset"));
});
var vars = new TemplateVars
{
["event"] = @event
};
var template = @"
{% assign ref = event.data.assets.iv[0] | asset %}
Text: {{ ref | assetText }}
";
var expected = $@"
Text: Hello Asset
";
var result = await sut.RenderAsync(template, vars);
Assert.Equal(Cleanup(expected), Cleanup(result));
}
[Fact]
public async Task Should_not_resolve_asset_text_if_too_big()
{
var assetId = DomainId.NewGuid();
var asset = CreateAsset(assetId, 1, 1_000_000);
var @event = new EnrichedContentEvent
{
Data =
new ContentData()
.AddField("assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId))),
AppId = appId
};
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId, EtagVersion.Any, A<CancellationToken>._))
.Returns(asset);
var vars = new TemplateVars
{
["event"] = @event
};
var template = @"
{% assign ref = event.data.assets.iv[0] | asset %}
Text: {{ ref | assetText }}
";
var expected = $@"
Text: ErrorTooBig
";
var result = await sut.RenderAsync(template, vars);
Assert.Equal(Cleanup(expected), Cleanup(result));
A.CallTo(() => assetFileStore.DownloadAsync(A<DomainId>._, A<DomainId>._, A<long>._, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_resolve_asset_text_from_event()
{
var @event = new EnrichedAssetEvent
{
Id = DomainId.NewGuid(),
FileVersion = 0,
FileSize = 100,
AppId = appId
};
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, @event.Id, @event.FileVersion, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.Invokes(x =>
{
var stream = x.GetArgument<Stream>(3)!;
stream.Write(Encoding.UTF8.GetBytes("Hello Asset"));
});
var vars = new TemplateVars
{
["event"] = @event
};
var template = @"
Text: {{ event | assetText }}
";
var expected = $@"
Text: Hello Asset
";
var result = await sut.RenderAsync(template, vars);
Assert.Equal(Cleanup(expected), Cleanup(result));
}
[Fact]
public async Task Should_resolve_asset_text_from_event_if_too_big()
{
var @event = new EnrichedAssetEvent
{
Id = DomainId.NewGuid(),
FileVersion = 0,
FileSize = 1_000_000,
AppId = appId
};
var vars = new TemplateVars
{
["event"] = @event
};
var template = @"
Text: {{ event | assetText }}
";
var expected = $@"
Text: ErrorTooBig
";
var result = await sut.RenderAsync(template, vars);
Assert.Equal(Cleanup(expected), Cleanup(result));
A.CallTo(() => assetFileStore.DownloadAsync(A<DomainId>._, A<DomainId>._, A<long>._, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
private IEnrichedAssetEntity CreateAsset(DomainId assetId, int index, int fileSize = 100)
{
return new AssetEntity
{
AppId = appId,
Id = assetId,
FileSize = fileSize,
FileName = $"file{index}.jpg",
};
} }
private static string Cleanup(string text) private static string Cleanup(string text)

182
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs

@ -6,14 +6,18 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.IO;
using System.Security.Claims; using System.Security.Claims;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Assets;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
@ -26,6 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture> public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
{ {
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly JintScriptEngine sut; private readonly JintScriptEngine sut;
@ -36,6 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(appProvider)
.AddSingleton(assetQuery) .AddSingleton(assetQuery)
.AddSingleton(assetFileStore)
.BuildServiceProvider(); .BuildServiceProvider();
var extensions = new IJintExtension[] var extensions = new IJintExtension[]
@ -78,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result1 = `Text: ${assets[0].fileName}`; var result1 = `Text: ${assets[0].fileName}`;
complete(`${result1}`); complete(`${result1}`);
})"; });";
var expected = @" var expected = @"
Text: file1.jpg Text: file1.jpg
@ -117,7 +123,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result2 = `Text: ${assets[1].fileName}`; var result2 = `Text: ${assets[1].fileName}`;
complete(`${result1}\n${result2}`); complete(`${result1}\n${result2}`);
})"; });";
var expected = @" var expected = @"
Text: file1.jpg Text: file1.jpg
@ -129,9 +135,177 @@ namespace Squidex.Domain.Apps.Entities.Assets
Assert.Equal(Cleanup(expected), Cleanup(result)); Assert.Equal(Cleanup(expected), Cleanup(result));
} }
private static IEnrichedAssetEntity CreateAsset(DomainId assetId, int index) [Fact]
public async Task Should_resolve_asset_text()
{
var assetId = DomainId.NewGuid();
var asset = CreateAsset(assetId, 1);
var user = new ClaimsPrincipal();
var data =
new ContentData()
.AddField("assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId)));
A.CallTo(() => assetQuery.QueryAsync(
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.User == user), null, A<Q>.That.HasIds(assetId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(2, asset));
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, assetId, asset.FileVersion, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.Invokes(x =>
{
var stream = x.GetArgument<Stream>(3)!;
stream.Write(Encoding.UTF8.GetBytes("Hello Asset"));
});
var vars = new ScriptVars { Data = data, AppId = appId.Id, User = user };
var script = @"
getAssets(data.assets.iv, function (assets) {
getAssetText(assets[0], function (text) {
var result = `Text: ${text}`;
complete(result);
});
});";
var expected = @"
Text: Hello Asset
";
var result = (await sut.ExecuteAsync(vars, script)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(result));
}
[Fact]
public async Task Should_not_resolve_asset_text_if_too_big()
{
var assetId = DomainId.NewGuid();
var asset = CreateAsset(assetId, 1, 1_000_000);
var user = new ClaimsPrincipal();
var data =
new ContentData()
.AddField("assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId)));
A.CallTo(() => assetQuery.QueryAsync(
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.User == user), null, A<Q>.That.HasIds(assetId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(2, asset));
var vars = new ScriptVars { Data = data, AppId = appId.Id, User = user };
var script = @"
getAssets(data.assets.iv, function (assets) {
getAssetText(assets[0], function (text) {
var result = `Text: ${text}`;
complete(result);
});
});";
var expected = @"
Text: ErrorTooBig
";
var result = (await sut.ExecuteAsync(vars, script)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(result));
A.CallTo(() => assetFileStore.DownloadAsync(A<DomainId>._, A<DomainId>._, A<long>._, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_resolve_asset_text_from_event()
{ {
return new AssetEntity { FileName = $"file{index}.jpg", Id = assetId }; var @event = new EnrichedAssetEvent
{
Id = DomainId.NewGuid(),
FileVersion = 0,
FileSize = 100,
AppId = appId
};
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, @event.Id, @event.FileVersion, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.Invokes(x =>
{
var stream = x.GetArgument<Stream>(3)!;
stream.Write(Encoding.UTF8.GetBytes("Hello Asset"));
});
var vars = new ScriptVars
{
["event"] = @event
};
var script = @"
getAssetText(event, function (text) {
var result = `Text: ${text}`;
complete(result);
});";
var expected = @"
Text: Hello Asset
";
var result = (await sut.ExecuteAsync(vars, script)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(result));
}
[Fact]
public async Task Should_resolve_asset_text_from_event_if_too_big()
{
var @event = new EnrichedAssetEvent
{
Id = DomainId.NewGuid(),
FileVersion = 0,
FileSize = 1_000_000,
AppId = appId
};
var vars = new ScriptVars
{
["event"] = @event
};
var script = @"
getAssetText(event, function (text) {
var result = `Text: ${text}`;
complete(result);
});";
var expected = @"
Text: ErrorTooBig
";
var result = (await sut.ExecuteAsync(vars, script)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(result));
A.CallTo(() => assetFileStore.DownloadAsync(A<DomainId>._, A<DomainId>._, A<long>._, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
private IEnrichedAssetEntity CreateAsset(DomainId assetId, int index, int fileSize = 100)
{
return new AssetEntity
{
AppId = appId,
Id = assetId,
FileSize = fileSize,
FileName = $"file{index}.jpg",
};
} }
private static string Cleanup(string text) private static string Cleanup(string text)

48
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesFluidExtensionTests.cs

@ -79,7 +79,53 @@ namespace Squidex.Domain.Apps.Entities.Contents
{% reference 'ref', id %} {% reference 'ref', id %}
Text: {{ ref.data.field1.iv }} {{ ref.data.field2.iv }} {{ ref.id }} Text: {{ ref.data.field1.iv }} {{ ref.data.field2.iv }} {{ ref.id }}
{% endfor %} {% endfor %}
"; ";
var expected = $@"
Text: Hello 1 World 1 {referenceId1}
Text: Hello 2 World 2 {referenceId2}
";
var result = await sut.RenderAsync(template, vars);
Assert.Equal(Cleanup(expected), Cleanup(result));
}
[Fact]
public async Task Should_resolve_references_in_loop_with_filter()
{
var referenceId1 = DomainId.NewGuid();
var reference1 = CreateReference(referenceId1, 1);
var referenceId2 = DomainId.NewGuid();
var reference2 = CreateReference(referenceId2, 2);
var @event = new EnrichedContentEvent
{
Data =
new ContentData()
.AddField("references",
new ContentFieldData()
.AddInvariant(JsonValue.Array(referenceId1, referenceId2))),
AppId = appId
};
A.CallTo(() => contentQuery.QueryAsync(A<Context>._, A<Q>.That.HasIds(referenceId1), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, reference1));
A.CallTo(() => contentQuery.QueryAsync(A<Context>._, A<Q>.That.HasIds(referenceId2), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, reference2));
var vars = new TemplateVars
{
["event"] = @event
};
var template = @"
{% for id in event.data.references.iv %}
{% assign ref = id | reference %}
Text: {{ ref.data.field1.iv }} {{ ref.data.field2.iv }} {{ ref.id }}
{% endfor %}
";
var expected = $@" var expected = $@"
Text: Hello 1 World 1 {referenceId1} Text: Hello 1 World 1 {referenceId1}

Loading…
Cancel
Save