Browse Source

Merge pull request #388 from Squidex/feature/reference-enrichment

Feature/reference enrichment
pull/390/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
b22cc05a17
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  2. 1
      src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs
  3. 5
      src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs
  4. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  5. 12
      src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  6. 4
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  7. 121
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  8. 15
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs
  9. 11
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs
  10. 13
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  11. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs
  12. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  13. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  14. 9
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  15. 168
      src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs
  16. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  17. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  18. 20
      src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs
  19. 5
      src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs
  20. 2
      src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs
  21. 2
      src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs
  22. 16
      src/Squidex.Domain.Apps.Entities/Context.cs
  23. 7
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs
  24. 1
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  25. 1
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs
  26. 5
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  27. 1
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  28. 1
      src/Squidex/Areas/Api/Controllers/History/HistoryController.cs
  29. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs
  30. 1
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs
  31. 7
      src/Squidex/Config/Domain/EntitiesServices.cs
  32. 2
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  33. 30
      src/Squidex/app/features/content/shared/content-item.component.ts
  34. 34
      src/Squidex/app/features/content/shared/references-dropdown.component.ts
  35. 4
      src/Squidex/app/features/schemas/pages/schema/forms/field-form-common.component.ts
  36. 15
      src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.html
  37. 3
      src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.ts
  38. 2
      src/Squidex/app/shared/services/contents.service.spec.ts
  39. 11
      src/Squidex/app/shared/services/contents.service.ts
  40. 1
      src/Squidex/app/shared/services/schemas.types.ts
  41. 106
      src/Squidex/app/shared/state/contents.forms.spec.ts
  42. 105
      src/Squidex/app/shared/state/contents.forms.ts
  43. 2
      src/Squidex/appsettings.json
  44. 57
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
  45. 102
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs
  46. 23
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByUserIndexCommandMiddlewareTests.cs
  47. 11
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs
  48. 21
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs
  49. 28
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs
  50. 13
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
  51. 215
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherReferencesTests.cs
  52. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs
  53. 23
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs
  54. 131
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  55. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs
  56. 61
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
  57. 52
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  58. 34
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  59. 57
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs
  60. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs
  61. 25
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/GrainTextIndexerTests.cs
  62. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs
  63. 29
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs
  64. 14
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs
  65. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexCommandMiddlewareTests.cs
  66. 2
      tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs
  67. 67
      tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs

2
src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public ContentFieldData AddValue(object value)
{
return AddJsonValue(InvariantPartitioning.Instance.Master.Key, JsonValue.Create(value));
return AddJsonValue(InvariantPartitioning.Key, JsonValue.Create(value));
}
public ContentFieldData AddValue(string key, object value)

1
src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs

@ -9,7 +9,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;

5
src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs

@ -15,6 +15,7 @@ namespace Squidex.Domain.Apps.Core
public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem
{
public static readonly InvariantPartitioning Instance = new InvariantPartitioning();
public static readonly string Key = "iv";
public int Count
{
@ -28,7 +29,7 @@ namespace Squidex.Domain.Apps.Core
string IFieldPartitionItem.Key
{
get { return "iv"; }
get { return Key; }
}
string IFieldPartitionItem.Name
@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Core
public bool TryGetItem(string key, out IFieldPartitionItem item)
{
var isFound = string.Equals(key, "iv", StringComparison.OrdinalIgnoreCase);
var isFound = string.Equals(key, Key, StringComparison.OrdinalIgnoreCase);
item = isFound ? this : null;

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

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public int? MaxItems { get; set; }
public bool ResolveReference { get; set; }
public bool AllowDuplicates { get; set; }
public ReferencesFieldEditor Editor { get; set; }

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

@ -6,6 +6,8 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
@ -51,5 +53,15 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
return schema.Properties.Label.WithFallback(schema.TypeName());
}
public static IEnumerable<IField<ReferencesFieldProperties>> ResolvingReferences(this Schema schema)
{
return schema.Fields.OfType<IField<ReferencesFieldProperties>>()
.Where(x =>
x.Properties.SchemaId != Guid.Empty &&
x.Properties.ResolveReference &&
x.Properties.MaxItems == 1 &&
(x.Properties.IsListField || schema.Fields.Count == 1));
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static FieldConverter ResolveInvariant(LanguagesConfig config)
{
var codeForInvariant = InvariantPartitioning.Instance.Master.Key;
var codeForInvariant = InvariantPartitioning.Key;
var codeForMasterLanguage = config.Master.Language.Iso2Code;
return (data, field) =>
@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static FieldConverter ResolveLanguages(LanguagesConfig config)
{
var codeForInvariant = InvariantPartitioning.Instance.Master.Key;
var codeForInvariant = InvariantPartitioning.Key;
return (data, field) =>
{

121
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -8,6 +8,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
@ -17,31 +19,132 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ContentReferencesExtensions
{
public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, Schema schema)
public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, Schema schema, Ids strategy = Ids.All)
{
Guard.NotNull(schema, nameof(schema));
var foundReferences = new HashSet<Guid>();
foreach (var field in schema.Fields)
{
var fieldData = source.GetOrDefault(field.Id);
var ids = source.GetReferencedIds(field, strategy);
if (fieldData == null)
foreach (var id in ids)
{
continue;
yield return id;
}
}
}
public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, IField field, Ids strategy = Ids.All)
{
Guard.NotNull(field, nameof(field));
foreach (var partitionValue in fieldData.Where(x => x.Value.Type != JsonValueType.Null))
if (source.TryGetValue(field.Id, out var fieldData))
{
foreach (var partitionValue in fieldData)
{
var ids = field.ExtractReferences(partitionValue.Value);
var ids = field.GetReferencedIds(partitionValue.Value, strategy);
foreach (var id in ids.Where(x => foundReferences.Add(x)))
foreach (var id in ids)
{
yield return id;
}
}
}
}
public static IEnumerable<Guid> GetReferencedIds(this NamedContentData source, Schema schema, Ids strategy = Ids.All)
{
Guard.NotNull(schema, nameof(schema));
return GetReferencedIds(source, schema.Fields, strategy);
}
public static IEnumerable<Guid> GetReferencedIds(this NamedContentData source, IEnumerable<IField> fields, Ids strategy = Ids.All)
{
Guard.NotNull(fields, nameof(fields));
foreach (var field in fields)
{
var ids = source.GetReferencedIds(field, strategy);
foreach (var id in ids)
{
yield return id;
}
}
}
public static IEnumerable<Guid> GetReferencedIds(this NamedContentData source, IField field, Ids strategy = Ids.All)
{
Guard.NotNull(field, nameof(field));
if (source.TryGetValue(field.Name, out var fieldData))
{
foreach (var partitionValue in fieldData)
{
var ids = field.GetReferencedIds(partitionValue.Value, strategy);
foreach (var id in ids)
{
yield return id;
}
}
}
}
public static JsonObject FormatReferences(this NamedContentData data, Schema schema, LanguagesConfig languages, string separator = ", ")
{
Guard.NotNull(schema, nameof(schema));
var result = JsonValue.Object();
foreach (var language in languages)
{
result[language.Key] = JsonValue.Create(data.FormatReferenceFields(schema, language.Key, separator));
}
return result;
}
private static string FormatReferenceFields(this NamedContentData data, Schema schema, string partition, string separator)
{
Guard.NotNull(schema, nameof(schema));
var sb = new StringBuilder();
void AddValue(object value)
{
if (sb.Length > 0)
{
sb.Append(separator);
}
sb.Append(value);
}
var referenceFields = schema.Fields.Where(x => x.RawProperties.IsReferenceField);
if (!referenceFields.Any())
{
referenceFields = schema.Fields.Take(1);
}
foreach (var referenceField in referenceFields)
{
if (data.TryGetValue(referenceField.Name, out var fieldData))
{
if (fieldData.TryGetValue(partition, out var value))
{
AddValue(value);
}
else if (fieldData.TryGetValue(InvariantPartitioning.Key, out var value2))
{
AddValue(value2);
}
}
}
return sb.ToString();
}
}
}

15
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs

@ -0,0 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public enum Ids
{
All,
ContentOnly
}
}

11
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs

@ -14,14 +14,14 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ReferencesExtensions
{
public static IEnumerable<Guid> ExtractReferences(this IField field, IJsonValue value)
public static IEnumerable<Guid> GetReferencedIds(this IField field, IJsonValue value, Ids strategy = Ids.All)
{
return ReferencesExtractor.ExtractReferences(field, value);
return ReferencesExtractor.ExtractReferences(field, value, strategy);
}
public static IJsonValue CleanReferences(this IField field, IJsonValue value, ICollection<Guid> oldReferences)
{
if (value.Type == JsonValueType.Null)
if (IsNull(value))
{
return value;
}
@ -29,6 +29,11 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return ReferencesCleaner.CleanReferences(field, value, oldReferences);
}
private static bool IsNull(IJsonValue value)
{
return value == null || value.Type == JsonValueType.Null;
}
public static JsonArray ToJsonArray(this HashSet<Guid> ids)
{
var result = JsonValue.Array();

13
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -16,15 +16,18 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>>
{
private readonly IJsonValue value;
private readonly Ids strategy;
private ReferencesExtractor(IJsonValue value)
private ReferencesExtractor(IJsonValue value, Ids strategy)
{
this.value = value;
this.strategy = strategy;
}
public static IEnumerable<Guid> ExtractReferences(IField field, IJsonValue value)
public static IEnumerable<Guid> ExtractReferences(IField field, IJsonValue value, Ids strategy)
{
return field.Accept(new ReferencesExtractor(value));
return field.Accept(new ReferencesExtractor(value, strategy));
}
public IEnumerable<Guid> Visit(IArrayField field)
@ -39,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
if (item.TryGetValue(nestedField.Name, out var nestedValue))
{
result.AddRange(nestedField.Accept(new ReferencesExtractor(nestedValue)));
result.AddRange(nestedField.Accept(new ReferencesExtractor(nestedValue, strategy)));
}
}
}
@ -59,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
var ids = value.ToGuidSet();
if (field.Properties.SchemaId != Guid.Empty)
if (strategy == Ids.All && field.Properties.SchemaId != Guid.Empty)
{
ids.Add(field.Properties.SchemaId);
}

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
}
else if (index == 1)
{
if (!property.Equals(InvariantPartitioning.Instance.Master.Key, StringComparison.OrdinalIgnoreCase))
if (!property.Equals(InvariantPartitioning.Key, StringComparison.OrdinalIgnoreCase))
{
sb.Append("(");
sb.Append(property);

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
var count = context.Path.Count();
if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Instance.Master.Key)))
if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Key)))
{
FilterNode filter = null;

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

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public static List<Guid> ToReferencedIds(this IdContentData data, Schema schema)
{
return data.GetReferencedIds(schema).ToList();
return data.GetReferencedIds(schema).Distinct().ToList();
}
public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds, IJsonSerializer serializer)

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

