Browse Source

Feature/asset enrichment (#423)

* Enrich the content with links to their assets.
pull/426/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
fca8bb8bcf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  3. 15
      src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  4. 10
      src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  5. 33
      src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs
  6. 4
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs
  7. 4
      src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs
  8. 37
      src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs
  9. 10
      src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs
  10. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs
  11. 156
      src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  12. 2
      src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs
  13. 2
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  14. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs
  15. 20
      src/Squidex/app/features/content/shared/content-value.component.ts
  16. 1
      src/Squidex/app/features/schemas/declarations.ts
  17. 2
      src/Squidex/app/features/schemas/module.ts
  18. 3
      src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.ts
  19. 2
      src/Squidex/app/features/schemas/pages/schema/forms/field-form.component.ts
  20. 17
      src/Squidex/app/features/schemas/pages/schema/types/assets-ui.component.html
  21. 2
      src/Squidex/app/features/schemas/pages/schema/types/assets-ui.component.scss
  22. 32
      src/Squidex/app/features/schemas/pages/schema/types/assets-ui.component.ts
  23. 8
      src/Squidex/app/shared/components/assets-selector.component.scss
  24. 2
      src/Squidex/app/shared/services/contents.service.ts
  25. 1
      src/Squidex/app/shared/services/schemas.types.ts
  26. 43
      src/Squidex/app/shared/state/contents.forms.spec.ts
  27. 27
      src/Squidex/app/shared/state/contents.forms.ts
  28. 20
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs
  29. 25
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs
  30. 12
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs
  31. 232
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherAssetsTests.cs
  32. 115
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherReferencesTests.cs
  33. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
  34. 8
      tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs

1
extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs

@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Extensions.Actions.Algolia namespace Squidex.Extensions.Actions.Algolia
{ {
[RuleAction( [RuleAction(
Title = "Algolia",
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M16 .842C7.633.842.842 7.625.842 16S7.625 31.158 16 31.158c8.374 0 15.158-6.791 15.158-15.166S24.375.842 16 .842zm0 25.83c-5.898 0-10.68-4.781-10.68-10.68S10.101 5.313 16 5.313s10.68 4.781 10.68 10.679-4.781 10.68-10.68 10.68zm0-19.156v7.956c0 .233.249.388.458.279l7.055-3.663a.312.312 0 0 0 .124-.434 8.807 8.807 0 0 0-7.319-4.447z'/></svg>", IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M16 .842C7.633.842.842 7.625.842 16S7.625 31.158 16 31.158c8.374 0 15.158-6.791 15.158-15.166S24.375.842 16 .842zm0 25.83c-5.898 0-10.68-4.781-10.68-10.68S10.101 5.313 16 5.313s10.68 4.781 10.68 10.679-4.781 10.68-10.68 10.68zm0-19.156v7.956c0 .233.249.388.458.279l7.055-3.663a.312.312 0 0 0 .124-.434 8.807 8.807 0 0 0-7.319-4.447z'/></svg>",
IconColor = "#0d9bf9", IconColor = "#0d9bf9",
Display = "Populate Algolia index", Display = "Populate Algolia index",

2
src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs

@ -35,6 +35,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public bool AllowDuplicates { get; set; } public bool AllowDuplicates { get; set; }
public bool ResolveImage { get; set; }
public ReadOnlyCollection<string> AllowedExtensions { get; set; } public ReadOnlyCollection<string> AllowedExtensions { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)

15
src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -89,7 +89,20 @@ namespace Squidex.Domain.Apps.Core.Schemas
.Where(x => .Where(x =>
x.Properties.ResolveReference && x.Properties.ResolveReference &&
x.Properties.MaxItems == 1 && x.Properties.MaxItems == 1 &&
(x.Properties.IsListField || schema.Fields.Count == 1)); x.IsListField(schema));
}
public static IEnumerable<IField<AssetsFieldProperties>> ResolvingAssets(this Schema schema)
{
return schema.Fields.OfType<IField<AssetsFieldProperties>>()
.Where(x =>
x.Properties.ResolveImage &&
x.IsListField(schema));
}
private static bool IsListField(this IField field, Schema schema)
{
return field.RawProperties.IsListField || schema.Fields.Count == 1;
} }
} }
} }

10
src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -24,6 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
private readonly IAssetEnricher assetEnricher; private readonly IAssetEnricher assetEnricher;
private readonly IAssetQueryService assetQuery; private readonly IAssetQueryService assetQuery;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator; private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
private readonly IContextProvider contextProvider;
private readonly IEnumerable<ITagGenerator<CreateAsset>> tagGenerators; private readonly IEnumerable<ITagGenerator<CreateAsset>> tagGenerators;
public AssetCommandMiddleware( public AssetCommandMiddleware(
@ -32,6 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
IAssetQueryService assetQuery, IAssetQueryService assetQuery,
IAssetStore assetStore, IAssetStore assetStore,
IAssetThumbnailGenerator assetThumbnailGenerator, IAssetThumbnailGenerator assetThumbnailGenerator,
IContextProvider contextProvider,
IEnumerable<ITagGenerator<CreateAsset>> tagGenerators) IEnumerable<ITagGenerator<CreateAsset>> tagGenerators)
: base(grainFactory) : base(grainFactory)
{ {
@ -39,12 +41,14 @@ namespace Squidex.Domain.Apps.Entities.Assets
Guard.NotNull(assetStore, nameof(assetStore)); Guard.NotNull(assetStore, nameof(assetStore));
Guard.NotNull(assetQuery, nameof(assetQuery)); Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator));
Guard.NotNull(contextProvider, nameof(contextProvider));
Guard.NotNull(tagGenerators, nameof(tagGenerators)); Guard.NotNull(tagGenerators, nameof(tagGenerators));
this.assetStore = assetStore; this.assetStore = assetStore;
this.assetEnricher = assetEnricher; this.assetEnricher = assetEnricher;
this.assetQuery = assetQuery; this.assetQuery = assetQuery;
this.assetThumbnailGenerator = assetThumbnailGenerator; this.assetThumbnailGenerator = assetThumbnailGenerator;
this.contextProvider = contextProvider;
this.tagGenerators = tagGenerators; this.tagGenerators = tagGenerators;
} }
@ -61,7 +65,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
try try
{ {
var existings = await assetQuery.QueryByHashAsync(createAsset.AppId.Id, createAsset.FileHash); var ctx = contextProvider.Context.Clone().WithNoAssetEnrichment();
var existings = await assetQuery.QueryByHashAsync(ctx, createAsset.AppId.Id, createAsset.FileHash);
foreach (var existing in existings) foreach (var existing in existings)
{ {
@ -127,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
if (context.PlainResult is IAssetEntity asset && !(context.PlainResult is IEnrichedAssetEntity)) if (context.PlainResult is IAssetEntity asset && !(context.PlainResult is IEnrichedAssetEntity))
{ {
var enriched = await assetEnricher.EnrichAsync(asset); var enriched = await assetEnricher.EnrichAsync(asset, contextProvider.Context);
context.Complete(enriched); context.Complete(enriched);
} }

33
src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Assets
{
public static class AssetExtensions
{
private const string HeaderNoEnrichment = "X-NoAssetEnrichment";
public static bool IsNoAssetEnrichment(this Context context)
{
return context.Headers.ContainsKey(HeaderNoEnrichment);
}
public static Context WithNoAssetEnrichment(this Context context, bool value = true)
{
if (value)
{
context.Headers[HeaderNoEnrichment] = "1";
}
else
{
context.Headers.Remove(HeaderNoEnrichment);
}
return context;
}
}
}

4
src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs

@ -12,8 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetEnricher public interface IAssetEnricher
{ {
Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset); Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset, Context context);
Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets); Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets, Context context);
} }
} }

