Browse Source

Splitted the enrichment in several steps.

pull/478/head
Sebastian 6 years ago
parent
commit
727be9fa9a
  1. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  2. 317
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  3. 64
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs
  5. 21
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs
  6. 93
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  7. 36
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs
  8. 44
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs
  9. 82
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs
  10. 129
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  11. 167
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
  12. 19
      backend/src/Squidex/Config/Domain/ContentsServices.cs
  13. 1
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
  14. 176
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
  15. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs
  16. 57
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs
  17. 79
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs
  18. 109
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs
  19. 80
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs
  20. 103
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs
  21. 83
      backend/tools/TestSuite/TestSuite.ApiTests/ContentCleanupTests.cs
  22. 4
      backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs

1
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Queries;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;

317
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -9,15 +9,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection;
@ -25,30 +18,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public sealed class ContentEnricher : IContentEnricher
{
private const string DefaultColor = StatusColors.Draft;
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 IEnumerable<IContentEnricherStep> steps;
private readonly Lazy<IContentQueryService> contentQuery;
private readonly IContentWorkflow contentWorkflow;
private IContentQueryService ContentQuery
{
get { return contentQuery.Value; }
}
public ContentEnricher(IAssetQueryService assetQuery, IAssetUrlGenerator assetUrlGenerator, Lazy<IContentQueryService> contentQuery, IContentWorkflow contentWorkflow)
public ContentEnricher(IEnumerable<IContentEnricherStep> steps, Lazy<IContentQueryService> contentQuery)
{
Guard.NotNull(assetQuery);
Guard.NotNull(assetUrlGenerator);
Guard.NotNull(steps);
Guard.NotNull(contentQuery);
Guard.NotNull(contentWorkflow);
this.assetQuery = assetQuery;
this.assetUrlGenerator = assetUrlGenerator;
this.steps = steps;
this.contentQuery = contentQuery;
this.contentWorkflow = contentWorkflow;
}
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context)
@ -73,308 +58,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
if (contents.Any())
{
var cache = new Dictionary<(Guid, Status), StatusInfo>();
foreach (var content in contents)
{
var result = SimpleMapper.Map(content, new ContentEntity());
await EnrichColorAsync(content, result, cache);
if (ShouldEnrichWithStatuses(context))
{
await EnrichNextsAsync(content, result, context);
await EnrichCanUpdateAsync(content, result, context);
}
results.Add(result);
}
foreach (var group in results.GroupBy(x => x.SchemaId.Id))
{
var schema = await ContentQuery.GetSchemaOrThrowAsync(context, group.Key.ToString());
foreach (var content in group)
{
content.CacheDependencies = new HashSet<object?>
{
app.Id,
app.Version,
schema.Id,
schema.Version
};
}
if (ShouldEnrichWithSchema(context))
{
var referenceFields = schema.SchemaDef.ReferencesFields().ToArray();
var schemaName = schema.SchemaDef.Name;
var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged();
foreach (var content in group)
{
content.ReferenceFields = referenceFields;
content.SchemaName = schemaName;
content.SchemaDisplayName = schemaDisplayName;
}
}
}
if (ShouldEnrich(context))
{
await EnrichReferencesAsync(context, results);
await EnrichAssetsAsync(context, results);
}
}
return results;
}
}
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());
var schemaCache = new Dictionary<Guid, Task<ISchemaEntity>>();
AddReferenceIds(ids, schema, group);
}
var references = await GetReferencesAsync(context, ids);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
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>();
foreach (var field in schema.SchemaDef.ResolvingReferences())
{
foreach (var content in contents)
{
if (content.ReferenceData == null)
{
content.ReferenceData = new NamedContentData();
}
var fieldReference = content.ReferenceData.GetOrAdd(field.Name, _ => new ContentFieldData())!;
try
{
if (content.DataDraft.TryGetValue(field.Name, out var fieldData) && fieldData != null)
{
foreach (var (partition, partitionValue) in fieldData)
{
var referencedContents =
field.GetReferencedIds(partitionValue, Ids.ContentOnly)
.Select(x => references[x])
.SelectMany(x => x)
.ToList();
if (referencedContents.Count == 1)
{
var reference = referencedContents[0];
var referencedSchema = await ContentQuery.GetSchemaOrThrowAsync(context, reference.SchemaId.Id.ToString());
content.CacheDependencies.Add(referencedSchema.Id);
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));
fieldReference.AddJsonValue(partition, value);
}
else if (referencedContents.Count > 1)
{
var value = CreateFallback(context, referencedContents);
fieldReference.AddJsonValue(partition, value);
}
}
}
}
catch (DomainObjectNotFoundException)
{
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)
Task<ISchemaEntity> GetSchema(Guid id)
{
content.ReferenceData = new NamedContentData();
return schemaCache.GetOrAdd(id, x => ContentQuery.GetSchemaOrThrowAsync(context, x.ToString()));
}
var fieldReference = content.ReferenceData.GetOrAdd(field.Name, _ => new ContentFieldData())!;
if (content.DataDraft.TryGetValue(field.Name, out var fieldData) && fieldData != null)
foreach (var step in steps)
{
foreach (var (partitionKey, partitionValue) in fieldData)
{
var referencedImage =
field.GetReferencedIds(partitionValue, Ids.ContentOnly)
.Select(x => assets[x])
.SelectMany(x => x)
.FirstOrDefault(x => x.Type == AssetType.Image);
if (referencedImage != null)
{
var url = assetUrlGenerator.GenerateUrl(referencedImage.Id.ToString());
content.CacheDependencies.Add(referencedImage.Id);
content.CacheDependencies.Add(referencedImage.Version);
fieldReference.AddJsonValue(partitionKey, JsonValue.Create(url));
}
}
await step.EnrichAsync(context, results, GetSchema);
}
}
}
}
private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema)
{
return content.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig);
}
private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents)
{
var text = $"{referencedContents.Count} Reference(s)";
var value = JsonValue.Object();
foreach (var partitionKey in context.App.LanguagesConfig.AllKeys)
{
value.Add(partitionKey, text);
}
return value;
}
private void AddReferenceIds(HashSet<Guid> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents)
{
foreach (var content in contents)
{
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));
}
}
private async Task<ILookup<Guid, IEnrichedContentEntity>> GetReferencesAsync(Context context, HashSet<Guid> ids)
{
if (ids.Count == 0)
{
return EmptyContents;
}
var references = await ContentQuery.QueryAsync(context.Clone().WithoutContentEnrichment(true), ids.ToList());
return references.ToLookup(x => x.Id);
}
private async Task<ILookup<Guid, IEnrichedAssetEntity>> GetAssetsAsync(Context context, HashSet<Guid> ids)
{
if (ids.Count == 0)
{
return EmptyAssets;
}
var assets = await assetQuery.QueryAsync(context.Clone().WithNoAssetEnrichment(true), null, Q.Empty.WithIds(ids));
return assets.ToLookup(x => x.Id);
}
private async Task EnrichCanUpdateAsync(IContentEntity content, ContentEntity result, Context context)
{
result.CanUpdate = await contentWorkflow.CanUpdateAsync(content, context.User);
}
private async Task EnrichNextsAsync(IContentEntity content, ContentEntity result, Context context)
{
result.Nexts = await contentWorkflow.GetNextsAsync(content, context.User);
}
private async Task EnrichColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache)
{
result.StatusColor = await GetColorAsync(content, cache);
}
private async Task<string> GetColorAsync(IContentEntity content, Dictionary<(Guid, Status), StatusInfo> cache)
{
if (!cache.TryGetValue((content.SchemaId.Id, content.Status), out var info))
{
info = await contentWorkflow.GetInfoAsync(content);
if (info == null)
{
info = new StatusInfo(content.Status, DefaultColor);
}
cache[(content.SchemaId.Id, content.Status)] = info;
return results;
}
return info.Color;
}
private static bool ShouldEnrichWithSchema(Context context)
{
return context.IsFrontendClient;
}
private static bool ShouldEnrichWithStatuses(Context context)
{
return context.IsFrontendClient || context.IsResolveFlow();
}
private static bool ShouldEnrich(Context context)
{
return context.IsFrontendClient && !context.IsNoEnrichment();
}
}
}