@ -17,22 +17,25 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentCommandMiddleware : GrainCommandMiddleware<ContentCommand, IContentGrain>
{
private readonly IContentEnricher contentEnricher;
private readonly IContextProvider contextProvider;
public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher)
public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher, IContextProvider contextProvider)
: base(grainFactory)
{
Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contextProvider, nameof(contextProvider));
this.contentEnricher = contentEnricher;
this.contextProvider = contextProvider;
}
public override async Task HandleAsync(CommandContext context, Func<Task> next)
{
await base.HandleAsync(context, next);
if (context.Command is SquidexCommand command && context.PlainResult is IContentEntity content && NotEnriched(context))
if (context.PlainResult is IContentEntity content && NotEnriched(context))
{
var enriched = await contentEnricher.EnrichAsync(content, command.User);
var enriched = await contentEnricher.EnrichAsync(content, contextProvider.Context);
context.Complete(enriched);
}

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

@ -8,10 +8,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection;
@ -20,60 +23,171 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentEnricher : IContentEnricher
{
private const string DefaultColor = StatusColors.Draft;
private static readonly ILookup<Guid, IEnrichedContentEntity> EmptyReferences = Enumerable.Empty<IEnrichedContentEntity>().ToLookup(x => x.Id);
private readonly Lazy<IContentQueryService> contentQuery;
private readonly IContentWorkflow contentWorkflow;
private readonly IContextProvider contextProvider;
public ContentEnricher(IContentWorkflow contentWorkflow, IContextProvider contextProvider)
private IContentQueryService ContentQuery
{
get { return contentQuery.Value; }
}
public ContentEnricher(Lazy<IContentQueryService> contentQuery, IContentWorkflow contentWorkflow)
{
Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(contentWorkflow, nameof(contentWorkflow));
Guard.NotNull(contextProvider, nameof(contextProvider));
this.contentQuery = contentQuery;
this.contentWorkflow = contentWorkflow;
this.contextProvider = contextProvider;
}
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, ClaimsPrincipal user)
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context)
{
Guard.NotNull(content, nameof(content));
var enriched = await EnrichAsync(Enumerable.Repeat(content, 1), user);
var enriched = await EnrichAsync(Enumerable.Repeat(content, 1), context);
return enriched[0];
}
public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, ClaimsPrincipal user)
public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context)
{
Guard.NotNull(contents, nameof(contents));
Guard.NotNull(user, nameof(user));
Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<ContentEnricher>())
{
var results = new List<ContentEntity>();
var cache = new Dictionary<(Guid, Status), StatusInfo>();
if (contents.Any())
{
var cache = new Dictionary<(Guid, Status), StatusInfo>();
foreach (var content in contents)
{
var result = SimpleMapper.Map(content, new ContentEntity());
await ResolveColorAsync(content, result, cache);
if (ShouldEnrichWithStatuses(context))
{
await ResolveNextsAsync(content, result, context);
await ResolveCanUpdateAsync(content, result);
}
results.Add(result);
}
if (ShouldEnrichWithReferences(context))
{
foreach (var group in results.GroupBy(x => x.SchemaId.Id))
{
await ResolveReferencesAsync(group.Key, group, context);
}
}
}
return results;
}
}
private async Task ResolveReferencesAsync(Guid schemaId, IEnumerable<ContentEntity> contents, Context context)
{
var schema = await ContentQuery.GetSchemaOrThrowAsync(context, schemaId.ToString());
var references = await GetReferencesAsync(schema, contents, context);
var formatted = new Dictionary<IContentEntity, JsonObject>();
foreach (var field in schema.SchemaDef.ResolvingReferences())
{
foreach (var content in contents)
{
var result = SimpleMapper.Map(content, new ContentEntity());
if (content.ReferenceData == null)
{
content.ReferenceData = new NamedContentData();
}
content.ReferenceData.GetOrAddNew(field.Name);
}
await ResolveColorAsync(content, result, cache);
try
{
var referencedSchemaId = field.Properties.SchemaId;
var referencedSchema = await ContentQuery.GetSchemaOrThrowAsync(context, referencedSchemaId.ToString());
if (ShouldEnrichWithStatuses())
foreach (var content in contents)
{
await ResolveNextsAsync(content, result, user);
await ResolveCanUpdateAsync(content, result);
var fieldReference = content.ReferenceData[field.Name];
if (content.DataDraft.TryGetValue(field.Name, out var fieldData))
{
foreach (var partitionValue in fieldData)
{
var referencedContents =
field.GetReferencedIds(partitionValue.Value, Ids.ContentOnly)
.Select(x => references[x])
.SelectMany(x => x)
.ToList();
if (referencedContents.Count == 1)
{
var value =
formatted.GetOrAdd(referencedContents[0],
x => x.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig));
fieldReference.AddJsonValue(partitionValue.Key, value);
}
else if (referencedContents.Count > 1)
{
var value = CreateFallback(context, referencedContents);
fieldReference.AddJsonValue(partitionValue.Key, value);
}
}
}
}
results.Add(result);
}
catch (DomainObjectNotFoundException)
{
continue;
}
}
}
return results;
private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents)
{
var text = $"{referencedContents.Count} Reference(s)";
var value = JsonValue.Object();
foreach (var language in context.App.LanguagesConfig)
{
value.Add(language.Key, text);
}
return value;
}
private bool ShouldEnrichWithStatuses()
private async Task<ILookup<Guid, IEnrichedContentEntity>> GetReferencesAsync(ISchemaEntity schema, IEnumerable<ContentEntity> contents, Context context)
{
return contextProvider.Context.IsFrontendClient || contextProvider.Context.IsResolveFlow();
var ids = new HashSet<Guid>();
foreach (var content in contents)
{
ids.AddRange(content.DataDraft.GetReferencedIds(schema.SchemaDef.ResolvingReferences(), Ids.ContentOnly));
}
if (ids.Count > 0)
{
var references = await ContentQuery.QueryAsync(context.Clone().WithNoEnrichment(true), ids.ToList());
return references.ToLookup(x => x.Id);
}
else
{
return EmptyReferences;
}
}
private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result)
@ -81,9 +195,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.CanUpdate = await contentWorkflow.CanUpdateAsync(content);
}
private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result, ClaimsPrincipal user)
private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result, Context context)
{
result.Nexts = await contentWorkflow.GetNextsAsync(content, user);
result.Nexts = await contentWorkflow.GetNextsAsync(content, context.User);
}
private async Task ResolveColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache)
@ -107,5 +221,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
return info.Color;
}
private static bool ShouldEnrichWithStatuses(Context context)
{
return context.IsFrontendClient || context.IsResolveFlow();
}
private static bool ShouldEnrichWithReferences(Context context)
{
return context.IsFrontendClient && !context.IsNoEnrichment();
}
}
}

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

@ -36,6 +36,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
public NamedContentData DataDraft { get; set; }
public NamedContentData ReferenceData { get; set; }
public Status Status { get; set; }
public StatusInfo[] Nexts { get; set; }

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

@ -186,7 +186,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var scriptText = schema.SchemaDef.Scripts.Query;
var scripting = !string.IsNullOrWhiteSpace(scriptText);
var enriched = await contentEnricher.EnrichAsync(contents, context.User);
var enriched = await contentEnricher.EnrichAsync(contents, context);
foreach (var content in enriched)
{

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

@ -19,8 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Contents
private const string HeaderLanguages = "X-Languages";
private const string HeaderResolveFlow = "X-ResolveFlow";
private const string HeaderResolveAssetUrls = "X-Resolve-Urls";
private const string HeaderNoEnrichment = "X-NoEnrichment";
private static readonly char[] Separators = { ',', ';' };
public static bool IsNoEnrichment(this Context context)
{
return context.Headers.ContainsKey(HeaderNoEnrichment);
}
public static Context WithNoEnrichment(this Context context, bool value = true)
{
if (value)
{
context.Headers[HeaderNoEnrichment] = "1";
}
else
{
context.Headers.Remove(HeaderNoEnrichment);
}
return context;
}
public static bool IsUnpublished(this Context context)
{
return context.Headers.ContainsKey(HeaderUnpublished);

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

@ -6,15 +6,14 @@
// ==========================================================================
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IContentEnricher
{
Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, ClaimsPrincipal user);
Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context);
Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, ClaimsPrincipal user);
Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context);
}
}

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

@ -16,5 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
string StatusColor { get; }
StatusInfo[] Nexts { get; }
NamedContentData ReferenceData { get; }
}
}

2
src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs

@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
private static readonly TimeSpan CommitDelay = TimeSpan.FromSeconds(10);
private static readonly MergeScheduler MergeScheduler = new ConcurrentMergeScheduler();
private static readonly Analyzer Analyzer = new MultiLanguageAnalyzer(Version);
private static readonly string[] Invariant = { InvariantPartitioning.Instance.Master.Key };
private static readonly string[] Invariant = { InvariantPartitioning.Key };
private readonly SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
private readonly IAssetStore assetStore;
private IDisposable timer;

16
src/Squidex.Domain.Apps.Entities/Context.cs

@ -28,6 +28,11 @@ namespace Squidex.Domain.Apps.Entities
get { return User?.Permissions() ?? PermissionSet.Empty; }
}
public bool IsFrontendClient
{
get { return User != null && User.IsInClient(DefaultClients.Frontend); }
}
public Context()
{
}
@ -39,9 +44,16 @@ namespace Squidex.Domain.Apps.Entities
App = app;
}
public bool IsFrontendClient
public Context Clone()
{
get { return User != null && User.IsInClient(DefaultClients.Frontend); }
var clone = new Context(User, App);
foreach (var kvp in Headers)
{
clone.Headers[kvp.Key] = kvp.Value;
}
return clone;
}
}
}

7
src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs

@ -218,6 +218,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
nameof(properties.MinItems),
nameof(properties.MaxItems));
}
if (properties.ResolveReference && properties.MaxItems != 1)
{
yield return new ValidationError("Can only resolve references when MaxItems is 1.",
nameof(properties.ResolveReference),
nameof(properties.MaxItems));
}
}
public IEnumerable<ValidationError> Visit(StringFieldProperties properties)

1
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security;
using Squidex.Shared;

1
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs

@ -10,7 +10,6 @@ using System.Linq;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Web;

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

@ -49,6 +49,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary>
public object DataDraft { get; set; }
/// <summary>
/// The reference data for the frontend UI.
/// </summary>
public NamedContentData ReferenceData { get; set; }
/// <summary>
/// Indicates if the draft data is pending.
/// </summary>

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

@ -8,7 +8,6 @@
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;

1
src/Squidex/Areas/Api/Controllers/History/HistoryController.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.History.Models;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared;
using Squidex.Web;

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

@ -28,6 +28,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary>
public bool AllowDuplicates { get; set; }
/// <summary>
/// True to resolve references in the content list.
/// </summary>
public bool ResolveReference { get; set; }
/// <summary>
/// The editor that is used to manage this field.
/// </summary>

1
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs

@ -8,7 +8,6 @@
using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Web;

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

@ -99,11 +99,14 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AssetEnricher>()
.As<IAssetEnricher>();
services.AddSingletonAs<ContentEnricher>()
.As<IContentEnricher>();
services.AddSingletonAs<AssetQueryService>()
.As<IAssetQueryService>();
services.AddSingletonAs<ContentEnricher>()
.As<IContentEnricher>();
services.AddSingletonAs(c => new Lazy<IContentQueryService>(() => c.GetRequiredService<IContentQueryService>()))
.AsSelf();
services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>();