4
src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs

@ -14,10 +14,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetQueryService public interface IAssetQueryService
{ {
Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Guid appId, string hash); Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Context context, Guid appId, string hash);
Task<IResultList<IEnrichedAssetEntity>> QueryAsync(Context context, Q query); Task<IResultList<IEnrichedAssetEntity>> QueryAsync(Context context, Q query);
Task<IEnrichedAssetEntity> FindAssetAsync(Guid id); Task<IEnrichedAssetEntity> FindAssetAsync(Context context, Guid id);
} }
} }

37
src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs

@ -26,32 +26,43 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
this.tagService = tagService; this.tagService = tagService;
} }
public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset) public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset, Context context)
{ {
Guard.NotNull(asset, nameof(asset)); Guard.NotNull(asset, nameof(asset));
Guard.NotNull(context, nameof(context));
var enriched = await EnrichAsync(Enumerable.Repeat(asset, 1)); var enriched = await EnrichAsync(Enumerable.Repeat(asset, 1), context);
return enriched[0]; return enriched[0];
} }
public async Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets) public async Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets, Context context)
{ {
Guard.NotNull(assets, nameof(assets)); Guard.NotNull(assets, nameof(assets));
Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<AssetEnricher>()) using (Profiler.TraceMethod<AssetEnricher>())
{ {
var results = new List<IEnrichedAssetEntity>(); var results = assets.Select(x => SimpleMapper.Map(x, new AssetEntity())).ToList();
if (ShouldEnrich(context))
{
await EnrichTagsAsync(results);
}
return results;
}
}
private async Task EnrichTagsAsync(List<AssetEntity> assets)
{
foreach (var group in assets.GroupBy(x => x.AppId.Id)) foreach (var group in assets.GroupBy(x => x.AppId.Id))
{ {
var tagsById = await CalculateTags(group); var tagsById = await CalculateTags(group);
foreach (var asset in group) foreach (var asset in group)
{ {
var result = SimpleMapper.Map(asset, new AssetEntity()); asset.TagNames = new HashSet<string>();
result.TagNames = new HashSet<string>();
if (asset.Tags != null) if (asset.Tags != null)
{ {
@ -59,16 +70,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
{ {
if (tagsById.TryGetValue(id, out var name)) if (tagsById.TryGetValue(id, out var name))
{ {
result.TagNames.Add(name); asset.TagNames.Add(name);
}
} }
} }
results.Add(result);
} }
} }
return results;
} }
} }
@ -78,5 +84,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
return await tagService.DenormalizeTagsAsync(group.Key, TagGroups.Assets, uniqueIds); return await tagService.DenormalizeTagsAsync(group.Key, TagGroups.Assets, uniqueIds);
} }
private static bool ShouldEnrich(Context context)
{
return !context.IsNoAssetEnrichment();
}
} }
} }

10
src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs

@ -33,25 +33,25 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
this.queryParser = queryParser; this.queryParser = queryParser;
} }
public async Task<IEnrichedAssetEntity> FindAssetAsync( Guid id) public async Task<IEnrichedAssetEntity> FindAssetAsync(Context context, Guid id)
{ {
var asset = await assetRepository.FindAssetAsync(id); var asset = await assetRepository.FindAssetAsync(id);
if (asset != null) if (asset != null)
{ {
return await assetEnricher.EnrichAsync(asset); return await assetEnricher.EnrichAsync(asset, context);
} }
return null; return null;
} }
public async Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Guid appId, string hash) public async Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Context context, Guid appId, string hash)
{ {
Guard.NotNull(hash, nameof(hash)); Guard.NotNull(hash, nameof(hash));
var assets = await assetRepository.QueryByHashAsync(appId, hash); var assets = await assetRepository.QueryByHashAsync(appId, hash);
return await assetEnricher.EnrichAsync(assets); return await assetEnricher.EnrichAsync(assets, context);
} }
public async Task<IResultList<IEnrichedAssetEntity>> QueryAsync(Context context, Q query) public async Task<IResultList<IEnrichedAssetEntity>> QueryAsync(Context context, Q query)
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
assets = await QueryByQueryAsync(context, query); assets = await QueryByQueryAsync(context, query);
} }
var enriched = await assetEnricher.EnrichAsync(assets); var enriched = await assetEnricher.EnrichAsync(assets, context);
return ResultList.Create(assets.Total, enriched); return ResultList.Create(assets.Total, enriched);
} }

2
src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs

@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return context.Headers.ContainsKey(HeaderNoEnrichment); return context.Headers.ContainsKey(HeaderNoEnrichment);
} }
public static Context WithNoEnrichment(this Context context, bool value = true) public static Context WithNoContentEnrichment(this Context context, bool value = true)
{ {
if (value) if (value)
{ {

156
src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -10,8 +10,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -23,7 +25,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public sealed class ContentEnricher : IContentEnricher public sealed class ContentEnricher : IContentEnricher
{ {
private const string DefaultColor = StatusColors.Draft; private const string DefaultColor = StatusColors.Draft;
private static readonly ILookup<Guid, IEnrichedContentEntity> EmptyReferences = Enumerable.Empty<IEnrichedContentEntity>().ToLookup(x => x.Id); private static readonly ILookup<Guid, IEnrichedContentEntity> EmptyContents = Enumerable.Empty<IEnrichedContentEntity>().ToLookup(x => x.Id);
private static readonly ILookup<Guid, IEnrichedAssetEntity> EmptyAssets = Enumerable.Empty<IEnrichedAssetEntity>().ToLookup(x => x.Id);
private readonly IAssetQueryService assetQuery;
private readonly IAssetUrlGenerator assetUrlGenerator;
private readonly Lazy<IContentQueryService> contentQuery; private readonly Lazy<IContentQueryService> contentQuery;
private readonly IContentWorkflow contentWorkflow; private readonly IContentWorkflow contentWorkflow;
@ -32,11 +37,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
get { return contentQuery.Value; } get { return contentQuery.Value; }
} }
public ContentEnricher(Lazy<IContentQueryService> contentQuery, IContentWorkflow contentWorkflow) public ContentEnricher(IAssetQueryService assetQuery, IAssetUrlGenerator assetUrlGenerator, Lazy<IContentQueryService> contentQuery, IContentWorkflow contentWorkflow)
{ {
Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(assetUrlGenerator, nameof(assetUrlGenerator));
Guard.NotNull(contentQuery, nameof(contentQuery)); Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(contentWorkflow, nameof(contentWorkflow)); Guard.NotNull(contentWorkflow, nameof(contentWorkflow));
this.assetQuery = assetQuery;
this.assetUrlGenerator = assetUrlGenerator;
this.contentQuery = contentQuery; this.contentQuery = contentQuery;
this.contentWorkflow = contentWorkflow; this.contentWorkflow = contentWorkflow;
} }
@ -69,12 +78,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
var result = SimpleMapper.Map(content, new ContentEntity()); var result = SimpleMapper.Map(content, new ContentEntity());
await ResolveColorAsync(content, result, cache); await EnrichColorAsync(content, result, cache);
if (ShouldEnrichWithStatuses(context)) if (ShouldEnrichWithStatuses(context))
{ {
await ResolveNextsAsync(content, result, context); await EnrichNextsAsync(content, result, context);
await ResolveCanUpdateAsync(content, result); await EnrichCanUpdateAsync(content, result);
} }
results.Add(result); results.Add(result);
@ -107,11 +116,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
content.SchemaDisplayName = schemaDisplayName; content.SchemaDisplayName = schemaDisplayName;
} }
} }
}
if (ShouldEnrich(context)) if (ShouldEnrich(context))
{ {
await ResolveReferencesAsync(schema, group, context); await EnrichReferencesAsync(context, results);
} await EnrichAssetsAsync(context, results);
} }
} }
@ -119,10 +129,50 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
} }
} }
private async Task ResolveReferencesAsync(ISchemaEntity schema, IEnumerable<ContentEntity> contents, Context context) private async Task EnrichAssetsAsync(Context context, List<ContentEntity> contents)
{
var ids = new HashSet<Guid>();
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await ContentQuery.GetSchemaOrThrowAsync(context, group.Key.ToString());
AddAssetIds(ids, schema, group);
}
var assets = await GetAssetsAsync(context, ids);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await ContentQuery.GetSchemaOrThrowAsync(context, group.Key.ToString());
ResolveAssets(schema, group, assets);
}
}
private async Task EnrichReferencesAsync(Context context, List<ContentEntity> contents)
{
var ids = new HashSet<Guid>();
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await ContentQuery.GetSchemaOrThrowAsync(context, group.Key.ToString());
AddReferenceIds(ids, schema, group);
}
var references = await GetReferencesAsync(context, ids);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{ {
var references = await GetReferencesAsync(schema, contents, context); var schema = await ContentQuery.GetSchemaOrThrowAsync(context, group.Key.ToString());
await ResolveReferencesAsync(context, schema, group, references);
}
}
private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, IEnumerable<ContentEntity> contents, ILookup<Guid, IEnrichedContentEntity> references)
{
var formatted = new Dictionary<IContentEntity, JsonObject>(); var formatted = new Dictionary<IContentEntity, JsonObject>();
foreach (var field in schema.SchemaDef.ResolvingReferences()) foreach (var field in schema.SchemaDef.ResolvingReferences())
@ -134,15 +184,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
content.ReferenceData = new NamedContentData(); content.ReferenceData = new NamedContentData();
} }
content.ReferenceData.GetOrAddNew(field.Name); var fieldReference = content.ReferenceData.GetOrAddNew(field.Name);
}
try try
{ {
foreach (var content in contents)
{
var fieldReference = content.ReferenceData[field.Name];
if (content.DataDraft.TryGetValue(field.Name, out var fieldData)) if (content.DataDraft.TryGetValue(field.Name, out var fieldData))
{ {
foreach (var partitionValue in fieldData) foreach (var partitionValue in fieldData)
@ -161,6 +206,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
content.CacheDependencies.Add(referencedSchema.Id); content.CacheDependencies.Add(referencedSchema.Id);
content.CacheDependencies.Add(referencedSchema.Version); content.CacheDependencies.Add(referencedSchema.Version);
content.CacheDependencies.Add(reference.Id);
content.CacheDependencies.Add(reference.Version);
var value = formatted.GetOrAdd(reference, x => Format(x, context, referencedSchema)); var value = formatted.GetOrAdd(reference, x => Format(x, context, referencedSchema));
@ -175,13 +222,51 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
} }
} }
} }
}
catch (DomainObjectNotFoundException) catch (DomainObjectNotFoundException)
{ {
continue; continue;
} }
} }
} }
}
private void ResolveAssets(ISchemaEntity schema, IGrouping<Guid, ContentEntity> contents, ILookup<Guid, IEnrichedAssetEntity> assets)
{
foreach (var field in schema.SchemaDef.ResolvingAssets())
{
foreach (var content in contents)
{
if (content.ReferenceData == null)
{
content.ReferenceData = new NamedContentData();
}
var fieldReference = content.ReferenceData.GetOrAddNew(field.Name);
if (content.DataDraft.TryGetValue(field.Name, out var fieldData))
{
foreach (var partitionValue in fieldData)
{
var referencedImage =
field.GetReferencedIds(partitionValue.Value, Ids.ContentOnly)
.Select(x => assets[x])
.SelectMany(x => x)
.FirstOrDefault(x => x.IsImage);
if (referencedImage != null)
{
var url = assetUrlGenerator.GenerateUrl(referencedImage.Id.ToString());
content.CacheDependencies.Add(referencedImage.Id);
content.CacheDependencies.Add(referencedImage.Version);
fieldReference.AddJsonValue(partitionValue.Key, JsonValue.Create(url));
}
}
}
}
}
}
private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema) private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema)
{ {
@ -202,38 +287,57 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
return value; return value;
} }
private async Task<ILookup<Guid, IEnrichedContentEntity>> GetReferencesAsync(ISchemaEntity schema, IEnumerable<ContentEntity> contents, Context context) private void AddReferenceIds(HashSet<Guid> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents)
{ {
var ids = new HashSet<Guid>();
foreach (var content in contents) foreach (var content in contents)
{ {
ids.AddRange(content.DataDraft.GetReferencedIds(schema.SchemaDef.ResolvingReferences(), Ids.ContentOnly)); ids.AddRange(content.DataDraft.GetReferencedIds(schema.SchemaDef.ResolvingReferences(), Ids.ContentOnly));
} }
}
private void AddAssetIds(HashSet<Guid> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents)
{
foreach (var content in contents)
{
ids.AddRange(content.DataDraft.GetReferencedIds(schema.SchemaDef.ResolvingAssets(), Ids.ContentOnly));
}
}
if (ids.Count > 0) private async Task<ILookup<Guid, IEnrichedContentEntity>> GetReferencesAsync(Context context, HashSet<Guid> ids)
{ {
var references = await ContentQuery.QueryAsync(context.Clone().WithNoEnrichment(true), ids.ToList()); if (ids.Count == 0)
{
return EmptyContents;
}
var references = await ContentQuery.QueryAsync(context.Clone().WithNoContentEnrichment(true), ids.ToList());
return references.ToLookup(x => x.Id); return references.ToLookup(x => x.Id);
} }
else
private async Task<ILookup<Guid, IEnrichedAssetEntity>> GetAssetsAsync(Context context, HashSet<Guid> ids)
{
if (ids.Count == 0)
{ {
return EmptyReferences; return EmptyAssets;
} }
var assets = await assetQuery.QueryAsync(context.Clone().WithNoAssetEnrichment(true), Q.Empty.WithIds(ids));
return assets.ToLookup(x => x.Id);
} }
private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result) private async Task EnrichCanUpdateAsync(IContentEntity content, ContentEntity result)
{ {
result.CanUpdate = await contentWorkflow.CanUpdateAsync(content); result.CanUpdate = await contentWorkflow.CanUpdateAsync(content);
} }
private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result, Context context) private async Task EnrichNextsAsync(IContentEntity content, ContentEntity result, Context context)
{ {
result.Nexts = await contentWorkflow.GetNextsAsync(content, context.User); result.Nexts = await contentWorkflow.GetNextsAsync(content, context.User);
} }
private async Task ResolveColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache) private async Task EnrichColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache)
{ {
result.StatusColor = await GetColorAsync(content, cache); result.StatusColor = await GetColorAsync(content, cache);
} }

