Browse Source

Merge branch 'master' of github.com:Squidex/squidex

pull/1034/head
Sebastian 3 years ago
parent
commit
e663f51e1a
  1. 2
      .github/workflows/check-updates.yml
  2. 2
      .github/workflows/dev.yml
  3. 2
      .github/workflows/release.yml
  4. 9
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs
  5. 3
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx
  6. 11
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs
  7. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  8. 55
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs
  9. 30
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs
  10. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  11. 42
      backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptAnyBodyAttribute.cs
  12. 13
      backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptQueryAttribute.cs
  13. 86
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsSharedController.cs
  14. 31
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/GraphQLQueryDto.cs
  15. 125
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  16. 7
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  17. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs
  18. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs
  19. 8
      frontend/package-lock.json
  20. 2
      frontend/package.json

2
.github/workflows/check-updates.yml

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.0.0
- uses: actions/checkout@v4.1.0
with:
token: ${{ secrets.WORKFLOW_SECRET }}

2
.github/workflows/dev.yml

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Prepare - Checkout
uses: actions/checkout@v4.0.0
uses: actions/checkout@v4.1.0
- name: Prepare - Inject short Variables
uses: rlespinasse/github-slug-action@v4.4.1

2
.github/workflows/release.yml

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Prepare - Checkout
uses: actions/checkout@v4.0.0
uses: actions/checkout@v4.1.0
- name: Prepare - Inject short Variables
uses: rlespinasse/github-slug-action@v4.4.1

9
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs

@ -762,6 +762,15 @@ namespace Squidex.Domain.Apps.Core {
}
}
/// <summary>
/// Looks up a localized string similar to The graphql request..
/// </summary>
public static string GraphqlRequest {
get {
return ResourceManager.GetString("GraphqlRequest", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The current item, if the field is part of an array..
/// </summary>

3
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx

@ -351,6 +351,9 @@
<data name="EventType" xml:space="preserve">
<value>The type of the event.</value>
</data>
<data name="GraphqlRequest" xml:space="preserve">
<value>The graphql request.</value>
</data>
<data name="ItemData" xml:space="preserve">
<value>The current item, if the field is part of an array.</value>
</data>

11
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs

@ -23,6 +23,7 @@ public static class ContentHeaders
public const string KeyNoResolveLanguages = "X-NoResolveLanguages";
public const string KeyResolveFlow = "X-ResolveFlow";
public const string KeyResolveUrls = "X-ResolveUrls";
public const string KeyResolveSchemaNames = "X-ResolveSchemaName";
public const string KeyUnpublished = "X-Unpublished";
public static void AddCacheHeaders(this Context context, IRequestCache cache)
@ -98,6 +99,16 @@ public static class ContentHeaders
return builder.WithBoolean(KeyResolveFlow, value);
}
public static bool ResolveSchemaNames(this Context context)
{
return context.AsBoolean(KeyResolveSchemaNames);
}
public static ICloneBuilder WithResolveSchemaNames(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(KeyResolveSchemaNames, value);
}
public static bool NoResolveLanguages(this Context context)
{
return context.AsBoolean(KeyNoResolveLanguages);

1
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -41,6 +41,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext
this.dataLoaders = dataLoaders;
Context = context.Clone(b => b
.WithResolveSchemaNames()
.WithNoCleanup()
.WithNoEnrichment()
.WithNoAssetEnrichment());

55
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs

@ -6,7 +6,9 @@
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using static Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents.ContentActions;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
@ -28,34 +30,62 @@ internal sealed class ApplicationQueries : ObjectGraphType
continue;
}
AddContentFind(schemaInfo, contentType);
AddContentQueries(builder, schemaInfo, contentType);
if (schemaInfo.Schema.SchemaDef.Type == SchemaType.Singleton)
{
// Mark the normal queries as deprecated to motivate using the new endpoint.
var deprecation = $"Use 'find{schemaInfo.TypeName}Singleton' instead.";
AddContentFind(schemaInfo, contentType, deprecation);
AddContentFindSingleton(schemaInfo, contentType);
AddContentQueries(builder, schemaInfo, contentType, deprecation);
}
else
{
AddContentFind(schemaInfo, contentType, null);
AddContentQueries(builder, schemaInfo, contentType, null);
}
}
Description = "The app queries.";
}
private void AddContentFind(SchemaInfo schemaInfo, IGraphType contentType)
private void AddContentFind(SchemaInfo schemaInfo, IGraphType contentType, string? deprecatedReason)
{
AddField(new FieldTypeWithSchemaId
{
Name = $"find{schemaInfo.TypeName}Content",
Arguments = ContentActions.Find.Arguments,
Arguments = Find.Arguments,
ResolvedType = contentType,
Resolver = ContentActions.Find.Resolver,
Resolver = Find.Resolver,
DeprecationReason = deprecatedReason,
Description = $"Find an {schemaInfo.DisplayName} content by id.",
SchemaId = schemaInfo.Schema.Id
});
}
private void AddContentQueries(Builder builder, SchemaInfo schemaInfo, IGraphType contentType)
private void AddContentFindSingleton(SchemaInfo schemaInfo, IGraphType contentType)
{
AddField(new FieldTypeWithSchemaId
{
Name = $"find{schemaInfo.TypeName}Singleton",
Arguments = FindSingleton.Arguments,
ResolvedType = contentType,
Resolver = FindSingleton.Resolver,
DeprecationReason = null,
Description = $"Find an {schemaInfo.DisplayName} singleton.",
SchemaId = schemaInfo.Schema.Id
});
}
private void AddContentQueries(Builder builder, SchemaInfo schemaInfo, IGraphType contentType, string? deprecatedReason)
{
AddField(new FieldTypeWithSchemaId
{
Name = $"query{schemaInfo.TypeName}Contents",
Arguments = ContentActions.QueryOrReferencing.Arguments,
Arguments = QueryOrReferencing.Arguments,
ResolvedType = new ListGraphType(new NonNullGraphType(contentType)),
Resolver = ContentActions.QueryOrReferencing.Query,
Resolver = QueryOrReferencing.Query,
DeprecationReason = deprecatedReason,
Description = $"Query {schemaInfo.DisplayName} content items.",
SchemaId = schemaInfo.Schema.Id
});
@ -70,9 +100,10 @@ internal sealed class ApplicationQueries : ObjectGraphType
AddField(new FieldTypeWithSchemaId
{
Name = $"query{schemaInfo.TypeName}ContentsWithTotal",
Arguments = ContentActions.QueryOrReferencing.Arguments,
Arguments = QueryOrReferencing.Arguments,
ResolvedType = contentResultTyp,
Resolver = ContentActions.QueryOrReferencing.QueryWithTotal,
Resolver = QueryOrReferencing.QueryWithTotal,
DeprecationReason = deprecatedReason,
Description = $"Query {schemaInfo.DisplayName} content items with total count.",
SchemaId = schemaInfo.Schema.Id
});
@ -90,9 +121,9 @@ internal sealed class ApplicationQueries : ObjectGraphType
AddField(new FieldType
{
Name = "queryContentsByIds",
Arguments = ContentActions.QueryByIds.Arguments,
Arguments = QueryByIds.Arguments,
ResolvedType = new NonNullGraphType(new ListGraphType(new NonNullGraphType(unionType))),
Resolver = ContentActions.QueryByIds.Resolver,
Resolver = QueryByIds.Resolver,
Description = "Query content items by IDs across schemeas."
});
}