2
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -169,6 +169,8 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
this.contentsState.create(value, publish)
.subscribe(() => {
this.contentForm.submitCompleted({ noReset: true });
this.back();
}, error => {
this.contentForm.submitFailed(error);

30
src/Squidex/app/features/content/shared/content-item.component.ts

@ -13,13 +13,11 @@ import {
ContentsState,
fadeAnimation,
FieldDto,
FieldFormatter,
fieldInvariant,
getContentValue,
ModalModel,
PatchContentForm,
RootFieldDto,
SchemaDetailsDto,
Types
SchemaDetailsDto
} from '@app/shared';
/* tslint:disable:component-selector */
@ -152,13 +150,9 @@ export class ContentItemComponent implements OnChanges {
this.values = [];
for (let field of this.schemaFields) {
const value = this.getRawValue(field);
const { value, formatted } = getContentValue(this.content, this.language, field);
if (Types.isUndefined(value)) {
this.values.push('');
} else {
this.values.push(FieldFormatter.format(field, value, true));
}
this.values.push(formatted);
if (this.patchForm) {
const formControl = this.patchForm.form.controls[field.name];
@ -170,21 +164,7 @@ export class ContentItemComponent implements OnChanges {
}
}
private getRawValue(field: RootFieldDto): any {
const contentField = this.content.dataDraft[field.name];
if (contentField) {
if (field.isLocalizable) {
return contentField[this.language.iso2Code];
} else {
return contentField[fieldInvariant];
}
}
return undefined;
}
public trackByField(index: number, field: FieldDto) {
public trackByField(field: FieldDto) {
return field.fieldId + this.schema.id;
}
}

34
src/Squidex/app/features/content/shared/references-dropdown.component.ts

@ -15,11 +15,9 @@ import {
AppsState,
ContentDto,
ContentsService,
FieldFormatter,
fieldInvariant,
getContentValue,
ImmutableArray,
MathHelper,
RootFieldDto,
SchemaDetailsDto,
SchemasService,
StatefulControlComponent,
@ -125,32 +123,12 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
return ImmutableArray.empty();
}
function getRawValue(field: RootFieldDto, data: any, language: AppLanguageDto): any {
const contentField = data[field.name];
if (contentField) {
if (field.isLocalizable) {
return contentField[language.iso2Code];
} else {
return contentField[fieldInvariant];
}
}
return undefined;
}
return contents.map(content => {
const values: any[] = [];
for (let field of schema.referenceFields) {
const value = getRawValue(field, content.data, this.languageField);
if (!Types.isUndefined(value)) {
values.push(FieldFormatter.format(field, value, false));
}
}
const name = values.join(', ');
const name =
schema.referenceFields
.map(f => getContentValue(content, this.languageField, f, false))
.map(v => v.formatted)
.join(', ');
return { name, id: content.id };
});

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

@ -55,7 +55,7 @@ import { FieldDto } from '@app/shared';
</div>
<div class="form-group row" *ngIf="field.properties.isContentField">
<div class="col-6 offset-3">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldListfield" formControlName="isListField" />
<label class="form-check-label" for="{{field.fieldId}}_fieldListfield">
@ -70,7 +70,7 @@ import { FieldDto } from '@app/shared';
</div>
<div class="form-group row" *ngIf="field.properties.isContentField">
<div class="col-6 offset-3">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldReferencefield" formControlName="isReferenceField" />
<label class="form-check-label" for="{{field.fieldId}}_fieldReferencefield">

15
src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.html

@ -19,4 +19,19 @@
</label>
</div>
</div>
<div class="form-group row" *ngIf="field.properties.isContentField">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldResolveReferences" formControlName="resolveReference" />
<label class="form-check-label" for="{{field.fieldId}}_fieldResolveReferences">
Resolve reference
</label>
</div>
<sqx-form-hint>
Check this item to resolve display names for referenced items for the content list.<br />Can only be checked when the MaxItems field in the validation tab is set to 1.
</sqx-form-hint>
</div>
</div>
</div>

3
src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.ts

@ -30,5 +30,8 @@ export class ReferencesUIComponent implements OnInit {
new FormControl(this.properties.editor, [
Validators.required
]));
this.editForm.setControl('resolveReference',
new FormControl(this.properties.resolveReference));
}
}

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

@ -366,6 +366,7 @@ describe('ContentsService', () => {
isPending: true,
data: {},
dataDraft: {},
referenceData: {},
version: `${id}`,
_links: {
update: { method: 'PUT', href: `/contents/id${id}` }
@ -389,5 +390,6 @@ export function createContent(id: number, suffix = '') {
true,
{},
{},
{},
new Version(`${id}`));
}

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

@ -55,6 +55,11 @@ export class ContentsDto extends ResultSet<ContentDto> {
}
}
export type ContentReferencesValue = { [partition: string]: string };
export type ContentReferences = { [fieldName: string ]: ContentFieldData<ContentReferencesValue> };
export type ContentFieldData<T = any> = { [partition: string]: T };
export type ContentData = { [fieldName: string ]: ContentFieldData };
export class ContentDto {
public readonly _links: ResourceLinks;
@ -77,8 +82,9 @@ export class ContentDto {
public readonly lastModifiedBy: string,
public readonly scheduleJob: ScheduleDto | null,
public readonly isPending: boolean,
public readonly data: object | any,
public readonly dataDraft: object,
public readonly data: ContentData | undefined,
public readonly dataDraft: ContentData,
public readonly referenceData: ContentReferences,
public readonly version: Version
) {
this._links = links;
@ -299,5 +305,6 @@ function parseContent(response: any) {
response.isPending === true,
response.data,
response.dataDraft,
response.referenceData,
new Version(response.version.toString()));
}

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

@ -327,6 +327,7 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
public readonly maxItems?: number;
public readonly editor: string;
public readonly schemaId?: string;
public readonly resolveReference?: boolean;
public readonly allowDuplicates?: boolean;
public get isSortable() {

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

@ -16,7 +16,10 @@ import {
FieldPropertiesDto,
FieldValidatorsFactory,
GeolocationFieldPropertiesDto,
getContentValue,
HtmlValue,
JsonFieldPropertiesDto,
LanguageDto,
NumberFieldPropertiesDto,
ReferencesFieldPropertiesDto,
RootFieldDto,
@ -25,7 +28,6 @@ import {
StringFieldPropertiesDto,
TagsFieldPropertiesDto
} from '@app/shared/internal';
import { HtmlValue } from './contents.forms';
describe('SchemaDetailsDto', () => {
it('should return label as display name', () => {
@ -314,7 +316,7 @@ describe('NumberField', () => {
});
it('should format to number', () => {
expect(FieldFormatter.format(field, 42)).toEqual(42);
expect(FieldFormatter.format(field, 42)).toEqual('42');
});
it('should format to stars if html allowed', () => {
@ -344,7 +346,7 @@ describe('NumberField', () => {
it('should not format to stars if html not allowed', () => {
const field2 = createField(new NumberFieldPropertiesDto('Stars'));
expect(FieldFormatter.format(field2, 3, false)).toEqual(3);
expect(FieldFormatter.format(field2, 3, false)).toEqual('3');
});
it('should return default value for default properties', () => {
@ -400,10 +402,106 @@ describe('StringField', () => {
});
});
describe('GetContentValue', () => {
const language = new LanguageDto('en', 'English');
const fieldInvariant = createField(new NumberFieldPropertiesDto('Input'), 1, 'invariant');
const fieldLocalized = createField(new NumberFieldPropertiesDto('Input'));
it('should resolve invariant field from references value', () => {
const content: any = {
referenceData: {
field1: {
iv: {
en: '13'
}
}
}
};
const result = getContentValue(content, language, fieldInvariant);
expect(result).toEqual({ value: '13', formatted: '13' });
});
it('should resolve localized field from references value', () => {
const content: any = {
referenceData: {
field1: {
en: {
en: '13'
}
}
}
};
const result = getContentValue(content, language, fieldLocalized);
expect(result).toEqual({ value: '13', formatted: '13' });
});
it('should return default value if reference field not found', () => {
const content: any = {
referenceData: {
field1: {
iv: {
en: '13'
}
}
}
};
const result = getContentValue(content, language, fieldLocalized);
expect(result).toEqual({ value: '- No Value -', formatted: '- No Value -' });
});
it('should resolve invariant field', () => {
const content: any = {
dataDraft: {
field1: {
iv: 13
}
}
};
const result = getContentValue(content, language, fieldInvariant);
expect(result).toEqual({ value: 13, formatted: '13' });
});
it('should resolve localized field', () => {
const content: any = {
dataDraft: {
field1: {
en: 13
}
}
};
const result = getContentValue(content, language, fieldLocalized);
expect(result).toEqual({ value: 13, formatted: '13' });
});
it('should return default values if field not found', () => {
const content: any = {
dataDraft: {
other: {
en: 13
}
}
};
const result = getContentValue(content, language, fieldLocalized);
expect(result).toEqual({ value: undefined, formatted: '' });
});
});
function createSchema(properties: SchemaPropertiesDto, index = 1, fields: RootFieldDto[]) {
return new SchemaDetailsDto({}, 'id' + index, 'schema' + index, '', properties, false, true, null!, null!, null!, null!, null!, fields);
}
function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'languages') {
function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'language') {
return new RootFieldDto({}, index, 'field' + index, properties, partitioning);
}

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

@ -19,6 +19,8 @@ import {
value$
} from '@app/framework';
import { ContentDto, ContentReferencesValue } from '../services/contents.service';
import { LanguageDto } from '../services/languages.service';
import { AppLanguageDto } from './../services/app-languages.service';
import { FieldDto, RootFieldDto, SchemaDetailsDto } from './../services/schemas.service';
import {
@ -56,7 +58,58 @@ export class SaveQueryForm extends Form<FormGroup, any> {
}
}
export class FieldFormatter implements FieldPropertiesVisitor<any> {
export type FieldValue = string | HtmlValue;
export function getContentValue(content: ContentDto, language: LanguageDto, field: RootFieldDto, allowHtml = true): { value: any, formatted: FieldValue } {
if (content.referenceData) {
const reference = content.referenceData[field.name];
if (reference) {
let fieldValue: ContentReferencesValue;
if (field.isLocalizable) {
fieldValue = reference[language.iso2Code];
} else {
fieldValue = reference[fieldInvariant];
}
let value: string | undefined =
fieldValue ?
fieldValue[language.iso2Code] :
undefined;
value = value || '- No Value -';
return { value, formatted: value };
}
}
const contentField = content.dataDraft[field.name];
if (contentField) {
let value: any;
if (field.isLocalizable) {
value = contentField[language.iso2Code];
} else {
value = contentField[fieldInvariant];
}
let formatted: any;
if (Types.isUndefined(value)) {
formatted = value || '';
} else {
formatted = FieldFormatter.format(field, value, allowHtml);
}
return { value, formatted };
}
return { value: undefined, formatted: '' };
}
export class FieldFormatter implements FieldPropertiesVisitor<FieldValue> {
constructor(
private readonly value: any,
private readonly allowHtml: boolean
@ -71,7 +124,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
return field.properties.accept(new FieldFormatter(value, allowHtml));
}
public visitDateTime(properties: DateTimeFieldPropertiesDto): string | any {
public visitDateTime(properties: DateTimeFieldPropertiesDto): FieldValue {
try {
const parsed = DateTime.parseISO_UTC(this.value);
@ -85,7 +138,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
}
}
public visitArray(properties: ArrayFieldPropertiesDto): string {
public visitArray(_: ArrayFieldPropertiesDto): string {
if (this.value.length) {
return `${this.value.length} Item(s)`;
} else {
@ -93,7 +146,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
}
}
public visitAssets(properties: AssetsFieldPropertiesDto): string {
public visitAssets(_: AssetsFieldPropertiesDto): string {
if (this.value.length) {
return `${this.value.length} Asset(s)`;
} else {
@ -101,7 +154,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
}
}
public visitReferences(properties: ReferencesFieldPropertiesDto): string {
public visitReferences(_: ReferencesFieldPropertiesDto): string {
if (this.value.length) {
return `${this.value.length} Reference(s)`;
} else {
@ -109,7 +162,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
}
}
public visitTags(properties: TagsFieldPropertiesDto): string {
public visitTags(_: TagsFieldPropertiesDto): string {
if (this.value.length) {
return this.value.join(', ');
} else {
@ -117,19 +170,19 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
}
}
public visitBoolean(properties: BooleanFieldPropertiesDto): string {
public visitBoolean(_: BooleanFieldPropertiesDto): string {
return this.value ? 'Yes' : 'No';
}
public visitGeolocation(properties: GeolocationFieldPropertiesDto): string {
public visitGeolocation(_: GeolocationFieldPropertiesDto): string {
return `${this.value.longitude}, ${this.value.latitude}`;
}
public visitJson(properties: JsonFieldPropertiesDto): string {
public visitJson(_: JsonFieldPropertiesDto): string {
return '<Json />';
}
public visitNumber(properties: NumberFieldPropertiesDto): string | HtmlValue | number {
public visitNumber(properties: NumberFieldPropertiesDto): FieldValue {
if (Types.isNumber(this.value) && properties.editor === 'Stars' && this.allowHtml) {
if (this.value <= 0 || this.value > 6) {
return new HtmlValue(`&#9733; ${this.value}`);
@ -143,15 +196,15 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
return new HtmlValue(html);
}
}
return this.value;
return `${this.value}`;
}
public visitString(properties: StringFieldPropertiesDto): any {
public visitString(_: StringFieldPropertiesDto): any {
return this.value;
}
public visitUI(properties: UIFieldPropertiesDto): any {
return this.value;
public visitUI(_: UIFieldPropertiesDto): any {
return '';
}
}
@ -257,23 +310,23 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor<ValidatorF
return validators;
}
public visitBoolean(properties: BooleanFieldPropertiesDto): ValidatorFn[] {
public visitBoolean(_: BooleanFieldPropertiesDto): ValidatorFn[] {
return [];
}
public visitDateTime(properties: DateTimeFieldPropertiesDto): ValidatorFn[] {
public visitDateTime(_: DateTimeFieldPropertiesDto): ValidatorFn[] {
return [];
}
public visitGeolocation(properties: GeolocationFieldPropertiesDto): ValidatorFn[] {
public visitGeolocation(_: GeolocationFieldPropertiesDto): ValidatorFn[] {
return [];
}
public visitJson(properties: JsonFieldPropertiesDto): ValidatorFn[] {
public visitJson(_: JsonFieldPropertiesDto): ValidatorFn[] {
return [];
}
public visitUI(properties: UIFieldPropertiesDto): ValidatorFn[] {
public visitUI(_: UIFieldPropertiesDto): ValidatorFn[] {
return [];
}
}
@ -300,11 +353,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return field.properties.accept(new FieldDefaultValue(now));
}
public visitArray(properties: ArrayFieldPropertiesDto): any {
public visitArray(_: ArrayFieldPropertiesDto): any {
return null;
}
public visitAssets(properties: AssetsFieldPropertiesDto): any {
public visitAssets(_: AssetsFieldPropertiesDto): any {
return null;
}
@ -312,11 +365,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return properties.defaultValue;
}
public visitGeolocation(properties: GeolocationFieldPropertiesDto): any {
public visitGeolocation(_: GeolocationFieldPropertiesDto): any {
return null;
}
public visitJson(properties: JsonFieldPropertiesDto): any {
public visitJson(_: JsonFieldPropertiesDto): any {
return null;
}
@ -324,7 +377,7 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return properties.defaultValue;
}
public visitReferences(properties: ReferencesFieldPropertiesDto): any {
public visitReferences(_: ReferencesFieldPropertiesDto): any {
return null;
}
@ -332,11 +385,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return properties.defaultValue;
}
public visitTags(properties: TagsFieldPropertiesDto): any {
public visitTags(_: TagsFieldPropertiesDto): any {
return null;
}
public visitUI(properties: UIFieldPropertiesDto): any {
public visitUI(_: UIFieldPropertiesDto): any {
return null;
}
}

2
src/Squidex/appsettings.json

@ -296,7 +296,7 @@
*
* Supported: MongoDB, Development
*/
"clustering": "Development",
"clustering": "MongoDB",
/*
* The port is used to share messages between all cluster members. Must be accessible within your cluster or network.
*/

57
tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs

@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
}
[Fact]
public void Should_remove_ids()
public void Should_get_ids_from_id_data()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
@ -56,6 +56,23 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
Assert.Equal(new[] { id1, id2 }, ids);
}
[Fact]
public void Should_get_ids_from_name_data()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var input =
new NamedContentData()
.AddField("assets1",
new ContentFieldData()
.AddValue("iv", JsonValue.Array(id1.ToString(), id2.ToString())));
var ids = input.GetReferencedIds(schema).ToArray();
Assert.Equal(new[] { id1, id2 }, ids);
}
[Fact]
public void Should_cleanup_deleted_ids()
{
@ -86,7 +103,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant);
var result = sut.ExtractReferences(CreateValue(id1, id2)).ToArray();
var result = sut.GetReferencedIds(CreateValue(id1, id2)).ToArray();
Assert.Equal(new[] { id1, id2 }, result);
}
@ -96,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
{
var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant);
var result = sut.ExtractReferences(null).ToArray();
var result = sut.GetReferencedIds(null).ToArray();
Assert.Empty(result);
}
@ -106,7 +123,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
{
var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant);
var result = sut.ExtractReferences(JsonValue.Create("invalid")).ToArray();
var result = sut.GetReferencedIds(JsonValue.Create("invalid")).ToArray();
Assert.Empty(result);
}
@ -116,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
{
var sut = Fields.String(1, "my-string", Partitioning.Invariant);
var result = sut.ExtractReferences(JsonValue.Create("invalid")).ToArray();
var result = sut.GetReferencedIds(JsonValue.Create("invalid")).ToArray();
Assert.Empty(result);
}
@ -153,6 +170,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant);
var token = CreateValue(id1, id2);
var result = sut.CleanReferences(token, HashSet.Of(Guid.NewGuid()));
Assert.Same(token, result);
@ -174,7 +192,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
JsonValue.Object()
.Add("my-refs", CreateValue(id1, id2)));
var result = sut.ExtractReferences(value).ToArray();
var result = sut.GetReferencedIds(value).ToArray();
Assert.Equal(new[] { id1, id2, schemaId }, result);
}
@ -188,18 +206,32 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var sut = Fields.References(1, "my-refs", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaId });
var result = sut.ExtractReferences(CreateValue(id1, id2)).ToArray();
var result = sut.GetReferencedIds(CreateValue(id1, id2)).ToArray();
Assert.Equal(new[] { id1, id2, schemaId }, result);
}
[Fact]
public void Should_return_ids_from_references_field_without_schema_id()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var sut = Fields.References(1, "my-refs", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaId });
var result = sut.GetReferencedIds(CreateValue(id1, id2), Ids.ContentOnly).ToArray();
Assert.Equal(new[] { id1, id2 }, result);
}
[Fact]
public void Should_return_list_from_references_field_with_schema_id_list_for_referenced_ids_when_null()
{
var sut = Fields.References(1, "my-refs", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaId });
var result = sut.ExtractReferences(JsonValue.Null).ToArray();
var result = sut.GetReferencedIds(JsonValue.Null).ToArray();
Assert.Equal(new[] { schemaId }, result);
}
@ -210,7 +242,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var sut = Fields.References(1, "my-refs", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaId });
var result = sut.ExtractReferences(JsonValue.Create("invalid")).ToArray();
var result = sut.GetReferencedIds(JsonValue.Create("invalid")).ToArray();
Assert.Equal(new[] { schemaId }, result);
}
@ -261,10 +293,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
var sut = Fields.References(1, "my-refs", Partitioning.Invariant);
var token = CreateValue(id1, id2);
var result = sut.CleanReferences(token, HashSet.Of(Guid.NewGuid()));
var value = CreateValue(id1, id2);
Assert.Same(token, result);
var result = sut.CleanReferences(value, HashSet.Of(Guid.NewGuid()));
Assert.Same(value, result);
}
private static IJsonValue CreateValue(params Guid[] ids)

102
tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs

@ -0,0 +1,102 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
{
public class ReferenceFormattingTests
{
private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE);
[Fact]
public void Should_format_data_with_reference_fields()
{
var data = CreateData();
var schema =
new Schema("my-schema")
.AddString(1, "ref1", Partitioning.Invariant,
new StringFieldProperties { IsReferenceField = true })
.AddString(2, "ref2", Partitioning.Invariant,
new StringFieldProperties { IsReferenceField = true })
.AddString(3, "non-ref", Partitioning.Invariant);
var formatted = data.FormatReferences(schema, languages);
var expected =
JsonValue.Object()
.Add("en", "EN, 12")
.Add("de", "DE, 12");
Assert.Equal(expected, formatted);
}
[Fact]
public void Should_format_data_with_first_field_if_no_reference_field_defined()
{
var data = CreateData();
var schema = CreateNoRefSchema();
var formatted = data.FormatReferences(schema, languages);
var expected =
JsonValue.Object()
.Add("en", "EN")
.Add("de", "DE");
Assert.Equal(expected, formatted);
}
[Fact]
public void Should_return_default_value_if_no_value_found()
{
var data = new NamedContentData();
var schema = CreateNoRefSchema();
var formatted = data.FormatReferences(schema, languages);
var expected =
JsonValue.Object()
.Add("en", string.Empty)
.Add("de", string.Empty);
Assert.Equal(expected, formatted);
}
private static Schema CreateNoRefSchema()
{
return new Schema("my-schema")
.AddString(1, "ref1", Partitioning.Invariant)
.AddString(2, "ref2", Partitioning.Invariant)
.AddString(3, "non-ref", Partitioning.Invariant);
}
private static NamedContentData CreateData()
{
return new NamedContentData()
.AddField("ref1",
new ContentFieldData()
.AddValue("en", "EN")
.AddValue("de", "DE"))
.AddField("ref2",
new ContentFieldData()
.AddValue("iv", 12))
.AddField("non-ref",
new ContentFieldData()
.AddValue("iv", "Ignored"));
}
}
}

23
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByUserIndexCommandMiddlewareTests.cs

@ -11,6 +11,7 @@ using FakeItEasy;
using Orleans;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -23,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly IAppsByUserIndex index = A.Fake<IAppsByUserIndex>();
private readonly Guid appId = Guid.NewGuid();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly string userId = "123";
private readonly AppsByUserIndexCommandMiddleware sut;
@ -39,12 +40,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
public async Task Should_add_app_to_index_on_create()
{
var context =
new CommandContext(new CreateApp { AppId = appId, Actor = new RefToken("user", userId) }, commandBus)
new CommandContext(new CreateApp { AppId = appId.Id, Actor = new RefToken("user", userId) }, commandBus)
.Complete();
await sut.HandleAsync(context);
A.CallTo(() => index.AddAppAsync(appId))
A.CallTo(() => index.AddAppAsync(appId.Id))
.MustHaveHappened();
}
@ -52,12 +53,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
public async Task Should_add_app_to_index_on_assign_of_contributor()
{
var context =
new CommandContext(new AssignContributor { AppId = appId, ContributorId = userId }, commandBus)
new CommandContext(new AssignContributor { AppId = appId.Id, ContributorId = userId }, commandBus)
.Complete();
await sut.HandleAsync(context);
A.CallTo(() => index.AddAppAsync(appId))
A.CallTo(() => index.AddAppAsync(appId.Id))
.MustHaveHappened();
}
@ -65,12 +66,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
public async Task Should_add_app_to_index_on_remove_of_contributor()
{
var context =
new CommandContext(new RemoveContributor { AppId = appId, ContributorId = userId }, commandBus)
new CommandContext(new RemoveContributor { AppId = appId.Id, ContributorId = userId }, commandBus)
.Complete();
await sut.HandleAsync(context);
A.CallTo(() => index.RemoveAppAsync(appId))
A.CallTo(() => index.RemoveAppAsync(appId.Id))
.MustHaveHappened();
}
@ -78,9 +79,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
public async Task Should_remove_app_from_index_on_archive()
{
var appGrain = A.Fake<IAppGrain>();
var appState = A.Fake<IAppEntity>();
var appState = Mocks.App(appId);
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId, null))
A.CallTo(() => grainFactory.GetGrain<IAppGrain>(appId.Id, null))
.Returns(appGrain);
A.CallTo(() => appGrain.GetStateAsync())
@ -90,12 +91,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
.Returns(AppContributors.Empty.Assign(userId, Role.Owner));
var context =
new CommandContext(new ArchiveApp { AppId = appId }, commandBus)
new CommandContext(new ArchiveApp { AppId = appId.Id }, commandBus)
.Complete();
await sut.HandleAsync(context);
A.CallTo(() => index.RemoveAppAsync(appId))
A.CallTo(() => index.RemoveAppAsync(appId.Id))
.MustHaveHappened();
}
}

11
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared.Users;
using Xunit;
@ -34,13 +37,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.Returns(true);
var app = A.Fake<IAppEntity>();
var result = Mocks.App(NamedId.Of(Guid.NewGuid(), "my-app"));
context.Complete(app);
context.Complete(result);
await sut.HandleAsync(context);
Assert.Same(context.Result<InvitedResult>().App, app);
Assert.Same(context.Result<InvitedResult>().App, result);
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.MustHaveHappened();
@ -55,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => userResolver.CreateUserIfNotExists("me@email.com", true))
.Returns(false);
var result = A.Fake<IAppEntity>();
var result = Mocks.App(NamedId.Of(Guid.NewGuid(), "my-app"));
context.Complete(result);

21
tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs

@ -9,8 +9,9 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
#pragma warning disable xUnit2017 // Do not use Contains() to check if a value exists in a collection
@ -19,13 +20,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
public class RolePermissionsProviderTests
{
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly RolePermissionsProvider sut;
public RolePermissionsProviderTests()
{
A.CallTo(() => app.Name).Returns("my-app");
app = Mocks.App(appId);
sut = new RolePermissionsProvider(appProvider);
}
@ -36,8 +38,8 @@ namespace Squidex.Domain.Apps.Entities.Apps
A.CallTo(() => appProvider.GetSchemasAsync(A<Guid>.Ignored))
.Returns(new List<ISchemaEntity>
{
CreateSchema("schema1"),
CreateSchema("schema2")
Mocks.Schema(appId, NamedId.Of(Guid.NewGuid(), "schema1")),
Mocks.Schema(appId, NamedId.Of(Guid.NewGuid(), "schema2")),
});
var result = await sut.GetPermissionsAsync(app);
@ -48,14 +50,5 @@ namespace Squidex.Domain.Apps.Entities.Apps
Assert.True(result.Contains("schemas.schema1.update"));
Assert.True(result.Contains("schemas.schema2.update"));
}
private static ISchemaEntity CreateSchema(string name)
{
var schema = A.Fake<ISchemaEntity>();
A.CallTo(() => schema.SchemaDef).Returns(new Schema(name));
return schema;
}
}
}

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

@ -8,13 +8,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
@ -25,23 +22,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
public class AssetQueryServiceTests
{
private readonly ITagService tagService = A.Fake<ITagService>();
private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly ITagService tagService = A.Fake<ITagService>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly Context context;
private readonly Context requestContext;
private readonly AssetQueryService sut;
public AssetQueryServiceTests()
{
var user = new ClaimsPrincipal(new ClaimsIdentity());
A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English);
context = new Context(user, app);
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId));
var options = Options.Create(new AssetOptions { DefaultPageSize = 30 });
@ -109,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2)))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
var result = await sut.QueryAsync(context, Q.Empty.WithIds(ids));
var result = await sut.QueryAsync(requestContext, Q.Empty.WithIds(ids));
Assert.Equal(8, result.Total);
@ -131,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2)))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
var result = await sut.QueryAsync(context, Q.Empty);
var result = await sut.QueryAsync(requestContext, Q.Empty);
Assert.Equal(8, result.Total);
@ -143,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var query = Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World");
await sut.QueryAsync(context, query);
await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending")))
.MustHaveHappened();
@ -154,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var query = Q.Empty.WithODataQuery("$top=200&$filter=fileName eq 'ABC'");
await sut.QueryAsync(context, query);
await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending")))
.MustHaveHappened();
@ -165,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var query = Q.Empty;
await sut.QueryAsync(context, query);
await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("Take: 30; Sort: lastModified Descending")))
.MustHaveHappened();
@ -176,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var query = Q.Empty.WithODataQuery("$top=300&$skip=20");
await sut.QueryAsync(context, query);
await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending")))
.MustHaveHappened();

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

