Browse Source

Enrichment for status color.

pull/377/head
Sebastian Stehle 7 years ago
parent
commit
d7477239fc
  1. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  2. 47
      src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  3. 4
      src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs
  4. 10
      src/Squidex.Domain.Apps.Entities/Assets/AssetEnricher.cs
  5. 2
      src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs
  6. 15
      src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs
  7. 4
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEntityEnriched.cs
  9. 6
      src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs
  10. 41
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  11. 56
      src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs
  12. 8
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  13. 140
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  14. 10
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  15. 1
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs
  16. 8
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs
  17. 8
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs
  18. 4
      src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs
  19. 6
      src/Squidex.Domain.Apps.Entities/Contents/IContentEntityEnriched.cs
  20. 6
      src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs
  21. 6
      src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs
  22. 10
      src/Squidex.Infrastructure/CollectionExtensions.cs
  23. 2
      src/Squidex.Infrastructure/ResultList.cs
  24. 2
      src/Squidex.Web/UrlHelperExtensions.cs
  25. 2
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  26. 2
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  27. 2
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs
  28. 12
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  29. 21
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  30. 20
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  31. 13
      src/Squidex/Config/Domain/EntitiesServices.cs
  32. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs
  33. 84
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs
  34. 68
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs
  35. 10
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs
  36. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs
  37. 70
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
  38. 85
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs
  39. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  40. 18
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs
  41. 32
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs
  42. 32
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  43. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  44. 7
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
if (fullTextIds?.Count == 0) if (fullTextIds?.Count == 0)
{ {
return ResultList.Create<IContentEntity>(0); return ResultList.CreateFrom<IContentEntity>(0);
} }
return await contents.QueryAsync(schema, query, fullTextIds, status, inDraft, includeDraft); return await contents.QueryAsync(schema, query, fullTextIds, status, inDraft, includeDraft);

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

@ -66,33 +66,29 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
var existings = await assetQuery.QueryByHashAsync(createAsset.AppId.Id, createAsset.FileHash); var existings = await assetQuery.QueryByHashAsync(createAsset.AppId.Id, createAsset.FileHash);
AssetCreatedResult result = null;
foreach (var existing in existings) foreach (var existing in existings)
{ {
if (IsDuplicate(createAsset, existing)) if (IsDuplicate(createAsset, existing))
{ {
result = new AssetCreatedResult(existing, true); var result = new AssetCreatedResult(existing, true);
}
break; context.Complete(result);
return;
}
} }
if (result == null)
{
foreach (var tagGenerator in tagGenerators) foreach (var tagGenerator in tagGenerators)
{ {
tagGenerator.GenerateTags(createAsset, createAsset.Tags); tagGenerator.GenerateTags(createAsset, createAsset.Tags);
} }
var asset = (IEnrichedAssetEntity)await ExecuteAndAdjustTagsAsync(createAsset); await HandleCoreAsync(context, next);
result = new AssetCreatedResult(asset, false); var asset = context.PlainResult as IAssetEntityEnriched;
await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null); context.Complete(new AssetCreatedResult(asset, false));
}
context.Complete(result); await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null);
} }
finally finally
{ {
@ -109,11 +105,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
try try
{ {
var result = (IEnrichedAssetEntity)await ExecuteAndAdjustTagsAsync(updateAsset); await HandleCoreAsync(context, next);
context.Complete(result); var asset = context.PlainResult as IAssetEntity;
await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.FileVersion, null); await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), asset.FileVersion, null);
} }
finally finally
{ {
@ -123,34 +119,23 @@ namespace Squidex.Domain.Apps.Entities.Assets
break; break;
} }
case AssetCommand command:
{
var result = await ExecuteAndAdjustTagsAsync(command);
context.Complete(result);
break;
}
default: default:
await base.HandleAsync(context, next); await HandleCoreAsync(context, next);
break; break;
} }
} }
private async Task<object> ExecuteAndAdjustTagsAsync(AssetCommand command) private async Task HandleCoreAsync(CommandContext context, Func<Task> next)
{ {
var result = await ExecuteCommandAsync(command); await HandleAsync(context, next);
if (result is IAssetEntity asset) if (context.PlainResult is IAssetEntity asset && !(context.PlainResult is IAssetEntityEnriched))
{ {
var enriched = await assetEnricher.EnrichAsync(asset); var enriched = await assetEnricher.EnrichAsync(asset);
return enriched; context.Complete(enriched);
} }
return result;
} }
private static bool IsDuplicate(CreateAsset createAsset, IAssetEntity asset) private static bool IsDuplicate(CreateAsset createAsset, IAssetEntity asset)

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

@ -9,11 +9,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
public sealed class AssetCreatedResult public sealed class AssetCreatedResult
{ {
public IEnrichedAssetEntity Asset { get; } public IAssetEntityEnriched Asset { get; }
public bool IsDuplicate { get; } public bool IsDuplicate { get; }
public AssetCreatedResult(IEnrichedAssetEntity asset, bool isDuplicate) public AssetCreatedResult(IAssetEntityEnriched asset, bool isDuplicate)
{ {
Asset = asset; Asset = asset;

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

@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
@ -25,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
this.tagService = tagService; this.tagService = tagService;
} }
public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset) public async Task<IAssetEntityEnriched> EnrichAsync(IAssetEntity asset)
{ {
Guard.NotNull(asset, nameof(asset)); Guard.NotNull(asset, nameof(asset));
@ -34,11 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
return enriched[0]; return enriched[0];
} }
public async Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets) public async Task<IReadOnlyList<IAssetEntityEnriched>> EnrichAsync(IEnumerable<IAssetEntity> assets)
{ {
Guard.NotNull(assets, nameof(assets)); Guard.NotNull(assets, nameof(assets));
var results = new List<IEnrichedAssetEntity>(); using (Profiler.TraceMethod<AssetEnricher>())
{
var results = new List<IAssetEntityEnriched>();
foreach (var group in assets.GroupBy(x => x.AppId.Id)) foreach (var group in assets.GroupBy(x => x.AppId.Id))
{ {
@ -67,6 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
return results; return results;
} }
}
private async Task<Dictionary<string, string>> CalculateTags(IGrouping<System.Guid, IAssetEntity> group) private async Task<Dictionary<string, string>> CalculateTags(IGrouping<System.Guid, IAssetEntity> group)
{ {

2
src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public sealed class AssetEntity : IEnrichedAssetEntity public sealed class AssetEntity : IAssetEntityEnriched
{ {
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }

15
src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.OData; using Microsoft.OData;
@ -50,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
this.options = options.Value; this.options = options.Value;
} }
public async Task<IEnrichedAssetEntity> FindAssetAsync( Guid id) public async Task<IAssetEntityEnriched> FindAssetAsync( Guid id)
{ {
var asset = await assetRepository.FindAssetAsync(id); var asset = await assetRepository.FindAssetAsync(id);
@ -62,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
return null; return null;
} }
public async Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Guid appId, string hash) public async Task<IReadOnlyList<IAssetEntityEnriched>> QueryByHashAsync(Guid appId, string hash)
{ {
Guard.NotNull(hash, nameof(hash)); Guard.NotNull(hash, nameof(hash));
@ -71,14 +70,14 @@ namespace Squidex.Domain.Apps.Entities.Assets
return await assetEnricher.EnrichAsync(assets); return await assetEnricher.EnrichAsync(assets);
} }
public async Task<IResultList<IEnrichedAssetEntity>> QueryAsync(QueryContext context, Q query) public async Task<IResultList<IAssetEntityEnriched>> QueryAsync(QueryContext context, Q query)
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
Guard.NotNull(query, nameof(query)); Guard.NotNull(query, nameof(query));
IResultList<IAssetEntity> assets; IResultList<IAssetEntity> assets;
if (query.Ids != null) if (query.Ids != null && query.Ids.Count > 0)
{ {
assets = await QueryByIdsAsync(context, query); assets = await QueryByIdsAsync(context, query);
} }
@ -89,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var enriched = await assetEnricher.EnrichAsync(assets); var enriched = await assetEnricher.EnrichAsync(assets);
return ResultList.Create<IEnrichedAssetEntity>(assets.Total, enriched); return ResultList.Create(assets.Total, enriched);
} }
private async Task<IResultList<IAssetEntity>> QueryByQueryAsync(QueryContext context, Q query) private async Task<IResultList<IAssetEntity>> QueryByQueryAsync(QueryContext context, Q query)
@ -108,9 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
private static IResultList<IAssetEntity> Sort(IResultList<IAssetEntity> assets, IReadOnlyList<Guid> ids) private static IResultList<IAssetEntity> Sort(IResultList<IAssetEntity> assets, IReadOnlyList<Guid> ids)
{ {
var sorted = ids.Select(id => assets.FirstOrDefault(x => x.Id == id)).Where(x => x != null); return assets.SortSet(x => x.Id, ids);
return ResultList.Create(assets.Total, sorted);
} }
private Query ParseQuery(QueryContext context, string query) private Query ParseQuery(QueryContext context, string query)

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<IAssetEntityEnriched> EnrichAsync(IAssetEntity asset);
Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets); Task<IReadOnlyList<IAssetEntityEnriched>> EnrichAsync(IEnumerable<IAssetEntity> assets);
} }
} }