2
src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs

@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
if (asset == null) if (asset == null)
{ {
asset = await assetQuery.FindAssetAsync(id); asset = await assetQuery.FindAssetAsync(context, id);
if (asset != null) if (asset != null)
{ {

2
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -142,7 +142,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> GetAsset(string app, Guid id) public async Task<IActionResult> GetAsset(string app, Guid id)
{ {
var asset = await assetQuery.FindAssetAsync(id); var asset = await assetQuery.FindAssetAsync(Context, id);
if (asset == null) if (asset == null)
{ {

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs

@ -68,6 +68,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary> /// </summary>
public bool MustBeImage { get; set; } public bool MustBeImage { get; set; }
/// <summary>
/// True to resolve first image in the content list.
/// </summary>
public bool ResolveImage { get; set; }
/// <summary> /// <summary>
/// The allowed file extensions. /// The allowed file extensions.
/// </summary> /// </summary>

20
src/Squidex/app/features/content/shared/content-value.component.ts

@ -16,15 +16,27 @@ import { HtmlValue, Types } from '@app/shared';
<span class="truncate">{{value}}</span> <span class="truncate">{{value}}</span>
</ng-container> </ng-container>
<ng-template #html> <ng-template #html>
<span class="truncate" [innerHTML]="value.html"></span> <span class="html-value" [innerHTML]="value.html"></span>
</ng-template>`, </ng-template>`,
styles: [`
.html-value {
position: relative;
}
::ng-deep .html-value img {
position: absolute;
top: 50%;
min-height: 50px;
max-height: 50px;
margin-top: -25px;
}`
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ContentValueComponent { export class ContentValueComponent {
@Input()
public value: any;
public get isPlain() { public get isPlain() {
return !Types.is(this.value, HtmlValue); return !Types.is(this.value, HtmlValue);
} }
@Input()
public value: any;
} }

1
src/Squidex/app/features/schemas/declarations.ts

@ -5,6 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
export * from './pages/schema/types/assets-ui.component';
export * from './pages/schema/types/assets-validation.component'; export * from './pages/schema/types/assets-validation.component';
export * from './pages/schema/types/boolean-ui.component'; export * from './pages/schema/types/boolean-ui.component';
export * from './pages/schema/types/boolean-validation.component'; export * from './pages/schema/types/boolean-validation.component';

2
src/Squidex/app/features/schemas/module.ts

@ -16,6 +16,7 @@ import {
} from '@app/shared'; } from '@app/shared';
import { import {
AssetsUIComponent,
AssetsValidationComponent, AssetsValidationComponent,
BooleanUIComponent, BooleanUIComponent,
BooleanValidationComponent, BooleanValidationComponent,
@ -80,6 +81,7 @@ const routes: Routes = [
SchemaMustExistGuard SchemaMustExistGuard
], ],
declarations: [ declarations: [
AssetsUIComponent,
AssetsValidationComponent, AssetsValidationComponent,
BooleanUIComponent, BooleanUIComponent,
BooleanValidationComponent, BooleanValidationComponent,

3
src/Squidex/app/features/schemas/pages/schema/forms/field-form-ui.component.ts

@ -14,6 +14,9 @@ import { FieldDto } from '@app/shared';
selector: 'sqx-field-form-ui', selector: 'sqx-field-form-ui',
template: ` template: `
<ng-container [ngSwitch]="field.rawProperties.fieldType"> <ng-container [ngSwitch]="field.rawProperties.fieldType">
<ng-container *ngSwitchCase="'Assets'">
<sqx-assets-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-assets-ui>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'"> <ng-container *ngSwitchCase="'Boolean'">
<sqx-boolean-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-ui> <sqx-boolean-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-ui>
</ng-container> </ng-container>

2
src/Squidex/app/features/schemas/pages/schema/forms/field-form.component.ts

@ -21,7 +21,7 @@ import { FieldDto, PatternDto } from '@app/shared';
<li class="nav-item" [class.hidden]="!field.properties.isContentField"> <li class="nav-item" [class.hidden]="!field.properties.isContentField">
<a class="nav-link" (click)="selectTab(1)" [class.active]="selectedTab === 1">Validation</a> <a class="nav-link" (click)="selectTab(1)" [class.active]="selectedTab === 1">Validation</a>
</li> </li>
<li class="nav-item" [class.hidden]="!field.properties.isContentField || field.properties.fieldType === 'Assets'"> <li class="nav-item" [class.hidden]="!field.properties.isContentField">
<a class="nav-link" (click)="selectTab(2)" [class.active]="selectedTab === 2">Editing</a> <a class="nav-link" (click)="selectTab(2)" [class.active]="selectedTab === 2">Editing</a>
</li> </li>
</ul> </ul>

17
src/Squidex/app/features/schemas/pages/schema/types/assets-ui.component.html

@ -0,0 +1,17 @@
<div [formGroup]="editForm">
<div class="form-group row" *ngIf="field.properties.isContentField">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldResolveImage" formControlName="resolveImage" />
<label class="form-check-label" for="{{field.fieldId}}_fieldResolveImage">
Resolve image
</label>
</div>
<sqx-form-hint>
Show the first referenced image in the content list.
</sqx-form-hint>
</div>
</div>
</div>

2
src/Squidex/app/features/schemas/pages/schema/types/assets-ui.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

32
src/Squidex/app/features/schemas/pages/schema/types/assets-ui.component.ts

@ -0,0 +1,32 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { AssetsFieldPropertiesDto, FieldDto } from '@app/shared';
@Component({
selector: 'sqx-assets-ui',
styleUrls: ['assets-ui.component.scss'],
templateUrl: 'assets-ui.component.html'
})
export class AssetsUIComponent implements OnInit {
@Input()
public editForm: FormGroup;
@Input()
public field: FieldDto;
@Input()
public properties: AssetsFieldPropertiesDto;
public ngOnInit() {
this.editForm.setControl('resolveImage',
new FormControl(this.properties.resolveImage));
}
}

8
src/Squidex/app/shared/components/assets-selector.component.scss

@ -19,4 +19,12 @@
} }
} }
} }
.modal-body {
background: $color-background;
}
.modal-tabs {
background: $color-dark-foreground;
}
} }

2
src/Squidex/app/shared/services/contents.service.ts

@ -59,7 +59,7 @@ export class ContentsDto extends ResultSet<ContentDto> {
} }
} }
export type ContentReferencesValue = { [partition: string]: string }; export type ContentReferencesValue = { [partition: string]: string } | string;
export type ContentReferences = { [fieldName: string ]: ContentFieldData<ContentReferencesValue> }; export type ContentReferences = { [fieldName: string ]: ContentFieldData<ContentReferencesValue> };
export type ContentFieldData<T = any> = { [partition: string]: T }; export type ContentFieldData<T = any> = { [partition: string]: T };
export type ContentData = { [fieldName: string ]: ContentFieldData }; export type ContentData = { [fieldName: string ]: ContentFieldData };