@ -19,7 +19,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentState>
{
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly Guid contentId = Guid.NewGuid();
private readonly Context requestContext = new Context();
private readonly ContentCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand
@ -33,7 +35,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentCommandMiddlewareTests()
{
sut = new ContentCommandMiddleware(A.Fake<IGrainFactory>(), contentEnricher);
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new ContentCommandMiddleware(A.Fake<IGrainFactory>(), contentEnricher, contextProvider);
}
[Fact]
@ -46,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await sut.HandleAsync(context);
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, User))
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, requestContext))
.MustNotHaveHappened();
}
@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Same(result, context.Result<IEnrichedContentEntity>());
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, User))
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, requestContext))
.MustNotHaveHappened();
}
@ -80,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(result, User))
A.CallTo(() => contentEnricher.EnrichAsync(result, requestContext))
.Returns(enriched);
await sut.HandleAsync(context);

215
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherReferencesTests.cs

@ -0,0 +1,215 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentEnricherReferencesTests
{
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> refSchemaId1 = NamedId.Of(Guid.NewGuid(), "my-ref1");
private readonly NamedId<Guid> refSchemaId2 = NamedId.Of(Guid.NewGuid(), "my-ref2");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly Context requestContext;
private readonly ContentEnricher sut;
public ContentEnricherReferencesTests()
{
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId, Language.DE));
var refSchemaDef =
new Schema("my-ref")
.AddString(1, "name", Partitioning.Invariant,
new StringFieldProperties { IsReferenceField = true })
.AddNumber(2, "number", Partitioning.Invariant,
new NumberFieldProperties { IsReferenceField = true });
var schemaDef =
new Schema(schemaId.Name)
.AddReferences(1, "ref1", Partitioning.Invariant, new ReferencesFieldProperties
{
ResolveReference = true,
IsListField = true,
MinItems = 1,
MaxItems = 1,
SchemaId = refSchemaId1.Id
})
.AddReferences(2, "ref2", Partitioning.Invariant, new ReferencesFieldProperties
{
ResolveReference = true,
IsListField = true,
MinItems = 1,
MaxItems = 1,
SchemaId = refSchemaId2.Id
});
void SetupSchema(NamedId<Guid> id, Schema def)
{
var schemaEntity = Mocks.Schema(appId, id, def);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(requestContext, id.Id.ToString()))
.Returns(schemaEntity);
}
SetupSchema(schemaId, schemaDef);
SetupSchema(refSchemaId1, refSchemaDef);
SetupSchema(refSchemaId2, refSchemaDef);
sut = new ContentEnricher(new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
}
[Fact]
public async Task Should_enrich_with_reference_data()
{
var ref1_1 = CreateRefContent(Guid.NewGuid(), "ref1_1", 13);
var ref1_2 = CreateRefContent(Guid.NewGuid(), "ref1_2", 17);
var ref2_1 = CreateRefContent(Guid.NewGuid(), "ref2_1", 23);
var ref2_2 = CreateRefContent(Guid.NewGuid(), "ref2_2", 29);
var source = new IContentEntity[]
{
CreateContent(new Guid[] { ref1_1.Id }, new Guid[] { ref2_1.Id }),
CreateContent(new Guid[] { ref1_2.Id }, new Guid[] { ref2_2.Id })
};
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4)))
.Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2));
var enriched = await sut.EnrichAsync(source, requestContext);
Assert.Equal(
new NamedContentData()
.AddField("ref1",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "ref1_1, 13")
.Add("de", "ref1_1, 13")))
.AddField("ref2",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "ref2_1, 23")
.Add("de", "ref2_1, 23"))),
enriched.ElementAt(0).ReferenceData);
Assert.Equal(
new NamedContentData()
.AddField("ref1",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "ref1_2, 17")
.Add("de", "ref1_2, 17")))
.AddField("ref2",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "ref2_2, 29")
.Add("de", "ref2_2, 29"))),
enriched.ElementAt(1).ReferenceData);
}
[Fact]
public async Task Should_not_enrich_when_content_has_more_items()
{
var ref1_1 = CreateRefContent(Guid.NewGuid(), "ref1_1", 13);
var ref1_2 = CreateRefContent(Guid.NewGuid(), "ref1_2", 17);
var ref2_1 = CreateRefContent(Guid.NewGuid(), "ref2_1", 23);
var ref2_2 = CreateRefContent(Guid.NewGuid(), "ref2_2", 29);
var source = new IContentEntity[]
{
CreateContent(new Guid[] { ref1_1.Id }, new Guid[] { ref2_1.Id, ref2_2.Id }),
CreateContent(new Guid[] { ref1_2.Id }, new Guid[] { ref2_1.Id, ref2_2.Id })
};
A.CallTo(() => contentQuery.QueryAsync(A<Context>.Ignored, A<IReadOnlyList<Guid>>.That.Matches(x => x.Count == 4)))
.Returns(ResultList.CreateFrom(4, ref1_1, ref1_2, ref2_1, ref2_2));
var enriched = await sut.EnrichAsync(source, requestContext);
Assert.Equal(
new NamedContentData()
.AddField("ref1",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "ref1_1, 13")
.Add("de", "ref1_1, 13")))
.AddField("ref2",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "2 Reference(s)")
.Add("de", "2 Reference(s)"))),
enriched.ElementAt(0).ReferenceData);
Assert.Equal(
new NamedContentData()
.AddField("ref1",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "ref1_2, 17")
.Add("de", "ref1_2, 17")))
.AddField("ref2",
new ContentFieldData()
.AddJsonValue("iv",
JsonValue.Object()
.Add("en", "2 Reference(s)")
.Add("de", "2 Reference(s)"))),
enriched.ElementAt(1).ReferenceData);
}
private IEnrichedContentEntity CreateContent(Guid[] ref1, Guid[] ref2)
{
return new ContentEntity
{
DataDraft =
new NamedContentData()
.AddField("ref1",
new ContentFieldData()
.AddJsonValue("iv", JsonValue.Array(ref1.Select(x => x.ToString()).ToArray())))
.AddField("ref2",
new ContentFieldData()
.AddJsonValue("iv", JsonValue.Array(ref2.Select(x => x.ToString()).ToArray()))),
SchemaId = schemaId
};
}
private IEnrichedContentEntity CreateRefContent(Guid id, string name, int number)
{
return new ContentEntity
{
DataDraft =
new NamedContentData()
.AddField("name",
new ContentFieldData()
.AddValue("iv", name))
.AddField("number",
new ContentFieldData()
.AddValue("iv", number)),
Id = id
};
}
}
}

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

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
@ -18,18 +17,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
public class ContentEnricherTests
{
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly ClaimsPrincipal user = new ClaimsPrincipal();
private readonly Context context = new Context();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly Context requestContext = new Context();
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ContentEnricher sut;
public ContentEnricherTests()
{
A.CallTo(() => contextProvider.Context)
.Returns(context);
sut = new ContentEnricher(contentWorkflow, contextProvider);
sut = new ContentEnricher(new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
}
[Fact]
@ -40,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(source, user);
var result = await sut.EnrichAsync(source, requestContext);
Assert.Equal(StatusColors.Published, result.StatusColor);
}
@ -53,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(Task.FromResult<StatusInfo>(null));
var result = await sut.EnrichAsync(source, user);
var result = await sut.EnrichAsync(source, requestContext);
Assert.Equal(StatusColors.Draft, result.StatusColor);
}
@ -61,14 +56,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_enrich_content_with_can_update()
{
context.WithResolveFlow(true);
requestContext.WithResolveFlow(true);
var source = new ContentEntity { SchemaId = schemaId };
A.CallTo(() => contentWorkflow.CanUpdateAsync(source))
.Returns(true);
var result = await sut.EnrichAsync(source, user);
var result = await sut.EnrichAsync(source, requestContext);
Assert.True(result.CanUpdate);
}
@ -76,11 +71,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context()
{
context.WithResolveFlow(false);
requestContext.WithResolveFlow(false);
var source = new ContentEntity { SchemaId = schemaId };
var result = await sut.EnrichAsync(source, user);
var result = await sut.EnrichAsync(source, requestContext);
Assert.False(result.CanUpdate);
@ -94,10 +89,16 @@ namespace Squidex.Domain.Apps.Entities.Contents
var source1 = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
var source2 = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
var source = new IContentEntity[]
{
source1,
source2
};
A.CallTo(() => contentWorkflow.GetInfoAsync(source1))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(new[] { source1, source2 }, user);
var result = await sut.EnrichAsync(source, requestContext);
Assert.Equal(StatusColors.Published, result[0].StatusColor);
Assert.Equal(StatusColors.Published, result[1].StatusColor);

23
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using FakeItEasy;
using NodaTime;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
@ -31,13 +30,13 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentGrainTests : HandlerTestBase<ContentState>
{
private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly Guid contentId = Guid.NewGuid();
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IContentRepository contentRepository = A.Dummy<IContentRepository>();
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(x => x.Wrapping(new DefaultContentWorkflow()));
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppEntity appEntity = A.Fake<IAppEntity>();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE);
private readonly ISchemaEntity schema;
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly NamedContentData invalidData =
new NamedContentData()
@ -66,7 +65,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
new ContentFieldData()
.AddValue("iv", 2));
private readonly NamedContentData patched;
private readonly Guid contentId = Guid.NewGuid();
private readonly ContentGrain sut;
protected override Guid Id
@ -76,6 +74,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentGrainTests()
{
app = Mocks.App(AppNamedId, Language.DE);
var scripts = new SchemaScripts
{
Change = "<change-script>",
@ -92,12 +92,13 @@ namespace Squidex.Domain.Apps.Entities.Contents
new NumberFieldProperties { IsRequired = false })
.ConfigureScripts(scripts);
A.CallTo(() => appEntity.LanguagesConfig).Returns(languagesConfig);
schema = Mocks.Schema(AppNamedId, SchemaNamedId, schemaDef);
A.CallTo(() => appProvider.GetAppAsync(AppName)).Returns(appEntity);
A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((appEntity, schema));
A.CallTo(() => appProvider.GetAppAsync(AppName))
.Returns(app);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId))
.Returns((app, schema));
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.ReturnsLazily(x => x.GetArgument<ScriptContext>(0).Data);

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