2
src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs → src/Squidex.Domain.Apps.Entities/Assets/IAssetEntityEnriched.cs

@ -9,7 +9,7 @@ using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IEnrichedAssetEntity : IAssetEntity public interface IAssetEntityEnriched : IAssetEntity
{ {
HashSet<string> TagNames { get; } HashSet<string> TagNames { get; }
} }

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

@ -16,10 +16,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
int DefaultPageSizeGraphQl { get; } int DefaultPageSizeGraphQl { get; }
Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Guid appId, string hash); Task<IReadOnlyList<IAssetEntityEnriched>> QueryByHashAsync(Guid appId, string hash);
Task<IResultList<IEnrichedAssetEntity>> QueryAsync(QueryContext contex, Q query); Task<IResultList<IAssetEntityEnriched>> QueryAsync(QueryContext contex, Q query);
Task<IEnrichedAssetEntity> FindAssetAsync(Guid id); Task<IAssetEntityEnriched> FindAssetAsync(Guid id);
} }
} }

41
src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentCommandMiddleware : GrainCommandMiddleware<ContentCommand, IContentGrain>
{
private readonly IContentEnricher contentEnricher;
public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher)
: base(grainFactory)
{
Guard.NotNull(contentEnricher, nameof(contentEnricher));
this.contentEnricher = contentEnricher;
}
public override async Task HandleAsync(CommandContext context, Func<Task> next)
{
await HandleAsync(context, next);
if (context.PlainResult is IContentEntity content)
{
var enriched = await contentEnricher.EnrichAsync(content);
context.Complete(enriched);
}
}
}
}

56
src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
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.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -15,6 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ContentEnricher : IContentEnricher public sealed class ContentEnricher : IContentEnricher
{ {
private const string DefaultColor = StatusColors.Draft;
private readonly IContentWorkflow contentWorkflow; private readonly IContentWorkflow contentWorkflow;
public ContentEnricher(IContentWorkflow contentWorkflow) public ContentEnricher(IContentWorkflow contentWorkflow)
@ -22,33 +26,65 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.contentWorkflow = contentWorkflow; this.contentWorkflow = contentWorkflow;
} }
public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents) public async Task<IContentEntityEnriched> EnrichAsync(IContentEntity content)
{ {
var results = new List<ContentEntity>(); Guard.NotNull(content, nameof(content));
var enriched = await EnrichAsync(Enumerable.Repeat(content, 1));
return enriched[0];
}
public async Task<IReadOnlyList<IContentEntityEnriched>> EnrichAsync(IEnumerable<IContentEntity> contents)
{
Guard.NotNull(contents, nameof(contents));
using (Profiler.TraceMethod<ContentEnricher>()) using (Profiler.TraceMethod<ContentEnricher>())
{ {
var cache = new Dictionary<Status, StatusInfo>(); var results = new List<ContentEntity>();
var cache = new Dictionary<(Guid, Status), StatusInfo>();
foreach (var content in contents) foreach (var content in contents)
{ {
var result = SimpleMapper.Map(content, new ContentEntity()); var result = SimpleMapper.Map(content, new ContentEntity());
if (!cache.TryGetValue(content.Status, out var info)) await ResolveColorAsync(content, result, cache);
{ await ResolveNextsAsync(content, result);
info = await contentWorkflow.GetInfoAsync(content.Status); await ResolveCanUpdateAsync(content, result);
results.Add(result);
}
return results;
}
}
cache[content.Status] = info; private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result)
{
result.CanUpdate = await contentWorkflow.CanUpdateAsync(content);
} }
result.StatusInfo = info; private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result)
{
result.Nexts = await contentWorkflow.GetNextsAsync(content); result.Nexts = await contentWorkflow.GetNextsAsync(content);
}
results.Add(result); private async Task ResolveColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache)
{
if (!cache.TryGetValue((content.SchemaId.Id, content.Status), out var info))
{
info = await contentWorkflow.GetInfoAsync(content.Status);
if (info == null)
{
info = new StatusInfo(content.Status, DefaultColor);
} }
cache[(content.SchemaId.Id, content.Status)] = info;
} }
return results; result.StatusColor = info.Color;
} }
} }
} }

8
src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ContentEntity : IEnrichedContentEntity public sealed class ContentEntity : IContentEntityEnriched
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
@ -38,10 +38,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
public Status Status { get; set; } public Status Status { get; set; }
public StatusInfo StatusInfo { get; set; }
public StatusInfo[] Nexts { get; set; } public StatusInfo[] Nexts { get; set; }
public string StatusColor { get; set; }
public bool CanUpdate { get; set; }
public bool IsPending { get; set; } public bool IsPending { get; set; }
} }
} }