1
src/Squidex/app/shared/services/schemas.types.ts

@ -174,6 +174,7 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
public readonly fieldType = 'Assets'; public readonly fieldType = 'Assets';
public readonly allowDuplicates?: boolean; public readonly allowDuplicates?: boolean;
public readonly resolveImage: boolean;
public readonly allowedExtensions?: ReadonlyArray<string>; public readonly allowedExtensions?: ReadonlyArray<string>;
public readonly aspectHeight?: number; public readonly aspectHeight?: number;
public readonly aspectWidth?: number; public readonly aspectWidth?: number;

43
src/Squidex/app/shared/state/contents.forms.spec.ts

@ -413,6 +413,49 @@ describe('GetContentValue', () => {
const language = new LanguageDto('en', 'English'); const language = new LanguageDto('en', 'English');
const fieldInvariant = createField({ properties: createProperties('Number'), partitioning: 'invariant' }); const fieldInvariant = createField({ properties: createProperties('Number'), partitioning: 'invariant' });
const fieldLocalized = createField({ properties: createProperties('Number') }); const fieldLocalized = createField({ properties: createProperties('Number') });
const fieldAssets = createField({ properties: createProperties('Assets') });
it('should resolve image url field from references value', () => {
const content: any = {
referenceData: {
field1: {
en: '13'
}
}
};
const result = getContentValue(content, language, fieldAssets);
expect(result).toEqual({ value: '13', formatted: new HtmlValue('<img src="13?width=50&height=50" />') });
});
it('should not image url if not found', () => {
const content: any = {
referenceData: {
field1: {
en: null
}
}
};
const result = getContentValue(content, language, fieldAssets);
expect(result).toEqual({ value: '- No Value -', formatted: '- No Value -' });
});
it('should resolve string field from references value', () => {
const content: any = {
referenceData: {
field1: {
iv: '13'
}
}
};
const result = getContentValue(content, language, fieldInvariant);
expect(result).toEqual({ value: '13', formatted: '13' });
});
it('should resolve invariant field from references value', () => { it('should resolve invariant field from references value', () => {
const content: any = { const content: any = {

27
src/Squidex/app/shared/state/contents.forms.ts

@ -64,7 +64,9 @@ export function getContentValue(content: ContentDto, language: LanguageDto, fiel
if (content.referenceData) { if (content.referenceData) {
const reference = content.referenceData[field.name]; const reference = content.referenceData[field.name];
if (reference) { const isAssets = field.properties.fieldType === 'Assets';
if (reference && (!isAssets || allowHtml)) {
let fieldValue: ContentReferencesValue; let fieldValue: ContentReferencesValue;
if (field.isLocalizable) { if (field.isLocalizable) {
@ -73,14 +75,25 @@ export function getContentValue(content: ContentDto, language: LanguageDto, fiel
fieldValue = reference[fieldInvariant]; fieldValue = reference[fieldInvariant];
} }
let value: string | undefined = let value: string | undefined = undefined;
fieldValue ?
fieldValue[language.iso2Code] : if (Types.isObject(fieldValue)) {
undefined; value = fieldValue[language.iso2Code];
} else if (Types.isString(fieldValue)) {
value = fieldValue;
}
let formatted: FieldValue = value!;
value = value || '- No Value -'; if (value) {
if (Types.isString(value) && isAssets) {
formatted = new HtmlValue(`<img src="${value}?width=50&height=50" />`);
}
} else {
value = formatted = '- No Value -';
}
return { value, formatted: value }; return { value, formatted };
} }
} }

20
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs

@ -33,6 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>(); private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>();
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>(); private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly IAssetStore assetStore = A.Fake<MemoryAssetStore>(); private readonly IAssetStore assetStore = A.Fake<MemoryAssetStore>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly ITagGenerator<CreateAsset> tagGenerator = A.Fake<ITagGenerator<CreateAsset>>(); private readonly ITagGenerator<CreateAsset> tagGenerator = A.Fake<ITagGenerator<CreateAsset>>();
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>(); private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
@ -41,6 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
private readonly ImageInfo image = new ImageInfo(2048, 2048); private readonly ImageInfo image = new ImageInfo(2048, 2048);
private readonly AssetGrain asset; private readonly AssetGrain asset;
private readonly AssetFile file; private readonly AssetFile file;
private readonly Context requestContext = Context.Anonymous();
private readonly AssetCommandMiddleware sut; private readonly AssetCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand public sealed class MyCommand : SquidexCommand
@ -59,10 +61,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
asset = new AssetGrain(Store, tagService, A.Fake<IActivationLimit>(), A.Dummy<ISemanticLog>()); asset = new AssetGrain(Store, tagService, A.Fake<IActivationLimit>(), A.Dummy<ISemanticLog>());
asset.ActivateAsync(Id).Wait(); asset.ActivateAsync(Id).Wait();
A.CallTo(() => assetEnricher.EnrichAsync(A<IAssetEntity>.Ignored)) A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
A.CallTo(() => assetEnricher.EnrichAsync(A<IAssetEntity>.Ignored, requestContext))
.ReturnsLazily(() => SimpleMapper.Map(asset.Snapshot, new AssetEntity())); .ReturnsLazily(() => SimpleMapper.Map(asset.Snapshot, new AssetEntity()));
A.CallTo(() => assetQuery.QueryByHashAsync(AppId, A<string>.Ignored)) A.CallTo(() => assetQuery.QueryByHashAsync(A<Context>.That.Matches(x => x.IsNoAssetEnrichment()), AppId, A<string>.Ignored))
.Returns(new List<IEnrichedAssetEntity>()); .Returns(new List<IEnrichedAssetEntity>());
A.CallTo(() => grainFactory.GetGrain<IAssetGrain>(Id, null)) A.CallTo(() => grainFactory.GetGrain<IAssetGrain>(Id, null))
@ -75,7 +80,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
assetEnricher, assetEnricher,
assetQuery, assetQuery,
assetStore, assetStore,
assetThumbnailGenerator, new[] { tagGenerator }); assetThumbnailGenerator,
contextProvider, new[] { tagGenerator });
} }
[Fact] [Fact]
@ -88,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.HandleAsync(context); await sut.HandleAsync(context);
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>.Ignored)) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>.Ignored, requestContext))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -106,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
Assert.Same(result, context.Result<IEnrichedAssetEntity>()); Assert.Same(result, context.Result<IEnrichedAssetEntity>());
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>.Ignored)) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>.Ignored, requestContext))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -122,7 +128,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var enriched = new AssetEntity(); var enriched = new AssetEntity();
A.CallTo(() => assetEnricher.EnrichAsync(result)) A.CallTo(() => assetEnricher.EnrichAsync(result, requestContext))
.Returns(enriched); .Returns(enriched);
await sut.HandleAsync(context); await sut.HandleAsync(context);
@ -296,7 +302,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
FileSize = fileSize FileSize = fileSize
}; };
A.CallTo(() => assetQuery.QueryByHashAsync(A<Guid>.Ignored, A<string>.Ignored)) A.CallTo(() => assetQuery.QueryByHashAsync(A<Context>.That.Matches(x => x.IsNoAssetEnrichment()), A<Guid>.Ignored, A<string>.Ignored))
.Returns(new List<IEnrichedAssetEntity> { duplicate }); .Returns(new List<IEnrichedAssetEntity> { duplicate });
} }