@ -13,7 +13,6 @@ using System.Threading.Tasks;
using FakeItEasy;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
@ -37,14 +36,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentQueryServiceTests
{
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetUrlGenerator urlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IContentVersionLoader contentVersionLoader = A.Fake<IContentVersionLoader>();
private readonly ISchemaEntity schema;
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetUrlGenerator urlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly Guid contentId = Guid.NewGuid();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
@ -53,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly ClaimsPrincipal user;
private readonly ClaimsIdentity identity = new ClaimsIdentity();
private readonly EdmModelBuilder modelBuilder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions())));
private readonly Context context;
private readonly Context requestContext;
private readonly ContentQueryService sut;
public static IEnumerable<object[]> ApiStatusTests = new[]
@ -66,22 +65,18 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
user = new ClaimsPrincipal(identity);
A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English);
app = Mocks.App(appId);
requestContext = new Context(user, app);
var schemaDef =
new Schema(schemaId.Name)
.ConfigureScripts(new SchemaScripts { Query = "<query-script>" });
A.CallTo(() => schema.Id).Returns(schemaId.Id);
A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
schema = Mocks.Schema(appId, schemaId, schemaDef);
SetupEnricher();
context = new Context(user, app);
var options = Options.Create(new ContentOptions { DefaultPageSize = 30 });
sut = new ContentQueryService(
@ -108,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
SetupSchemaFound();
var result = await sut.GetSchemaOrThrowAsync(context, schemaId.Name);
var result = await sut.GetSchemaOrThrowAsync(requestContext, schemaId.Id.ToString());
Assert.Equal(schema, result);
}
@ -118,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
SetupSchemaFound();
var result = await sut.GetSchemaOrThrowAsync(context, schemaId.Name);
var result = await sut.GetSchemaOrThrowAsync(requestContext, schemaId.Name);
Assert.Equal(schema, result);
}
@ -128,7 +123,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
SetupSchemaNotFound();
var ctx = context;
var ctx = requestContext;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaOrThrowAsync(ctx, schemaId.Name));
}
@ -138,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
SetupSchemaNotFound();
var ctx = context;
var ctx = requestContext;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaOrThrowAsync(ctx, schemaId.Name));
}
@ -146,14 +141,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_apply_default_page_size()
{
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
var query = Q.Empty;
await sut.QueryAsync(context, schemaId.Name, query);
await sut.QueryAsync(requestContext, schemaId.Name, query);
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(Status.Published), false, A<Query>.That.Is("Take: 30; Sort: lastModified Descending"), false))
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(Status.Published), false,
A<Query>.That.Is("Take: 30; Sort: lastModified Descending"), false))
.MustHaveHappened();
}
@ -162,24 +158,25 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var status = new[] { Status.Published };
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
var query = Q.Empty.WithODataQuery("$top=300&$skip=20");
await sut.QueryAsync(context, schemaId.Name, query);
await sut.QueryAsync(requestContext, schemaId.Name, query);
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(status), false, A<Query>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"), false))
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(status), false,
A<Query>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"), false))
.MustHaveHappened();
}
[Fact]
public async Task Should_throw_for_single_content_if_no_permission()
{
SetupClaims(false, false);
SetupUser(false, false);
SetupSchemaFound();
var ctx = context;
var ctx = requestContext;
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindContentAsync(ctx, schemaId.Name, contentId));
}
@ -189,11 +186,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var status = new[] { Status.Published };
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
SetupContent(status, null, includeDraft: false);
var ctx = context;
var ctx = requestContext;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(async () => await sut.FindContentAsync(ctx, schemaId.Name, contentId));
}
@ -203,12 +200,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(contentId);
SetupClaims(isFrontend: true);
SetupUser(isFrontend: true);
SetupSchemaFound();
SetupScripting(contentId);
SetupSchemaScripting(contentId);
SetupContent(null, content, includeDraft: true);
var ctx = context;
var ctx = requestContext;
var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId);
@ -225,12 +222,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(contentId);
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
SetupScripting(contentId);
SetupSchemaScripting(contentId);
SetupContent(status, content, unpublished == 1);
var ctx = context.WithUnpublished(unpublished == 1);
var ctx = requestContext.WithUnpublished(unpublished == 1);
var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId);
@ -246,14 +243,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(contentId);
SetupClaims(true);
SetupUser(true);
SetupSchemaFound();
SetupScripting(contentId);
SetupSchemaScripting(contentId);
A.CallTo(() => contentVersionLoader.LoadAsync(contentId, 10))
.Returns(content);
var ctx = context;
var ctx = requestContext;
var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId, 10);
@ -264,10 +261,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_throw_for_query_if_no_permission()
{
SetupClaims(false, false);
SetupUser(false, false);
SetupSchemaFound();
var ctx = context;
var ctx = requestContext;
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.QueryAsync(ctx, schemaId.Name, Q.Empty));
}
@ -279,12 +276,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
var content = CreateContent(contentId);
SetupClaims(isFrontend: true);
SetupUser(isFrontend: true);
SetupSchemaFound();
SetupScripting(contentId);
SetupSchemaScripting(contentId);
SetupContents(null, count, total, content, inDraft: true, includeDraft: true);
var ctx = context;
var ctx = requestContext;
var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty);
@ -305,12 +302,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
var content = CreateContent(contentId);
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
SetupScripting(contentId);
SetupSchemaScripting(contentId);
SetupContents(status, count, total, content, inDraft: false, unpublished == 1);
var ctx = context.WithUnpublished(unpublished == 1);
var ctx = requestContext.WithUnpublished(unpublished == 1);
var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty);
@ -326,12 +323,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_throw_if_query_is_invalid()
{
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
var query = Q.Empty.WithODataQuery("$filter=invalid");
await Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context, schemaId.Name, query));
await Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(requestContext, schemaId.Name, query));
}
[Fact]
@ -341,12 +338,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ids = Enumerable.Range(0, count).Select(x => Guid.NewGuid()).ToList();
SetupClaims(isFrontend: true);
SetupUser(isFrontend: true);
SetupSchemaFound();
SetupScripting(ids.ToArray());
SetupSchemaScripting(ids.ToArray());
SetupContents(null, total, ids, includeDraft: true);
var ctx = context;
var ctx = requestContext;
var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids));
@ -365,12 +362,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ids = Enumerable.Range(0, count).Select(x => Guid.NewGuid()).ToList();
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
SetupScripting(ids.ToArray());
SetupSchemaScripting(ids.ToArray());
SetupContents(status, total, ids, unpublished == 1);
var ctx = context.WithUnpublished(unpublished == 1);
var ctx = requestContext.WithUnpublished(unpublished == 1);
var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids));
@ -388,12 +385,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ids = Enumerable.Range(0, count).Select(x => Guid.NewGuid()).ToList();
SetupClaims(isFrontend: true);
SetupUser(isFrontend: true);
SetupSchemaFound();
SetupScripting(ids.ToArray());
SetupSchemaScripting(ids.ToArray());
SetupContents(null, ids, includeDraft: true);
var ctx = context;
var ctx = requestContext;
var result = await sut.QueryAsync(ctx, ids);
@ -411,12 +408,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ids = Enumerable.Range(0, count).Select(x => Guid.NewGuid()).ToList();
SetupClaims(isFrontend: false);
SetupUser(isFrontend: false);
SetupSchemaFound();
SetupScripting(ids.ToArray());
SetupSchemaScripting(ids.ToArray());
SetupContents(status, ids, unpublished == 1);
var ctx = context.WithUnpublished(unpublished == 1);
var ctx = requestContext.WithUnpublished(unpublished == 1);
var result = await sut.QueryAsync(ctx, ids);
@ -431,12 +428,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var ids = Enumerable.Range(0, 1).Select(x => Guid.NewGuid()).ToList();
SetupClaims(isFrontend: false, allowSchema: false);
SetupUser(isFrontend: false, allowSchema: false);
SetupSchemaFound();
SetupScripting(ids.ToArray());
SetupSchemaScripting(ids.ToArray());
SetupContents(new Status[0], ids, includeDraft: false);
var ctx = context;
var ctx = requestContext;
var result = await sut.QueryAsync(ctx, ids);
@ -448,10 +445,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var ids = new List<Guid>();
SetupClaims(isFrontend: false, allowSchema: false);
SetupUser(isFrontend: false, allowSchema: false);
SetupSchemaFound();
var ctx = context;
var ctx = requestContext;
var result = await sut.QueryAsync(ctx, ids);
@ -461,7 +458,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
.MustNotHaveHappened();
}
private void SetupClaims(bool isFrontend, bool allowSchema = true)
private void SetupUser(bool isFrontend, bool allowSchema = true)
{
if (isFrontend)
{
@ -474,7 +471,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
}
private void SetupScripting(params Guid[] ids)
private void SetupSchemaScripting(params Guid[] ids)
{
foreach (var id in ids)
{
@ -527,7 +524,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private void SetupEnricher()
{
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<IContentEntity>>.Ignored, user))
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<IContentEntity>>.Ignored, requestContext))
.ReturnsLazily(x =>
{
var input = (IEnumerable<IContentEntity>)x.Arguments[0];

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs

@ -12,6 +12,7 @@ using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
@ -26,10 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public DefaultWorkflowsValidatorTests()
{
var schema = A.Fake<ISchemaEntity>();
A.CallTo(() => schema.Id).Returns(schemaId.Id);
A.CallTo(() => schema.SchemaDef).Returns(new Schema(schemaId.Name));
var schema = Mocks.Schema(appId, schemaId, new Schema(schemaId.Name));
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A<Guid>.Ignored, false))
.Returns(Task.FromResult<ISchemaEntity>(null));

61
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs

@ -7,14 +7,13 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
@ -22,11 +21,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public class DynamicContentWorkflowTests
{
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly NamedId<Guid> simpleSchemaId = NamedId.Of(Guid.NewGuid(), "my-simple-schema");
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppEntity appEntity = A.Fake<IAppEntity>();
private readonly DynamicContentWorkflow sut;
private readonly Workflow workflow = new Workflow(
@ -62,6 +61,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
public DynamicContentWorkflowTests()
{
app = Mocks.App(appId);
simpleWorkflow = new Workflow(
Status.Draft,
new Dictionary<Status, WorkflowStep>
@ -86,9 +87,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
var workflows = Workflows.Empty.Set(workflow).Set(Guid.NewGuid(), simpleWorkflow);
A.CallTo(() => appProvider.GetAppAsync(appId.Id))
.Returns(appEntity);
.Returns(app);
A.CallTo(() => appEntity.Workflows)
A.CallTo(() => app.Workflows)
.Returns(workflows);
sut = new DynamicContentWorkflow(new JintScriptEngine(), appProvider);
@ -99,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var expected = new StatusInfo(Status.Draft, StatusColors.Draft);
var result = await sut.GetInitialStatusAsync(CreateSchema());
var result = await sut.GetInitialStatusAsync(Mocks.Schema(appId, schemaId));
result.Should().BeEquivalentTo(expected);
}
@ -109,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanPublishOnCreateAsync(CreateSchema(), content.DataDraft, User("Editor"));
var result = await sut.CanPublishOnCreateAsync(Mocks.Schema(appId, schemaId), content.DataDraft, Mocks.FrontendUser("Editor"));
Assert.True(result);
}
@ -119,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(Status.Draft, 4);
var result = await sut.CanPublishOnCreateAsync(CreateSchema(), content.DataDraft, User("Editor"));
var result = await sut.CanPublishOnCreateAsync(Mocks.Schema(appId, schemaId), content.DataDraft, Mocks.FrontendUser("Editor"));
Assert.False(result);
}
@ -129,7 +130,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanPublishOnCreateAsync(CreateSchema(), content.DataDraft, User("Developer"));
var result = await sut.CanPublishOnCreateAsync(Mocks.Schema(appId, schemaId), content.DataDraft, Mocks.FrontendUser("Developer"));
Assert.False(result);
}
@ -139,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanMoveToAsync(content, Status.Published, User("Editor"));
var result = await sut.CanMoveToAsync(content, Status.Published, Mocks.FrontendUser("Editor"));
Assert.True(result);
}
@ -149,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanMoveToAsync(content, Status.Published, User("Developer"));
var result = await sut.CanMoveToAsync(content, Status.Published, Mocks.FrontendUser("Developer"));
Assert.False(result);
}
@ -159,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var content = CreateContent(Status.Draft, 4);
var result = await sut.CanMoveToAsync(content, Status.Published, User("Editor"));
var result = await sut.CanMoveToAsync(content, Status.Published, Mocks.FrontendUser("Editor"));
Assert.False(result);
}
@ -204,7 +205,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
new StatusInfo(Status.Archived, StatusColors.Archived)
};
var result = await sut.GetNextsAsync(content, User("Developer"));
var result = await sut.GetNextsAsync(content, Mocks.FrontendUser("Developer"));
result.Should().BeEquivalentTo(expected);
}
@ -219,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
new StatusInfo(Status.Archived, StatusColors.Archived)
};
var result = await sut.GetNextsAsync(content, User("Editor"));
var result = await sut.GetNextsAsync(content, Mocks.FrontendUser("Editor"));
result.Should().BeEquivalentTo(expected);
}
@ -235,7 +236,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
new StatusInfo(Status.Published, StatusColors.Published)
};
var result = await sut.GetNextsAsync(content, User("Editor"));
var result = await sut.GetNextsAsync(content, Mocks.FrontendUser("Editor"));
result.Should().BeEquivalentTo(expected);
}
@ -281,7 +282,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
new StatusInfo(Status.Published, StatusColors.Published)
};
var result = await sut.GetAllAsync(CreateSchema());
var result = await sut.GetAllAsync(Mocks.Schema(appId, schemaId));
result.Should().BeEquivalentTo(expected);
}
@ -295,7 +296,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
new StatusInfo(Status.Published, StatusColors.Published)
};
var result = await sut.GetAllAsync(CreateSchema(true));
var result = await sut.GetAllAsync(Mocks.Schema(appId, simpleSchemaId));
result.Should().BeEquivalentTo(expected);
}
@ -303,7 +304,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_return_all_statuses_for_default_workflow_when_no_workflow_configured()
{
A.CallTo(() => appEntity.Workflows).Returns(Workflows.Empty);
A.CallTo(() => app.Workflows).Returns(Workflows.Empty);
var expected = new[]
{
@ -312,21 +313,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
new StatusInfo(Status.Published, StatusColors.Published)
};
var result = await sut.GetAllAsync(CreateSchema(true));
var result = await sut.GetAllAsync(Mocks.Schema(appId, simpleSchemaId));
result.Should().BeEquivalentTo(expected);
}
private ISchemaEntity CreateSchema(bool simple = false)
{
var schema = A.Fake<ISchemaEntity>();
A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.Id).Returns(simple ? simpleSchemaId.Id : schemaId.Id);
return schema;
}
private IContentEntity CreateContent(Status status, int value, bool simple = false)
{
var content = new ContentEntity { AppId = appId, Status = status };
@ -348,15 +339,5 @@ namespace Squidex.Domain.Apps.Entities.Contents
return content;
}
private ClaimsPrincipal User(string role)
{
var userIdentity = new ClaimsIdentity();
var userPrincipal = new ClaimsPrincipal(userIdentity);
userIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
return userPrincipal;
}
}
}

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