140
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -34,10 +34,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentQueryService : IContentQueryService public sealed class ContentQueryService : IContentQueryService
{ {
private static readonly Status[] StatusPublishedOnly = { Status.Published }; private static readonly Status[] StatusPublishedOnly = { Status.Published };
private readonly IContentRepository contentRepository; private static readonly IResultList<IContentEntityEnriched> EmptyContents = ResultList.CreateFrom<IContentEntityEnriched>(0);
private readonly IContentVersionLoader contentVersionLoader;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly IAssetUrlGenerator assetUrlGenerator; private readonly IAssetUrlGenerator assetUrlGenerator;
private readonly IContentEnricher contentEnricher;
private readonly IContentRepository contentRepository;
private readonly IContentVersionLoader contentVersionLoader;
private readonly IScriptEngine scriptEngine; private readonly IScriptEngine scriptEngine;
private readonly ContentOptions options; private readonly ContentOptions options;
private readonly EdmModelBuilder modelBuilder; private readonly EdmModelBuilder modelBuilder;
@ -50,6 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentQueryService( public ContentQueryService(
IAppProvider appProvider, IAppProvider appProvider,
IAssetUrlGenerator assetUrlGenerator, IAssetUrlGenerator assetUrlGenerator,
IContentEnricher contentEnricher,
IContentRepository contentRepository, IContentRepository contentRepository,
IContentVersionLoader contentVersionLoader, IContentVersionLoader contentVersionLoader,
IScriptEngine scriptEngine, IScriptEngine scriptEngine,
@ -58,6 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(assetUrlGenerator, nameof(assetUrlGenerator)); Guard.NotNull(assetUrlGenerator, nameof(assetUrlGenerator));
Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contentRepository, nameof(contentRepository)); Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(contentVersionLoader, nameof(contentVersionLoader)); Guard.NotNull(contentVersionLoader, nameof(contentVersionLoader));
Guard.NotNull(modelBuilder, nameof(modelBuilder)); Guard.NotNull(modelBuilder, nameof(modelBuilder));
@ -66,6 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.appProvider = appProvider; this.appProvider = appProvider;
this.assetUrlGenerator = assetUrlGenerator; this.assetUrlGenerator = assetUrlGenerator;
this.contentEnricher = contentEnricher;
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
this.contentVersionLoader = contentVersionLoader; this.contentVersionLoader = contentVersionLoader;
this.modelBuilder = modelBuilder; this.modelBuilder = modelBuilder;
@ -73,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.scriptEngine = scriptEngine; this.scriptEngine = scriptEngine;
} }
public async Task<IContentEntity> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = -1) public async Task<IContentEntityEnriched> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = -1)
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
@ -83,25 +88,27 @@ namespace Squidex.Domain.Apps.Entities.Contents
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var isVersioned = version > EtagVersion.Empty; IContentEntity content;
var status = GetStatus(context);
var content = if (version > EtagVersion.Empty)
isVersioned ? {
await FindContentByVersionAsync(id, version) : content = await FindByVersionAsync(id, version);
await FindContentAsync(context, id, status, schema); }
else
{
content = await FindCoreAsync(context, id, schema);
}
if (content == null || (content.Status != Status.Published && !context.IsFrontendClient) || content.SchemaId.Id != schema.Id) if (content == null || (content.Status != Status.Published && !context.IsFrontendClient) || content.SchemaId.Id != schema.Id)
{ {
throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity));
} }
return Transform(context, schema, content); return await TransformAsync(context, schema, content);
} }
} }
public async Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string schemaIdOrName, Q query) public async Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, string schemaIdOrName, Q query)
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
@ -111,95 +118,88 @@ namespace Squidex.Domain.Apps.Entities.Contents
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var status = GetStatus(context);
IResultList<IContentEntity> contents; IResultList<IContentEntity> contents;
if (query.Ids?.Count > 0) if (query.Ids != null && query.Ids.Count > 0)
{ {
contents = await QueryAsync(context, schema, query.Ids.ToHashSet(), status); contents = await QueryByIdsAsync(context, query, schema);
contents = SortSet(contents, query.Ids);
} }
else else
{ {
var parsedQuery = ParseQuery(context, query.ODataQuery, schema); contents = await QueryByQueryAsync(context, query, schema);
contents = await QueryAsync(context, schema, parsedQuery, status);
} }
return Transform(context, schema, contents); return await TransformAsync(context, schema, contents);
} }
} }
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids) public async Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids)
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var status = GetStatus(context); if (ids == null || ids.Count == 0)
{
return EmptyContents;
}
List<IContentEntity> result; var results = new List<IContentEntityEnriched>();
if (ids?.Count > 0) var contents = await QueryCoreAsync(context, ids);
{
var contents = await QueryAsync(context, ids, status);
var permissions = context.User.Permissions(); var permissions = context.User.Permissions();
contents = contents.Where(x => HasPermission(permissions, x.Schema)).ToList(); foreach (var group in contents.GroupBy(x => x.Schema.Id))
{
var schema = group.First().Schema;
result = contents.Select(x => Transform(context, x.Schema, x.Content)).ToList(); if (HasPermission(permissions, schema))
result = SortList(result, ids).ToList();
}
else
{ {
result = new List<IContentEntity>(); var enriched = await TransformCoreAsync(context, schema, group.Select(x => x.Content));
results.AddRange(enriched);
}
} }
return result; return ResultList.Create(results.Count, results.SortList(x => x.Id, ids));
} }
} }
private IResultList<IContentEntity> Transform(QueryContext context, ISchemaEntity schema, IResultList<IContentEntity> contents) private async Task<IResultList<IContentEntityEnriched>> TransformAsync(QueryContext context, ISchemaEntity schema, IResultList<IContentEntity> contents)
{ {
var transformed = TransformCore(context, schema, contents); var transformed = await TransformCoreAsync(context, schema, contents);
return ResultList.Create(contents.Total, transformed); return ResultList.Create(contents.Total, transformed);
} }
private IContentEntity Transform(QueryContext context, ISchemaEntity schema, IContentEntity content) private async Task<IContentEntityEnriched> TransformAsync(QueryContext context, ISchemaEntity schema, IContentEntity content)
{
return TransformCore(context, schema, Enumerable.Repeat(content, 1)).FirstOrDefault();
}
private static IResultList<IContentEntity> SortSet(IResultList<IContentEntity> contents, IReadOnlyList<Guid> ids)
{ {
return ResultList.Create(contents.Total, SortList(contents, ids)); var transformed = await TransformCoreAsync(context, schema, Enumerable.Repeat(content, 1));
}
private static IEnumerable<IContentEntity> SortList(IEnumerable<IContentEntity> contents, IReadOnlyList<Guid> ids) return transformed[0];
{
return ids.Select(id => contents.FirstOrDefault(x => x.Id == id)).Where(x => x != null);
} }
private IEnumerable<IContentEntity> TransformCore(QueryContext context, ISchemaEntity schema, IEnumerable<IContentEntity> contents) private async Task<IReadOnlyList<IContentEntityEnriched>> TransformCoreAsync(QueryContext context, ISchemaEntity schema, IEnumerable<IContentEntity> contents)
{ {
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var results = new List<IContentEntityEnriched>();
var converters = GenerateConverters(context).ToArray(); var converters = GenerateConverters(context).ToArray();
var scriptText = schema.SchemaDef.Scripts.Query; var scriptText = schema.SchemaDef.Scripts.Query;
var scripting = !string.IsNullOrWhiteSpace(scriptText);
var isScripting = !string.IsNullOrWhiteSpace(scriptText); var enriched = await contentEnricher.EnrichAsync(contents);
foreach (var content in contents) foreach (var content in enriched)
{ {
var result = SimpleMapper.Map(content, new ContentEntity()); var result = SimpleMapper.Map(content, new ContentEntity());
if (result.Data != null) if (result.Data != null)
{ {
if (!context.IsFrontendClient && isScripting) if (!context.IsFrontendClient && scripting)
{ {
var ctx = new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id }; var ctx = new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id };
@ -218,8 +218,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.DataDraft = null; result.DataDraft = null;
} }
yield return result; results.Add(result);
} }
return results;
} }
} }
@ -344,32 +346,46 @@ namespace Squidex.Domain.Apps.Entities.Contents
} }
} }
private Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids, Status[] status) private async Task<IResultList<IContentEntity>> QueryByQueryAsync(QueryContext context, Q query, ISchemaEntity schema)
{
var parsedQuery = ParseQuery(context, query.ODataQuery, schema);
return await QueryCoreAsync(context, schema, parsedQuery);
}
private async Task<IResultList<IContentEntity>> QueryByIdsAsync(QueryContext context, Q query, ISchemaEntity schema)
{
var contents = await QueryCoreAsync(context, schema, query.Ids.ToHashSet());
return contents.SortSet(x => x.Id, query.Ids);
}
private Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryCoreAsync(QueryContext context, IReadOnlyList<Guid> ids)
{ {
return contentRepository.QueryAsync(context.App, status, new HashSet<Guid>(ids), ShouldIncludeDraft(context)); return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet<Guid>(ids), WithDraft(context));
} }
private Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, ISchemaEntity schema, Query query, Status[] status) private Task<IResultList<IContentEntity>> QueryCoreAsync(QueryContext context, ISchemaEntity schema, Query query)
{ {
return contentRepository.QueryAsync(context.App, schema, status, context.IsFrontendClient, query, ShouldIncludeDraft(context)); return contentRepository.QueryAsync(context.App, schema, GetStatus(context), context.IsFrontendClient, query, WithDraft(context));
} }
private Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, ISchemaEntity schema, HashSet<Guid> ids, Status[] status) private Task<IResultList<IContentEntity>> QueryCoreAsync(QueryContext context, ISchemaEntity schema, HashSet<Guid> ids)
{ {
return contentRepository.QueryAsync(context.App, schema, status, ids, ShouldIncludeDraft(context)); return contentRepository.QueryAsync(context.App, schema, GetStatus(context), ids, WithDraft(context));
} }
private Task<IContentEntity> FindContentAsync(QueryContext context, Guid id, Status[] status, ISchemaEntity schema) private Task<IContentEntity> FindCoreAsync(QueryContext context, Guid id, ISchemaEntity schema)
{ {
return contentRepository.FindContentAsync(context.App, schema, status, id, ShouldIncludeDraft(context)); return contentRepository.FindContentAsync(context.App, schema, GetStatus(context), id, WithDraft(context));
} }
private Task<IContentEntity> FindContentByVersionAsync(Guid id, long version) private Task<IContentEntity> FindByVersionAsync(Guid id, long version)
{ {
return contentVersionLoader.LoadAsync(id, version); return contentVersionLoader.LoadAsync(id, version);
} }
private static bool ShouldIncludeDraft(QueryContext context) private static bool WithDraft(QueryContext context)
{ {
return context.ApiStatus == StatusForApi.All || context.IsFrontendClient; return context.ApiStatus == StatusForApi.All || context.IsFrontendClient;
} }