25
tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs

@ -19,6 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
{ {
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly Context requestContext = Context.Anonymous();
private readonly AssetEnricher sut; private readonly AssetEnricher sut;
public AssetEnricherTests() public AssetEnricherTests()
@ -31,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
{ {
var source = new AssetEntity { AppId = appId }; var source = new AssetEntity { AppId = appId };
var result = await sut.EnrichAsync(source); var result = await sut.EnrichAsync(source, requestContext);
Assert.Empty(result.TagNames); Assert.Empty(result.TagNames);
} }
@ -56,11 +57,29 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
["id2"] = "name2" ["id2"] = "name2"
}); });
var result = await sut.EnrichAsync(source); var result = await sut.EnrichAsync(source, requestContext);
Assert.Equal(new HashSet<string> { "name1", "name2" }, result.TagNames); Assert.Equal(new HashSet<string> { "name1", "name2" }, result.TagNames);
} }
[Fact]
public async Task Should_not_enrich_asset_with_tag_names_if_disabled()
{
var source = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
var result = await sut.EnrichAsync(source, requestContext.Clone().WithNoAssetEnrichment());
Assert.Null(result.TagNames);
}
[Fact] [Fact]
public async Task Should_enrich_multiple_assets_with_tag_names() public async Task Should_enrich_multiple_assets_with_tag_names()
{ {
@ -92,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
["id3"] = "name3" ["id3"] = "name3"
}); });
var result = await sut.EnrichAsync(new[] { source1, source2 }); var result = await sut.EnrichAsync(new[] { source1, source2 }, requestContext);
Assert.Equal(new HashSet<string> { "name1", "name2" }, result[0].TagNames); Assert.Equal(new HashSet<string> { "name1", "name2" }, result[0].TagNames);
Assert.Equal(new HashSet<string> { "name2", "name3" }, result[1].TagNames); Assert.Equal(new HashSet<string> { "name2", "name3" }, result[1].TagNames);

12
tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs

@ -47,10 +47,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
A.CallTo(() => assetRepository.FindAssetAsync(found.Id, false)) A.CallTo(() => assetRepository.FindAssetAsync(found.Id, false))
.Returns(found); .Returns(found);
A.CallTo(() => assetEnricher.EnrichAsync(found)) A.CallTo(() => assetEnricher.EnrichAsync(found, requestContext))
.Returns(enriched); .Returns(enriched);
var result = await sut.FindAssetAsync(found.Id); var result = await sut.FindAssetAsync(requestContext, found.Id);
Assert.Same(enriched, result); Assert.Same(enriched, result);
} }
@ -65,10 +65,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
A.CallTo(() => assetRepository.QueryByHashAsync(appId.Id, "hash")) A.CallTo(() => assetRepository.QueryByHashAsync(appId.Id, "hash"))
.Returns(new List<IAssetEntity> { found }); .Returns(new List<IAssetEntity> { found });
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found))) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found), requestContext))
.Returns(new List<IEnrichedAssetEntity> { enriched }); .Returns(new List<IEnrichedAssetEntity> { enriched });
var result = await sut.QueryByHashAsync(appId.Id, "hash"); var result = await sut.QueryByHashAsync(requestContext, appId.Id, "hash");
Assert.Same(enriched, result.Single()); Assert.Same(enriched, result.Single());
} }
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<HashSet<Guid>>.That.IsSameSequenceAs(ids))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<HashSet<Guid>>.That.IsSameSequenceAs(ids)))
.Returns(ResultList.CreateFrom(8, found1, found2)); .Returns(ResultList.CreateFrom(8, found1, found2));
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2))) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2), requestContext))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 }); .Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
var result = await sut.QueryAsync(requestContext, Q.Empty.WithIds(ids)); var result = await sut.QueryAsync(requestContext, Q.Empty.WithIds(ids));
@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<ClrQuery>.Ignored)) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<ClrQuery>.Ignored))
.Returns(ResultList.CreateFrom(8, found1, found2)); .Returns(ResultList.CreateFrom(8, found1, found2));
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2))) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2), requestContext))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 }); .Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
var result = await sut.QueryAsync(requestContext, Q.Empty); var result = await sut.QueryAsync(requestContext, Q.Empty);

232
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherAssetsTests.cs