@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
[InlineData(" ")]
public async Task Should_return_empty_object_for_empty_query(string query)
{
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query")))
.Returns(ResultList.CreateFrom(0, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query")))
.Returns(ResultList.CreateFrom(10, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -215,7 +215,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId)))
.Returns(ResultList.CreateFrom(1, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -301,10 +301,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
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.Id.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5")))
.Returns(ResultList.CreateFrom(0, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -442,10 +442,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
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.Id.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5")))
.Returns(ResultList.CreateFrom(10, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -548,10 +548,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -640,10 +640,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -736,13 +736,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.Ignored))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), A<Q>.Ignored))
.Returns(ResultList.CreateFrom(0, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -794,13 +794,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.Ignored))
.Returns(ResultList.CreateFrom(0, assetRef));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -856,7 +856,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId2)))
.Returns(ResultList.CreateFrom(0, asset2));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query1 }, new GraphQLQuery { Query = query2 });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query1 }, new GraphQLQuery { Query = query2 });
var expected = new object[]
{
@ -909,10 +909,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var json = serializer.Serialize(result);
@ -947,10 +947,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -993,10 +993,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(), MatchId(contentId)))
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query });
var expected = new
{
@ -1019,12 +1019,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private Context MatchsAssetContext()
{
return A<Context>.That.Matches(x => x.App == app && x.User == user);
return A<Context>.That.Matches(x => x.App == app && x.User == requestContext.User);
}
private Context MatchsContentContext()
{
return A<Context>.That.Matches(x => x.App == app && x.User == user);
return A<Context>.That.Matches(x => x.App == app && x.User == requestContext.User);
}
}
}

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

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using FakeItEasy;
using GraphQL;
using GraphQL.DataLoader;
@ -16,13 +15,13 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using NodaTime;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.TestData;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
@ -36,21 +35,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public class GraphQLTestBase
{
protected readonly Guid schemaId = Guid.NewGuid();
protected readonly Guid appId = Guid.NewGuid();
protected readonly string appName = "my-app";
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
protected readonly IAppEntity app;
protected readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
protected readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
protected readonly IJsonSerializer serializer = TestUtils.CreateSerializer(TypeNameHandling.None);
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
protected readonly IDependencyResolver dependencyResolver;
protected readonly IAppEntity app = A.Dummy<IAppEntity>();
protected readonly Context context;
protected readonly ClaimsPrincipal user = new ClaimsPrincipal();
protected readonly IJsonSerializer serializer = TestUtils.CreateSerializer(TypeNameHandling.None);
protected readonly ISchemaEntity schema;
protected readonly Context requestContext;
protected readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
protected readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
protected readonly IGraphQLService sut;
public GraphQLTestBase()
{
app = Mocks.App(appId, Language.DE, Language.GermanGermany);
var schemaDef =
new Schema("my-schema")
.AddJson(1, "my-json", Partitioning.Invariant,
@ -68,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
.AddDateTime(7, "my-datetime", Partitioning.Invariant,
new DateTimeFieldProperties())
.AddReferences(8, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaId })
new ReferencesFieldProperties { SchemaId = schemaId.Id })
.AddReferences(9, "my-invalid", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = Guid.NewGuid() })
.AddGeolocation(10, "my-geolocation", Partitioning.Invariant,
@ -84,14 +83,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
.ConfigureScripts(new SchemaScripts { Query = "<query-script>" })
.Publish();
A.CallTo(() => app.Id).Returns(appId);
A.CallTo(() => app.Name).Returns(appName);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE, Language.GermanGermany));
context = new Context(user, app);
schema = Mocks.Schema(appId, schemaId, schemaDef);
A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
requestContext = new Context(Mocks.FrontendUser(), app);
sut = CreateSut();
}
@ -213,7 +207,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
var appProvider = A.Fake<IAppProvider>();
A.CallTo(() => appProvider.GetSchemasAsync(appId))
A.CallTo(() => appProvider.GetSchemasAsync(appId.Id))
.Returns(new List<ISchemaEntity> { schema });
var dataLoaderContext = new DataLoaderContextAccessor();

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy;
@ -22,19 +23,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
{
public class GuardContentTests
{
private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly ClaimsPrincipal user = new ClaimsPrincipal();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly ClaimsPrincipal user = Mocks.FrontendUser();
private readonly Instant dueTimeInPast = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1));
public GuardContentTests()
{
SetupSingleton(false);
}
[Fact]
public async Task CanCreate_should_throw_exception_if_data_is_null()
{
var schema = CreateSchema(false);
var command = new CreateContent();
await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command),
@ -44,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanCreate_should_throw_exception_if_singleton()
{
SetupSingleton(true);
var schema = CreateSchema(true);
var command = new CreateContent { Data = new NamedContentData() };
@ -54,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id()
{
SetupSingleton(true);
var schema = CreateSchema(true);
var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id };
@ -64,7 +62,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanCreate_should_throw_exception_publish_not_allowed()
{
SetupCanCreatePublish(false);
var schema = CreateSchema(false);
SetupCanCreatePublish(schema, false);
var command = new CreateContent { Data = new NamedContentData(), Publish = true };
@ -74,7 +74,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanCreate_should_not_throw_exception_publishing_allowed()
{
SetupCanCreatePublish(true);
var schema = CreateSchema(false);
SetupCanCreatePublish(schema, true);
var command = new CreateContent { Data = new NamedContentData(), Publish = true };
@ -84,6 +86,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanCreate_should_not_throw_exception_if_data_is_not_null()
{
var schema = CreateSchema(false);
var command = new CreateContent { Data = new NamedContentData() };
await GuardContent.CanCreate(schema, contentWorkflow, command);
@ -92,6 +96,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanUpdate_should_throw_exception_if_data_is_null()
{
var schema = CreateSchema(false);
SetupCanUpdate(true);
var content = CreateContent(Status.Draft, false);
@ -104,6 +110,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanUpdate_should_throw_exception_if_workflow_blocks_it()
{
var schema = CreateSchema(false);
SetupCanUpdate(false);
var content = CreateContent(Status.Draft, false);
@ -126,6 +134,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanPatch_should_throw_exception_if_data_is_null()
{
var schema = CreateSchema(false);
SetupCanUpdate(true);
var content = CreateContent(Status.Draft, false);
@ -138,6 +148,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanPatch_should_throw_exception_if_workflow_blocks_it()
{
var schema = CreateSchema(false);
SetupCanUpdate(false);
var content = CreateContent(Status.Draft, false);
@ -160,6 +172,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_publishing_without_pending_changes()
{
var schema = CreateSchema(false);
var content = CreateContent(Status.Published, false);
var command = new ChangeContentStatus { Status = Status.Published };
@ -170,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_singleton()
{
SetupSingleton(true);
var schema = CreateSchema(true);
var content = CreateContent(Status.Published, false);
var command = new ChangeContentStatus { Status = Status.Draft };
@ -181,7 +195,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_not_throw_exception_if_publishing_with_pending_changes()
{
SetupSingleton(true);
var schema = CreateSchema(true);
var content = CreateContent(Status.Published, true);
var command = new ChangeContentStatus { Status = Status.Published };
@ -192,6 +206,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_due_date_in_past()
{
var schema = CreateSchema(false);
var content = CreateContent(Status.Draft, false);
var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast, User = user };
@ -205,6 +221,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_status_flow_not_valid()
{
var schema = CreateSchema(false);
var content = CreateContent(Status.Draft, false);
var command = new ChangeContentStatus { Status = Status.Published, User = user };
@ -218,6 +236,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_not_throw_exception_if_status_flow_valid()
{
var schema = CreateSchema(false);
var content = CreateContent(Status.Draft, false);
var command = new ChangeContentStatus { Status = Status.Published, User = user };
@ -246,7 +266,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDelete_should_throw_exception_if_singleton()
{
SetupSingleton(true);
var schema = CreateSchema(true);
var command = new DeleteContent();
@ -256,6 +276,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDelete_should_not_throw_exception()
{
var schema = CreateSchema(false);
var command = new DeleteContent();
GuardContent.CanDelete(schema, command);
@ -267,16 +289,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
.Returns(canUpdate);
}
private void SetupCanCreatePublish(bool canCreate)
private void SetupCanCreatePublish(ISchemaEntity schema, bool canCreate)
{
A.CallTo(() => contentWorkflow.CanPublishOnCreateAsync(schema, A<NamedContentData>.Ignored, user))
.Returns(canCreate);
}
private void SetupSingleton(bool isSingleton)
private ISchemaEntity CreateSchema(bool isSingleton)
{
A.CallTo(() => schema.SchemaDef)
.Returns(new Schema("schema", isSingleton: isSingleton));
return Mocks.Schema(appId, NamedId.Of(Guid.NewGuid(), "my-schema"), new Schema("schema", isSingleton: isSingleton));
}
private IContentEntity CreateContent(Status status, bool isPending)

31
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs

@ -12,6 +12,8 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
using Xunit;
@ -20,9 +22,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public class FilterTagTransformerTests
{
private readonly ITagService tagService = A.Fake<ITagService>();
private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
private readonly Guid appId = Guid.NewGuid();
private readonly Guid schemaId = Guid.NewGuid();
private readonly ISchemaEntity schema;
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
public FilterTagTransformerTests()
{
@ -32,18 +34,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
.AddTags(2, "tags2", Partitioning.Invariant, new TagsFieldProperties { Normalization = TagsFieldNormalization.Schema })
.AddString(3, "string", Partitioning.Invariant);
A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
schema = Mocks.Schema(appId, schemaId, schemaDef);
}
[Fact]
public void Should_normalize_tags()
{
A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Schemas(schemaId), A<HashSet<string>>.That.Contains("name1")))
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Schemas(schemaId.Id), A<HashSet<string>>.That.Contains("name1")))
.Returns(new Dictionary<string, string> { ["name1"] = "id1" });
var source = FilterBuilder.Eq("data.tags2.iv", "name1");
var result = FilterTagTransformer.Transform(source, appId, schema, tagService);
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.tags2.iv == 'id1'", result.ToString());
}
@ -51,11 +52,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact]
public void Should_not_fail_when_tags_not_found()
{
A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1")))
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1")))
.Returns(new Dictionary<string, string>());
var source = FilterBuilder.Eq("data.tags2.iv", "name1");
var result = FilterTagTransformer.Transform(source, appId, schema, tagService);
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.tags2.iv == 'name1'", result.ToString());
}
@ -64,11 +65,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public void Should_not_normalize_other_tags_field()
{
var source = FilterBuilder.Eq("data.tags1.iv", "value");
var result = FilterTagTransformer.Transform(source, appId, schema, tagService);
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.tags1.iv == 'value'", result.ToString());
A.CallTo(() => tagService.GetTagIdsAsync(appId, A<string>.Ignored, A<HashSet<string>>.Ignored))
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, A<string>.Ignored, A<HashSet<string>>.Ignored))
.MustNotHaveHappened();
}
@ -76,11 +77,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public void Should_not_normalize_other_typed_field()
{
var source = FilterBuilder.Eq("data.string.iv", "value");
var result = FilterTagTransformer.Transform(source, appId, schema, tagService);
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.string.iv == 'value'", result.ToString());
A.CallTo(() => tagService.GetTagIdsAsync(appId, A<string>.Ignored, A<HashSet<string>>.Ignored))
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, A<string>.Ignored, A<HashSet<string>>.Ignored))
.MustNotHaveHappened();
}
@ -88,11 +89,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public void Should_not_normalize_non_data_field()
{
var source = FilterBuilder.Eq("no.data", "value");
var result = FilterTagTransformer.Transform(source, appId, schema, tagService);
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("no.data == 'value'", result.ToString());
A.CallTo(() => tagService.GetTagIdsAsync(appId, A<string>.Ignored, A<HashSet<string>>.Ignored))
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, A<string>.Ignored, A<HashSet<string>>.Ignored))
.MustNotHaveHappened();
}
}

25
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/GrainTextIndexerTests.cs

@ -10,9 +10,9 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -23,16 +23,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public class GrainTextIndexerTests
{
private readonly IAppEntity app;
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
private readonly ITextIndexerGrain grain = A.Fake<ITextIndexerGrain>();
private readonly Guid schemaId = Guid.NewGuid();
private readonly Guid contentId = Guid.NewGuid();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly NamedContentData data = new NamedContentData();
private readonly GrainTextIndexer sut;
public GrainTextIndexerTests()
{
A.CallTo(() => grainFactory.GetGrain<ITextIndexerGrain>(schemaId, null))
app = Mocks.App(appId);
A.CallTo(() => grainFactory.GetGrain<ITextIndexerGrain>(schemaId.Id, null))
.Returns(grain);
sut = new GrainTextIndexer(grainFactory);
@ -109,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
A.CallTo(() => grain.SearchAsync("Search", A<SearchContext>.Ignored))
.Returns(foundIds);
var ids = await sut.SearchAsync("Search", GetApp(), schemaId, Scope.Draft);
var ids = await sut.SearchAsync("Search", app, schemaId.Id, Scope.Draft);
Assert.Equal(foundIds, ids);
}
@ -117,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
[Fact]
public async Task Should_not_call_grain_when_input_is_empty()
{
var ids = await sut.SearchAsync(string.Empty, GetApp(), schemaId, Scope.Published);
var ids = await sut.SearchAsync(string.Empty, app, schemaId.Id, Scope.Published);
Assert.Null(ids);
@ -125,19 +129,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
.MustNotHaveHappened();
}
private static IAppEntity GetApp()
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.EN, Language.DE));
return app;
}
private Envelope<IEvent> E(ContentEvent contentEvent)
{
contentEvent.ContentId = contentId;
contentEvent.SchemaId = NamedId.Of(schemaId, "my-schema");
contentEvent.SchemaId = schemaId;
return new Envelope<IEvent>(contentEvent);
}

6
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs

@ -11,7 +11,6 @@ using FakeItEasy;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
@ -26,6 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
private readonly Uri validUrl = new Uri("https://squidex.io");
private readonly Rule rule_0 = new Rule(new ContentChangedTriggerV2(), new TestAction());
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
public sealed class TestAction : RuleAction
@ -35,8 +35,8 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
public GuardRuleTests()
{
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A<Guid>.Ignored, false))
.Returns(A.Fake<ISchemaEntity>());
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(Mocks.Schema(appId, schemaId));
}
[Fact]