64
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
@ -29,7 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
private static readonly Status[] StatusPublishedOnly = { Status.Published };
private static readonly IResultList<IEnrichedContentEntity> EmptyContents = ResultList.CreateFrom<IEnrichedContentEntity>(0);
private readonly IAppProvider appProvider;
private readonly IAssetUrlGenerator assetUrlGenerator;
private readonly IContentEnricher contentEnricher;
private readonly IContentRepository contentRepository;
private readonly IContentLoader contentVersionLoader;
@ -38,7 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public ContentQueryService(
IAppProvider appProvider,
IAssetUrlGenerator assetUrlGenerator,
IContentEnricher contentEnricher,
IContentRepository contentRepository,
IContentLoader contentVersionLoader,
@ -46,7 +43,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
ContentQueryParser queryParser)
{
Guard.NotNull(appProvider);
Guard.NotNull(assetUrlGenerator);
Guard.NotNull(contentEnricher);
Guard.NotNull(contentRepository);
Guard.NotNull(contentVersionLoader);
@ -54,7 +50,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
Guard.NotNull(scriptEngine);
this.appProvider = appProvider;
this.assetUrlGenerator = assetUrlGenerator;
this.contentEnricher = contentEnricher;
this.contentRepository = contentRepository;
this.contentVersionLoader = contentVersionLoader;
@ -169,8 +164,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var results = new List<IEnrichedContentEntity>();
var converters = GenerateConverters(context).ToArray();
var script = schema.SchemaDef.Scripts.Query;
var scripting = !string.IsNullOrWhiteSpace(script);
@ -180,25 +173,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
var result = SimpleMapper.Map(content, new ContentEntity());
if (result.Data != null)
if (result.Data != null && !context.IsFrontendClient && scripting)
{
if (!context.IsFrontendClient && scripting)
{
var ctx = new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id };
result.Data = scriptEngine.Transform(ctx, script);
}
result.Data = result.Data.ConvertName2Name(schema.SchemaDef, converters);
}
var ctx = new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id };
if (result.DataDraft != null && (context.IsUnpublished() || context.IsFrontendClient))
{
result.DataDraft = result.DataDraft.ConvertName2Name(schema.SchemaDef, converters);
}
else
{
result.DataDraft = null!;
result.Data = scriptEngine.Transform(ctx, script);
}
results.Add(result);
@ -208,43 +187,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
}
private IEnumerable<FieldConverter> GenerateConverters(Context context)
{
if (!context.IsFrontendClient)
{
yield return FieldConverters.ExcludeHidden();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden());
}
yield return FieldConverters.ExcludeChangedTypes();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes());
yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig);
yield return FieldConverters.ResolveLanguages(context.App.LanguagesConfig);
if (!context.IsFrontendClient)
{
if (!context.IsNoResolveLanguages())
{
yield return FieldConverters.ResolveFallbackLanguages(context.App.LanguagesConfig);
}
var languages = context.Languages();
if (languages.Any())
{
yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, languages);
}
var assetUrls = context.AssetUrls();
if (assetUrls.Any())
{
yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), assetUrlGenerator);
}
}
}
public async Task<ISchemaEntity> GetSchemaOrThrowAsync(Context context, string schemaIdOrName)
{
ISchemaEntity? schema = null;

2
backend/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs

@ -8,7 +8,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public interface IContentEnricher
{

21
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public delegate Task<ISchemaEntity> ProvideSchema(Guid id);
public interface IContentEnricherStep
{
Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas);
}
}