10
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public sealed class GraphQLExecutionContext : QueryExecutionContext public sealed class GraphQLExecutionContext : QueryExecutionContext
{ {
private static readonly List<IEnrichedAssetEntity> EmptyAssets = new List<IEnrichedAssetEntity>(); private static readonly List<IAssetEntityEnriched> EmptyAssets = new List<IAssetEntityEnriched>();
private static readonly List<IContentEntity> EmptyContents = new List<IContentEntity>(); private static readonly List<IContentEntity> EmptyContents = new List<IContentEntity>();
private readonly IDataLoaderContextAccessor dataLoaderContextAccessor; private readonly IDataLoaderContextAccessor dataLoaderContextAccessor;
private readonly IDependencyResolver resolver; private readonly IDependencyResolver resolver;
@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
execution.UserContext = this; execution.UserContext = this;
} }
public override Task<IEnrichedAssetEntity> FindAssetAsync(Guid id) public override Task<IAssetEntityEnriched> FindAssetAsync(Guid id)
{ {
var dataLoader = GetAssetsLoader(); var dataLoader = GetAssetsLoader();
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return dataLoader.LoadAsync(id); return dataLoader.LoadAsync(id);
} }
public async Task<IReadOnlyList<IEnrichedAssetEntity>> GetReferencedAssetsAsync(IJsonValue value) public async Task<IReadOnlyList<IAssetEntityEnriched>> GetReferencedAssetsAsync(IJsonValue value)
{ {
var ids = ParseIds(value); var ids = ParseIds(value);
@ -95,9 +95,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return await dataLoader.LoadManyAsync(ids); return await dataLoader.LoadManyAsync(ids);
} }
private IDataLoader<Guid, IEnrichedAssetEntity> GetAssetsLoader() private IDataLoader<Guid, IAssetEntityEnriched> GetAssetsLoader()
{ {
return dataLoaderContextAccessor.Context.GetOrAddBatchLoader<Guid, IEnrichedAssetEntity>("Assets", return dataLoaderContextAccessor.Context.GetOrAddBatchLoader<Guid, IAssetEntityEnriched>("Assets",
async batch => async batch =>
{ {
var result = await GetReferencedAssetsAsync(new List<Guid>(batch)); var result = await GetReferencedAssetsAsync(new List<Guid>(batch));

1
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs

@ -7,7 +7,6 @@
using System; using System;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types

8
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class AssetGraphType : ObjectGraphType<IEnrichedAssetEntity> public sealed class AssetGraphType : ObjectGraphType<IAssetEntityEnriched>
{ {
public AssetGraphType(IGraphModel model) public AssetGraphType(IGraphModel model)
{ {
@ -167,7 +167,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
Name = "tags", Name = "tags",
ResolvedType = null, ResolvedType = null,
Resolver = Resolve(x => x.Tags), Resolver = Resolve(x => x.TagNames),
Description = "The asset tags.", Description = "The asset tags.",
Type = AllTypes.NonNullTagsType Type = AllTypes.NonNullTagsType
}); });
@ -186,9 +186,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = "An asset"; Description = "An asset";
} }
private static IFieldResolver Resolve(Func<IEnrichedAssetEntity, object> action) private static IFieldResolver Resolve(Func<IAssetEntityEnriched, object> action)
{ {
return new FuncFieldResolver<IEnrichedAssetEntity, object>(c => action(c.Source)); return new FuncFieldResolver<IAssetEntityEnriched, object>(c => action(c.Source));
} }
} }
} }

8
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs

@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class ContentGraphType : ObjectGraphType<IContentEntity> public sealed class ContentGraphType : ObjectGraphType<IContentEntityEnriched>
{ {
public void Initialize(IGraphModel model, ISchemaEntity schema, IComplexGraphType contentDataType) public void Initialize(IGraphModel model, ISchemaEntity schema, IComplexGraphType contentDataType)
{ {
@ -78,7 +78,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = $"The the status of the {schemaName} content." Description = $"The the status of the {schemaName} content."
}); });
/*
AddField(new FieldType AddField(new FieldType
{ {
Name = "statusColor", Name = "statusColor",
@ -86,7 +85,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Resolver = Resolve(x => x.StatusColor), Resolver = Resolve(x => x.StatusColor),
Description = $"The color status of the {schemaName} content." Description = $"The color status of the {schemaName} content."
}); });
*/
AddField(new FieldType AddField(new FieldType
{ {
@ -118,9 +116,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = $"The structure of a {schemaName} content type."; Description = $"The structure of a {schemaName} content type.";
} }
private static IFieldResolver Resolve(Func<IContentEntity, object> action) private static IFieldResolver Resolve(Func<IContentEntityEnriched, object> action)
{ {
return new FuncFieldResolver<IContentEntity, object>(c => action(c.Source)); return new FuncFieldResolver<IContentEntityEnriched, object>(c => action(c.Source));
} }
} }
} }

4
src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs

@ -12,6 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentEnricher public interface IContentEnricher
{ {
Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents); Task<IContentEntityEnriched> EnrichAsync(IContentEntity content);
Task<IReadOnlyList<IContentEntityEnriched>> EnrichAsync(IEnumerable<IContentEntity> contents);
} }
} }

6
src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs → src/Squidex.Domain.Apps.Entities/Contents/IContentEntityEnriched.cs

@ -9,9 +9,11 @@ using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IEnrichedContentEntity : IContentEntity public interface IContentEntityEnriched : IContentEntity
{ {
StatusInfo StatusInfo { get; } bool CanUpdate { get; }
string StatusColor { get; }
StatusInfo[] Nexts { get; } StatusInfo[] Nexts { get; }
} }

6
src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs

@ -17,11 +17,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
int DefaultPageSizeGraphQl { get; } int DefaultPageSizeGraphQl { get; }
Task<IReadOnlyList<IContentEntity>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids); Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids);
Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string schemaIdOrName, Q query); Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, string schemaIdOrName, Q query);
Task<IContentEntity> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any); Task<IContentEntityEnriched> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any);
Task<ISchemaEntity> GetSchemaOrThrowAsync(QueryContext context, string schemaIdOrName); Task<ISchemaEntity> GetSchemaOrThrowAsync(QueryContext context, string schemaIdOrName);
} }

