From eb5246492ca8334963735d9bb14c80f148de7233 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 28 Jul 2023 16:06:09 +0200 Subject: [PATCH] Component fixes. (#1010) --- .../Contents/GraphQL/Types/Builder.cs | 13 +- .../Types/Contents/DataInputGraphType.cs | 51 +---- .../Types/Contents/NestedInputGraphType.cs | 18 +- .../Contents/GraphQL/Types/FieldMap.cs | 135 +++++++++++++ .../GraphQL/Types/Primitives/JsonGraphType.cs | 39 +++- .../GraphQL/Types/SharedExtensions.cs | 23 ++- .../AppProviderExtensionsTests.cs | 18 +- .../Contents/DefaultContentWorkflowTests.cs | 2 +- .../DomainObject/Guards/GuardContentTests.cs | 10 +- .../Contents/DynamicContentWorkflowTests.cs | 2 +- .../GraphQL/GraphQLIntrospectionTests.cs | 22 +-- .../Contents/GraphQL/GraphQLMutationTests.cs | 31 +-- .../Contents/GraphQL/GraphQLQueriesTests.cs | 61 +++--- .../Contents/GraphQL/GraphQLTestBase.cs | 2 +- .../Contents/GraphQL/TestContent.cs | 185 +++++++++++------- .../Contents/GraphQL/TestQuery.cs | 2 +- .../Contents/GraphQL/TestSchemas.cs | 45 ++--- .../Contents/MongoDb/ContentsQueryFixture.cs | 6 +- .../Queries/ResolveReferencesTests.cs | 46 ++--- .../Schemas/SchemasSearchSourceTests.cs | 2 +- .../TestHelpers/Mocks.cs | 27 ++- .../TestSuite.ApiTests/GraphQLFixture.cs | 60 ++++++ .../TestSuite.ApiTests/GraphQLTests.cs | 75 +++++++ .../TestSuite.ApiTests.csproj | 1 + .../TestSuite.Shared/Model/Geography.cs | 9 + 25 files changed, 612 insertions(+), 273 deletions(-) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs index 99034c52bf..e69653ee15 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs @@ -48,6 +48,8 @@ static Builder() public IInterfaceGraphType ComponentInterface { get; } = new ComponentInterfaceGraphType(); + public FieldMap FieldMap { get; private set; } + public Builder(IAppEntity app, GraphQLOptions options) { partitionResolver = app.PartitionResolver(); @@ -64,7 +66,7 @@ public GraphQLSchema BuildSchema(IEnumerable schemas) allSchemas.AddRange(SchemaInfo.Build(schemas, typeNames).Where(x => x.Fields.Count > 0)); // Only published normal schemas (not components are used for entities). - var normalSchemas = allSchemas.Where(x => x.Schema.SchemaDef.IsPublished && x.Schema.SchemaDef.Type != SchemaType.Component).ToList(); + var normalSchemas = allSchemas.Where(IsNormalSchema).ToList(); foreach (var schemaInfo in normalSchemas) { @@ -74,7 +76,7 @@ public GraphQLSchema BuildSchema(IEnumerable schemas) contentResultTypes[schemaInfo] = new ContentResultGraphType(contentType, schemaInfo); } - foreach (var schemaInfo in normalSchemas) + foreach (var schemaInfo in allSchemas) { var componentType = new ComponentGraphType(schemaInfo); @@ -92,6 +94,8 @@ public GraphQLSchema BuildSchema(IEnumerable schemas) newSchema.Directives.Register(SharedTypes.CacheDirective); newSchema.Directives.Register(SharedTypes.OptimizeFieldQueriesDirective); + FieldMap = new FieldMap(allSchemas); + if (normalSchemas.Any()) { var mutations = new ApplicationMutations(this, normalSchemas); @@ -132,6 +136,11 @@ public GraphQLSchema BuildSchema(IEnumerable schemas) return newSchema; } + private static bool IsNormalSchema(SchemaInfo schema) + { + return schema.Schema.SchemaDef.IsPublished && schema.Schema.SchemaDef.Type != SchemaType.Component; + } + public FieldGraphSchema GetGraphType(FieldInfo fieldInfo) { return fieldInfo.Field.Accept(fieldVisitor, fieldInfo); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs index 8c77001a31..6db4255ea6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs @@ -6,16 +6,15 @@ // ========================================================================== using GraphQL.Types; -using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; internal sealed class DataInputGraphType : InputObjectGraphType { + private readonly FieldMap fieldMap; + public DataInputGraphType(Builder builder, SchemaInfo schemaInfo) { // The name is used for equal comparison. Therefore it is important to treat it as readonly. @@ -62,52 +61,12 @@ public DataInputGraphType(Builder builder, SchemaInfo schemaInfo) } Description = $"The structure of the {schemaInfo.DisplayName} data input type."; + + fieldMap = builder.FieldMap; } public override object ParseDictionary(IDictionary value) { - var result = new ContentData(); - - static ContentFieldData ToFieldData(IDictionary source, IComplexGraphType type) - { - var result = new ContentFieldData(); - - foreach (var field in type.Fields) - { - if (source.TryGetValue(field.Name, out var value)) - { - if (value is IEnumerable list && field.ResolvedType?.InnerType() is IComplexGraphType nestedType) - { - var array = new JsonArray(list.Count()); - - foreach (var item in list) - { - if (item is JsonValue { Value: JsonObject } nested) - { - array.Add(nested); - } - } - - result[field.SourceName()] = array; - } - else - { - result[field.SourceName()] = JsonGraphType.ParseJson(value); - } - } - } - - return result; - } - - foreach (var field in Fields) - { - if (field.ResolvedType is IComplexGraphType complexType && value.TryGetValue(field.Name, out var fieldValue) && fieldValue is IDictionary nested) - { - result[field.SourceName()] = ToFieldData(nested, complexType); - } - } - - return result; + return fieldMap.MapData(this, value); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs index 2e3b0b6dbe..d9a42ddf01 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs @@ -6,13 +6,13 @@ // ========================================================================== using GraphQL.Types; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; -using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; internal sealed class NestedInputGraphType : InputObjectGraphType { + private readonly FieldMap fieldMap; + public NestedInputGraphType(Builder builder, FieldInfo fieldInfo) { // The name is used for equal comparison. Therefore it is important to treat it as readonly. @@ -38,20 +38,12 @@ public NestedInputGraphType(Builder builder, FieldInfo fieldInfo) } Description = $"The structure of the {fieldInfo.DisplayName} nested schema."; + + fieldMap = builder.FieldMap; } public override object ParseDictionary(IDictionary value) { - var result = JsonValue.Object(); - - foreach (var field in Fields) - { - if (value.TryGetValue(field.Name, out var fieldValue)) - { - result[field.SourceName()] = JsonGraphType.ParseJson(fieldValue); - } - } - - return new JsonValue(result); + return fieldMap.MapNested(this, value); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs new file mode 100644 index 0000000000..b649aecd23 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs @@ -0,0 +1,135 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL.Types; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; +using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; + +internal sealed class FieldMap +{ + private readonly Dictionary> schemas = new Dictionary>(); + + public FieldMap(IEnumerable source) + { + foreach (var schema in source) + { + var fieldMap = schema.Fields.ToDictionary(x => x.FieldName, x => x.Field.Name); + + schemas[schema.Schema.Id.ToString()] = fieldMap; + schemas[schema.Schema.SchemaDef.Name] = fieldMap; + } + } + + public ContentData MapData(IInputObjectGraphType inputType, IDictionary source) + { + var result = new ContentData(); + + foreach (var field in inputType.Fields) + { + if (field.ResolvedType is IComplexGraphType complexType && source.TryGetValue(field.Name, out var value) && value is IDictionary nested) + { + result[field.SourceName()] = MapField(nested, complexType); + } + } + + return result; + } + + public JsonValue MapNested(IInputObjectGraphType inputType, IDictionary source) + { + var result = new JsonObject(source.Count); + + foreach (var field in inputType.Fields) + { + if (source.TryGetValue(field.Name, out var value)) + { + result[field.SourceName()] = MapValue(JsonGraphType.ParseJson(value)); + } + } + + return result; + } + + private ContentFieldData MapField(IDictionary source, IComplexGraphType type) + { + var result = new ContentFieldData(); + + foreach (var field in type.Fields) + { + if (source.TryGetValue(field.Name, out var value)) + { + result[field.SourceName()] = MapValue(JsonGraphType.ParseJson(value)); + } + } + + return result; + } + + private JsonValue MapValue(JsonValue source) + { + switch (source.Value) + { + case JsonArray arr: + return MapArray(arr); + case JsonObject obj: + return MapObject(obj); + default: + return source; + } + } + + private JsonValue MapArray(JsonArray source) + { + var result = new JsonArray(source.Count); + + foreach (var value in source) + { + result.Add(MapValue(value)); + } + + return result; + } + + private JsonValue MapObject(JsonObject source) + { + Dictionary? fieldMap = null; + + if (source.TryGetValue(Component.Discriminator, out var d1) && d1.Value is string discriminator) + { + schemas.TryGetValue(discriminator, out fieldMap); + } + else if (source.TryGetValue(Component.Descriptor, out var d2) && d2.Value is string descriptor) + { + schemas.TryGetValue(descriptor, out fieldMap); + } + + if (fieldMap == null) + { + return source; + } + + var result = new JsonObject(source.Count); + + foreach (var (key, value) in source) + { + var sourceName = key; + + if (fieldMap != null && fieldMap.TryGetValue(key, out var name)) + { + sourceName = name; + } + + result[sourceName] = MapValue(value); + } + + return result; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs index d2d65061fd..e66cd4c67c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs @@ -23,8 +23,10 @@ public sealed class JsonGraphType : JsonNoopGraphType return ParseJson(value); } - public static JsonValue ParseJson(object? input) + public static JsonValue ParseJson(object? input, Func?>? keyMap = null) { + keyMap ??= x => null; + switch (input) { case GraphQLBooleanValue booleanValue: @@ -44,7 +46,7 @@ public static JsonValue ParseJson(object? input) case GraphQLListValue listValue: { - var json = new JsonArray(); + var json = new JsonArray(listValue.Values?.Count ?? 0); if (listValue.Values != null) { @@ -59,13 +61,22 @@ public static JsonValue ParseJson(object? input) case GraphQLObjectValue objectValue: { - var json = JsonValue.Object(); + var json = new JsonObject(objectValue.Fields?.Count ?? 0); if (objectValue.Fields != null) { + var map = keyMap(objectValue); + foreach (var field in objectValue.Fields) { - json[field.Name.ToString()] = ParseJson(field.Value); + var sourceField = field.Name.ToString(); + + if (map?.TryGetValue(sourceField, out var temp) == true) + { + sourceField = temp; + } + + json[sourceField] = ParseJson(field.Value); } } @@ -74,7 +85,7 @@ public static JsonValue ParseJson(object? input) case IEnumerable list: { - var json = new JsonArray(); + var json = new JsonArray(list.Count()); foreach (var item in list) { @@ -86,11 +97,23 @@ public static JsonValue ParseJson(object? input) case IDictionary obj: { - var json = JsonValue.Object(); + var json = new JsonObject(obj.Count); - foreach (var (key, value) in obj) + if (obj.Count > 0) { - json[key] = ParseJson(value); + var map = keyMap(obj); + + foreach (var (key, value) in obj) + { + var sourceField = key; + + if (map?.TryGetValue(sourceField, out var temp) == true) + { + sourceField = temp; + } + + json[sourceField] = ParseJson(value); + } } return json; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs index 5c5ae090b9..6d8c1e0cb3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs @@ -10,9 +10,7 @@ using GraphQL.Utilities; using GraphQLParser; using GraphQLParser.AST; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Directives; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.ObjectPool; @@ -93,7 +91,7 @@ internal static string SourceName(this FieldType field) return typed.SourceName; } - throw new InvalidOperationException("Invalid field type"); + return field.Name; } internal static DomainId SchemaId(this FieldType field) @@ -126,6 +124,25 @@ internal static NamedId SchemaNamedId(this FieldType field) return type; } + public static bool TryGetValue(this GraphQLObjectValue source, string fieldName, out object value) + { + value = null!; + + if (source.Fields != null) + { + foreach (var field in source.Fields) + { + if (field.Name == fieldName) + { + value = field.Value; + return true; + } + } + } + + return false; + } + public static TimeSpan CacheDuration(this IResolveFieldContext context) { return CacheDirective.CacheDuration(context); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs index 0c7aa5a137..968142a054 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs @@ -35,7 +35,7 @@ public async Task Should_do_nothing_if_no_component_found() public async Task Should_resolve_self_as_component() { var schema = - Mocks.Schema(AppId, schemaId, + Mocks.Schema(AppId, schemaId.Id, new Schema(schemaId.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { @@ -60,7 +60,7 @@ public async Task Should_resolve_from_component() .Returns(component); var schema = - Mocks.Schema(AppId, schemaId, + Mocks.Schema(AppId, schemaId.Id, new Schema(schemaId.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { @@ -82,7 +82,7 @@ public async Task Should_resolve_from_components() .Returns(component); var schema = - Mocks.Schema(AppId, schemaId, + Mocks.Schema(AppId, schemaId.Id, new Schema(schemaId.Name) .AddComponents(1, "1", Partitioning.Invariant, new ComponentsFieldProperties { @@ -104,7 +104,7 @@ public async Task Should_resolve_from_array() .Returns(component); var schema = - Mocks.Schema(AppId, schemaId, + Mocks.Schema(AppId, schemaId.Id, new Schema(schemaId.Name) .AddArray(1, "1", Partitioning.Invariant, a => a .AddComponent(2, "2", new ComponentFieldProperties @@ -122,7 +122,7 @@ public async Task Should_resolve_from_array() public async Task Should_resolve_self_referencing_component() { var component = - Mocks.Schema(AppId, componentId1, + Mocks.Schema(AppId, componentId1.Id, new Schema(componentId1.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { @@ -133,7 +133,7 @@ public async Task Should_resolve_self_referencing_component() .Returns(component); var schema = - Mocks.Schema(AppId, schemaId, + Mocks.Schema(AppId, schemaId.Id, new Schema(schemaId.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { @@ -150,7 +150,7 @@ public async Task Should_resolve_self_referencing_component() public async Task Should_resolve_component_of_component() { var component1 = - Mocks.Schema(AppId, componentId1, + Mocks.Schema(AppId, componentId1.Id, new Schema(componentId1.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { @@ -158,7 +158,7 @@ public async Task Should_resolve_component_of_component() })); var component2 = - Mocks.Schema(AppId, componentId2, + Mocks.Schema(AppId, componentId2.Id, new Schema(componentId2.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { @@ -172,7 +172,7 @@ public async Task Should_resolve_component_of_component() .Returns(component2); var schema = - Mocks.Schema(AppId, schemaId, + Mocks.Schema(AppId, schemaId.Id, new Schema(schemaId.Name) .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs index 11e402258d..be664c1a2c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs @@ -192,6 +192,6 @@ private static ISchemaEntity CreateSchema(bool validateOnPublish) ValidateOnPublish = validateOnPublish }); - return Mocks.Schema(NamedId.Of(DomainId.NewGuid(), "my-app"), NamedId.Of(DomainId.NewGuid(), schema.Name), schema); + return Mocks.Schema(NamedId.Of(DomainId.NewGuid(), "my-app"), DomainId.NewGuid(), schema); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs index 18fc31046c..e0e11f2856 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs @@ -35,19 +35,19 @@ public class GuardContentTests : GivenContext, IClassFixture commandBus.PublishAsync(A.Ignored, A._)) .Returns(commandContext); @@ -93,7 +94,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { }, Variables = new { - data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id), + data = TestContent.Input(content, TestSchemas.Reference1.Id, TestSchemas.Reference2.Id), }, Permission = PermissionIds.AppContentsCreate }); @@ -111,7 +112,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => x.ExpectedVersion == EtagVersion.Any && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Status == Status.Published && x.Data.Equals(content.Data)), A._)) @@ -138,7 +139,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { }, Variables = new { - data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + data = TestContent.Input(content, TestSchemas.Reference1.Id, TestSchemas.Reference2.Id) }, Permission = PermissionIds.AppContentsCreate }); @@ -157,7 +158,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { A.That.Matches(x => x.ExpectedVersion == EtagVersion.Any && x.ContentId == contentId && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Status == Status.Published && x.Data.Equals(content.Data)), A._)) @@ -234,7 +235,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { }, Variables = new { - data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + data = TestContent.Input(content, TestSchemas.Reference1.Id, TestSchemas.Reference2.Id) }, Permission = PermissionIds.AppContentsUpdateOwn }); @@ -253,7 +254,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { A.That.Matches(x => x.ContentId == contentId && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Data.Equals(content.Data)), A._)) .MustHaveHappened(); @@ -325,7 +326,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { }, Variables = new { - data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + data = TestContent.Input(content, TestSchemas.Reference1.Id, TestSchemas.Reference2.Id) }, Permission = PermissionIds.AppContentsUpsert }); @@ -344,7 +345,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { A.That.Matches(x => x.ContentId == contentId && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Status == Status.Published && x.Data.Equals(content.Data)), A._)) @@ -421,7 +422,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { }, Variables = new { - data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + data = TestContent.Input(content, TestSchemas.Reference1.Id, TestSchemas.Reference2.Id) }, Permission = PermissionIds.AppContentsUpdateOwn }); @@ -440,7 +441,7 @@ mutation MyMutation($data: MySchemaDataInputDto!) { A.That.Matches(x => x.ContentId == contentId && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Data.Equals(content.Data)), A._)) .MustHaveHappened(); @@ -534,7 +535,7 @@ public async Task Should_return_single_content_if_changing_status() x.ContentId == contentId && x.DueTime == dueTime && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Status == Status.Published), A._)) .MustHaveHappened(); @@ -576,7 +577,7 @@ public async Task Should_return_single_content_if_changing_status_without_due_ti x.ContentId == contentId && x.DueTime == null && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Status == Status.Published), A._)) .MustHaveHappened(); @@ -618,7 +619,7 @@ public async Task Should_return_single_content_if_changing_status_with_null_due_ x.ContentId == contentId && x.DueTime == null && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && + x.SchemaId.Equals(TestSchemas.Default.NamedId()) && x.Status == Status.Published), A._)) .MustHaveHappened(); @@ -708,7 +709,7 @@ public async Task Should_return_new_version_if_deleting_content() A.That.Matches(x => x.ContentId == contentId && x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId)), + x.SchemaId.Equals(TestSchemas.Default.NamedId())), A._)) .MustHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index 9fa80dc1aa..79666087b5 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -7,6 +7,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -502,7 +503,7 @@ public async Task Should_return_null_if_single_content_not_found() public async Task Should_return_null_if_single_content_from_another_schema() { var contentId = DomainId.NewGuid(); - var content = TestContent.CreateRef(TestSchemas.Ref1Id, contentId, "ref1-field", "ref1"); + var content = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentId, "reference1-field", "reference1"); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentId), @@ -612,7 +613,7 @@ public async Task Should_return_single_content_if_finding_content_with_version() public async Task Should_also_fetch_embedded_contents_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -641,9 +642,9 @@ public async Task Should_also_fetch_embedded_contents_if_field_is_included_in_qu ... on Content { id } - ... on MyRefSchema1 { + ... on MyReference1 { data { - schemaRef1Field { + reference1Field { iv } } @@ -681,9 +682,9 @@ ... on MyRefSchema1 { id = contentRefId, data = new { - schemaRef1Field = new + reference1Field = new { - iv = "ref1" + iv = "reference1" } } } @@ -702,7 +703,7 @@ ... on MyRefSchema1 { public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -728,7 +729,7 @@ public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_ iv { id data { - schemaRef1Field { + reference1Field { iv } } @@ -761,9 +762,9 @@ public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_ id = contentRefId, data = new { - schemaRef1Field = new + reference1Field = new { - iv = "ref1" + iv = "reference1" } } } @@ -781,7 +782,7 @@ public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_ public async Task Should_also_fetch_referenced_contents_from_flat_data_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -843,7 +844,7 @@ public async Task Should_also_fetch_referenced_contents_from_flat_data_if_field_ public async Task Should_cache_referenced_contents_from_flat_data_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -914,7 +915,7 @@ myReferences @cache(duration: 1000) { public async Task Should_also_fetch_referencing_contents_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -933,7 +934,7 @@ public async Task Should_also_fetch_referencing_contents_if_field_is_included_in { Query = @" query { - findMyRefSchema1Content(id: '{contentId}') { + findMyReference1Content(id: '{contentId}') { id referencingMySchemaContents(top: 30, skip: 5) { id @@ -955,7 +956,7 @@ public async Task Should_also_fetch_referencing_contents_if_field_is_included_in { data = new { - findMyRefSchema1Content = new + findMyReference1Content = new { id = contentRefId, referencingMySchemaContents = new[] @@ -983,7 +984,7 @@ public async Task Should_also_fetch_referencing_contents_if_field_is_included_in public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -1002,7 +1003,7 @@ public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_ { Query = @" query { - findMyRefSchema1Content(id: '{contentId}') { + findMyReference1Content(id: '{contentId}') { id referencingMySchemaContentsWithTotal(top: 30, skip: 5) { total @@ -1027,7 +1028,7 @@ public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_ { data = new { - findMyRefSchema1Content = new + findMyReference1Content = new { id = contentRefId, referencingMySchemaContentsWithTotal = new @@ -1059,7 +1060,7 @@ public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_ public async Task Should_also_fetch_references_contents_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -1080,7 +1081,7 @@ public async Task Should_also_fetch_references_contents_if_field_is_included_in_ query { findMySchemaContent(id: '{contentId}') { id - referencesMyRefSchema1Contents(top: 30, skip: 5) { + referencesMyReference1Contents(top: 30, skip: 5) { id } } @@ -1098,7 +1099,7 @@ public async Task Should_also_fetch_references_contents_if_field_is_included_in_ findMySchemaContent = new { id = contentId, - referencesMyRefSchema1Contents = new[] + referencesMyReference1Contents = new[] { new { @@ -1116,7 +1117,7 @@ public async Task Should_also_fetch_references_contents_if_field_is_included_in_ public async Task Should_also_fetch_references_contents_with_total_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -1137,7 +1138,7 @@ public async Task Should_also_fetch_references_contents_with_total_if_field_is_i query { findMySchemaContent(id: '{contentId}') { id - referencesMyRefSchema1ContentsWithTotal(top: 30, skip: 5) { + referencesMyReference1ContentsWithTotal(top: 30, skip: 5) { total items { id @@ -1158,7 +1159,7 @@ public async Task Should_also_fetch_references_contents_with_total_if_field_is_i findMySchemaContent = new { id = contentId, - referencesMyRefSchema1ContentsWithTotal = new + referencesMyReference1ContentsWithTotal = new { total = 10, items = new[] @@ -1180,7 +1181,7 @@ public async Task Should_also_fetch_references_contents_with_total_if_field_is_i public async Task Should_also_fetch_union_contents_if_field_is_included_in_query() { var contentRefId = DomainId.NewGuid(); - var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1"); + var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1"); var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); @@ -1207,9 +1208,9 @@ public async Task Should_also_fetch_union_contents_if_field_is_included_in_query ... on Content { id } - ... on MyRefSchema1 { + ... on MyReference1 { data { - schemaRef1Field { + reference1Field { iv } } @@ -1244,12 +1245,12 @@ ... on MyRefSchema1 { id = contentRefId, data = new { - schemaRef1Field = new + reference1Field = new { - iv = "ref1" + iv = "reference1" } }, - __typename = "MyRefSchema1" + __typename = "MyReference1" } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index ccb84af7aa..50be7d694a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -76,7 +76,7 @@ protected void AssertResult(object expected, ExecutionResult actual) protected Task ExecuteAsync(TestQuery query) { // Use a shared instance to test caching. - sut ??= CreateSut(TestSchemas.Default, TestSchemas.Ref1, TestSchemas.Ref2); + sut ??= CreateSut(TestSchemas.Default, TestSchemas.Reference1, TestSchemas.Reference2, TestSchemas.Component); var options = query.ToOptions(sut.Services); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs index 9b115f2eab..a593e3993e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs @@ -7,6 +7,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; @@ -88,7 +89,7 @@ public static class TestContent iv { schemaId schemaName - schemaRef1Field + componentField } } myComponents__Dynamic { @@ -97,15 +98,20 @@ public static class TestContent myComponents { iv { __typename - ... on MyRefSchema1Component { + ... on MyReference1Component { schemaId schemaName - schemaRef1Field + reference1Field } - ... on MyRefSchema2Component { + ... on MyReference2Component { schemaId schemaName - schemaRef2Field + reference2Field + } + ... on MyComponentComponent { + schemaId + schemaName + componentField } } } @@ -176,20 +182,25 @@ ... on MyRefSchema2Component { myComponent { schemaId schemaName - schemaRef1Field + componentField } myComponents__Dynamic myComponents { __typename - ... on MyRefSchema1Component { + ... on MyReference1Component { + schemaId + schemaName + reference1Field + } + ... on MyReference2Component { schemaId schemaName - schemaRef1Field + reference2Field } - ... on MyRefSchema2Component { + ... on MyComponentComponent { schemaId schemaName - schemaRef2Field + componentField } } myTags @@ -249,21 +260,25 @@ public static IEnrichedContentEntity Create(DomainId id, DomainId refId = defaul new ContentFieldData() .AddInvariant( JsonValue.Object() - .Add(Component.Discriminator, TestSchemas.Ref1.Id) - .Add(Component.Descriptor, TestSchemas.Ref1.SchemaDef.Name) - .Add("schemaRef1Field", "Component1"))) + .Add(Component.Discriminator, TestSchemas.Component.Id) + .Add(Component.Descriptor, TestSchemas.Component.SchemaDef.Name) + .Add("component-field", "Component1"))) .AddField("my-components", new ContentFieldData() .AddInvariant( JsonValue.Array( JsonValue.Object() - .Add(Component.Discriminator, TestSchemas.Ref1.Id) - .Add(Component.Descriptor, TestSchemas.Ref1.SchemaDef.Name) - .Add("schemaRef1Field", "Component1"), + .Add(Component.Discriminator, TestSchemas.Reference1.Id) + .Add(Component.Descriptor, TestSchemas.Reference1.SchemaDef.Name) + .Add("reference1-field", "Component1"), JsonValue.Object() - .Add(Component.Discriminator, TestSchemas.Ref2.Id) - .Add(Component.Descriptor, TestSchemas.Ref2.SchemaDef.Name) - .Add("schemaRef2Field", "Component2")))) + .Add(Component.Discriminator, TestSchemas.Reference2.Id) + .Add(Component.Descriptor, TestSchemas.Reference2.SchemaDef.Name) + .Add("reference2-field", "Component2"), + JsonValue.Object() + .Add(Component.Discriminator, TestSchemas.Component.Id) + .Add(Component.Descriptor, TestSchemas.Component.SchemaDef.Name) + .Add("component-field", "Component3")))) .AddField("my-json", new ContentFieldData() .AddInvariant( @@ -321,7 +336,7 @@ public static IEnrichedContentEntity Create(DomainId id, DomainId refId = defaul LastModified = now, LastModifiedBy = RefToken.Client("client1"), Data = data, - SchemaId = TestSchemas.DefaultId, + SchemaId = TestSchemas.Default.NamedId(), Status = Status.Draft, StatusColor = "red", NewStatus = Status.Published, @@ -492,9 +507,9 @@ public static object Input(IContentEntity content, DomainId refId = default, Dom { iv = new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["componentField"] = "Component1" } }, ["myComponents"] = new @@ -503,15 +518,21 @@ public static object Input(IContentEntity content, DomainId refId = default, Dom { new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Reference1.Id.ToString(), + ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["reference1Field"] = "Component1" }, new Dictionary { - ["schemaId"] = TestSchemas.Ref2.Id.ToString(), - ["schemaName"] = TestSchemas.Ref2.SchemaDef.Name, - ["schemaRef2Field"] = "Component2" + ["schemaId"] = TestSchemas.Reference2.Id.ToString(), + ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["reference2Field"] = "Component2" + }, + new Dictionary + { + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["componentField"] = "Component3" } } }, @@ -659,18 +680,18 @@ private static object Data(IContentEntity content) { iv = new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["component-field"] = "Component1" } }, ["myComponent"] = new { iv = new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["componentField"] = "Component1" } }, ["myComponents__Dynamic"] = new @@ -679,15 +700,21 @@ private static object Data(IContentEntity content) { new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Reference1.Id.ToString(), + ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["reference1-field"] = "Component1" + }, + new Dictionary + { + ["schemaId"] = TestSchemas.Reference2.Id.ToString(), + ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["reference2-field"] = "Component2" }, new Dictionary { - ["schemaId"] = TestSchemas.Ref2.Id.ToString(), - ["schemaName"] = TestSchemas.Ref2.SchemaDef.Name, - ["schemaRef2Field"] = "Component2" + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["component-field"] = "Component3" } } }, @@ -697,17 +724,24 @@ private static object Data(IContentEntity content) { new Dictionary { - ["__typename"] = "MyRefSchema1Component", - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["__typename"] = "MyReference1Component", + ["schemaId"] = TestSchemas.Reference1.Id.ToString(), + ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["reference1Field"] = "Component1" + }, + new Dictionary + { + ["__typename"] = "MyReference2Component", + ["schemaId"] = TestSchemas.Reference2.Id.ToString(), + ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["reference2Field"] = "Component2" }, new Dictionary { - ["__typename"] = "MyRefSchema2Component", - ["schemaId"] = TestSchemas.Ref2.Id.ToString(), - ["schemaName"] = TestSchemas.Ref2.SchemaDef.Name, - ["schemaRef2Field"] = "Component2" + ["__typename"] = "MyComponentComponent", + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["componentField"] = "Component3" } } }, @@ -788,46 +822,59 @@ private static object FlatData(IContentEntity content) }, ["myComponent__Dynamic"] = new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["component-field"] = "Component1" }, ["myComponent"] = new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["componentField"] = "Component1" }, ["myComponents__Dynamic"] = new[] { new Dictionary { - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["schemaId"] = TestSchemas.Reference1.Id.ToString(), + ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["reference1-field"] = "Component1" }, new Dictionary { - ["schemaId"] = TestSchemas.Ref2.Id.ToString(), - ["schemaName"] = TestSchemas.Ref2.SchemaDef.Name, - ["schemaRef2Field"] = "Component2" + ["schemaId"] = TestSchemas.Reference2.Id.ToString(), + ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["reference2-field"] = "Component2" + }, + new Dictionary + { + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["component-field"] = "Component3" } }, ["myComponents"] = new object[] { new Dictionary { - ["__typename"] = "MyRefSchema1Component", - ["schemaId"] = TestSchemas.Ref1.Id.ToString(), - ["schemaName"] = TestSchemas.Ref1.SchemaDef.Name, - ["schemaRef1Field"] = "Component1" + ["__typename"] = "MyReference1Component", + ["schemaId"] = TestSchemas.Reference1.Id.ToString(), + ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["reference1Field"] = "Component1" + }, + new Dictionary + { + ["__typename"] = "MyReference2Component", + ["schemaId"] = TestSchemas.Reference2.Id.ToString(), + ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["reference2Field"] = "Component2" }, new Dictionary { - ["__typename"] = "MyRefSchema2Component", - ["schemaId"] = TestSchemas.Ref2.Id.ToString(), - ["schemaName"] = TestSchemas.Ref2.SchemaDef.Name, - ["schemaRef2Field"] = "Component2" + ["__typename"] = "MyComponentComponent", + ["schemaId"] = TestSchemas.Component.Id.ToString(), + ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["componentField"] = "Component3" } }, ["myTags"] = new[] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs index 4fed4bdc5c..3dbcad65f9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs @@ -73,7 +73,7 @@ static Context BuildContext(string? permissionId) return new Context(Mocks.FrontendUser(), TestApp.Default); } - var permission = PermissionIds.ForApp(permissionId, TestApp.Default.Name, TestSchemas.DefaultId.Name).Id; + var permission = PermissionIds.ForApp(permissionId, TestApp.Default.Name, TestSchemas.Default.SchemaDef.Name).Id; return new Context(Mocks.FrontendUser(permission: permission), TestApp.Default); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs index f8340ef340..0946c4d6ca 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs @@ -16,26 +16,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; public static class TestSchemas { - public static readonly NamedId DefaultId = NamedId.Of(DomainId.NewGuid(), "my-schema"); - public static readonly NamedId Ref1Id = NamedId.Of(DomainId.NewGuid(), "my-ref-schema1"); - public static readonly NamedId Ref2Id = NamedId.Of(DomainId.NewGuid(), "my-ref-schema2"); - public static readonly ISchemaEntity Default; - public static readonly ISchemaEntity Ref1; - public static readonly ISchemaEntity Ref2; + public static readonly ISchemaEntity Reference1; + public static readonly ISchemaEntity Reference2; + public static readonly ISchemaEntity Component; static TestSchemas() { - Ref1 = Mocks.Schema(TestApp.DefaultId, Ref1Id, - new Schema(Ref1Id.Name) - .Publish() - .AddString(1, "schemaRef1Field", Partitioning.Invariant)); - - Ref2 = Mocks.Schema(TestApp.DefaultId, Ref2Id, - new Schema(Ref2Id.Name) - .Publish() - .AddString(1, "schemaRef2Field", Partitioning.Invariant)); - var enums = ReadonlyList.Create("EnumA", "EnumB", "EnumC"); var jsonSchema = @" @@ -56,8 +43,22 @@ type JsonNested { nestedArray: [String] }"; - Default = Mocks.Schema(TestApp.DefaultId, DefaultId, - new Schema(DefaultId.Name) + Component = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), + new Schema("my-component", type: SchemaType.Component) + .AddString(1, "component-field", Partitioning.Invariant)); + + Reference1 = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), + new Schema("my-reference1") + .Publish() + .AddString(1, "reference1-field", Partitioning.Invariant)); + + Reference2 = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), + new Schema("my-reference2") + .Publish() + .AddString(1, "reference2-field", Partitioning.Invariant)); + + Default = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), + new Schema("my-schema") .Publish() .AddJson(1, "my-json", Partitioning.Invariant, new JsonFieldProperties()) @@ -78,15 +79,15 @@ type JsonNested { .AddDateTime(9, "my-datetime", Partitioning.Invariant, new DateTimeFieldProperties()) .AddReferences(10, "my-references", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = Ref1Id.Id }) + new ReferencesFieldProperties { SchemaId = Reference1.Id }) .AddReferences(11, "my-union", Partitioning.Invariant, new ReferencesFieldProperties()) .AddGeolocation(12, "my-geolocation", Partitioning.Invariant, new GeolocationFieldProperties()) .AddComponent(13, "my-component", Partitioning.Invariant, - new ComponentFieldProperties { SchemaId = Ref1Id.Id }) + new ComponentFieldProperties { SchemaId = Component.Id }) .AddComponents(14, "my-components", Partitioning.Invariant, - new ComponentsFieldProperties { SchemaIds = ReadonlyList.Create(Ref1.Id, Ref2.Id) }) + new ComponentsFieldProperties { SchemaIds = ReadonlyList.Create(Reference1.Id, Reference2.Id, Component.Id) }) .AddTags(15, "my-tags", Partitioning.Invariant, new TagsFieldProperties()) .AddTags(16, "my-tags-enum", Partitioning.Invariant, @@ -97,7 +98,7 @@ type JsonNested { .AddNumber(122, "nested-number", new NumberFieldProperties())) .AddString(17, "my-embeds", Partitioning.Invariant, - new StringFieldProperties { IsEmbeddable = true, SchemaIds = ReadonlyList.Create(Ref1.Id, Ref2.Id) }) + new StringFieldProperties { IsEmbeddable = true, SchemaIds = ReadonlyList.Create(Reference1.Id, Reference2.Id) }) .SetScripts(new SchemaScripts { Query = "" })); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs index cbaed3237e..ede912c85c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs @@ -221,12 +221,10 @@ private static ISchemaEntity CreateSchema(DomainId appId, DomainId schemaId) new Schema("my-schema") .AddField(Fields.Number(1, "value", Partitioning.Invariant)); - var schema = + return Mocks.Schema( NamedId.Of(appId, "my-app"), - NamedId.Of(schemaId, "my-schema"), + schemaId, schemaDef); - - return schema; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs index 99d529364a..4546689579 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs @@ -21,14 +21,14 @@ public class ResolveReferencesTests : GivenContext, IClassFixture(); private readonly IRequestCache requestCache = A.Fake(); - private readonly NamedId refSchemaId1 = NamedId.Of(DomainId.NewGuid(), "my-ref1"); - private readonly NamedId refSchemaId2 = NamedId.Of(DomainId.NewGuid(), "my-ref2"); + private readonly NamedId referenceSchemaId1 = NamedId.Of(DomainId.NewGuid(), "my-ref1"); + private readonly NamedId referenceSchemaId2 = NamedId.Of(DomainId.NewGuid(), "my-ref2"); private readonly ProvideSchema schemaProvider; private readonly ResolveReferences sut; public ResolveReferencesTests() { - var refSchemaDef = + var referencedSchemaDef = new Schema("my-ref") .AddString(1, "name", Partitioning.Invariant, new StringFieldProperties()) @@ -43,14 +43,14 @@ public ResolveReferencesTests() ResolveReference = true, MinItems = 1, MaxItems = 1, - SchemaId = refSchemaId1.Id + SchemaId = referenceSchemaId1.Id }) .AddReferences(2, "ref2", Partitioning.Invariant, new ReferencesFieldProperties { ResolveReference = true, MinItems = 1, MaxItems = 1, - SchemaId = refSchemaId2.Id + SchemaId = referenceSchemaId2.Id }) .SetFieldsInLists("ref1", "ref2"); @@ -63,13 +63,13 @@ public ResolveReferencesTests() { return Task.FromResult((Schema, ResolvedComponents.Empty)); } - else if (x == refSchemaId1.Id) + else if (x == referenceSchemaId1.Id) { - return Task.FromResult((Mocks.Schema(AppId, refSchemaId1, refSchemaDef), ResolvedComponents.Empty)); + return Task.FromResult((Mocks.Schema(AppId, referenceSchemaId1.Id, referencedSchemaDef), ResolvedComponents.Empty)); } - else if (x == refSchemaId2.Id) + else if (x == referenceSchemaId2.Id) { - return Task.FromResult((Mocks.Schema(AppId, refSchemaId2, refSchemaDef), ResolvedComponents.Empty)); + return Task.FromResult((Mocks.Schema(AppId, referenceSchemaId2.Id, referencedSchemaDef), ResolvedComponents.Empty)); } else { @@ -83,10 +83,10 @@ public ResolveReferencesTests() [Fact] public async Task Should_add_referenced_id_and_as_dependency() { - var ref1_1 = CreateRefContent(DomainId.NewGuid(), 1, "ref1_1", 13, refSchemaId1); - var ref1_2 = CreateRefContent(DomainId.NewGuid(), 2, "ref1_2", 17, refSchemaId1); - var ref2_1 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_1", 23, refSchemaId2); - var ref2_2 = CreateRefContent(DomainId.NewGuid(), 4, "ref2_2", 29, refSchemaId2); + var ref1_1 = CreateRefContent(DomainId.NewGuid(), 1, "ref1_1", 13, referenceSchemaId1); + var ref1_2 = CreateRefContent(DomainId.NewGuid(), 2, "ref1_2", 17, referenceSchemaId1); + var ref2_1 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_1", 23, referenceSchemaId2); + var ref2_2 = CreateRefContent(DomainId.NewGuid(), 4, "ref2_2", 29, referenceSchemaId2); var contents = new[] { @@ -100,10 +100,10 @@ public async Task Should_add_referenced_id_and_as_dependency() await sut.EnrichAsync(FrontendContext, contents, schemaProvider, default); - A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, refSchemaId1.Id), 0)) + A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, referenceSchemaId1.Id), 0)) .MustHaveHappened(); - A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, refSchemaId2.Id), 0)) + A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, referenceSchemaId2.Id), 0)) .MustHaveHappened(); A.CallTo(() => requestCache.AddDependency(ref1_1.UniqueId, ref1_1.Version)) @@ -122,10 +122,10 @@ public async Task Should_add_referenced_id_and_as_dependency() [Fact] public async Task Should_enrich_with_reference_data() { - var ref1_1 = CreateRefContent(DomainId.NewGuid(), 1, "ref1_1", 13, refSchemaId1); - var ref1_2 = CreateRefContent(DomainId.NewGuid(), 2, "ref1_2", 17, refSchemaId1); - var ref2_1 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_1", 23, refSchemaId2); - var ref2_2 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_2", 29, refSchemaId2); + var ref1_1 = CreateRefContent(DomainId.NewGuid(), 1, "ref1_1", 13, referenceSchemaId1); + var ref1_2 = CreateRefContent(DomainId.NewGuid(), 2, "ref1_2", 17, referenceSchemaId1); + var ref2_1 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_1", 23, referenceSchemaId2); + var ref2_2 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_2", 29, referenceSchemaId2); var contents = new[] { @@ -175,10 +175,10 @@ public async Task Should_enrich_with_reference_data() [Fact] public async Task Should_not_enrich_if_content_has_more_items() { - var ref1_1 = CreateRefContent(DomainId.NewGuid(), 1, "ref1_1", 13, refSchemaId1); - var ref1_2 = CreateRefContent(DomainId.NewGuid(), 2, "ref1_2", 17, refSchemaId1); - var ref2_1 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_1", 23, refSchemaId2); - var ref2_2 = CreateRefContent(DomainId.NewGuid(), 4, "ref2_2", 29, refSchemaId2); + var ref1_1 = CreateRefContent(DomainId.NewGuid(), 1, "ref1_1", 13, referenceSchemaId1); + var ref1_2 = CreateRefContent(DomainId.NewGuid(), 2, "ref1_2", 17, referenceSchemaId1); + var ref2_1 = CreateRefContent(DomainId.NewGuid(), 3, "ref2_1", 23, referenceSchemaId2); + var ref2_2 = CreateRefContent(DomainId.NewGuid(), 4, "ref2_2", 29, referenceSchemaId2); var contents = new[] { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs index a722d64fb9..1784296ad4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs @@ -119,6 +119,6 @@ public async Task Should_return_actual_to_schema_and_contents_if_schema_is_singl private ISchemaEntity CreateSchema(string name, SchemaType type = SchemaType.Default) { - return Mocks.Schema(AppId, NamedId.Of(DomainId.NewGuid(), name), new Schema(name, type: type)); + return Mocks.Schema(AppId, DomainId.NewGuid(), new Schema(name, type: type)); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs index 613c5fbbe8..f282f06e43 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs @@ -39,18 +39,29 @@ public static IAppEntity App(NamedId appId, params Language[] language return app; } - public static ISchemaEntity Schema(NamedId appId, NamedId schemaId, Schema? schemaDef = null) + public static ISchemaEntity Schema(NamedId appId, NamedId schemaId) { - var schema = A.Fake(); + var schemaEntity = A.Fake(); + var schemaDef = new Schema(schemaId.Name).Publish(); - schemaDef ??= new Schema(schemaId.Name).Publish(); + A.CallTo(() => schemaEntity.Id).Returns(schemaId.Id); + A.CallTo(() => schemaEntity.AppId).Returns(appId); + A.CallTo(() => schemaEntity.SchemaDef).Returns(schemaDef); + A.CallTo(() => schemaEntity.UniqueId).Returns(DomainId.Combine(appId, schemaId.Id)); - A.CallTo(() => schema.Id).Returns(schemaId.Id); - A.CallTo(() => schema.AppId).Returns(appId); - A.CallTo(() => schema.SchemaDef).Returns(schemaDef); - A.CallTo(() => schema.UniqueId).Returns(DomainId.Combine(appId, schemaId.Id)); + return schemaEntity; + } + + public static ISchemaEntity Schema(NamedId appId, DomainId schemaId, Schema schemaDef) + { + var schemaEntity = A.Fake(); + + A.CallTo(() => schemaEntity.Id).Returns(schemaId); + A.CallTo(() => schemaEntity.AppId).Returns(appId); + A.CallTo(() => schemaEntity.SchemaDef).Returns(schemaDef); + A.CallTo(() => schemaEntity.UniqueId).Returns(DomainId.Combine(appId, schemaId)); - return schema; + return schemaEntity; } public static ITeamEntity Team(DomainId teamId, string teamName) diff --git a/tools/TestSuite/TestSuite.ApiTests/GraphQLFixture.cs b/tools/TestSuite/TestSuite.ApiTests/GraphQLFixture.cs index 6d81165199..dee9a19bed 100644 --- a/tools/TestSuite/TestSuite.ApiTests/GraphQLFixture.cs +++ b/tools/TestSuite/TestSuite.ApiTests/GraphQLFixture.cs @@ -63,6 +63,23 @@ async Task CreateSchemaAsync(CreateSchemaDto request) } } + var createLocationRequest = new CreateSchemaDto + { + Name = "location", + Fields = new List + { + new UpsertSchemaFieldDto + { + Name = "name", + Properties = new StringFieldPropertiesDto() + } + }, + Type = SchemaType.Component + }; + + var locationsId = await CreateSchemaAsync(createLocationRequest); + + // STEP 1: Create cities schema. var createCitiesRequest = new CreateSchemaDto { @@ -73,6 +90,28 @@ async Task CreateSchemaAsync(CreateSchemaDto request) { Name = "name", Properties = new StringFieldPropertiesDto() + }, + new UpsertSchemaFieldDto + { + Name = "topLocation", + Properties = new ComponentFieldPropertiesDto + { + SchemaIds = new List + { + locationsId + } + } + }, + new UpsertSchemaFieldDto + { + Name = "locations", + Properties = new ComponentsFieldPropertiesDto + { + SchemaIds = new List + { + locationsId + } + } } }, IsPublished = true @@ -149,6 +188,27 @@ async Task CreateCityAsync(string name) name = new { iv = name + }, + topLocation = new + { + iv = new + { + name = $"{name} Top Location" + } + }, + locations = new + { + iv = new[] + { + new + { + name = $"{name} Location 1", + }, + new + { + name = $"{name} Location 2", + } + } } }; diff --git a/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs b/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs index 31f932dd52..aa1973fc6d 100644 --- a/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs +++ b/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Net.Http.Json; +using FluentAssertions; using Newtonsoft.Json.Linq; using Squidex.ClientLibrary; using Squidex.ClientLibrary.Utils; @@ -61,6 +62,80 @@ public async Task Should_query_json() Assert.Equal(2, result["findMyWritesContent"]["flatData"]["json"]["obj"]["value"].Value()); } + [Fact] + public async Task Should_query_graphql_with_components() + { + var query = new + { + query = @" + { + cities: queryCitiesContents { + data: flatData { + name, + topLocation { + name + }, + locations { + name + } + } + } + }" + }; + + var result = await _.Client.SharedDynamicContents.GraphQlAsync(query); + + var cities = result["cities"].ToObject>(); + + cities.Should().BeEquivalentTo(new List + { + new City + { + Data = new CityData + { + Name = "Leipzig", + TopLocation = new LocationData + { + Name = "Leipzig Top Location" + }, + Locations = new List + { + new LocationData + { + Name = "Leipzig Location 1" + }, + new LocationData + { + Name = "Leipzig Location 2" + } + } + } + }, + new City + { + Data = new CityData + { + Name = "Munich", + TopLocation = new LocationData + { + Name = "Munich Top Location" + }, + Locations = new List + { + new LocationData + { + Name = "Munich Location 1" + }, + new LocationData + { + Name = "Munich Location 2" + } + } + } + } + }); + } + [Fact] public async Task Should_query_graphql_by_reference_selector() { diff --git a/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj b/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj index 334b241f77..6e1c2c1d41 100644 --- a/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj +++ b/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj @@ -15,6 +15,7 @@ + diff --git a/tools/TestSuite/TestSuite.Shared/Model/Geography.cs b/tools/TestSuite/TestSuite.Shared/Model/Geography.cs index ca5a91d1d8..6b6472ea56 100644 --- a/tools/TestSuite/TestSuite.Shared/Model/Geography.cs +++ b/tools/TestSuite/TestSuite.Shared/Model/Geography.cs @@ -39,6 +39,15 @@ public sealed class City } public sealed class CityData +{ + public string Name { get; set; } + + public LocationData TopLocation { get; set; } + + public List Locations { get; set; } +} + +public sealed class LocationData { public string Name { get; set; } }