93
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs

@ -0,0 +1,93 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
public sealed class ConvertData : IContentEnricherStep
{
private readonly IAssetUrlGenerator assetUrlGenerator;
public ConvertData(IAssetUrlGenerator assetUrlGenerator)
{
Guard.NotNull(assetUrlGenerator);
this.assetUrlGenerator = assetUrlGenerator;
}
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
var converters = GenerateConverters(context).ToArray();
var resolveDataDraft = context.IsUnpublished() || context.IsFrontendClient;
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
foreach (var content in group)
{
if (content.Data != null)
{
content.Data = content.Data.ConvertName2Name(schema.SchemaDef, converters);
}
if (content.DataDraft != null && resolveDataDraft)
{
content.DataDraft = content.DataDraft.ConvertName2Name(schema.SchemaDef, converters);
}
else
{
content.DataDraft = null!;
}
}
}
}
private IEnumerable<FieldConverter> GenerateConverters(Context context)
{
if (!context.IsFrontendClient)
{
yield return FieldConverters.ExcludeHidden();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden());
}
yield return FieldConverters.ExcludeChangedTypes();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes());
yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig);
yield return FieldConverters.ResolveLanguages(context.App.LanguagesConfig);
if (!context.IsFrontendClient)
{
if (!context.IsNoResolveLanguages())
{
yield return FieldConverters.ResolveFallbackLanguages(context.App.LanguagesConfig);
}
var languages = context.Languages();
if (languages.Any())
{
yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, languages);
}
var assetUrls = context.AssetUrls();
if (assetUrls.Any())
{
yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), assetUrlGenerator);
}
}
}
}
}

36
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
public sealed class EnrichForCaching : IContentEnricherStep
{
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
var app = context.App;
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
foreach (var content in group)
{
content.CacheDependencies ??= new HashSet<object?>();
content.CacheDependencies.Add(app.Id);
content.CacheDependencies.Add(app.Version);
content.CacheDependencies.Add(schema.Id);
content.CacheDependencies.Add(schema.Version);
}
}
}
}
}

44
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs

@ -0,0 +1,44 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
public sealed class EnrichWithSchema : IContentEnricherStep
{
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
var schemaName = schema.SchemaDef.Name;
var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged();
foreach (var content in group)
{
content.SchemaName = schemaName;
content.SchemaDisplayName = schemaDisplayName;
}
if (context.IsFrontendClient)
{
var referenceFields = schema.SchemaDef.ReferencesFields().ToArray();
foreach (var content in group)
{
content.ReferenceFields = referenceFields;
}
}
}
}
}
}

82
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs

@ -0,0 +1,82 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
public sealed class EnrichWithWorkflows : IContentEnricherStep
{
private const string DefaultColor = StatusColors.Draft;
private readonly IContentWorkflow contentWorkflow;
public EnrichWithWorkflows(IContentWorkflow contentWorkflow)
{
Guard.NotNull(contentWorkflow);
this.contentWorkflow = contentWorkflow;
}
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
var cache = new Dictionary<(Guid, Status), StatusInfo>();
foreach (var content in contents)
{
await EnrichColorAsync(content, content, cache);
if (ShouldEnrichWithStatuses(context))
{
await EnrichNextsAsync(content, context);
await EnrichCanUpdateAsync(content, context);
}
}
}
private async Task EnrichNextsAsync(ContentEntity content, Context context)
{
content.Nexts = await contentWorkflow.GetNextsAsync(content, context.User);
}
private async Task EnrichCanUpdateAsync( ContentEntity content, Context context)
{
content.CanUpdate = await contentWorkflow.CanUpdateAsync(content, context.User);
}
private async Task EnrichColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache)
{
result.StatusColor = await GetColorAsync(content, cache);
}
private async Task<string> GetColorAsync(IContentEntity content, Dictionary<(Guid, Status), StatusInfo> cache)
{
if (!cache.TryGetValue((content.SchemaId.Id, content.Status), out var info))
{
info = await contentWorkflow.GetInfoAsync(content);
if (info == null)
{
info = new StatusInfo(content.Status, DefaultColor);
}
cache[(content.SchemaId.Id, content.Status)] = info;
}
return info.Color;
}
private static bool ShouldEnrichWithStatuses(Context context)
{
return context.IsFrontendClient || context.IsResolveFlow();
}
}
}

129
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs

@ -0,0 +1,129 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
public sealed class ResolveAssets : IContentEnricherStep
{
private static readonly ILookup<Guid, IEnrichedAssetEntity> EmptyAssets = Enumerable.Empty<IEnrichedAssetEntity>().ToLookup(x => x.Id);
private readonly IAssetUrlGenerator assetUrlGenerator;
private readonly IAssetQueryService assetQuery;
public ResolveAssets(IAssetUrlGenerator assetUrlGenerator, IAssetQueryService assetQuery)
{
Guard.NotNull(assetUrlGenerator);
Guard.NotNull(assetQuery);
this.assetUrlGenerator = assetUrlGenerator;
this.assetQuery = assetQuery;
}
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
if (ShouldEnrich(context))
{
var ids = new HashSet<Guid>();
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
AddAssetIds(ids, schema, group);
}
var assets = await GetAssetsAsync(context, ids);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
ResolveAssetsUrls(schema, group, assets);
}
}
}
private void ResolveAssetsUrls(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.GetOrAdd(field.Name, _ => new ContentFieldData())!;
if (content.DataDraft.TryGetValue(field.Name, out var fieldData) && fieldData != null)
{
foreach (var (partitionKey, partitionValue) in fieldData)
{
var referencedImage =
field.GetReferencedIds(partitionValue, Ids.ContentOnly)
.Select(x => assets[x])
.SelectMany(x => x)
.FirstOrDefault(x => x.Type == AssetType.Image);
if (referencedImage != null)
{
var url = assetUrlGenerator.GenerateUrl(referencedImage.Id.ToString());
content.CacheDependencies ??= new HashSet<object?>();
content.CacheDependencies.Add(referencedImage.Id);
content.CacheDependencies.Add(referencedImage.Version);
fieldReference.AddJsonValue(partitionKey, JsonValue.Create(url));
}
}
}
}
}
}
private async Task<ILookup<Guid, IEnrichedAssetEntity>> GetAssetsAsync(Context context, HashSet<Guid> ids)
{
if (ids.Count == 0)
{
return EmptyAssets;
}
var assets = await assetQuery.QueryAsync(context.Clone().WithNoAssetEnrichment(true), null, Q.Empty.WithIds(ids));
return assets.ToLookup(x => x.Id);
}
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));
}
}
private static bool ShouldEnrich(Context context)
{
return context.IsFrontendClient && !context.IsNoEnrichment();
}
}
}