30
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs

@ -96,6 +96,36 @@ internal static class ContentActions
});
}
public static class FindSingleton
{
public static readonly QueryArguments Arguments = new QueryArguments
{
new QueryArgument(Scalars.Int)
{
Name = "version",
Description = FieldDescriptions.QueryVersion,
DefaultValue = null
}
};
public static readonly IFieldResolver Resolver = Resolvers.Sync<object, object?>((_, fieldContext, context) =>
{
var contentSchemaId = fieldContext.FieldDefinition.SchemaId();
var contentVersion = fieldContext.GetArgument<int?>("version");
if (contentVersion >= 0)
{
return context.GetContent(contentSchemaId, contentSchemaId, contentVersion.Value);
}
else
{
return context.GetContent(contentSchemaId, contentSchemaId,
fieldContext.FieldNames(),
fieldContext.CacheDuration());
}
});
}
public static class QueryByIds
{
public static readonly QueryArguments Arguments = new QueryArguments

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

@ -149,7 +149,10 @@ public sealed class ConvertData : IContentEnricherStep
{
converter.Add(new ResolveAssetUrls(context.App.NamedId(), urlGenerator, assetUrls));
}
}
if (!context.IsFrontendClient || context.ResolveSchemaNames())
{
converter.Add(new AddSchemaNames(components));
}

42
backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptAnyBodyAttribute.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NJsonSchema;
using NSwag;
using NSwag.Annotations;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Domain.Apps.Core;
namespace Squidex.Areas.Api.Config.OpenApi;
public sealed class AcceptAnyBodyAttribute : OpenApiOperationProcessorAttribute
{
public AcceptAnyBodyAttribute()
: base(typeof(Processor))
{
}
public sealed class Processor : IOperationProcessor
{
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.Parameters.Add(
new OpenApiParameter
{
Name = "request",
Kind = OpenApiParameterKind.Body,
Schema = new JsonSchema
{
},
Description = FieldDescriptions.GraphqlRequest
});
return true;
}
}
}