29
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs

@ -12,6 +12,7 @@ using FakeItEasy;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Xunit;
@ -21,8 +22,8 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers
public class ContentChangedTriggerTests
{
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly Guid appId = Guid.NewGuid();
private readonly Guid schemaId = Guid.NewGuid();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
[Fact]
public async Task Should_add_error_if_schema_id_is_not_defined()
@ -32,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers
Schemas = ReadOnlyCollection.Create(new ContentChangedTriggerSchemaV2())
};
var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider);
var errors = await RuleTriggerValidator.ValidateAsync(appId.Id, trigger, appProvider);
errors.Should().BeEquivalentTo(
new List<ValidationError>
@ -40,27 +41,27 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers
new ValidationError("Schema id is required.", "Schemas")
});
A.CallTo(() => appProvider.GetSchemaAsync(appId, A<Guid>.Ignored, false))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A<Guid>.Ignored, false))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_add_error_if_schemas_ids_are_not_valid()
{
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(Task.FromResult<ISchemaEntity>(null));
var trigger = new ContentChangedTriggerV2
{
Schemas = ReadOnlyCollection.Create(new ContentChangedTriggerSchemaV2 { SchemaId = schemaId })
Schemas = ReadOnlyCollection.Create(new ContentChangedTriggerSchemaV2 { SchemaId = schemaId.Id })
};
var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider);
var errors = await RuleTriggerValidator.ValidateAsync(appId.Id, trigger, appProvider);
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError($"Schema {schemaId} does not exist.", "Schemas")
new ValidationError($"Schema {schemaId.Id} does not exist.", "Schemas")
});
}
@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers
{
var trigger = new ContentChangedTriggerV2();
var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider);
var errors = await RuleTriggerValidator.ValidateAsync(appId.Id, trigger, appProvider);
Assert.Empty(errors);
}
@ -82,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers
Schemas = ReadOnlyCollection.Empty<ContentChangedTriggerSchemaV2>()
};
var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider);
var errors = await RuleTriggerValidator.ValidateAsync(appId.Id, trigger, appProvider);
Assert.Empty(errors);
}
@ -90,15 +91,15 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers
[Fact]
public async Task Should_not_add_error_if_schemas_ids_are_valid()
{
A.CallTo(() => appProvider.GetSchemaAsync(appId, A<Guid>.Ignored, false))
.Returns(A.Fake<ISchemaEntity>());
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A<Guid>.Ignored, false))
.Returns(Mocks.Schema(appId, schemaId));
var trigger = new ContentChangedTriggerV2
{
Schemas = ReadOnlyCollection.Create(new ContentChangedTriggerSchemaV2 { SchemaId = schemaId })
Schemas = ReadOnlyCollection.Create(new ContentChangedTriggerSchemaV2 { SchemaId = schemaId.Id })
};
var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider);
var errors = await RuleTriggerValidator.ValidateAsync(appId.Id, trigger, appProvider);
Assert.Empty(errors);
}