167
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs

@ -0,0 +1,167 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{
public sealed class ResolveReferences : IContentEnricherStep
{
private static readonly ILookup<Guid, IEnrichedContentEntity> EmptyContents = Enumerable.Empty<IEnrichedContentEntity>().ToLookup(x => x.Id);
private readonly Lazy<IContentQueryService> contentQuery;
private IContentQueryService ContentQuery
{
get { return contentQuery.Value; }
}
public ResolveReferences(Lazy<IContentQueryService> contentQuery)
{
Guard.NotNull(contentQuery);
this.contentQuery = contentQuery;
}
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
if (ShouldEnrich(context))
{
var ids = new HashSet<Guid>();
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
AddReferenceIds(ids, schema, group);
}
var references = await GetReferencesAsync(context, ids);
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
var schema = await schemas(group.Key);
await ResolveReferencesAsync(context, schema, group, references, schemas);
}
}
}
private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, IEnumerable<ContentEntity> contents, ILookup<Guid, IEnrichedContentEntity> references, ProvideSchema schemas)
{
var formatted = new Dictionary<IContentEntity, JsonObject>();
foreach (var field in schema.SchemaDef.ResolvingReferences())
{
foreach (var content in contents)
{
if (content.ReferenceData == null)
{
content.ReferenceData = new NamedContentData();
}
var fieldReference = content.ReferenceData.GetOrAdd(field.Name, _ => new ContentFieldData())!;
try
{
if (content.DataDraft.TryGetValue(field.Name, out var fieldData) && fieldData != null)
{
foreach (var (partition, partitionValue) in fieldData)
{
var referencedContents =
field.GetReferencedIds(partitionValue, Ids.ContentOnly)
.Select(x => references[x])
.SelectMany(x => x)
.ToList();
if (referencedContents.Count == 1)
{
var reference = referencedContents[0];
var referencedSchema = await schemas(reference.SchemaId.Id);
content.CacheDependencies ??= new HashSet<object?>();
content.CacheDependencies.Add(referencedSchema.Id);
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));
fieldReference.AddJsonValue(partition, value);
}
else if (referencedContents.Count > 1)
{
var value = CreateFallback(context, referencedContents);
fieldReference.AddJsonValue(partition, value);
}
}
}
}
catch (DomainObjectNotFoundException)
{
continue;
}
}
}
}
private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema)
{
return content.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig);
}
private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents)
{
var text = $"{referencedContents.Count} Reference(s)";
var value = JsonValue.Object();
foreach (var partitionKey in context.App.LanguagesConfig.AllKeys)
{
value.Add(partitionKey, text);
}
return value;
}
private void AddReferenceIds(HashSet<Guid> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents)
{
foreach (var content in contents)
{
ids.AddRange(content.DataDraft.GetReferencedIds(schema.SchemaDef.ResolvingReferences(), Ids.ContentOnly));
}
}
private async Task<ILookup<Guid, IEnrichedContentEntity>> GetReferencesAsync(Context context, HashSet<Guid> ids)
{
if (ids.Count == 0)
{
return EmptyContents;
}
var references = await ContentQuery.QueryAsync(context.Clone().WithoutContentEnrichment(true), ids.ToList());
return references.ToLookup(x => x.Id);
}
private static bool ShouldEnrich(Context context)
{
return context.IsFrontendClient && !context.IsNoEnrichment();
}
}
}

19
backend/src/Squidex/Config/Domain/ContentsServices.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Queries;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Infrastructure.EventSourcing;
@ -39,6 +40,24 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>();
services.AddSingletonAs<ConvertData>()
.As<IContentEnricherStep>();
services.AddSingletonAs<EnrichForCaching>()
.As<IContentEnricherStep>();
services.AddSingletonAs<EnrichWithSchema>()
.As<IContentEnricherStep>();
services.AddSingletonAs<EnrichWithWorkflows>()
.As<IContentEnricherStep>();
services.AddSingletonAs<ResolveAssets>()
.As<IContentEnricherStep>();
services.AddSingletonAs<ResolveReferences>()
.As<IContentEnricherStep>();
services.AddSingletonAs<ContentEnricher>()
.As<IContentEnricher>();

1
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -9,6 +9,7 @@ using System;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Domain.Apps.Entities.Contents.Queries;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Commands;

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