@ -0,0 +1,232 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class ContentEnricherAssetsTests
{
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly Context requestContext;
private readonly ContentEnricher sut;
public ContentEnricherAssetsTests()
{
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId, Language.DE));
var schemaDef =
new Schema(schemaId.Name)
.AddAssets(1, "asset1", Partitioning.Invariant, new AssetsFieldProperties
{
ResolveImage = true,
MinItems = 2,
MaxItems = 3,
IsListField = true
})
.AddAssets(2, "asset2", Partitioning.Language, new AssetsFieldProperties
{
ResolveImage = true,
MinItems = 1,
MaxItems = 1,
IsListField = true,
});
A.CallTo(() => assetUrlGenerator.GenerateUrl(A<string>.Ignored))
.ReturnsLazily(new Func<string, string>(id => $"url/to/{id}"));
void SetupSchema(NamedId<Guid> id, Schema def)
{
var schemaEntity = Mocks.Schema(appId, id, def);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, id.Id.ToString()))
.Returns(schemaEntity);
}
SetupSchema(schemaId, schemaDef);
sut = new ContentEnricher(assetQuery, assetUrlGenerator, new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
}
[Fact]
public async Task Should_add_assets_id_and_versions_as_dependency()
{
var image1 = CreateAsset(Guid.NewGuid(), 1, true);
var image2 = CreateAsset(Guid.NewGuid(), 2, true);
var document1 = CreateAsset(Guid.NewGuid(), 3, false);
var document2 = CreateAsset(Guid.NewGuid(), 4, false);
var source = new IContentEntity[]
{
CreateContent(
new[] { document1.Id, image1.Id },
new[] { document1.Id }),
CreateContent(
new[] { document1.Id },
new[] { document2.Id, image2.Id })
};
A.CallTo(() => assetQuery.QueryAsync(A<Context>.That.Matches(x => x.IsNoAssetEnrichment()), A<Q>.That.Matches(x => x.Ids.Count == 4)))
.Returns(ResultList.CreateFrom(4, image1, image2, document1, document2));
var enriched = await sut.EnrichAsync(source, requestContext);
var enriched1 = enriched.ElementAt(0);
Assert.Contains(image1.Id, enriched1.CacheDependencies);
Assert.Contains(image1.Version, enriched1.CacheDependencies);
var enriched2 = enriched.ElementAt(1);
Assert.Contains(image2.Id, enriched2.CacheDependencies);
Assert.Contains(image2.Version, enriched2.CacheDependencies);
}
[Fact]
public async Task Should_enrich_with_asset_urls()
{
var image1 = CreateAsset(Guid.NewGuid(), 1, true);
var image2 = CreateAsset(Guid.NewGuid(), 2, true);
var document1 = CreateAsset(Guid.NewGuid(), 3, false);
var document2 = CreateAsset(Guid.NewGuid(), 4, false);
var source = new IContentEntity[]
{
CreateContent(
new[] { document1.Id, image1.Id },
new[] { document1.Id }),
CreateContent(
new[] { document1.Id },
new[] { document2.Id, image2.Id })
};
A.CallTo(() => assetQuery.QueryAsync(A<Context>.That.Matches(x => x.IsNoAssetEnrichment()), A<Q>.That.Matches(x => x.Ids.Count == 4)))
.Returns(ResultList.CreateFrom(4, image1, image2, document1, document2));
var enriched = await sut.EnrichAsync(source, requestContext);
Assert.Equal(
new NamedContentData()
.AddField("asset1",
new ContentFieldData()
.AddValue("iv",
$"url/to/{image1.Id}"))
.AddField("asset2",
new ContentFieldData()),
enriched.ElementAt(0).ReferenceData);
Assert.Equal(
new NamedContentData()
.AddField("asset1",
new ContentFieldData())
.AddField("asset2",
new ContentFieldData()
.AddValue("en",
$"url/to/{image2.Id}")),
enriched.ElementAt(1).ReferenceData);
}
[Fact]
public async Task Should_not_enrich_references_if_not_api_user()
{
var source = new IContentEntity[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
Assert.Null(enriched.ElementAt(0).ReferenceData);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, A<Q>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_enrich_references_if_disabled()
{
var source = new IContentEntity[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
Assert.Null(enriched.ElementAt(0).ReferenceData);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, A<Q>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_invoke_query_service_if_no_assets_found()
{
var source = new IContentEntity[]
{
CreateContent(new Guid[0], new Guid[0])
};
var enriched = await sut.EnrichAsync(source, requestContext);
Assert.NotNull(enriched.ElementAt(0).ReferenceData);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, A<Q>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_invoke_query_service_if_nothing_to_enrich()
{
var source = new IContentEntity[0];
await sut.EnrichAsync(source, requestContext);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, A<Q>.Ignored))
.MustNotHaveHappened();
}
private IEnrichedContentEntity CreateContent(Guid[] assets1, Guid[] assets2)
{
return new ContentEntity
{
DataDraft =
new NamedContentData()
.AddField("asset1",
new ContentFieldData()
.AddJsonValue("iv", JsonValue.Array(assets1.Select(x => x.ToString()).ToArray())))
.AddField("asset2",
new ContentFieldData()
.AddJsonValue("en", JsonValue.Array(assets2.Select(x => x.ToString()).ToArray()))),
SchemaId = schemaId
};
}
private static IEnrichedAssetEntity CreateAsset(Guid id, int version, bool isImage)
{
return new AssetEntity { Id = id, IsImage = isImage, Version = version };
}
}
}

115
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherReferencesTests.cs

@ -12,7 +12,9 @@ using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -24,6 +26,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> refSchemaId1 = NamedId.Of(Guid.NewGuid(), "my-ref1"); private readonly NamedId<Guid> refSchemaId1 = NamedId.Of(Guid.NewGuid(), "my-ref1");
private readonly NamedId<Guid> refSchemaId2 = NamedId.Of(Guid.NewGuid(), "my-ref2"); private readonly NamedId<Guid> refSchemaId2 = NamedId.Of(Guid.NewGuid(), "my-ref2");
@ -73,16 +77,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
SetupSchema(refSchemaId1, refSchemaDef); SetupSchema(refSchemaId1, refSchemaDef);
SetupSchema(refSchemaId2, refSchemaDef); SetupSchema(refSchemaId2, refSchemaDef);
sut = new ContentEnricher(new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow); sut = new ContentEnricher(assetQuery, assetUrlGenerator, new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
} }
[Fact] [Fact]
public async Task Should_add_referenced_id_as_dependency() public async Task Should_add_referenced_id_and__as_dependency()
{ {
var ref1_1 = CreateRefContent(Guid.NewGuid(), "ref1_1", 13, refSchemaId1); var ref1_1 = CreateRefContent(Guid.NewGuid(), 1, "ref1_1", 13, refSchemaId1);
var ref1_2 = CreateRefContent(Guid.NewGuid(), "ref1_2", 17, refSchemaId1); var ref1_2 = CreateRefContent(Guid.NewGuid(), 2, "ref1_2", 17, refSchemaId1);
var ref2_1 = CreateRefContent(Guid.NewGuid(), "ref2_1", 23, refSchemaId2); var ref2_1 = CreateRefContent(Guid.NewGuid(), 3, "ref2_1", 23, refSchemaId2);
var ref2_2 = CreateRefContent(Guid.NewGuid(), "ref2_2", 29, refSchemaId2); var ref2_2 = CreateRefContent(Guid.NewGuid(), 4, "ref2_2", 29, refSchemaId2);
var source = new IContentEntity[] var source = new IContentEntity[]
{ {
@ -96,22 +100,35 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var enriched = await sut.EnrichAsync(source, requestContext); var enriched = await sut.EnrichAsync(source, requestContext);
var enriched1 = enriched.ElementAt(0); var enriched1 = enriched.ElementAt(0);
var enriched2 = enriched.ElementAt(1);
Assert.Contains(refSchemaId1.Id, enriched1.CacheDependencies); Assert.Contains(refSchemaId1.Id, enriched1.CacheDependencies);
Assert.Contains(refSchemaId2.Id, enriched1.CacheDependencies); Assert.Contains(refSchemaId2.Id, enriched1.CacheDependencies);
Assert.Contains(ref1_1.Id, enriched1.CacheDependencies);
Assert.Contains(ref1_1.Version, enriched1.CacheDependencies);
Assert.Contains(ref2_1.Id, enriched1.CacheDependencies);
Assert.Contains(ref2_1.Version, enriched1.CacheDependencies);
var enriched2 = enriched.ElementAt(1);
Assert.Contains(refSchemaId1.Id, enriched2.CacheDependencies); Assert.Contains(refSchemaId1.Id, enriched2.CacheDependencies);
Assert.Contains(refSchemaId2.Id, enriched2.CacheDependencies); Assert.Contains(refSchemaId2.Id, enriched2.CacheDependencies);
Assert.Contains(ref1_2.Id, enriched2.CacheDependencies);
Assert.Contains(ref1_2.Version, enriched2.CacheDependencies);
Assert.Contains(ref2_2.Id, enriched2.CacheDependencies);
Assert.Contains(ref2_2.Version, enriched2.CacheDependencies);
} }
[Fact] [Fact]
public async Task Should_enrich_with_reference_data() public async Task Should_enrich_with_reference_data()
{ {
var ref1_1 = CreateRefContent(Guid.NewGuid(), "ref1_1", 13, refSchemaId1); var ref1_1 = CreateRefContent(Guid.NewGuid(), 1, "ref1_1", 13, refSchemaId1);
var ref1_2 = CreateRefContent(Guid.NewGuid(), "ref1_2", 17, refSchemaId1); var ref1_2 = CreateRefContent(Guid.NewGuid(), 2, "ref1_2", 17, refSchemaId1);
var ref2_1 = CreateRefContent(Guid.NewGuid(), "ref2_1", 23, refSchemaId2); var ref2_1 = CreateRefContent(Guid.NewGuid(), 3, "ref2_1", 23, refSchemaId2);
var ref2_2 = CreateRefContent(Guid.NewGuid(), "ref2_2", 29, refSchemaId2); var ref2_2 = CreateRefContent(Guid.NewGuid(), 3, "ref2_2", 29, refSchemaId2);
var source = new IContentEntity[] var source = new IContentEntity[]
{ {
@ -119,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
CreateContent(new[] { ref1_2.Id }, new[] { ref2_2.Id }) CreateContent(new[] { ref1_2.Id }, new[] { ref2_2.Id })
}; };
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4))) A.CallTo(() => contentQuery.QueryAsync(A<Context>.That.Matches(x => x.IsNoEnrichment()), A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4)))
.Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2)); .Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2));
var enriched = await sut.EnrichAsync(source, requestContext); var enriched = await sut.EnrichAsync(source, requestContext);
@ -160,10 +177,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact] [Fact]
public async Task Should_not_enrich_when_content_has_more_items() public async Task Should_not_enrich_when_content_has_more_items()
{ {
var ref1_1 = CreateRefContent(Guid.NewGuid(), "ref1_1", 13, refSchemaId1); var ref1_1 = CreateRefContent(Guid.NewGuid(), 1, "ref1_1", 13, refSchemaId1);
var ref1_2 = CreateRefContent(Guid.NewGuid(), "ref1_2", 17, refSchemaId1); var ref1_2 = CreateRefContent(Guid.NewGuid(), 2, "ref1_2", 17, refSchemaId1);
var ref2_1 = CreateRefContent(Guid.NewGuid(), "ref2_1", 23, refSchemaId2); var ref2_1 = CreateRefContent(Guid.NewGuid(), 3, "ref2_1", 23, refSchemaId2);
var ref2_2 = CreateRefContent(Guid.NewGuid(), "ref2_2", 29, refSchemaId2); var ref2_2 = CreateRefContent(Guid.NewGuid(), 4, "ref2_2", 29, refSchemaId2);
var source = new IContentEntity[] var source = new IContentEntity[]
{ {
@ -171,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
CreateContent(new[] { ref1_2.Id }, new[] { ref2_1.Id, ref2_2.Id }) CreateContent(new[] { ref1_2.Id }, new[] { ref2_1.Id, ref2_2.Id })
}; };
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4))) A.CallTo(() => contentQuery.QueryAsync(A<Context>.That.Matches(x => x.IsNoEnrichment()), A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4)))
.Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2)); .Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2));
var enriched = await sut.EnrichAsync(source, requestContext); var enriched = await sut.EnrichAsync(source, requestContext);
@ -209,6 +226,65 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
enriched.ElementAt(1).ReferenceData); enriched.ElementAt(1).ReferenceData);
} }
[Fact]
public async Task Should_not_enrich_references_if_not_api_user()
{
var source = new IContentEntity[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
Assert.Null(enriched.ElementAt(0).ReferenceData);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_enrich_references_if_disabled()
{
var source = new IContentEntity[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
Assert.Null(enriched.ElementAt(0).ReferenceData);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_invoke_query_service_if_no_references_found()
{
var source = new IContentEntity[]
{
CreateContent(new Guid[0], new Guid[0])
};
var enriched = await sut.EnrichAsync(source, requestContext);
Assert.NotNull(enriched.ElementAt(0).ReferenceData);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_invoke_query_service_if_nothing_to_enrich()
{
var source = new IContentEntity[0];
await sut.EnrichAsync(source, requestContext);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
}
private IEnrichedContentEntity CreateContent(Guid[] ref1, Guid[] ref2) private IEnrichedContentEntity CreateContent(Guid[] ref1, Guid[] ref2)
{ {
return new ContentEntity return new ContentEntity
@ -225,7 +301,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}; };
} }
private static IEnrichedContentEntity CreateRefContent(Guid id, string name, int number, NamedId<Guid> schemaId) private static IEnrichedContentEntity CreateRefContent(Guid id, int version, string name, int number, NamedId<Guid> schemaId)
{ {
return new ContentEntity return new ContentEntity
{ {
@ -238,7 +314,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
.AddField("number", .AddField("number",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", number)), .AddValue("iv", number)),
SchemaId = schemaId SchemaId = schemaId,
Version = version
}; };
} }
} }

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs

@ -9,6 +9,8 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -20,6 +22,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly ISchemaEntity schema; private readonly ISchemaEntity schema;
private readonly Context requestContext; private readonly Context requestContext;
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
@ -35,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>.Ignored, schemaId.Id.ToString())) A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>.Ignored, schemaId.Id.ToString()))
.Returns(schema); .Returns(schema);
sut = new ContentEnricher(new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow); sut = new ContentEnricher(assetQuery, assetUrlGenerator, new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
} }
[Fact] [Fact]