6
src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public class QueryExecutionContext public class QueryExecutionContext
{ {
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>(); private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>();
private readonly ConcurrentDictionary<Guid, IEnrichedAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IEnrichedAssetEntity>(); private readonly ConcurrentDictionary<Guid, IAssetEntityEnriched> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntityEnriched>();
private readonly IContentQueryService contentQuery; private readonly IContentQueryService contentQuery;
private readonly IAssetQueryService assetQuery; private readonly IAssetQueryService assetQuery;
private readonly QueryContext context; private readonly QueryContext context;
@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.context = context; this.context = context;
} }
public virtual async Task<IEnrichedAssetEntity> FindAssetAsync(Guid id) public virtual async Task<IAssetEntityEnriched> FindAssetAsync(Guid id)
{ {
var asset = cachedAssets.GetOrDefault(id); var asset = cachedAssets.GetOrDefault(id);
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return result; return result;
} }
public virtual async Task<IReadOnlyList<IEnrichedAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids) public virtual async Task<IReadOnlyList<IAssetEntityEnriched>> GetReferencedAssetsAsync(ICollection<Guid> ids)
{ {
Guard.NotNull(ids, nameof(ids)); Guard.NotNull(ids, nameof(ids));

10
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -13,6 +13,16 @@ namespace Squidex.Infrastructure
{ {
public static class CollectionExtensions public static class CollectionExtensions
{ {
public static IResultList<T> SortSet<T, TKey>(this IResultList<T> input, Func<T, TKey> idProvider, IReadOnlyList<TKey> ids) where T : class
{
return ResultList.Create(input.Total, SortList(input, idProvider, ids));
}
public static IEnumerable<T> SortList<T, TKey>(this IEnumerable<T> input, Func<T, TKey> idProvider, IReadOnlyList<TKey> ids) where T : class
{
return ids.Select(id => input.FirstOrDefault(x => Equals(idProvider(x), id))).Where(x => x != null);
}
public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> source) public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> source)
{ {
foreach (var value in source) foreach (var value in source)

2
src/Squidex.Infrastructure/ResultList.cs

@ -27,7 +27,7 @@ namespace Squidex.Infrastructure
return new Impl<T>(items, total); return new Impl<T>(items, total);
} }
public static IResultList<T> Create<T>(long total, params T[] items) public static IResultList<T> CreateFrom<T>(long total, params T[] items)
{ {
return new Impl<T>(items, total); return new Impl<T>(items, total);
} }

2
src/Squidex.Web/UrlHelperExtensions.cs

@ -38,7 +38,7 @@ namespace Squidex.Web
public static string Url<T>(this Controller controller, Func<T, string> action, object values = null) where T : Controller public static string Url<T>(this Controller controller, Func<T, string> action, object values = null) where T : Controller
{ {
return controller.Url.Url<T>(action, values); return controller.Url.Url(action, values);
} }
} }
} }

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