@ -6,11 +6,11 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
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.TestHelpers;
using Squidex.Infrastructure;
@ -20,179 +20,89 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class ContentEnricherTests
{
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 ISchemaEntity schema;
private readonly Context requestContext;
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ContentEnricher sut;
public ContentEnricherTests()
private sealed class ResolveSchema : IContentEnricherStep
{
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
public ISchemaEntity Schema { get; private set; }
schema = Mocks.Schema(appId, schemaId);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>.Ignored, schemaId.Id.ToString()))
.Returns(schema);
sut = new ContentEnricher(assetQuery, assetUrlGenerator, new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
}
[Fact]
public async Task Should_add_app_version_and_schema_as_dependency()
{
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(source, requestContext);
Assert.Contains(requestContext.App.Version, result.CacheDependencies);
Assert.Contains(schema.Id, result.CacheDependencies);
Assert.Contains(schema.Version, result.CacheDependencies);
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas)
{
foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
{
Schema = await schemas(group.Key);
}
}
}
[Fact]
public async Task Should_enrich_with_reference_fields()
public ContentEnricherTests()
{
var ctx = new Context(Mocks.FrontendUser(), requestContext.App);
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
var result = await sut.EnrichAsync(source, ctx);
schema = Mocks.Schema(appId, schemaId);
Assert.NotNull(result.ReferenceFields);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, schemaId.Id.ToString()))
.Returns(schema);
}
[Fact]
public async Task Should_not_enrich_with_reference_fields_when_not_frontend()
public async Task Should_not_invoke_steps()
{
var source = PublishedContent();
var source = new IContentEntity[0];
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var step1 = A.Fake<IContentEnricherStep>();
var step2 = A.Fake<IContentEnricherStep>();
var result = await sut.EnrichAsync(source, requestContext);
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
Assert.Null(result.ReferenceFields);
}
[Fact]
public async Task Should_enrich_with_schema_names()
{
var ctx = new Context(Mocks.FrontendUser(), requestContext.App);
await sut.EnrichAsync(source, requestContext);
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(source, ctx);
A.CallTo(() => step1.EnrichAsync(requestContext, A<IEnumerable<ContentEntity>>.Ignored, A<ProvideSchema>.Ignored))
.MustNotHaveHappened();
Assert.Equal("my-schema", result.SchemaName);
Assert.Equal("my-schema", result.SchemaDisplayName);
A.CallTo(() => step2.EnrichAsync(requestContext, A<IEnumerable<ContentEntity>>.Ignored, A<ProvideSchema>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_enrich_with_schema_names_when_not_frontend()
public async Task Should_invoke_steps()
{
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(source, requestContext);
Assert.Null(result.SchemaName);
Assert.Null(result.SchemaDisplayName);
}
var step1 = A.Fake<IContentEnricherStep>();
var step2 = A.Fake<IContentEnricherStep>();
[Fact]
public async Task Should_enrich_content_with_status_color()
{
var source = PublishedContent();
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
await sut.EnrichAsync(source, requestContext);
var result = await sut.EnrichAsync(source, requestContext);
A.CallTo(() => step1.EnrichAsync(requestContext, A<IEnumerable<ContentEntity>>.Ignored, A<ProvideSchema>.Ignored))
.MustHaveHappened();
Assert.Equal(StatusColors.Published, result.StatusColor);
A.CallTo(() => step2.EnrichAsync(requestContext, A<IEnumerable<ContentEntity>>.Ignored, A<ProvideSchema>.Ignored))
.MustHaveHappened();
}
[Fact]
public async Task Should_enrich_content_with_default_color_if_not_found()
public async Task Should_provide_and_cache_schema()
{
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(Task.FromResult<StatusInfo>(null!));
var result = await sut.EnrichAsync(source, requestContext);
Assert.Equal(StatusColors.Draft, result.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_can_update()
{
requestContext.WithResolveFlow(true);
var source = new ContentEntity { SchemaId = schemaId };
A.CallTo(() => contentWorkflow.CanUpdateAsync(source, requestContext.User))
.Returns(true);
var result = await sut.EnrichAsync(source, requestContext);
Assert.True(result.CanUpdate);
}
[Fact]
public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context()
{
requestContext.WithResolveFlow(false);
var source = new ContentEntity { SchemaId = schemaId };
var result = await sut.EnrichAsync(source, requestContext);
Assert.False(result.CanUpdate);
A.CallTo(() => contentWorkflow.CanUpdateAsync(source, requestContext.User))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_multiple_contents_and_cache_color()
{
var source1 = PublishedContent();
var source2 = PublishedContent();
var source = new IContentEntity[]
{
source1,
source2
};
var step1 = new ResolveSchema();
var step2 = new ResolveSchema();
A.CallTo(() => contentWorkflow.GetInfoAsync(source1))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var sut = new ContentEnricher(new[] { step1, step2 }, new Lazy<IContentQueryService>(() => contentQuery));
var result = await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(source, requestContext);
Assert.Equal(StatusColors.Published, result[0].StatusColor);
Assert.Equal(StatusColors.Published, result[1].StatusColor);
Assert.Same(schema, step1.Schema);
Assert.Same(schema, step1.Schema);
A.CallTo(() => contentWorkflow.GetInfoAsync(A<IContentEntity>.Ignored))
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, schemaId.Id.ToString()))
.MustHaveHappenedOnceExactly();
}

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs

@ -35,7 +35,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetUrlGenerator urlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IContentLoader contentVersionLoader = A.Fake<IContentLoader>();
@ -79,7 +78,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
sut = new ContentQueryService(
appProvider,
urlGenerator,
contentEnricher,
contentRepository,
contentVersionLoader,

57
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs

@ -0,0 +1,57 @@
// ==========================================================================
// 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 Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class EnrichForCachingTests
{
private readonly ISchemaEntity schema;
private readonly Context requestContext;
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ProvideSchema schemaProvider;
private readonly EnrichForCaching sut;
public EnrichForCachingTests()
{
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema);
sut = new EnrichForCaching();
}
[Fact]
public async Task Should_add_app_version_and_schema_as_dependency()
{
var source = PublishedContent();
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider);
Assert.Contains(requestContext.App.Version, source.CacheDependencies);
Assert.Contains(schema.Id, source.CacheDependencies);
Assert.Contains(schema.Version, source.CacheDependencies);
}
private ContentEntity PublishedContent()
{
return new ContentEntity { Status = Status.Published, SchemaId = schemaId };
}
}
}

79
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs

@ -0,0 +1,79 @@
// ==========================================================================
// 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 Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class EnrichWithSchemaTests
{
private readonly ISchemaEntity schema;
private readonly Context requestContext;
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ProvideSchema schemaProvider;
private readonly EnrichWithSchema sut;
public EnrichWithSchemaTests()
{
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema);
sut = new EnrichWithSchema();
}
[Fact]
public async Task Should_enrich_with_reference_fields()
{
var ctx = new Context(Mocks.FrontendUser(), requestContext.App);
var source = PublishedContent();
await sut.EnrichAsync(ctx, Enumerable.Repeat(source, 1), schemaProvider);
Assert.NotNull(source.ReferenceFields);
}
[Fact]
public async Task Should_not_enrich_with_reference_fields_when_not_frontend()
{
var source = PublishedContent();
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider);
Assert.Null(source.ReferenceFields);
}
[Fact]
public async Task Should_enrich_with_schema_names()
{
var ctx = new Context(Mocks.FrontendUser(), requestContext.App);
var source = PublishedContent();
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider);
Assert.Equal("my-schema", source.SchemaName);
Assert.Equal("my-schema", source.SchemaDisplayName);
}
private ContentEntity PublishedContent()
{
return new ContentEntity { Status = Status.Published, SchemaId = schemaId };
}
}
}