13
backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptQueryAttribute.cs

@ -18,18 +18,13 @@ public sealed class AcceptQueryAttribute : OpenApiOperationProcessorAttribute
{
}
public sealed class Processor : IOperationProcessor
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
public sealed record Processor(bool SupportsSearch) : IOperationProcessor
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
{
private readonly bool supportsSearch;
public Processor(bool supportsSearch)
{
this.supportsSearch = supportsSearch;
}
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.AddQuery(supportsSearch);
context.OperationDescription.Operation.AddQuery(SupportsSearch);
return true;
}
}

86
backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsSharedController.cs

@ -21,6 +21,7 @@ using Squidex.Web.Pipeline;
namespace Squidex.Areas.Api.Controllers.Contents;
[SchemaMustBePublished]
[ApiExplorerSettings(GroupName = nameof(Contents))]
public sealed class ContentsSharedController : ApiController
{
private readonly IContentQueryService contentQuery;
@ -39,18 +40,97 @@ public sealed class ContentsSharedController : ApiController
/// GraphQL endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The request parameters.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[Route("content/{app}/graphql/")]
[Route("content/{app}/graphql/batch")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult GetGraphQL(string app)
public IActionResult GetGraphQL(string app, GraphQLQueryDto request)
{
var options = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
}
/// <summary>
/// GraphQL endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[HttpPost("content/{app}/graphql/")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptAnyBody]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult PostGraphQL(string app)
{
var options = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
}
/// <summary>
/// GraphQL batch endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The request object.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[HttpGet("content/{app}/graphql/batch")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult GetGraphQLBatch(string app, GraphQLQueryDto request)
{
var options = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};
return new GraphQLExecutionActionResult<DummySchema>(options);
}
/// <summary>
/// GraphQL batch endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[HttpPost("content/{app}/graphql/batch")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptAnyBody]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult PostGraphQLBatch(string app)
{
var options = new GraphQLHttpMiddlewareOptions
{
@ -143,7 +223,7 @@ public sealed class ContentsSharedController : ApiController
[ProducesResponseType(typeof(BulkResultDto[]), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsReadOwn)]
[ApiCosts(5)]
public async Task<IActionResult> BulkUpdateContents(string app, string schema, [FromBody] BulkUpdateContentsDto request)
public async Task<IActionResult> BulkUpdateAllContents(string app, string schema, [FromBody] BulkUpdateContentsDto request)
{
var command = request.ToCommand(true);

31
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/GraphQLQueryDto.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
namespace Squidex.Areas.Api.Controllers.Contents.Models;
public sealed class GraphQLQueryDto
{
/// <summary>
/// The optional version of the asset.
/// </summary>
[FromQuery(Name = "The query string")]
public string Query { get; set; }
/// <summary>
/// The optional operation variables.
/// </summary>
[FromQuery(Name = "variables")]
public string? Variables { get; set; }
/// <summary>
/// The optional operation name.
/// </summary>
[FromQuery(Name = "operationName")]
public string? OperationName { get; set; }
}

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

@ -53,7 +53,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$skip=0&$search=\"Hello\"" && x.NoTotal),
A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content));
@ -345,7 +345,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.NoTotal),
A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content));
@ -384,7 +384,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.NoTotal),
A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content));
@ -423,7 +423,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && !x.NoTotal),
A<CancellationToken>._))
.Returns(ResultList.CreateFrom(10, content));
@ -503,7 +503,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
public async Task Should_return_null_if_single_content_from_another_schema()
{
var contentId = DomainId.NewGuid();
var content = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentId, "reference1-field", "reference1");
var content = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentId, "reference1-field", "reference1");
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId),
@ -537,7 +537,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
}
[Fact]
public async Task Should_return_single_content_if_finding_content()
public async Task Should_find_single_content()
{
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
@ -574,12 +574,12 @@ public class GraphQLQueriesTests : GraphQLTestBase
}
[Fact]
public async Task Should_return_single_content_if_finding_content_with_version()
public async Task Should_find_single_content_with_version()
{
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), contentId, 3,
A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), contentId, 3,
A<CancellationToken>._))
.Returns(content);
@ -609,11 +609,102 @@ public class GraphQLQueriesTests : GraphQLTestBase
AssertResult(expected, actual);
}
[Fact]
public async Task Should_find_singleton_content()
{
var contentId = TestSchemas.Singleton.Id;
var content = TestContent.CreateSimple(TestSchemas.Singleton.NamedId(), contentId, "singleton-field", "Hello");
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId),
A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
var actual = await ExecuteAsync(new TestQuery
{
Query = @"
query {
findMySingletonSingleton {
id,
flatData {
singletonField
}
}
}",
Args = new
{
contentId
}
});
var expected = new
{
data = new
{
findMySingletonSingleton = new
{
id = contentId,
flatData = new
{
singletonField = "Hello"
}
}
}
};
AssertResult(expected, actual);
}
[Fact]
public async Task Should_find_singleton_content_with_version()
{
var contentId = TestSchemas.Singleton.Id;
var content = TestContent.CreateSimple(TestSchemas.Singleton.NamedId(), contentId, "singleton-field", "Hello");
A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), contentId, 3,
A<CancellationToken>._))
.Returns(content);
var actual = await ExecuteAsync(new TestQuery
{
Query = @"
query {
findMySingletonSingleton(version: 3) {
id,
flatData {
singletonField
}
}
}",
Args = new
{
contentId
}
});
var expected = new
{
data = new
{
findMySingletonSingleton = new
{
id = contentId,
flatData = new
{
singletonField = "Hello"
}
}
}
};
AssertResult(expected, actual);
}
[Fact]
public async Task Should_also_fetch_embedded_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -703,7 +794,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -782,7 +873,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
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.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -844,7 +935,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
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.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -915,7 +1006,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
public async Task Should_also_fetch_referencing_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -984,7 +1075,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
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.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -1060,7 +1151,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
public async Task Should_also_fetch_references_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -1117,7 +1208,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
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.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
@ -1181,7 +1272,7 @@ public class GraphQLQueriesTests : GraphQLTestBase
public async Task Should_also_fetch_union_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentRef = TestContent.CreateSimple(TestSchemas.Reference1.NamedId(), contentRefId, "reference1-field", "reference1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);

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

@ -76,7 +76,12 @@ public abstract class GraphQLTestBase : IClassFixture<TranslationsFixture>
protected Task<ExecutionResult> ExecuteAsync(TestQuery query)
{
// Use a shared instance to test caching.
sut ??= CreateSut(TestSchemas.Default, TestSchemas.Reference1, TestSchemas.Reference2, TestSchemas.Component);
sut ??= CreateSut(
TestSchemas.Default,
TestSchemas.Reference1,
TestSchemas.Reference2,
TestSchemas.Singleton,
TestSchemas.Component);
var options = query.ToOptions(sut.Services);

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

@ -346,7 +346,7 @@ public static class TestContent
return content;
}
public static IEnrichedContentEntity CreateRef(NamedId<DomainId> schemaId, DomainId id, string field, string value)
public static IEnrichedContentEntity CreateSimple(NamedId<DomainId> schemaId, DomainId id, string field, string value)
{
var now = SystemClock.Instance.GetCurrentInstant();

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

@ -19,6 +19,7 @@ public static class TestSchemas
public static readonly ISchemaEntity Default;
public static readonly ISchemaEntity Reference1;
public static readonly ISchemaEntity Reference2;
public static readonly ISchemaEntity Singleton;
public static readonly ISchemaEntity Component;
static TestSchemas()
@ -57,6 +58,11 @@ public static class TestSchemas
.Publish()
.AddString(1, "reference2-field", Partitioning.Invariant));
Singleton = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(),
new Schema("my-singleton", type: SchemaType.Singleton)
.Publish()
.AddString(1, "singleton-field", Partitioning.Invariant));
Default = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(),
new Schema("my-schema")
.Publish()

8
frontend/package-lock.json

@ -37,7 +37,7 @@
"date-fns": "2.30.0",
"font-awesome": "4.7.0",
"graphiql": "3.0.5",
"graphql": "16.7.1",
"graphql": "^16.8.1",
"graphql-ws": "^5.14.0",
"image-focus": "1.2.1",
"keycharm": "0.4.0",
@ -20460,9 +20460,9 @@
}
},
"node_modules/graphql": {
"version": "16.7.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz",
"integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==",
"version": "16.8.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}

2
frontend/package.json

@ -44,7 +44,7 @@
"date-fns": "2.30.0",
"font-awesome": "4.7.0",
"graphiql": "3.0.5",
"graphql": "16.7.1",
"graphql": "16.8.1",
"graphql-ws": "^5.14.0",
"image-focus": "1.2.1",
"keycharm": "0.4.0",

Loading…
Cancel
Save