8
tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs

@ -23,20 +23,20 @@ namespace Squidex.Web.CommandMiddlewares
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>(); private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly Context appContext = Context.Anonymous(); private readonly Context requestContext = Context.Anonymous();
private readonly EnrichWithAppIdCommandMiddleware sut; private readonly EnrichWithAppIdCommandMiddleware sut;
public EnrichWithAppIdCommandMiddlewareTests() public EnrichWithAppIdCommandMiddlewareTests()
{ {
A.CallTo(() => contextProvider.Context) A.CallTo(() => contextProvider.Context)
.Returns(appContext); .Returns(requestContext);
var app = A.Fake<IAppEntity>(); var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Id).Returns(appId.Id); A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name); A.CallTo(() => app.Name).Returns(appId.Name);
appContext.App = app; requestContext.App = app;
sut = new EnrichWithAppIdCommandMiddleware(contextProvider); sut = new EnrichWithAppIdCommandMiddleware(contextProvider);
} }
@ -44,7 +44,7 @@ namespace Squidex.Web.CommandMiddlewares
[Fact] [Fact]
public async Task Should_throw_exception_if_app_not_found() public async Task Should_throw_exception_if_app_not_found()
{ {
appContext.App = null; requestContext.App = null;
var command = new CreateContent(); var command = new CreateContent();
var context = Ctx(command); var context = Ctx(command);

Loading…
Cancel
Save