109
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs

@ -0,0 +1,109 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class EnrichWithWorkflowsTests
{
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly ISchemaEntity schema;
private readonly Context requestContext;
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ProvideSchema schemaProvider;
private readonly EnrichWithWorkflows sut;
public EnrichWithWorkflowsTests()
{
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult(schema);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>.Ignored, schemaId.Id.ToString()))
.Returns(schema);
sut = new EnrichWithWorkflows(contentWorkflow);
}
[Fact]
public async Task Should_enrich_content_with_status_color()
{
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
await sut.EnrichAsync(requestContext, new[] { source }, schemaProvider);
Assert.Equal(StatusColors.Published, source.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_default_color_if_not_found()
{
var source = PublishedContent();
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(Task.FromResult<StatusInfo>(null!));
var ctx = requestContext.WithResolveFlow(true);
await sut.EnrichAsync(ctx, new[] { source }, schemaProvider);
Assert.Equal(StatusColors.Draft, source.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_can_update()
{
var source = new ContentEntity { SchemaId = schemaId };
A.CallTo(() => contentWorkflow.CanUpdateAsync(source, requestContext.User))
.Returns(true);
var ctx = requestContext.WithResolveFlow(true);
await sut.EnrichAsync(ctx, new[] { source }, schemaProvider);
Assert.True(source.CanUpdate);
}
[Fact]
public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context()
{
requestContext.WithResolveFlow(false);
var source = new ContentEntity { SchemaId = schemaId };
var ctx = requestContext.WithResolveFlow(false);
await sut.EnrichAsync(ctx, new[] { source }, schemaProvider);
Assert.False(source.CanUpdate);
A.CallTo(() => contentWorkflow.CanUpdateAsync(source, requestContext.User))
.MustNotHaveHappened();
}
private ContentEntity PublishedContent()
{
return new ContentEntity { Status = Status.Published, SchemaId = schemaId };
}
}
}

80
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherAssetsTests.cs → backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs

@ -15,6 +15,8 @@ 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.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
@ -22,7 +24,7 @@ using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class ContentEnricherAssetsTests
public class ResolveAssetsTests
{
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
@ -30,10 +32,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
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 ProvideSchema schemaProvider;
private readonly Context requestContext;
private readonly ContentEnricher sut;
private readonly ResolveAssets sut;
public ContentEnricherAssetsTests()
public ResolveAssetsTests()
{
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId, Language.DE));
@ -56,17 +59,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => assetUrlGenerator.GenerateUrl(A<string>.Ignored))
.ReturnsLazily(new Func<string, string>(id => $"url/to/{id}"));
void SetupSchema(NamedId<Guid> id, Schema def)
schemaProvider = x =>
{
var schemaEntity = Mocks.Schema(appId, id, def);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, id.Id.ToString()))
.Returns(schemaEntity);
}
SetupSchema(schemaId, schemaDef);
if (x == schemaId.Id)
{
return Task.FromResult(Mocks.Schema(appId, schemaId, schemaDef));
}
else
{
throw new DomainObjectNotFoundException(x.ToString(), typeof(ISchemaEntity));
}
};
sut = new ContentEnricher(assetQuery, assetUrlGenerator, new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
sut = new ResolveAssets(assetUrlGenerator, assetQuery);
}
[Fact]
@ -78,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var document1 = CreateAsset(Guid.NewGuid(), 3, AssetType.Unknown);
var document2 = CreateAsset(Guid.NewGuid(), 4, AssetType.Unknown);
var source = new IContentEntity[]
var source = new[]
{
CreateContent(
new[] { document1.Id, image1.Id },
@ -91,14 +96,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => assetQuery.QueryAsync(A<Context>.That.Matches(x => x.IsNoAssetEnrichment()), null, A<Q>.That.Matches(x => x.Ids.Count == 4)))
.Returns(ResultList.CreateFrom(4, image1, image2, document1, document2));
var enriched = await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(requestContext, source, schemaProvider);
var enriched1 = enriched.ElementAt(0);
var enriched1 = source[0];
Assert.Contains(image1.Id, enriched1.CacheDependencies);
Assert.Contains(image1.Version, enriched1.CacheDependencies);
var enriched2 = enriched.ElementAt(1);
var enriched2 = source[1];
Assert.Contains(image2.Id, enriched2.CacheDependencies);
Assert.Contains(image2.Version, enriched2.CacheDependencies);
@ -113,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var document1 = CreateAsset(Guid.NewGuid(), 3, AssetType.Unknown);
var document2 = CreateAsset(Guid.NewGuid(), 4, AssetType.Unknown);
var source = new IContentEntity[]
var source = new[]
{
CreateContent(
new[] { document1.Id, image1.Id },
@ -126,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => assetQuery.QueryAsync(A<Context>.That.Matches(x => x.IsNoAssetEnrichment()), null, A<Q>.That.Matches(x => x.Ids.Count == 4)))
.Returns(ResultList.CreateFrom(4, image1, image2, document1, document2));
var enriched = await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(requestContext, source, schemaProvider);
Assert.Equal(
new NamedContentData()
@ -136,7 +141,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
$"url/to/{image1.Id}"))
.AddField("asset2",
new ContentFieldData()),
enriched.ElementAt(0).ReferenceData);
source[0].ReferenceData);
Assert.Equal(
new NamedContentData()
@ -146,20 +151,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
new ContentFieldData()
.AddValue("en",
$"url/to/{image2.Id}")),
enriched.ElementAt(1).ReferenceData);
source[1].ReferenceData);
}
[Fact]
public async Task Should_not_enrich_references_if_not_api_user()
{
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
var ctx = new Context(Mocks.ApiUser(), Mocks.App(appId));
await sut.EnrichAsync(ctx, source, schemaProvider);
Assert.Null(enriched.ElementAt(0).ReferenceData);
Assert.Null(source[0].ReferenceData);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, null, A<Q>.Ignored))
.MustNotHaveHappened();
@ -168,14 +175,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact]
public async Task Should_not_enrich_references_if_disabled()
{
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
var ctx = new Context(Mocks.FrontendUser(), Mocks.App(appId)).WithoutContentEnrichment(true);
Assert.Null(enriched.ElementAt(0).ReferenceData);
await sut.EnrichAsync(ctx, source, schemaProvider);
Assert.Null(source[0].ReferenceData);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, null, A<Q>.Ignored))
.MustNotHaveHappened();
@ -184,31 +193,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact]
public async Task Should_not_invoke_query_service_if_no_assets_found()
{
var source = new IContentEntity[]
var source = new[]
{
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, null, 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(requestContext, source, schemaProvider);
await sut.EnrichAsync(source, requestContext);
Assert.NotNull(source[0].ReferenceData);
A.CallTo(() => assetQuery.QueryAsync(A<Context>.Ignored, null, A<Q>.Ignored))
.MustNotHaveHappened();
}
private IEnrichedContentEntity CreateContent(Guid[] assets1, Guid[] assets2)
private ContentEntity CreateContent(Guid[] assets1, Guid[] assets2)
{
return new ContentEntity
{

103
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherReferencesTests.cs → backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs

@ -12,9 +12,9 @@ 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.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
@ -22,20 +22,18 @@ using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
public class ContentEnricherReferencesTests
public class ResolveReferencesTests
{
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> refSchemaId1 = NamedId.Of(Guid.NewGuid(), "my-ref1");
private readonly NamedId<Guid> refSchemaId2 = NamedId.Of(Guid.NewGuid(), "my-ref2");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ProvideSchema schemaProvider;
private readonly Context requestContext;
private readonly ContentEnricher sut;
private readonly ResolveReferences sut;
public ContentEnricherReferencesTests()
public ResolveReferencesTests()
{
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId, Language.DE));
@ -65,19 +63,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
})
.ConfigureFieldsInLists("ref1", "ref2");
void SetupSchema(NamedId<Guid> id, Schema def)
schemaProvider = x =>
{
var schemaEntity = Mocks.Schema(appId, id, def);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, id.Id.ToString()))
.Returns(schemaEntity);
}
SetupSchema(schemaId, schemaDef);
SetupSchema(refSchemaId1, refSchemaDef);
SetupSchema(refSchemaId2, refSchemaDef);
if (x == schemaId.Id)
{
return Task.FromResult(Mocks.Schema(appId, schemaId, schemaDef));
}
else if (x == refSchemaId1.Id)
{
return Task.FromResult(Mocks.Schema(appId, refSchemaId1, refSchemaDef));
}
else if (x == refSchemaId2.Id)
{
return Task.FromResult(Mocks.Schema(appId, refSchemaId2, refSchemaDef));
}
else
{
throw new DomainObjectNotFoundException(x.ToString(), typeof(ISchemaEntity));
}
};
sut = new ContentEnricher(assetQuery, assetUrlGenerator, new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
sut = new ResolveReferences(new Lazy<IContentQueryService>(() => contentQuery));
}
[Fact]
@ -88,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var ref2_1 = CreateRefContent(Guid.NewGuid(), 3, "ref2_1", 23, refSchemaId2);
var ref2_2 = CreateRefContent(Guid.NewGuid(), 4, "ref2_2", 29, refSchemaId2);
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new[] { ref1_1.Id }, new[] { ref2_1.Id }),
CreateContent(new[] { ref1_2.Id }, new[] { ref2_2.Id })
@ -97,9 +103,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4)))
.Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2));
var enriched = await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(requestContext, source, schemaProvider);
var enriched1 = enriched.ElementAt(0);
var enriched1 = source[0];
Assert.Contains(refSchemaId1.Id, enriched1.CacheDependencies);
Assert.Contains(refSchemaId2.Id, enriched1.CacheDependencies);
@ -110,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
Assert.Contains(ref2_1.Id, enriched1.CacheDependencies);
Assert.Contains(ref2_1.Version, enriched1.CacheDependencies);
var enriched2 = enriched.ElementAt(1);
var enriched2 = source[1];
Assert.Contains(refSchemaId1.Id, enriched2.CacheDependencies);
Assert.Contains(refSchemaId2.Id, enriched2.CacheDependencies);
@ -130,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var ref2_1 = CreateRefContent(Guid.NewGuid(), 3, "ref2_1", 23, refSchemaId2);
var ref2_2 = CreateRefContent(Guid.NewGuid(), 3, "ref2_2", 29, refSchemaId2);
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new[] { ref1_1.Id }, new[] { ref2_1.Id }),
CreateContent(new[] { ref1_2.Id }, new[] { ref2_2.Id })
@ -139,7 +145,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
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));
var enriched = await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(requestContext, source, schemaProvider);
Assert.Equal(
new NamedContentData()
@ -155,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
JsonValue.Object()
.Add("en", "ref2_1, 23")
.Add("de", "ref2_1, 23"))),
enriched.ElementAt(0).ReferenceData);
source[0].ReferenceData);
Assert.Equal(
new NamedContentData()
@ -171,7 +177,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
JsonValue.Object()
.Add("en", "ref2_2, 29")
.Add("de", "ref2_2, 29"))),
enriched.ElementAt(1).ReferenceData);
source[1].ReferenceData);
}
[Fact]
@ -182,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
var ref2_1 = CreateRefContent(Guid.NewGuid(), 3, "ref2_1", 23, refSchemaId2);
var ref2_2 = CreateRefContent(Guid.NewGuid(), 4, "ref2_2", 29, refSchemaId2);
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new[] { ref1_1.Id }, new[] { ref2_1.Id, ref2_2.Id }),
CreateContent(new[] { ref1_2.Id }, new[] { ref2_1.Id, ref2_2.Id })
@ -191,7 +197,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
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));
var enriched = await sut.EnrichAsync(source, requestContext);
await sut.EnrichAsync(requestContext, source, schemaProvider);
Assert.Equal(
new NamedContentData()
@ -207,7 +213,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
JsonValue.Object()
.Add("en", "2 Reference(s)")
.Add("de", "2 Reference(s)"))),
enriched.ElementAt(0).ReferenceData);
source[0].ReferenceData);
Assert.Equal(
new NamedContentData()
@ -223,20 +229,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
JsonValue.Object()
.Add("en", "2 Reference(s)")
.Add("de", "2 Reference(s)"))),
enriched.ElementAt(1).ReferenceData);
source[1].ReferenceData);
}
[Fact]
public async Task Should_not_enrich_references_if_not_api_user()
{
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
var ctx = new Context(Mocks.ApiUser(), Mocks.App(appId));
await sut.EnrichAsync(ctx, source, schemaProvider);
Assert.Null(enriched.ElementAt(0).ReferenceData);
Assert.Null(source[0].ReferenceData);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
@ -245,14 +253,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact]
public async Task Should_not_enrich_references_if_disabled()
{
var source = new IContentEntity[]
var source = new[]
{
CreateContent(new Guid[] { Guid.NewGuid() }, new Guid[0])
};
var enriched = await sut.EnrichAsync(source, new Context(Mocks.ApiUser(), Mocks.App(appId)));
var ctx = new Context(Mocks.FrontendUser(), Mocks.App(appId)).WithoutContentEnrichment(true);
Assert.Null(enriched.ElementAt(0).ReferenceData);
await sut.EnrichAsync(ctx, source, schemaProvider);
Assert.Null(source[0].ReferenceData);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
@ -261,31 +271,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact]
public async Task Should_not_invoke_query_service_if_no_references_found()
{
var source = new IContentEntity[]
var source = new[]
{
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(requestContext, source, schemaProvider);
await sut.EnrichAsync(source, requestContext);
Assert.NotNull(source[0].ReferenceData);
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<List<Guid>>.Ignored))
.MustNotHaveHappened();
}
private IEnrichedContentEntity CreateContent(Guid[] ref1, Guid[] ref2)
private ContentEntity CreateContent(Guid[] ref1, Guid[] ref2)
{
return new ContentEntity
{

83
backend/tools/TestSuite/TestSuite.ApiTests/ContentCleanupTests.cs

@ -0,0 +1,83 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.ClientLibrary.Management;
using TestSuite.Fixtures;
using TestSuite.Model;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public class ContentCleanupTests : IClassFixture<ClientFixture>
{
public ClientFixture _ { get; }
public ContentCleanupTests(ClientFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_cleanup_old_data_from_update_response()
{
var schemaName = $"schema-{DateTime.UtcNow.Ticks}";
// STEP 1: Create a schema.
var schema = await _.Schemas.PostSchemaAsync(_.AppName, new CreateSchemaDto
{
Name = schemaName,
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "number",
Properties = new NumberFieldPropertiesDto
{
IsRequired = true
}
},
new UpsertSchemaFieldDto
{
Name = "string",
Properties = new StringFieldPropertiesDto
{
IsRequired = false
}
}
},
IsPublished = true
});
var contents = _.ClientManager.GetClient<TestEntity, TestEntityData>(schemaName);
// STEP 2: Create a content for this schema.
var data = new TestEntityData { Number = 12, String = "hello" };
var content_1 = await contents.CreateAsync(data);
Assert.Equal(data.String, content_1.DataDraft.String);
// STEP 3: Delete a field from schema.
await _.Schemas.DeleteFieldAsync(_.AppName, schema.Name, schema.Fields.ElementAt(1).FieldId);
// STEP 4: Make any update.
var content_2 = await contents.ChangeStatusAsync(content_1.Id, "Published");
// Should not return deleted field.
Assert.Null(content_2.DataDraft.String);
}
}
}

4
backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs

@ -36,9 +36,7 @@ namespace TestSuite.Fixtures
{
try
{
var schemas = ClientManager.CreateSchemasClient();
await schemas.PostSchemaAsync(AppName, new CreateSchemaDto
await Schemas.PostSchemaAsync(AppName, new CreateSchemaDto
{
Name = SchemaName,
Fields = new List<UpsertSchemaFieldDto>

Loading…
Cancel
Save