@ -265,7 +265,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
{ {
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);
var result = context.Result<IEnrichedAssetEntity>(); var result = context.Result<IAssetEntityEnriched>();
var response = AssetDto.FromAsset(result, this, app); var response = AssetDto.FromAsset(result, this, app);
return response; return response;

2
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -118,7 +118,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
[JsonProperty("_meta")] [JsonProperty("_meta")]
public AssetMetadata Metadata { get; set; } public AssetMetadata Metadata { get; set; }
public static AssetDto FromAsset(IEnrichedAssetEntity asset, ApiController controller, string app, bool isDuplicate = false) public static AssetDto FromAsset(IAssetEntityEnriched asset, ApiController controller, string app, bool isDuplicate = false)
{ {
var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() });

2
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs

@ -37,7 +37,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
return Items.ToSurrogateKeys(); return Items.ToSurrogateKeys();
} }
public static AssetsDto FromAssets(IResultList<IEnrichedAssetEntity> assets, ApiController controller, string app) public static AssetsDto FromAssets(IResultList<IAssetEntityEnriched> assets, ApiController controller, string app)
{ {
var response = new AssetsDto var response = new AssetsDto
{ {

12
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Web; using Squidex.Web;
@ -127,9 +126,8 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
var context = Context(); var context = Context();
var contents = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids); var contents = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids);
var contentsList = ResultList.Create<IContentEntity>(contents.Count, contents);
var response = await ContentsDto.FromContentsAsync(contentsList, context, this, null, contentWorkflow); var response = await ContentsDto.FromContentsAsync(contents, context, this, null, contentWorkflow);
if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys) if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys)
{ {
@ -201,7 +199,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context(); var context = Context();
var content = await contentQuery.FindContentAsync(context, name, id); var content = await contentQuery.FindContentAsync(context, name, id);
var response = await ContentDto.FromContentAsync(context, content, contentWorkflow, this); var response = ContentDto.FromContent(context, content, this);
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.Value.EnableSurrogateKeys)
{ {
@ -237,7 +235,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context(); var context = Context();
var content = await contentQuery.FindContentAsync(context, name, id, version); var content = await contentQuery.FindContentAsync(context, name, id, version);
var response = await ContentDto.FromContentAsync(context, content, contentWorkflow, this); var response = ContentDto.FromContent(context, content, this);
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.Value.EnableSurrogateKeys)
{ {
@ -447,8 +445,8 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);
var result = context.Result<IContentEntity>(); var result = context.Result<IContentEntityEnriched>();
var response = await ContentDto.FromContentAsync(null, result, contentWorkflow, this); var response = ContentDto.FromContent(null, result, this);
return response; return response;
} }

21
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -7,7 +7,6 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
@ -85,11 +84,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary> /// </summary>
public long Version { get; set; } public long Version { get; set; }
public static ValueTask<ContentDto> FromContentAsync( public static ContentDto FromContent(QueryContext context, IContentEntityEnriched content, ApiController controller)
QueryContext context,
IContentEntity content,
IContentWorkflow contentWorkflow,
ApiController controller)
{ {
var response = SimpleMapper.Map(content, new ContentDto()); var response = SimpleMapper.Map(content, new ContentDto());
@ -109,14 +104,10 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
response.ScheduleJob = SimpleMapper.Map(content.ScheduleJob, new ScheduleJobDto()); response.ScheduleJob = SimpleMapper.Map(content.ScheduleJob, new ScheduleJobDto());
} }
return response.CreateLinksAsync(content, controller, content.AppId.Name, content.SchemaId.Name, contentWorkflow); return response.CreateLinksAsync(content, controller, content.AppId.Name, content.SchemaId.Name);
} }
private async ValueTask<ContentDto> CreateLinksAsync(IContentEntity content, private ContentDto CreateLinksAsync(IContentEntityEnriched content, ApiController controller, string app, string schema)
ApiController controller,
string app,
string schema,
IContentWorkflow contentWorkflow)
{ {
var values = new { app, name = schema, id = Id }; var values = new { app, name = schema, id = Id };
@ -144,7 +135,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
if (controller.HasPermission(Permissions.AppContentsUpdate, app, schema)) if (controller.HasPermission(Permissions.AppContentsUpdate, app, schema))
{ {
if (await contentWorkflow.CanUpdateAsync(content)) if (content.CanUpdate)
{ {
AddPutLink("update", controller.Url<ContentsController>(x => nameof(x.PutContent), values)); AddPutLink("update", controller.Url<ContentsController>(x => nameof(x.PutContent), values));
} }
@ -162,9 +153,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
AddDeleteLink("delete", controller.Url<ContentsController>(x => nameof(x.DeleteContent), values)); AddDeleteLink("delete", controller.Url<ContentsController>(x => nameof(x.DeleteContent), values));
} }
var nextStatuses = await contentWorkflow.GetNextsAsync(content); foreach (var next in content.Nexts)
foreach (var next in nextStatuses)
{ {
if (controller.HasPermission(Helper.StatusPermission(app, schema, next.Status))) if (controller.HasPermission(Helper.StatusPermission(app, schema, next.Status)))
{ {

20
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -47,20 +47,16 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
return Items.ToSurrogateKeys(); return Items.ToSurrogateKeys();
} }
public static async Task<ContentsDto> FromContentsAsync(IResultList<IContentEntity> contents, QueryContext context, public static async Task<ContentsDto> FromContentsAsync(IResultList<IContentEntityEnriched> contents,
ApiController controller, QueryContext context, ApiController controller, ISchemaEntity schema, IContentWorkflow contentWorkflow)
ISchemaEntity schema,
IContentWorkflow contentWorkflow)
{ {
var result = new ContentsDto var result = new ContentsDto
{ {
Total = contents.Total, Total = contents.Total,
Items = new ContentDto[contents.Count] Items = contents.Select(x => ContentDto.FromContent(context, x, controller)).ToArray()
}; };
await Task.WhenAll( await result.AssignStatusesAsync(contentWorkflow, schema);
result.AssignContentsAsync(contentWorkflow, contents, context, controller),
result.AssignStatusesAsync(contentWorkflow, schema));
return result.CreateLinks(controller, schema.AppId.Name, schema.SchemaDef.Name); return result.CreateLinks(controller, schema.AppId.Name, schema.SchemaDef.Name);
} }
@ -72,14 +68,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
Statuses = allStatuses.Select(StatusInfoDto.FromStatusInfo).ToArray(); Statuses = allStatuses.Select(StatusInfoDto.FromStatusInfo).ToArray();
} }
private async Task AssignContentsAsync(IContentWorkflow contentWorkflow, IResultList<IContentEntity> contents, QueryContext context, ApiController controller)
{
for (var i = 0; i < Items.Length; i++)
{
Items[i] = await ContentDto.FromContentAsync(context, contents[i], contentWorkflow, controller);
}
}
private ContentsDto CreateLinks(ApiController controller, string app, string schema) private ContentsDto CreateLinks(ApiController controller, string app, string schema)
{ {
if (schema != null) if (schema != null)

13
src/Squidex/Config/Domain/EntitiesServices.cs

@ -32,7 +32,6 @@ using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.Comments; using Squidex.Domain.Apps.Entities.Comments;
using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text;
@ -97,9 +96,15 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppProvider>() services.AddSingletonAs<AppProvider>()
.As<IAppProvider>(); .As<IAppProvider>();
services.AddSingletonAs<AssetEnricher>()
.As<IAssetEnricher>();
services.AddSingletonAs<AssetQueryService>() services.AddSingletonAs<AssetQueryService>()
.As<IAssetQueryService>(); .As<IAssetQueryService>();
services.AddSingletonAs<ContentEnricher>()
.As<IContentEnricher>();
services.AddSingletonAs<ContentQueryService>() services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>(); .As<IContentQueryService>();
@ -222,6 +227,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AssetCommandMiddleware>() services.AddSingletonAs<AssetCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<ContentCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<AppsByNameIndexCommandMiddleware>() services.AddSingletonAs<AppsByNameIndexCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
@ -231,9 +239,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<GrainCommandMiddleware<CommentsCommand, ICommentGrain>>() services.AddSingletonAs<GrainCommandMiddleware<CommentsCommand, ICommentGrain>>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<ContentCommand, IContentGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<SchemaCommand, ISchemaGrain>>() services.AddSingletonAs<GrainCommandMiddleware<SchemaCommand, ISchemaGrain>>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();

2
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs

@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
.Returns(assetGrain); .Returns(assetGrain);
A.CallTo(() => assetGrain.GetStateAsync(12)) A.CallTo(() => assetGrain.GetStateAsync(12))
.Returns(A.Fake<IAssetEntity>().AsJ()); .Returns(J.Of<IAssetEntity>(new AssetEntity()));
var result = await sut.CreateEnrichedEventAsync(envelope); var result = await sut.CreateEnrichedEventAsync(envelope);

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

@ -11,8 +11,10 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using FluentAssertions;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Tags; using Squidex.Domain.Apps.Entities.Tags;
@ -20,6 +22,7 @@ using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection;
using Xunit; using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
@ -52,8 +55,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
asset = new AssetGrain(Store, tagService, A.Dummy<ISemanticLog>()); asset = new AssetGrain(Store, tagService, A.Dummy<ISemanticLog>());
asset.ActivateAsync(Id).Wait(); asset.ActivateAsync(Id).Wait();
A.CallTo(() => assetEnricher.EnrichAsync(A<IAssetEntity>.Ignored))
.ReturnsLazily(() => SimpleMapper.Map(asset.Snapshot, new AssetEntity()));
A.CallTo(() => assetQuery.QueryByHashAsync(AppId, A<string>.Ignored)) A.CallTo(() => assetQuery.QueryByHashAsync(AppId, A<string>.Ignored))
.Returns(new List<IEnrichedAssetEntity>()); .Returns(new List<IAssetEntityEnriched>());
A.CallTo(() => grainFactory.GetGrain<IAssetGrain>(Id, null)) A.CallTo(() => grainFactory.GetGrain<IAssetGrain>(Id, null))
.Returns(asset); .Returns(asset);
@ -65,6 +71,40 @@ namespace Squidex.Domain.Apps.Entities.Assets
assetThumbnailGenerator, new[] { tagGenerator }); assetThumbnailGenerator, new[] { tagGenerator });
} }
[Fact]
public async Task Should_not_invoke_enricher_for_other_result()
{
var command = CreateCommand(new CreateApp());
var context = CreateContextForCommand(command);
context.Complete(12);
await sut.HandleAsync(context);
A.CallTo(() => assetEnricher.EnrichAsync(A<IAssetEntityEnriched>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_asset_result()
{
var result = new AssetEntity();
var command = CreateCommand(new CreateAsset());
var context = CreateContextForCommand(command);
context.Complete(result);
var enriched = new AssetEntity();
A.CallTo(() => assetEnricher.EnrichAsync(result))
.Returns(enriched);
await sut.HandleAsync(context);
Assert.Equal(enriched, context.Result<IAssetEntityEnriched>());
}
[Fact] [Fact]
public async Task Create_should_create_domain_object() public async Task Create_should_create_domain_object()
{ {
@ -78,12 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = context.Result<AssetCreatedResult>(); var result = context.Result<AssetCreatedResult>();
Assert.Equal(assetId, result.Asset.Id); result.Asset.Should().BeEquivalentTo(asset.Snapshot, x => x.ExcludingMissingMembers());
Assert.Contains("tag1", command.Tags);
Assert.Contains("tag2", command.Tags);
AssertAssetHasBeenUploaded(0, context.ContextId);
AssertAssetImageChecked();
} }
[Fact] [Fact]
@ -132,19 +167,21 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
[Fact] [Fact]
public async Task Create_should_resolve_tag_names_for_duplicate() public async Task Create_should_pass_through_duplicate()
{ {
var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file });
var context = CreateContextForCommand(command); var context = CreateContextForCommand(command);
SetupSameHashAsset(file.FileName, file.FileSize, out _); SetupSameHashAsset(file.FileName, file.FileSize, out var duplicate);
SetupImageInfo(); SetupImageInfo();
await sut.HandleAsync(context); await sut.HandleAsync(context);
// var result = context.Result<AssetCreatedResult>(); var result = context.Result<AssetCreatedResult>();
// Assert.Equal(new HashSet<string> { "foundTag1", "foundTag2" }, result.Tags); Assert.True(result.IsDuplicate);
result.Should().BeEquivalentTo(duplicate, x => x.ExcludingMissingMembers());
} }
[Fact] [Fact]
@ -193,7 +230,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
[Fact] [Fact]
public async Task Update_should_resolve_tags() public async Task Update_should_enrich_asset()
{ {
var command = CreateCommand(new UpdateAsset { AssetId = assetId, File = file }); var command = CreateCommand(new UpdateAsset { AssetId = assetId, File = file });
var context = CreateContextForCommand(command); var context = CreateContextForCommand(command);
@ -204,13 +241,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.HandleAsync(context); await sut.HandleAsync(context);
// var result = context.Result<AssetResult>(); var result = context.Result<IAssetEntityEnriched>();
// Assert.Equal(new HashSet<string> { "foundTag1", "foundTag2" }, result.Tags); result.Should().BeEquivalentTo(asset.Snapshot, x => x.ExcludingMissingMembers());
} }
[Fact] [Fact]
public async Task AnnotateAsset_should_resolve_tags() public async Task AnnotateAsset_should_enrich_asset()
{ {
var command = CreateCommand(new AnnotateAsset { AssetId = assetId, FileName = "newName" }); var command = CreateCommand(new AnnotateAsset { AssetId = assetId, FileName = "newName" });
var context = CreateContextForCommand(command); var context = CreateContextForCommand(command);
@ -221,9 +258,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.HandleAsync(context); await sut.HandleAsync(context);
// var result = context.Result<AssetResult>(); var result = context.Result<IAssetEntityEnriched>();
// Assert.Equal(new HashSet<string> { "foundTag1", "foundTag2" }, result.Tags); result.Should().BeEquivalentTo(asset.Snapshot, x => x.ExcludingMissingMembers());
} }
private Task ExecuteCreateAsync() private Task ExecuteCreateAsync()
@ -253,15 +290,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
.MustHaveHappened(); .MustHaveHappened();
} }
private void SetupSameHashAsset(string fileName, long fileSize, out IEnrichedAssetEntity existing) private void SetupSameHashAsset(string fileName, long fileSize, out IAssetEntityEnriched duplicate)
{ {
var temp = existing = A.Fake<IEnrichedAssetEntity>(); duplicate = new AssetEntity
{
A.CallTo(() => temp.FileName).Returns(fileName); FileName = fileName,
A.CallTo(() => temp.FileSize).Returns(fileSize); FileSize = fileSize
};
A.CallTo(() => assetQuery.QueryByHashAsync(A<Guid>.Ignored, A<string>.Ignored)) A.CallTo(() => assetQuery.QueryByHashAsync(A<Guid>.Ignored, A<string>.Ignored))
.Returns(new List<IEnrichedAssetEntity> { existing }); .Returns(new List<IAssetEntityEnriched> { duplicate });
} }
private void SetupImageInfo() private void SetupImageInfo()