14
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs

@ -44,6 +44,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties
});
}
[Fact]
public void Should_add_error_if_resolving_references_with_more_than_one_max_items()
{
var sut = new ReferencesFieldProperties { ResolveReference = true, MaxItems = 2 };
var errors = FieldPropertiesValidator.Validate(sut).ToList();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Can only resolve references when MaxItems is 1.", "ResolveReference", "MaxItems")
});
}
[Fact]
public void Should_not_add_error_if_min_items_greater_equals_to_max_items()
{

31
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexCommandMiddlewareTests.cs

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -22,12 +23,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly ISchemasByAppIndex index = A.Fake<ISchemasByAppIndex>();
private readonly Guid appId = Guid.NewGuid();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly SchemasByAppIndexCommandMiddleware sut;
public SchemasByAppIndexCommandMiddlewareTests()
{
A.CallTo(() => grainFactory.GetGrain<ISchemasByAppIndex>(appId, null))
A.CallTo(() => grainFactory.GetGrain<ISchemasByAppIndex>(appId.Id, null))
.Returns(index);
sut = new SchemasByAppIndexCommandMiddleware(grainFactory);
@ -36,13 +38,14 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
[Fact]
public async Task Should_add_schema_to_index_on_create()
{
var context =
new CommandContext(new CreateSchema { SchemaId = appId, Name = "my-schema", AppId = BuildAppId() }, commandBus)
.Complete();
var command = new CreateSchema { SchemaId = schemaId.Id, Name = schemaId.Name, AppId = appId };
var context = new CommandContext(command, commandBus);
context.Complete();
await sut.HandleAsync(context);
A.CallTo(() => index.AddSchemaAsync(appId, "my-schema"))
A.CallTo(() => index.AddSchemaAsync(schemaId.Id, schemaId.Name))
.MustHaveHappened();
}
@ -50,30 +53,22 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
public async Task Should_remove_schema_from_index_on_delete()
{
var schemaGrain = A.Fake<ISchemaGrain>();
var schemaState = A.Fake<ISchemaEntity>();
var schemaState = Mocks.Schema(appId, schemaId);
A.CallTo(() => grainFactory.GetGrain<ISchemaGrain>(appId, null))
A.CallTo(() => grainFactory.GetGrain<ISchemaGrain>(schemaId.Id, null))
.Returns(schemaGrain);
A.CallTo(() => schemaGrain.GetStateAsync())
.Returns(J.AsTask(schemaState));
A.CallTo(() => schemaState.AppId)
.Returns(BuildAppId());
var context =
new CommandContext(new DeleteSchema { SchemaId = appId }, commandBus)
new CommandContext(new DeleteSchema { SchemaId = schemaId.Id }, commandBus)
.Complete();
await sut.HandleAsync(context);
A.CallTo(() => index.RemoveSchemaAsync(appId))
A.CallTo(() => index.RemoveSchemaAsync(schemaId.Id))
.MustHaveHappened();
}
private NamedId<Guid> BuildAppId()
{
return NamedId.Of(appId, "my-app");
}
}
}

2
tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs

@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers
protected string SchemaName { get; } = "my-schema";
protected ClaimsPrincipal User { get; } = new ClaimsPrincipal();
protected ClaimsPrincipal User { get; } = Mocks.FrontendUser();
protected NamedId<Guid> AppNamedId
{

67
tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs

@ -0,0 +1,67 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Security.Claims;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
namespace Squidex.Domain.Apps.Entities.TestHelpers
{
public static class Mocks
{
public static IAppEntity App(NamedId<Guid> appId, params Language[] languages)
{
var config = LanguagesConfig.English;
foreach (var language in languages)
{
config = config.Set(language);
}
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(config);
return app;
}
public static ISchemaEntity Schema(NamedId<Guid> appId, NamedId<Guid> schemaId, Schema schemaDef = null)
{
var schema = A.Fake<ISchemaEntity>();
A.CallTo(() => schema.Id).Returns(schemaId.Id);
A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef ?? new Schema(schemaId.Name));
return schema;
}
public static ClaimsPrincipal FrontendUser(string role = null)
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
claimsIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, DefaultClients.Frontend));
if (role != null)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
return claimsPrincipal;
}
}
}
Loading…
Cancel
Save