68
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
using Xunit; using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
@ -15,6 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public class AssetEnricherTests public class AssetEnricherTests
{ {
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 AssetEnricher sut; private readonly AssetEnricher sut;
public AssetEnricherTests() public AssetEnricherTests()
@ -25,11 +29,73 @@ namespace Squidex.Domain.Apps.Entities.Assets
[Fact] [Fact]
public async Task Should_not_enrich_if_asset_contains_null_tags() public async Task Should_not_enrich_if_asset_contains_null_tags()
{ {
var source = new AssetEntity(); var source = new AssetEntity { AppId = appId };
var result = await sut.EnrichAsync(source); var result = await sut.EnrichAsync(source);
Assert.Empty(result.TagNames); Assert.Empty(result.TagNames);
} }
[Fact]
public async Task Should_enrich_asset_with_tag_names()
{
var source = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.IsSameSequenceAs("id1", "id2")))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2"
});
var result = await sut.EnrichAsync(source);
Assert.Equal(new HashSet<string> { "name1", "name2" }, result.TagNames);
}
[Fact]
public async Task Should_enrich_multiple_assets_with_tag_names()
{
var source1 = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
var source2 = new AssetEntity
{
Tags = new HashSet<string>
{
"id2",
"id3"
},
AppId = appId
};
A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.IsSameSequenceAs("id1", "id2", "id3")))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2",
["id3"] = "name3"
});
var result = await sut.EnrichAsync(new[] { source1, source2 });
Assert.Equal(new HashSet<string> { "name1", "name2" }, result[0].TagNames);
Assert.Equal(new HashSet<string> { "name2", "name3" }, result[1].TagNames);
}
} }
} }

10
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs

@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
.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)))
.Returns(new List<IEnrichedAssetEntity> { enriched }); .Returns(new List<IAssetEntityEnriched> { enriched });
var result = await sut.QueryByHashAsync(appId.Id, "hash"); var result = await sut.QueryByHashAsync(appId.Id, "hash");
@ -104,10 +104,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
var ids = HashSet.Of(found1.Id, found2.Id); var ids = HashSet.Of(found1.Id, found2.Id);
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.Create(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)))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 }); .Returns(new List<IAssetEntityEnriched> { enriched1, enriched2 });
var result = await sut.QueryAsync(context, Q.Empty.WithIds(ids)); var result = await sut.QueryAsync(context, Q.Empty.WithIds(ids));
@ -126,10 +126,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
var enriched2 = new AssetEntity(); var enriched2 = new AssetEntity();
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.Ignored)) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.Ignored))
.Returns(ResultList.Create(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)))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 }); .Returns(new List<IAssetEntityEnriched> { enriched1, enriched2 });
var result = await sut.QueryAsync(context, Q.Empty); var result = await sut.QueryAsync(context, Q.Empty);

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs

@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
.Returns(contentGrain); .Returns(contentGrain);
A.CallTo(() => contentGrain.GetStateAsync(12)) A.CallTo(() => contentGrain.GetStateAsync(12))
.Returns(A.Fake<IContentEntity>().AsJ()); .Returns(J.Of<IContentEntity>(new ContentEntity { SchemaId = SchemaMatch }));
var result = await sut.CreateEnrichedEventAsync(envelope); var result = await sut.CreateEnrichedEventAsync(envelope);

70
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -0,0 +1,70 @@
// ==========================================================================
// 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 Orleans;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Commands;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentState>
{
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly Guid contentId = Guid.NewGuid();
private readonly ContentCommandMiddleware sut;
protected override Guid Id
{
get { return contentId; }
}
public ContentCommandMiddlewareTests()
{
sut = new ContentCommandMiddleware(A.Fake<IGrainFactory>(), contentEnricher);
}
[Fact]
public async Task Should_not_invoke_enricher_for_other_result()
{
var command = CreateCommand(new CreateContent());
var context = CreateContextForCommand(command);
context.Complete(12);
await sut.HandleAsync(context);
A.CallTo(() => contentEnricher.EnrichAsync(A<IContentEntityEnriched>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_content_result()
{
var result = new ContentEntity();
var command = CreateCommand(new CreateContent());
var context = CreateContextForCommand(command);
context.Complete(result);
var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(result))
.Returns(enriched);
await sut.HandleAsync(context);
Assert.Equal(enriched, context.Result<IContentEntityEnriched>());
}
}
}

85
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs

@ -0,0 +1,85 @@
// ==========================================================================
// 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.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentEnricherTests
{
private readonly IContentWorkflow workflow = A.Fake<IContentWorkflow>();
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ContentEnricher sut;
public ContentEnricherTests()
{
sut = new ContentEnricher(workflow);
}
[Fact]
public async Task Should_enrich_content_with_status_color()
{
var source = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(source);
Assert.Equal(StatusColors.Published, result.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_default_color_if_not_found()
{
var source = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.Returns(Task.FromResult<StatusInfo>(null));
var result = await sut.EnrichAsync(source);
Assert.Equal(StatusColors.Draft, result.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_can_update()
{
var source = new ContentEntity { SchemaId = schemaId };
A.CallTo(() => workflow.CanUpdateAsync(source))
.Returns(true);
var result = await sut.EnrichAsync(source);
Assert.True(result.CanUpdate);
}
[Fact]
public async Task Should_enrich_multiple_contents_and_cache_color()
{
var source1 = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
var source2 = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(new[] { source1, source2 });
Assert.Equal(StatusColors.Published, result[0].StatusColor);
Assert.Equal(StatusColors.Published, result[1].StatusColor);
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.MustHaveHappenedOnceExactly();
}
}
}

31
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -25,6 +25,7 @@ using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
@ -36,6 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public class ContentQueryServiceTests public class ContentQueryServiceTests
{ {
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IContentVersionLoader contentVersionLoader = A.Fake<IContentVersionLoader>(); private readonly IContentVersionLoader contentVersionLoader = A.Fake<IContentVersionLoader>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>(); private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
@ -76,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => schema.AppId).Returns(appId); A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef); A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
SetupEnricher();
context = QueryContext.Create(app, user); context = QueryContext.Create(app, user);
var options = Options.Create(new ContentOptions { DefaultPageSize = 30 }); var options = Options.Create(new ContentOptions { DefaultPageSize = 30 });
@ -83,6 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
sut = new ContentQueryService( sut = new ContentQueryService(
appProvider, appProvider,
urlGenerator, urlGenerator,
contentEnricher,
contentRepository, contentRepository,
contentVersionLoader, contentVersionLoader,
scriptEngine, scriptEngine,
@ -520,6 +525,17 @@ namespace Squidex.Domain.Apps.Entities.Contents
.Returns((ISchemaEntity)null); .Returns((ISchemaEntity)null);
} }
private void SetupEnricher()
{
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<IContentEntity>>.Ignored))
.ReturnsLazily(x =>
{
var input = (IEnumerable<IContentEntity>)x.Arguments[0];
return Task.FromResult<IReadOnlyList<IContentEntityEnriched>>(input.Select(c => SimpleMapper.Map(c, new ContentEntity())).ToList());
});
}
private IContentEntity CreateContent(Guid id) private IContentEntity CreateContent(Guid id)
{ {
return CreateContent(id, Status.Published); return CreateContent(id, Status.Published);
@ -527,13 +543,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
private IContentEntity CreateContent(Guid id, Status status) private IContentEntity CreateContent(Guid id, Status status)
{ {
var content = A.Fake<IContentEntity>(); var content = new ContentEntity
{
A.CallTo(() => content.Id).Returns(id); Id = id,
A.CallTo(() => content.Data).Returns(contentData); Data = contentData,
A.CallTo(() => content.DataDraft).Returns(contentData); DataDraft = contentData,
A.CallTo(() => content.Status).Returns(status); SchemaId = schemaId,
A.CallTo(() => content.SchemaId).Returns(schemaId); Status = status,
};
return content; return content;
} }

18
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task Should_throw_exception_if_no_state_returned() public async Task Should_throw_exception_if_no_state_returned()
{ {
A.CallTo(() => grain.GetStateAsync(10)) A.CallTo(() => grain.GetStateAsync(10))
.Returns(new J<IContentEntity>(null)); .Returns(J.Of<IContentEntity>(null));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
} }
@ -42,13 +42,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_throw_exception_if_state_has_other_version() public async Task Should_throw_exception_if_state_has_other_version()
{ {
var entity = A.Fake<IContentEntity>(); var content = new ContentEntity { Version = 5 };
A.CallTo(() => entity.Version)
.Returns(5);
A.CallTo(() => grain.GetStateAsync(10)) A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity)); .Returns(J.Of<IContentEntity>(content));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
} }
@ -56,17 +53,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_return_content_from_state() public async Task Should_return_content_from_state()
{ {
var entity = A.Fake<IContentEntity>(); var content = new ContentEntity { Version = 10 };
A.CallTo(() => entity.Version)
.Returns(10);
A.CallTo(() => grain.GetStateAsync(10)) A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity)); .Returns(J.Of<IContentEntity>(content));
var result = await sut.LoadAsync(id, 10); var result = await sut.LoadAsync(id, 10);
Assert.Same(entity, result); Assert.Same(content, result);
} }
} }
} }

32
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions; using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Xunit; using Xunit;
@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_check_is_valid_next() public async Task Should_check_is_valid_next()
{ {
var entity = CreateContent(Status.Published); var content = new ContentEntity { Status = Status.Published };
var result = await sut.CanMoveToAsync(entity, Status.Draft); var result = await sut.CanMoveToAsync(content, Status.Draft);
Assert.True(result); Assert.True(result);
} }
@ -40,9 +39,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_be_able_to_update_published() public async Task Should_be_able_to_update_published()
{ {
var entity = CreateContent(Status.Published); var content = new ContentEntity { Status = Status.Published };
var result = await sut.CanUpdateAsync(entity); var result = await sut.CanUpdateAsync(content);
Assert.True(result); Assert.True(result);
} }
@ -50,9 +49,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_be_able_to_update_draft() public async Task Should_be_able_to_update_draft()
{ {
var entity = CreateContent(Status.Published); var content = new ContentEntity { Status = Status.Published };
var result = await sut.CanUpdateAsync(entity); var result = await sut.CanUpdateAsync(content);
Assert.True(result); Assert.True(result);
} }
@ -60,9 +59,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_not_be_able_to_update_archived() public async Task Should_not_be_able_to_update_archived()
{ {
var entity = CreateContent(Status.Archived); var content = new ContentEntity { Status = Status.Archived };
var result = await sut.CanUpdateAsync(entity); var result = await sut.CanUpdateAsync(content);
Assert.False(result); Assert.False(result);
} }
@ -70,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_get_next_statuses_for_draft() public async Task Should_get_next_statuses_for_draft()
{ {
var content = CreateContent(Status.Draft); var content = new ContentEntity { Status = Status.Draft };
var expected = new[] var expected = new[]
{ {
@ -86,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_get_next_statuses_for_archived() public async Task Should_get_next_statuses_for_archived()
{ {
var content = CreateContent(Status.Archived); var content = new ContentEntity { Status = Status.Archived };
var expected = new[] var expected = new[]
{ {
@ -101,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_get_next_statuses_for_published() public async Task Should_get_next_statuses_for_published()
{ {
var content = CreateContent(Status.Published); var content = new ContentEntity { Status = Status.Published };
var expected = new[] var expected = new[]
{ {
@ -128,14 +127,5 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.Should().BeEquivalentTo(expected); result.Should().BeEquivalentTo(expected);
} }
private IContentEntity CreateContent(Status status)
{
var content = A.Fake<IContentEntity>();
A.CallTo(() => content.Status).Returns(status);
return content;
}
} }
} }

32
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var asset = CreateAsset(Guid.NewGuid()); var asset = CreateAsset(Guid.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query"))) A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query")))
.Returns(ResultList.Create(0, asset)); .Returns(ResultList.CreateFrom(0, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -138,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var asset = CreateAsset(Guid.NewGuid()); var asset = CreateAsset(Guid.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query"))) A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query")))
.Returns(ResultList.Create(10, asset)); .Returns(ResultList.CreateFrom(10, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -213,7 +213,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", assetId.ToString()); }".Replace("<ID>", assetId.ToString());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId))) A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId)))
.Returns(ResultList.Create(1, asset)); .Returns(ResultList.CreateFrom(1, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -302,7 +302,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var content = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty); var content = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5"))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5")))
.Returns(ResultList.Create(0, content)); .Returns(ResultList.CreateFrom(0, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -443,7 +443,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var content = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty); var content = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5"))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5")))
.Returns(ResultList.Create(10, content)); .Returns(ResultList.CreateFrom(10, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -549,7 +549,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -641,7 +641,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -737,10 +737,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.Ignored)) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.Ignored))
.Returns(ResultList.Create(0, contentRef)); .Returns(ResultList.CreateFrom(0, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -795,10 +795,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.Ignored)) A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.Ignored))
.Returns(ResultList.Create(0, assetRef)); .Returns(ResultList.CreateFrom(0, assetRef));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -851,10 +851,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", assetId2.ToString()); }".Replace("<ID>", assetId2.ToString());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId1))) A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId1)))
.Returns(ResultList.Create(0, asset1)); .Returns(ResultList.CreateFrom(0, asset1));
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId2))) A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId2)))
.Returns(ResultList.Create(0, asset2)); .Returns(ResultList.CreateFrom(0, asset2));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query1 }, new GraphQLQuery { Query = query2 }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query1 }, new GraphQLQuery { Query = query2 });
@ -910,7 +910,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -948,7 +948,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -994,7 +994,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString()); }".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId))) A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content)); .Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sut = CreateSut(); sut = CreateSut();
} }
protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null, NamedContentData dataDraft = null) protected static IContentEntityEnriched CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null, NamedContentData dataDraft = null)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
@ -158,13 +158,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
Data = data, Data = data,
DataDraft = dataDraft, DataDraft = dataDraft,
Status = Status.Draft, Status = Status.Draft,
StatusInfo = new StatusInfo(Status.Draft, "red") StatusColor = "red"
}; };
return content; return content;
} }
protected static IEnrichedAssetEntity CreateAsset(Guid id) protected static IAssetEntityEnriched CreateAsset(Guid id)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();

7
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -270,12 +270,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
private IContentEntity CreateContent(Status status, bool isPending) private IContentEntity CreateContent(Status status, bool isPending)
{ {
var content = A.Fake<IContentEntity>(); return new ContentEntity { Status = status, IsPending = isPending };
A.CallTo(() => content.Status).Returns(status);
A.CallTo(() => content.IsPending).Returns(isPending);
return content;
} }
} }
} }

Loading…
Cancel
Save