diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs index abf875f7f..f3abb5e6b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs @@ -35,7 +35,14 @@ public sealed record Component(string Type, JsonObject Data, Schema Schema) return false; } - if (!o.TryGetValue(Discriminator, out var found) || found.Value is not string s) + return IsValid(o, out discriminator); + } + + public static bool IsValid(JsonObject obj, [MaybeNullWhen(false)] out string discriminator) + { + discriminator = null!; + + if (!obj.TryGetValue(Discriminator, out var found) || found.Value is not string s) { return false; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs index 66b664203..bcfe70732 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds; -internal sealed class ReferencesCleaner : IFieldVisitor +internal sealed class ReferencesCleaner : IFieldPropertiesVisitor { private static readonly ReferencesCleaner Instance = new ReferencesCleaner(); @@ -27,70 +27,70 @@ internal sealed class ReferencesCleaner : IFieldVisitor field, Args args) + public JsonValue Visit(ArrayFieldProperties properties, Args args) { - return CleanIds(args); + return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(AssetsFieldProperties properties, Args args) { return CleanIds(args); } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(ReferencesFieldProperties properties, Args args) { - return args.Value; + return CleanIds(args); } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(BooleanFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(ComponentFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(ComponentsFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(DateTimeFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(GeolocationFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(JsonFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(NumberFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(StringFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IField field, Args args) + public JsonValue Visit(TagsFieldProperties properties, Args args) { return args.Value; } - public JsonValue Visit(IArrayField field, Args args) + public JsonValue Visit(UIFieldProperties properties, Args args) { return args.Value; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index 89c0eea0c..e8a8f1c80 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -18,7 +18,7 @@ using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent; -public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonError? Error), JsonValueConverter.Args> +public sealed class JsonValueConverter : IFieldPropertiesVisitor<(object? Result, JsonError? Error), JsonValueConverter.Args> { private static readonly JsonValueConverter Instance = new JsonValueConverter(); @@ -36,45 +36,45 @@ public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonErro var args = new Args(value, serializer, components); - return field.Accept(Instance, args); + return field.RawProperties.Accept(Instance, args); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(JsonFieldProperties properties, Args args) { return (args.Value, null); } - public (object? Result, JsonError? Error) Visit(IArrayField field, Args args) + public (object? Result, JsonError? Error) Visit(ArrayFieldProperties properties, Args args) { return ConvertToObjectList(args.Value); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(AssetsFieldProperties properties, Args args) { return ConvertToIdList(args.Value); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(ComponentFieldProperties properties, Args args) { - return ConvertToComponent(args.Value, args.Components, field.Properties.SchemaIds); + return ConvertToComponent(args.Value, args.Components, properties.SchemaIds); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(ComponentsFieldProperties properties, Args args) { - return ConvertToComponentList(args.Value, args.Components, field.Properties.SchemaIds); + return ConvertToComponentList(args.Value, args.Components, properties.SchemaIds); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(ReferencesFieldProperties properties, Args args) { return ConvertToIdList(args.Value); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(TagsFieldProperties properties, Args args) { return ConvertToStringList(args.Value); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(BooleanFieldProperties properties, Args args) { if (args.Value.Value is bool b) { @@ -84,7 +84,7 @@ public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonErro return (null, new JsonError(T.Get("contents.invalidBoolean"))); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(NumberFieldProperties properties, Args args) { if (args.Value.Value is double d) { @@ -94,7 +94,7 @@ public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonErro return (null, new JsonError(T.Get("contents.invalidNumber"))); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(StringFieldProperties properties, Args args) { if (args.Value.Value is string s) { @@ -104,12 +104,12 @@ public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonErro return (null, new JsonError(T.Get("contents.invalidString"))); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(UIFieldProperties properties, Args args) { return (args.Value, null); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(DateTimeFieldProperties properties, Args args) { if (args.Value.Value is string s) { @@ -126,7 +126,7 @@ public sealed class JsonValueConverter : IFieldVisitor<(object? Result, JsonErro return (null, new JsonError(T.Get("contents.invalidString"))); } - public (object? Result, JsonError? Error) Visit(IField field, Args args) + public (object? Result, JsonError? Error) Visit(GeolocationFieldProperties properties, Args args) { var result = GeoJsonValue.TryParse(args.Value, args.Serializer, out var value); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs index efbde081f..bfc90bcb4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs @@ -16,7 +16,7 @@ using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ValidateContent; -public sealed class JsonValueValidator : IFieldVisitor +public sealed class JsonValueValidator : IFieldPropertiesVisitor { private static readonly JsonValueValidator Instance = new JsonValueValidator(); @@ -33,35 +33,35 @@ public sealed class JsonValueValidator : IFieldVisitor field, Args args) + public bool Visit(AssetsFieldProperties properties, Args args) { return IsValidStringList(args.Value); } - public bool Visit(IField field, Args args) + public bool Visit(BooleanFieldProperties properties, Args args) { return args.Value.Value is bool; } - public bool Visit(IField field, Args args) + public bool Visit(ComponentFieldProperties properties, Args args) { return IsValidComponent(args.Value); } - public bool Visit(IField field, Args args) + public bool Visit(ComponentsFieldProperties properties, Args args) { return IsValidComponentList(args.Value); } - public bool Visit(IField field, Args args) + public bool Visit(DateTimeFieldProperties properties, Args args) { if (args.Value.Value is string s) { @@ -73,39 +73,39 @@ public sealed class JsonValueValidator : IFieldVisitor field, Args args) + public bool Visit(GeolocationFieldProperties properties, Args args) { var result = GeoJsonValue.TryParse(args.Value, args.Serializer, out _); return result == GeoJsonParseResult.Success; } - public bool Visit(IField field, Args args) + public bool Visit(JsonFieldProperties properties, Args args) { return true; } - public bool Visit(IField field, Args args) + public bool Visit(NumberFieldProperties properties, Args args) { return args.Value.Value is double; } - public bool Visit(IField field, Args args) + public bool Visit(ReferencesFieldProperties properties, Args args) { return IsValidStringList(args.Value); } - public bool Visit(IField field, Args args) + public bool Visit(StringFieldProperties properties, Args args) { return args.Value.Value is string; } - public bool Visit(IField field, Args args) + public bool Visit(TagsFieldProperties properties, Args args) { return IsValidStringList(args.Value); } - public bool Visit(IField field, Args args) + public bool Visit(UIFieldProperties properties, Args args) { return true; } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index a8ef6eff1..0c40f3108 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -9,7 +9,6 @@ using MongoDB.Bson.Serialization.Attributes; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; -using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Infrastructure; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs index 221e0a333..90ebc965c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs @@ -241,15 +241,21 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable private Task InvalidateItAsync(DomainId id, string name) { - return appCache.RemoveAsync( + // Do not use cancellation here as we already so far. + return appCache.RemoveAsync(new[] + { GetCacheKey(id), - GetCacheKey(name)); + GetCacheKey(name) + }); } private Task CacheItAsync(IAppEntity app) { - return Task.WhenAll( - appCache.AddAsync(GetCacheKey(app.Id), app, CacheDuration), - appCache.AddAsync(GetCacheKey(app.Name), app, CacheDuration)); + // Do not use cancellation here as we already so far. + return appCache.AddAsync(new[] + { + new KeyValuePair(GetCacheKey(app.Id), app), + new KeyValuePair(GetCacheKey(app.Name), app), + }, CacheDuration); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs index ad32ff49e..219527931 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs @@ -186,7 +186,7 @@ public sealed class AssetQueryService : IAssetQueryService if (q.Ids is { Count: > 0 }) { - assets = assets.SortSet(x => x.Id, q.Ids); + assets = assets.Sorted(x => x.Id, q.Ids); } return await TransformAsync(context, assets, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs index c483a7e61..e32626b5c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.Text; -using Fluid.Values; using Microsoft.Extensions.DependencyInjection; using Squidex.Assets; using Squidex.Domain.Apps.Core.Assets; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs index 8d25f778f..bae1f0394 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs @@ -8,7 +8,6 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; -using static OpenIddict.Abstractions.OpenIddictConstants; #pragma warning disable IDE0060 // Remove unused parameter diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingBatchLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingBatchLoader.cs index a8f4068f7..fe90dee19 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingBatchLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingBatchLoader.cs @@ -6,7 +6,6 @@ // ========================================================================== using GraphQL.DataLoader; -using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; #pragma warning disable MA0048 // File name must match type name diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingDataLoaderExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingDataLoaderExtensions.cs index 95de4f527..fa337db16 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingDataLoaderExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Cache/CachingDataLoaderExtensions.cs @@ -7,8 +7,6 @@ using GraphQL.DataLoader; using Squidex.Infrastructure.Caching; -using Squidex.Infrastructure.Translations; -using TagLib.IFD.Tags; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs index 0dcf99056..b4ca02731 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs @@ -54,6 +54,7 @@ public sealed class CachingGraphQLResolver : IConfigureExecution var context = ((GraphQLExecutionContext)options.UserContext!).Context; options.Schema = await GetSchemaAsync(context.App); + options.HandleError(serviceProvider); return await next(options); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs index d923af5b3..b59285f1a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs @@ -7,7 +7,6 @@ using GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; -using Squidex.Domain.Apps.Entities.Schemas; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; 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 739c735de..99034c52b 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 @@ -127,7 +127,6 @@ internal sealed class Builder newSchema.RegisterType(customType); } - newSchema.RegisterVisitor(ErrorVisitor.Instance); newSchema.Initialize(); return newSchema; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs index 98fd028aa..6d5140535 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs @@ -70,7 +70,9 @@ internal sealed class ComponentGraphType : ObjectGraphType return false; } - return Component.IsValid(json, out var discriminator) && discriminator == schemaId; + JsonValue current = json; + + return Component.IsValid(current, out var discriminator) && discriminator == schemaId; }; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs index e9892bac9..bda4e47f0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs @@ -8,17 +8,12 @@ using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; -using GraphQLParser; -using GraphQLParser.AST; using NodaTime; -using Squidex.CLI.Commands.Models; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Subscriptions; using Squidex.Domain.Apps.Entities.Contents.Commands; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Directives; using Squidex.Infrastructure; using Squidex.Shared; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs index 284657819..77f2d333b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs @@ -7,11 +7,9 @@ using GraphQL.Resolvers; using GraphQL.Types; -using Namotion.Reflection; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; -using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Infrastructure.Json.Objects; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs index ec7c8e029..d615e0953 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs @@ -8,7 +8,6 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ErrorVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ErrorVisitor.cs index d5c4f2be2..f6ca66e71 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ErrorVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ErrorVisitor.cs @@ -6,8 +6,6 @@ // ========================================================================== using GraphQL; -using GraphQL.Resolvers; -using GraphQL.Types; using GraphQL.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -16,104 +14,31 @@ using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; -internal sealed class ErrorVisitor : BaseSchemaNodeVisitor +internal static class ErrorVisitor { - public static readonly ErrorVisitor Instance = new ErrorVisitor(); - - internal sealed class ErrorResolver : IFieldResolver + public static void HandleError(this ExecutionOptions options, IServiceProvider services) { - private readonly IFieldResolver inner; - - public ErrorResolver(IFieldResolver inner) + options.UnhandledExceptionDelegate = context => { - this.inner = inner; - } - - public async ValueTask ResolveAsync(IResolveFieldContext context) - { - try - { - return await inner.ResolveAsync(context); - } - catch (ValidationException ex) - { - throw new ExecutionError(ex.Message); - } - catch (DomainException ex) - { - throw new ExecutionError(ex.Message); - } - catch (Exception ex) - { - var logFactory = context.RequestServices!.GetRequiredService(); + var log = services.GetRequiredService().CreateLogger("GraphQL"); - logFactory.CreateLogger("GraphQL").LogError(ex, "Failed to resolve field {field}.", context.FieldDefinition.Name); - throw; - } - } - } + var fieldName = context.FieldContext?.FieldDefinition?.Name; - internal sealed class ErrorSourceStreamResolver : ISourceStreamResolver - { - private readonly ISourceStreamResolver inner; - - public ErrorSourceStreamResolver(ISourceStreamResolver inner) - { - this.inner = inner; - } - - public async ValueTask> ResolveAsync(IResolveFieldContext context) - { - try + if (!string.IsNullOrWhiteSpace(fieldName)) { - return await inner.ResolveAsync(context); + log.LogError(context.OriginalException, "Failed to resolve field {field}.", fieldName); } - catch (ValidationException ex) + else { - throw new ExecutionError(ex.Message); + log.LogError(context.OriginalException, "Failed to resolve execute query."); } - catch (DomainException ex) - { - throw new ExecutionError(ex.Message); - } - catch (Exception ex) - { - var logFactory = context.RequestServices!.GetRequiredService(); - - logFactory.CreateLogger("GraphQL").LogError(ex, "Failed to resolve field {field}.", context.FieldDefinition.Name); - throw; - } - } - } - - private ErrorVisitor() - { - } - public override void VisitObjectFieldDefinition(FieldType field, IObjectGraphType type, ISchema schema) - { - if (type.Name.StartsWith("__", StringComparison.Ordinal)) - { - return; - } - - if (field.StreamResolver != null) - { - if (field.StreamResolver is ErrorSourceStreamResolver) - { - return; - } - - field.StreamResolver = new ErrorSourceStreamResolver(field.StreamResolver); - } - else - { - if (field.Resolver is ErrorResolver) + if (context.OriginalException is ValidationException or DomainException) { - return; + context.Exception = new ExecutionError(context.OriginalException.Message); } - field.Resolver = new ErrorResolver(field.Resolver ?? NameFieldResolver.Instance); - } + return Task.CompletedTask; + }; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs index ea42b0f00..4e920e6f9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs @@ -6,7 +6,6 @@ // ========================================================================== using GraphQL.Resolvers; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Infrastructure; using Squidex.Shared.Users; 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 9978dc3f0..8ae27e91b 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,7 +10,6 @@ using GraphQL.Types; using GraphQL.Utilities; using GraphQLParser; using GraphQLParser.AST; -using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Directives; using Squidex.Domain.Apps.Entities.Schemas; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 1793f2a73..f3463c973 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -132,7 +132,7 @@ public sealed class ContentQueryService : IContentQueryService if (q.Ids is { Count: > 0 }) { - contents = contents.SortSet(x => x.Id, q.Ids); + contents = contents.Sorted(x => x.Id, q.Ids); } return await TransformAsync(context, contents, ct); @@ -166,7 +166,7 @@ public sealed class ContentQueryService : IContentQueryService if (q.Ids is { Count: > 0 }) { - contents = contents.SortSet(x => x.Id, q.Ids); + contents = contents.Sorted(x => x.Id, q.Ids); } return await TransformAsync(context, contents, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs index d1c9b26aa..b4360deb9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs @@ -42,7 +42,7 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex foreach (var schema in schemas.Where(IsValid)) { - await InvalidateItAsync(appId, schema.Id, schema.SchemaDef.Name); + await CacheItAsync(schema); } return schemas.Where(IsValid).ToList(); @@ -221,15 +221,21 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex private Task InvalidateItAsync(DomainId appId, DomainId id, string name) { - return schemaCache.RemoveAsync( + // Do not use cancellation here as we already so far. + return schemaCache.RemoveAsync(new[] + { GetCacheKey(appId, id), - GetCacheKey(appId, name)); + GetCacheKey(appId, name) + }); } private Task CacheItAsync(ISchemaEntity schema) { - return Task.WhenAll( - schemaCache.AddAsync(GetCacheKey(schema.AppId.Id, schema.Id), schema, CacheDuration), - schemaCache.AddAsync(GetCacheKey(schema.AppId.Id, schema.SchemaDef.Name), schema, CacheDuration)); + // Do not use cancellation here as we already so far. + return schemaCache.AddAsync(new[] + { + new KeyValuePair(GetCacheKey(schema.AppId.Id, schema.Id), schema), + new KeyValuePair(GetCacheKey(schema.AppId.Id, schema.SchemaDef.Name), schema), + }, CacheDuration); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 0f04dda77..3219d7b43 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -29,7 +29,7 @@ - + diff --git a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs index 47221d2ab..e0944972d 100644 --- a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -147,16 +147,6 @@ public static class CollectionExtensions return reverse ? source.Reverse() : source; } - public static IResultList SortSet(this IResultList input, Func idProvider, IReadOnlyList ids) where T : class - { - return ResultList.Create(input.Total, SortList(input, idProvider, ids)); - } - - public static IEnumerable SortList(this IEnumerable input, Func idProvider, IReadOnlyList ids) where T : class - { - return ids.Select(id => input.FirstOrDefault(x => Equals(idProvider(x), id))).NotNull(); - } - public static IEnumerable Duplicates(this IEnumerable input) { return input.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x.Key); diff --git a/backend/src/Squidex.Infrastructure/ResultList.cs b/backend/src/Squidex.Infrastructure/ResultList.cs index 232401def..f35749bab 100644 --- a/backend/src/Squidex.Infrastructure/ResultList.cs +++ b/backend/src/Squidex.Infrastructure/ResultList.cs @@ -48,4 +48,55 @@ public static class ResultList { return new Impl(items.ToList(), total); } + + public static IResultList Sorted(this IResultList input, Func idProvider, IReadOnlyList ids) where TKey : notnull where T : class + { + if (input.Count == 0) + { + return Empty(); + } + + var result = new List(ids.Count); + + if (input.Count >= 5) + { + var dictionary = new Dictionary(input.Count); + + foreach (var item in input) + { + dictionary[idProvider(item)] = item; + } + + foreach (var id in ids) + { + if (dictionary.TryGetValue(id, out var item)) + { + result.Add(item); + } + } + } + else + { + foreach (var id in ids) + { + T? item = null; + + foreach (var candidate in input) + { + if (Equals(id, idProvider(candidate))) + { + item = candidate; + break; + } + } + + if (item != null) + { + result.Add(item); + } + } + } + + return Create(input.Total, result); + } } diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 07098f4f9..badc75a90 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -24,12 +24,12 @@ - - - - - - + + + + + + diff --git a/backend/src/Squidex/Config/Domain/AssetServices.cs b/backend/src/Squidex/Config/Domain/AssetServices.cs index 8e4b464da..5f76c34c9 100644 --- a/backend/src/Squidex/Config/Domain/AssetServices.cs +++ b/backend/src/Squidex/Config/Domain/AssetServices.cs @@ -5,19 +5,14 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using FluentFTP; using MongoDB.Driver.GridFS; -using Squidex.Assets; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Queries; using Squidex.Domain.Apps.Entities.Assets.Queries.Steps; using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.Search; -using Squidex.Hosting; using Squidex.Infrastructure.EventSourcing; -using tusdotnet.FileLocks; -using tusdotnet.Interfaces; namespace Squidex.Config.Domain; diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index 205341cac..5b559795f 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -6,7 +6,6 @@ // ========================================================================== using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; using NodaTime; using Squidex.Areas.Api.Controllers.Contents.Generator; using Squidex.Areas.Api.Controllers.News; @@ -27,8 +26,6 @@ using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.UsageTracking; using Squidex.Pipeline.Robots; using Squidex.Shared; -using Squidex.Text.Translations; -using Squidex.Text.Translations.GoogleCloud; using Squidex.Web; using Squidex.Web.Pipeline; diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 1035ca714..168b59cb0 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -13,7 +13,6 @@ using Migrations.Migrations.MongoDb; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.Core.Extensions.DiagnosticSources; -using Squidex.Assets; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.Apps.Repositories; @@ -42,7 +41,6 @@ using Squidex.Domain.Apps.Entities.Teams.Repositories; using Squidex.Domain.Users; using Squidex.Domain.Users.InMemory; using Squidex.Domain.Users.MongoDb; -using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Diagnostics; diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index bb9a3ce16..ecbab32c3 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -62,18 +62,18 @@ - - - - - - - - + + + + + + + + - - - + + + diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index 7fd57b6cf..a503b5c7d 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -5,7 +5,6 @@ // 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.ConvertContent; using Squidex.Domain.Apps.Core.Schemas; diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs index 4b8bcff17..aec7515aa 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs @@ -7,7 +7,6 @@ using System.Net; using System.Text; -using Jint.Runtime; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Scripting; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetsBulkUpdateCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetsBulkUpdateCommandMiddlewareTests.cs index 80b68c879..1e627a106 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetsBulkUpdateCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetsBulkUpdateCommandMiddlewareTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.Extensions.Logging; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs index d88f613fe..306b9f5b1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.Extensions.Logging; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs index 5eddc052b..43cfa946c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL; using GraphQL.Types; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; @@ -22,7 +21,9 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_introspect() { - const string query = @" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query IntrospectionQuery { __schema { queryType { @@ -107,9 +108,9 @@ public class GraphQLIntrospectionTests : GraphQLTestBase } } } - }"; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query, OperationName = "IntrospectionQuery" }); + }", + OperationName = "IntrospectionQuery" + }); var json = serializer.Serialize(actual); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index 829f0e2e3..da02a8806 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -5,11 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Text.Json; -using GraphQL; using NodaTime.Text; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -34,14 +31,15 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permission_to_create() { - var query = @" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - createMySchemaContent(data: { myNumber: { iv: 42 } }) { + createMySchemaContent(data: { }) { id } - }"; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); var expected = new { @@ -79,18 +77,26 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_creating_content() { - var query = CreateQuery(@" - mutation { - createMySchemaContent(data: , publish: true) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsCreate; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + mutation MyMutation($data: MySchemaDataInputDto!) { + createMySchemaContent(data: $data, publish: true) { + {fields} + } + }", + Args = new + { + fields = TestContent.AllFields + }, + Variables = new + { + data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id), + }, + Permission = PermissionIds.AppContentsCreate + }); var expected = new { @@ -115,55 +121,27 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_creating_content_with_custom_id() { - var query = CreateQuery(@" - mutation { - createMySchemaContent(data: , id: '123', publish: true) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsCreate; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); - - var expected = new + var actual = await ExecuteAsync(new TestQuery { - data = new - { - createMySchemaContent = TestContent.Response(content) - } - }; - - AssertResult(expected, actual); - - A.CallTo(() => commandBus.PublishAsync( - A.That.Matches(x => - x.ExpectedVersion == EtagVersion.Any && - x.ContentId == DomainId.Create("123") && - x.SchemaId.Equals(TestSchemas.DefaultId) && - x.Status == Status.Published && - x.Data.Equals(content.Data)), - A._)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_return_single_content_if_creating_content_with_variable() - { - var query = CreateQuery(@" - mutation OP($data: MySchemaDataInputDto!) { - createMySchemaContent(data: $data, publish: true) { - + Query = @" + mutation MyMutation($data: MySchemaDataInputDto!) { + createMySchemaContent(data: $data, id: '{contentId}', publish: true) { + {fields} } - }", contentId, content); - - commandContext.Complete(content); - - var permission = PermissionIds.AppContentsCreate; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, permission); + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Variables = new + { + data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + }, + Permission = PermissionIds.AppContentsCreate + }); var expected = new { @@ -178,6 +156,7 @@ public class GraphQLMutationTests : GraphQLTestBase A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => x.ExpectedVersion == EtagVersion.Any && + x.ContentId == contentId && x.SchemaId.Equals(TestSchemas.DefaultId) && x.Status == Status.Published && x.Data.Equals(content.Data)), @@ -188,14 +167,19 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permission_to_update() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - updateMySchemaContent(id: '', data: { myNumber: { iv: 42 } }) { + updateMySchemaContent(id: '{contentId}', data: { }) { id } - }", contentId, content); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -233,54 +217,27 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_updating_content() { - var query = CreateQuery(@" - mutation { - updateMySchemaContent(id: '', data: , expectedVersion: 10) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsUpdateOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); - - var expected = new + var actual = await ExecuteAsync(new TestQuery { - data = new - { - updateMySchemaContent = TestContent.Response(content) - } - }; - - AssertResult(expected, actual); - - A.CallTo(() => commandBus.PublishAsync( - A.That.Matches(x => - x.ContentId == content.Id && - x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && - x.Data.Equals(content.Data)), - A._)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_return_single_content_if_updating_content_with_variable() - { - var query = CreateQuery(@" - mutation OP($data: MySchemaDataInputDto!) { - updateMySchemaContent(id: '', data: $data, expectedVersion: 10) { - + Query = @" + mutation MyMutation($data: MySchemaDataInputDto!) { + updateMySchemaContent(id: '{contentId}', data: $data, expectedVersion: 10) { + {fields} } - }", contentId, content); - - commandContext.Complete(content); - - var permission = PermissionIds.AppContentsUpdateOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, permission); + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Variables = new + { + data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + }, + Permission = PermissionIds.AppContentsUpdateOwn + }); var expected = new { @@ -294,7 +251,7 @@ public class GraphQLMutationTests : GraphQLTestBase A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.ContentId == content.Id && + x.ContentId == contentId && x.ExpectedVersion == 10 && x.SchemaId.Equals(TestSchemas.DefaultId) && x.Data.Equals(content.Data)), @@ -305,14 +262,15 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permission_to_upsert() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - upsertMySchemaContent(id: '', data: { myNumber: { iv: 42 } }) { + upsertMySchemaContent(id: '{contentId}', data: { }) { id } - }", contentId, content); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); var expected = new { @@ -350,55 +308,27 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_upserting_content() { - var query = CreateQuery(@" - mutation { - upsertMySchemaContent(id: '', data: , publish: true, expectedVersion: 10) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsUpsert; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); - - var expected = new + var actual = await ExecuteAsync(new TestQuery { - data = new - { - upsertMySchemaContent = TestContent.Response(content) - } - }; - - AssertResult(expected, actual); - - A.CallTo(() => commandBus.PublishAsync( - A.That.Matches(x => - x.ContentId == content.Id && - x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && - x.Status == Status.Published && - x.Data.Equals(content.Data)), - A._)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_return_single_content_if_upserting_content_with_variable() - { - var query = CreateQuery(@" - mutation OP($data: MySchemaDataInputDto!) { - upsertMySchemaContent(id: '', data: $data, publish: true, expectedVersion: 10) { - + Query = @" + mutation MyMutation($data: MySchemaDataInputDto!) { + upsertMySchemaContent(id: '{contentId}', data: $data, publish: true, expectedVersion: 10) { + {fields} } - }", contentId, content); - - commandContext.Complete(content); - - var permission = PermissionIds.AppContentsUpsert; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, permission); + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Variables = new + { + data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + }, + Permission = PermissionIds.AppContentsUpsert + }); var expected = new { @@ -412,7 +342,7 @@ public class GraphQLMutationTests : GraphQLTestBase A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.ContentId == content.Id && + x.ContentId == contentId && x.ExpectedVersion == 10 && x.SchemaId.Equals(TestSchemas.DefaultId) && x.Status == Status.Published && @@ -424,14 +354,19 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permission_to_patch() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - patchMySchemaContent(id: '', data: { myNumber: { iv: 42 } }) { + patchMySchemaContent(id: '{contentId}', data: { }) { id } - }", contentId, content); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + }, + }); var expected = new { @@ -469,54 +404,27 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_patching_content() { - var query = CreateQuery(@" - mutation { - patchMySchemaContent(id: '', data: , expectedVersion: 10) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsUpdateOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); - - var expected = new + var actual = await ExecuteAsync(new TestQuery { - data = new - { - patchMySchemaContent = TestContent.Response(content) - } - }; - - AssertResult(expected, actual); - - A.CallTo(() => commandBus.PublishAsync( - A.That.Matches(x => - x.ContentId == content.Id && - x.ExpectedVersion == 10 && - x.SchemaId.Equals(TestSchemas.DefaultId) && - x.Data.Equals(content.Data)), - A._)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_return_single_content_if_patching_content_with_variable() - { - var query = CreateQuery(@" - mutation OP($data: MySchemaDataInputDto!) { - patchMySchemaContent(id: '', data: $data, expectedVersion: 10) { - + Query = @" + mutation MyMutation($data: MySchemaDataInputDto!) { + patchMySchemaContent(id: '{contentId}', data: $data, expectedVersion: 10) { + {fields} } - }", contentId, content); - - commandContext.Complete(content); - - var permission = PermissionIds.AppContentsUpdateOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query, Variables = GetInput() }, permission); + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Variables = new + { + data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) + }, + Permission = PermissionIds.AppContentsUpdateOwn + }); var expected = new { @@ -530,7 +438,7 @@ public class GraphQLMutationTests : GraphQLTestBase A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => - x.ContentId == content.Id && + x.ContentId == contentId && x.ExpectedVersion == 10 && x.SchemaId.Equals(TestSchemas.DefaultId) && x.Data.Equals(content.Data)), @@ -541,14 +449,19 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permission_to_change_status() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - changeMySchemaContent(id: '', status: 'Published') { + changeMySchemaContent(id: '{contentId}', status: 'Published') { id } - }", contentId, content); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + }, + }); var expected = new { @@ -588,18 +501,23 @@ public class GraphQLMutationTests : GraphQLTestBase { var dueTime = InstantPattern.General.Parse("2021-12-12T11:10:09Z").Value; - var query = CreateQuery(@" - mutation { - changeMySchemaContent(id: '', status: 'Published', dueTime: '2021-12-12T11:10:09Z', expectedVersion: 10) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsChangeStatusOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + mutation { + changeMySchemaContent(id: '{contentId}', status: 'Published', dueTime: '2021-12-12T11:10:09Z', expectedVersion: 10) { + {fields} + } + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Permission = PermissionIds.AppContentsChangeStatusOwn + }); var expected = new { @@ -625,18 +543,23 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_changing_status_without_due_time() { - var query = CreateQuery(@" - mutation { - changeMySchemaContent(id: '', status: 'Published', expectedVersion: 10) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsChangeStatusOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + mutation { + changeMySchemaContent(id: '{contentId}', status: 'Published', expectedVersion: 10) { + {fields} + } + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Permission = PermissionIds.AppContentsChangeStatusOwn + }); var expected = new { @@ -662,18 +585,23 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_single_content_if_changing_status_with_null_due_time() { - var query = CreateQuery(@" - mutation { - changeMySchemaContent(id: '', status: 'Published', dueTime: null, expectedVersion: 10) { - - } - }", contentId, content); - commandContext.Complete(content); - var permission = PermissionIds.AppContentsChangeStatusOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + mutation { + changeMySchemaContent(id: '{contentId}', status: 'Published', dueTime: null, expectedVersion: 10) { + {fields} + } + }", + Args = new + { + contentId, + fields = TestContent.AllFields + }, + Permission = PermissionIds.AppContentsChangeStatusOwn + }); var expected = new { @@ -699,14 +627,19 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permission_to_delete() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - deleteMySchemaContent(id: '') { + deleteMySchemaContent(id: '{contentId}') { version } - }", contentId, content); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + }, + }); var expected = new { @@ -741,18 +674,22 @@ public class GraphQLMutationTests : GraphQLTestBase [Fact] public async Task Should_return_new_version_if_deleting_content() { - var query = CreateQuery(@" + commandContext.Complete(CommandResult.Empty(contentId, 1, 0)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" mutation { - deleteMySchemaContent(id: '', expectedVersion: 10) { - version + deleteMySchemaContent(id: '{contentId}', expectedVersion: 10) { + version } - }", contentId, content); - - commandContext.Complete(CommandResult.Empty(contentId, 13, 12)); - - var permission = PermissionIds.AppContentsDeleteOwn; - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission); + }", + Args = new + { + contentId + }, + Permission = PermissionIds.AppContentsDeleteOwn + }); var expected = new { @@ -760,7 +697,7 @@ public class GraphQLMutationTests : GraphQLTestBase { deleteMySchemaContent = new { - version = 13 + version = 1 } } }; @@ -775,16 +712,4 @@ public class GraphQLMutationTests : GraphQLTestBase A._)) .MustHaveHappened(); } - - private Inputs GetInput() - { - var input = new - { - data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id) - }; - - var element = JsonSerializer.SerializeToElement(input, TestUtils.DefaultOptions()); - - return serializer.ReadNode(element)!; - } } 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 1308ea81a..9fa80dc1a 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 @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using GraphQL; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -20,7 +19,10 @@ public class GraphQLQueriesTests : GraphQLTestBase [InlineData(" ")] public async Task Should_return_error_empty_query(string query) { - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = query + }); var expected = new { @@ -50,19 +52,24 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" - query { - queryMySchemaContents(search: ""Hello"") { - - } - }"); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.Matches(x => x.QueryAsOdata == "?$skip=0&$search=\"Hello\"" && x.NoTotal), A._)) .Returns(ResultList.CreateFrom(0, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + queryMySchemaContents(search: 'Hello') { + {fields} + } + }", + Args = new + { + fields = TestContent.AllFlatFields + } + }); var expected = new { @@ -84,9 +91,16 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIds(contentId), + A._)) + .Returns(ResultList.CreateFrom(0, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - queryContentsByIds(ids: [""""]) { + queryContentsByIds(ids: ['{contentId}']) { ... on Content { id } @@ -96,14 +110,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIds(contentId), - A._)) - .Returns(ResultList.CreateFrom(0, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -132,21 +144,26 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" - query { - queryContentsByIds(ids: [""""]) { - ... on Content { - data: data__dynamic - } - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIds(contentId), A._)) .Returns(ResultList.CreateFrom(0, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + queryContentsByIds(ids: ['{contentId}']) { + ... on Content { + data: data__dynamic + } + } + }", + Args = new + { + contentId + } + }); var expected = new { @@ -168,13 +185,6 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_return_multiple_assets_if_querying_assets() { - var query = CreateQuery(@" - query { - queryAssets(filter: 'my-query', top: 30, skip: 5) { - - } - }"); - var asset = TestAsset.Create(DomainId.NewGuid()); A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, @@ -182,7 +192,19 @@ public class GraphQLQueriesTests : GraphQLTestBase A._)) .Returns(ResultList.CreateFrom(0, asset)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + queryAssets(filter: 'my-query', top: 30, skip: 5) { + {fields} + } + }", + Args = new + { + fields = TestAsset.AllFields + } + }); var expected = new { @@ -201,16 +223,6 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_return_multiple_assets_with_total_if_querying_assets_with_total() { - var query = CreateQuery(@" - query { - queryAssetsWithTotal(filter: 'my-query', top: 30, skip: 5) { - total - items { - - } - } - }"); - var asset = TestAsset.Create(DomainId.NewGuid()); A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, @@ -218,7 +230,22 @@ public class GraphQLQueriesTests : GraphQLTestBase A._)) .Returns(ResultList.CreateFrom(10, asset)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + queryAssetsWithTotal(filter: 'my-query', top: 30, skip: 5) { + total + items { + {fields} + } + } + }", + Args = new + { + fields = TestAsset.AllFields + } + }); var expected = new { @@ -243,20 +270,25 @@ public class GraphQLQueriesTests : GraphQLTestBase { var assetId = DomainId.NewGuid(); - var query = CreateQuery(@" - query { - findAsset(id: '') { - id, - version - } - }", assetId); - A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A.That.HasIdsWithoutTotal(assetId), A._)) .Returns(ResultList.CreateFrom(1)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findAsset(id: '{assetId}') { + id, + version + } + }", + Args = new + { + assetId + } + }); var expected = new { @@ -275,19 +307,25 @@ public class GraphQLQueriesTests : GraphQLTestBase var assetId = DomainId.NewGuid(); var asset = TestAsset.Create(assetId); - var query = CreateQuery(@" - query { - findAsset(id: '') { - - } - }", assetId); - A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A.That.HasIdsWithoutTotal(assetId), A._)) .Returns(ResultList.CreateFrom(1, asset)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findAsset(id: '{assetId}') { + {fields} + } + }", + Args = new + { + assetId, + fields = TestAsset.AllFields + } + }); var expected = new { @@ -306,19 +344,24 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" - query { - queryMySchemaContents(top: 30, skip: 5) { - - } - }"); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.NoTotal), A._)) .Returns(ResultList.CreateFrom(0, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + queryMySchemaContents(top: 30, skip: 5) { + {fields} + } + }", + Args = new + { + fields = TestContent.AllFlatFields + } + }); var expected = new { @@ -340,19 +383,24 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" - query { - queryMySchemaContents(top: 30, skip: 5) { - - } - }"); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.NoTotal), A._)) .Returns(ResultList.CreateFrom(0, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + queryMySchemaContents(top: 30, skip: 5) { + {fields} + } + }", + Args = new + { + fields = TestContent.AllFields + } + }); var expected = new { @@ -374,22 +422,27 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), + A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && !x.NoTotal), + A._)) + .Returns(ResultList.CreateFrom(10, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { queryMySchemaContentsWithTotal(top: 30, skip: 5) { total items { - + {fields} } } - }"); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), - A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && !x.NoTotal), - A._)) - .Returns(ResultList.CreateFrom(10, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + fields = TestContent.AllFields + } + }); var expected = new { @@ -414,20 +467,25 @@ public class GraphQLQueriesTests : GraphQLTestBase { var contentId = DomainId.NewGuid(); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '') { - id, - version - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentId), A._)) .Returns(ResultList.CreateFrom(1)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}') { + id, + version + } + }", + Args = new + { + contentId + } + }); var expected = new { @@ -446,20 +504,25 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.CreateRef(TestSchemas.Ref1Id, contentId, "ref1-field", "ref1"); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '') { - id, - version - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentId), A._)) .Returns(ResultList.CreateFrom(10, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}') { + id, + version + } + }", + Args = new + { + contentId + } + }); var expected = new { @@ -478,19 +541,25 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '') { - - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentId), A._)) .Returns(ResultList.CreateFrom(1, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}') { + {fields} + } + }", + Args = new + { + contentId, + fields = TestContent.AllFields + } + }); var expected = new { @@ -509,18 +578,24 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '', version: 3) { - - } - }", contentId); - A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), contentId, 3, A._)) .Returns(content); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}', version: 3) { + {fields} + } + }", + Args = new + { + contentId, + fields = TestContent.AllFields + } + }); var expected = new { @@ -542,9 +617,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentRefId), + A._)) + .Returns(ResultList.CreateFrom(0, contentRef)); + + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id data { myEmbeds { @@ -566,19 +653,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentRefId), - A._)) - .Returns(ResultList.CreateFrom(0, contentRef)); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), - A._)) - .Returns(ResultList.CreateFrom(1, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -627,9 +707,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentRefId), + A._)) + .Returns(ResultList.CreateFrom(0, contentRef)); + + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id data { myReferences { @@ -644,19 +736,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentRefId), - A._)) - .Returns(ResultList.CreateFrom(0, contentRef)); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), - A._)) - .Returns(ResultList.CreateFrom(1, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -701,18 +786,6 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '') { - id - flatData { - myReferences { - id - } - } - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentRefId), A._)) @@ -723,7 +796,24 @@ public class GraphQLQueriesTests : GraphQLTestBase A._)) .Returns(ResultList.CreateFrom(1, content)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}') { + id + flatData { + myReferences { + id + } + } + } + }", + Args = new + { + contentId + } + }); var expected = new { @@ -758,18 +848,6 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '') { - id - flatData { - myReferences @cache(duration: 1000) { - id - } - } - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentRefId), A._)) @@ -780,8 +858,27 @@ public class GraphQLQueriesTests : GraphQLTestBase A._)) .Returns(ResultList.CreateFrom(1, content)); - var actual1 = await ExecuteAsync(new ExecutionOptions { Query = query }); - var actual2 = await ExecuteAsync(new ExecutionOptions { Query = query }); + var query = new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}') { + id + flatData { + myReferences @cache(duration: 1000) { + id + } + } + } + }", + Args = new + { + contentId + } + }; + + var actual1 = await ExecuteAsync(query); + var actual2 = await ExecuteAsync(query); var expected = new { @@ -822,9 +919,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentRefId), + A._)) + .Returns(ResultList.CreateFrom(1, contentRef)); + + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), + A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Reference == contentRefId && x.NoTotal), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMyRefSchema1Content(id: '') { + findMyRefSchema1Content(id: '{contentId}') { id referencingMySchemaContents(top: 30, skip: 5) { id @@ -835,18 +944,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentRefId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentRefId), A._)) - .Returns(ResultList.CreateFrom(1, contentRef)); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), - A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Reference == contentRefId && x.NoTotal), - A._)) - .Returns(ResultList.CreateFrom(1, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId = contentRefId + } + }); var expected = new { @@ -885,9 +988,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentRefId), + A._)) + .Returns(ResultList.CreateFrom(1, contentRef)); + + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), + A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Reference == contentRefId && !x.NoTotal), + A._)) + .Returns(ResultList.CreateFrom(10, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMyRefSchema1Content(id: '') { + findMyRefSchema1Content(id: '{contentId}') { id referencingMySchemaContentsWithTotal(top: 30, skip: 5) { total @@ -901,18 +1016,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentRefId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentRefId), A._)) - .Returns(ResultList.CreateFrom(1, contentRef)); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), - A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Reference == contentRefId && !x.NoTotal), - A._)) - .Returns(ResultList.CreateFrom(10, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId = contentRefId + } + }); var expected = new { @@ -955,18 +1064,9 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" - query { - findMySchemaContent(id: '') { - id - referencesMyRefSchema1Contents(top: 30, skip: 5) { - id - } - } - }", contentId); - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) + A.That.HasIdsWithoutTotal(contentId), + A._)) .Returns(ResultList.CreateFrom(1, content)); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), contentRef.SchemaId.Id.ToString(), @@ -974,7 +1074,22 @@ public class GraphQLQueriesTests : GraphQLTestBase A._)) .Returns(ResultList.CreateFrom(1, contentRef)); - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + query { + findMySchemaContent(id: '{contentId}') { + id + referencesMyRefSchema1Contents(top: 30, skip: 5) { + id + } + } + }", + Args = new + { + contentId + } + }); var expected = new { @@ -1006,9 +1121,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), contentRef.SchemaId.Id.ToString(), + A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Referencing == contentId), + A._)) + .Returns(ResultList.CreateFrom(10, contentRef)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id referencesMyRefSchema1ContentsWithTotal(top: 30, skip: 5) { total @@ -1017,18 +1144,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1, content)); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), contentRef.SchemaId.Id.ToString(), - A.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Referencing == contentId), - A._)) - .Returns(ResultList.CreateFrom(10, contentRef)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -1064,9 +1185,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, contentRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentRefId), + A._)) + .Returns(ResultList.CreateFrom(0, contentRef)); + + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id data { myUnion { @@ -1086,17 +1219,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentRefId), A._)) - .Returns(ResultList.CreateFrom(0, contentRef)); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -1142,9 +1270,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, assetId: assetRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, + A.That.HasIdsWithoutTotal(assetRefId), + A._)) + .Returns(ResultList.CreateFrom(0, assetRef)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id data { myEmbeds { @@ -1157,17 +1297,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1, content)); - - A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, - A.That.HasIdsWithoutTotal(assetRefId), A._)) - .Returns(ResultList.CreateFrom(0, assetRef)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -1209,9 +1344,21 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, assetId: assetRefId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, + A.That.HasIdsWithoutTotal(assetRefId), + A._)) + .Returns(ResultList.CreateFrom(0, assetRef)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id data { myAssets { @@ -1221,17 +1368,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1, content)); - - A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, - A.That.HasIdsWithoutTotal(assetRefId), A._)) - .Returns(ResultList.CreateFrom(0, assetRef)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { @@ -1266,9 +1408,16 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId, data: new ContentData()); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { id version created @@ -1282,13 +1431,12 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var json = serializer.Serialize(actual); @@ -1298,7 +1446,9 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_query_only_selected_fields() { - var query = CreateQuery(@" + await ExecuteAsync(new TestQuery + { + Query = @" query { queryMySchemaContents @optimizeFieldQueries { data { @@ -1307,9 +1457,8 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }"); - - await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.HasFields(new[] { "my-number" }), @@ -1320,16 +1469,17 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_query_only_selected_flat_fields() { - var query = CreateQuery(@" + await ExecuteAsync(new TestQuery + { + Query = @" query { queryMySchemaContents @optimizeFieldQueries { flatData { myNumber } } - }"); - - await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.HasFields(new[] { "my-number" }), @@ -1340,7 +1490,9 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_query_all_fields_when_directive_not_applied() { - var query = CreateQuery(@" + await ExecuteAsync(new TestQuery + { + Query = @" query { queryMySchemaContents { data { @@ -1349,9 +1501,8 @@ public class GraphQLQueriesTests : GraphQLTestBase } } } - }"); - - await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.Matches(x => x.Fields == null), @@ -1362,7 +1513,9 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_query_all_fields_when_dynamic_data_is_queried() { - var query = CreateQuery(@" + await ExecuteAsync(new TestQuery + { + Query = @" query { queryMySchemaContents @optimizeFieldQueries { flatData { @@ -1370,9 +1523,8 @@ public class GraphQLQueriesTests : GraphQLTestBase } data__dynamic } - }"); - - await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), A.That.Matches(x => x.Fields == null), @@ -1383,18 +1535,19 @@ public class GraphQLQueriesTests : GraphQLTestBase [Fact] public async Task Should_query_all_fields_across_schemas() { - var query = CreateQuery(@" + await ExecuteAsync(new TestQuery + { + Query = @" query { - queryContentsByIds(ids: [""42""]) @optimizeFieldQueries { + queryContentsByIds(ids: ['42']) @optimizeFieldQueries { ...on MySchema { flatData { myNumber } } } - }"); - - await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasFields(new[] { "my-number" }), @@ -1408,20 +1561,26 @@ public class GraphQLQueriesTests : GraphQLTestBase var contentId = DomainId.NewGuid(); var content = TestContent.Create(contentId); - var query = CreateQuery(@" + A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), + A.That.HasIdsWithoutTotal(contentId), + A._)) + .Returns(ResultList.CreateFrom(1, content)); + + var actual = await ExecuteAsync(new TestQuery + { + Query = @" query { - findMySchemaContent(id: '') { + findMySchemaContent(id: '{contentId}') { createdByUser { id } } - }", contentId); - - A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), - A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1, content)); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }", + Args = new + { + contentId + } + }); var expected = new { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLSubscriptionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLSubscriptionTests.cs index 7df4208ed..3fec58dbe 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLSubscriptionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLSubscriptionTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.Reactive.Linq; -using GraphQL; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Subscriptions; @@ -22,15 +21,6 @@ public class GraphQLSubscriptionTests : GraphQLTestBase { var id = DomainId.NewGuid(); - var query = CreateQuery(@" - subscription { - assetChanges { - id, - fileName, - fileSize - } - }"); - var stream = Observable.Return( new EnrichedAssetEvent @@ -43,9 +33,18 @@ public class GraphQLSubscriptionTests : GraphQLTestBase A.CallTo(() => subscriptionService.Subscribe(A._)) .Returns(stream); - var permission = PermissionIds.ForApp(PermissionIds.AppAssetsRead, TestApp.Default.Name); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission.Id); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + subscription { + assetChanges { + id, + fileName, + fileSize + } + }", + Permission = PermissionIds.AppAssetsRead + }); var expected = new { @@ -66,16 +65,17 @@ public class GraphQLSubscriptionTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permissions_for_assets() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" subscription { assetChanges { id, fileName, fileSize } - }"); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); var expected = new { @@ -109,14 +109,6 @@ public class GraphQLSubscriptionTests : GraphQLTestBase { var id = DomainId.NewGuid(); - var query = CreateQuery(@" - subscription { - contentChanges { - id, - data - } - }"); - var stream = Observable.Return( new EnrichedContentEvent @@ -131,9 +123,17 @@ public class GraphQLSubscriptionTests : GraphQLTestBase A.CallTo(() => subscriptionService.Subscribe(A._)) .Returns(stream); - var permission = PermissionIds.ForApp(PermissionIds.AppContentsRead, TestApp.Default.Name, "random-schema"); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }, permission.Id); + var actual = await ExecuteAsync(new TestQuery + { + Query = @" + subscription { + contentChanges { + id, + data + } + }", + Permission = PermissionIds.AppContentsRead + }); var expected = new { @@ -159,15 +159,16 @@ public class GraphQLSubscriptionTests : GraphQLTestBase [Fact] public async Task Should_return_error_if_user_has_no_permissions_for_contents() { - var query = CreateQuery(@" + var actual = await ExecuteAsync(new TestQuery + { + Query = @" subscription { contentChanges { id, data } - }"); - - var actual = await ExecuteAsync(new ExecutionOptions { Query = query }); + }" + }); var expected = new { 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 6de067fb4..ccb84af7a 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 @@ -7,7 +7,6 @@ using System.Reactive.Linq; using System.Reactive.Threading.Tasks; -using System.Text.RegularExpressions; using GraphQL; using GraphQL.DataLoader; using GraphQL.Execution; @@ -25,7 +24,6 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Tasks; using Squidex.Messaging.Subscriptions; -using Squidex.Shared; using Squidex.Shared.Users; #pragma warning disable SA1401 // Fields must be private @@ -43,6 +41,11 @@ public abstract class GraphQLTestBase : IClassFixture protected readonly Context requestContext; private CachingGraphQLResolver? sut; + protected class QueryOptions + { + public string Query { get; set; } + } + protected GraphQLTestBase() { A.CallTo(() => userResolver.QueryManyAsync(A._, default)) @@ -50,7 +53,11 @@ public abstract class GraphQLTestBase : IClassFixture { var ids = x.GetArgument(0)!; - var users = ids.Select(id => UserMocks.User(id, $"{id}@email.com", $"name_{id}")); + var users = ids.Select(id => + UserMocks.User( + id, + $"{id}@email.com", + $"{id}name")); return Task.FromResult(users.ToDictionary(x => x.Id)); }); @@ -66,34 +73,19 @@ public abstract class GraphQLTestBase : IClassFixture Assert.Equal(isonOutputExpected, jsonOutputResult); } - protected Task ExecuteAsync(ExecutionOptions options) - { - return ExecuteCoreAsync(options, requestContext); - } - - protected Task ExecuteAsync(ExecutionOptions options, string permissionId) - { - return ExecuteCoreAsync(options, BuildContext(permissionId)); - } - - protected async Task ExecuteCoreAsync(ExecutionOptions options, Context context) + protected Task ExecuteAsync(TestQuery query) { // Use a shared instance to test caching. sut ??= CreateSut(TestSchemas.Default, TestSchemas.Ref1, TestSchemas.Ref2); - // Provide the context to the test if services need to be resolved. - var graphQLContext = ActivatorUtilities.CreateInstance(sut.Services, context)!; - - options.UserContext = graphQLContext; + var options = query.ToOptions(sut.Services); - // Register data loader and other listeners. - foreach (var listener in sut.Services.GetRequiredService>()) - { - options.Listeners.Add(listener); - } + return ExecuteAsync(sut, options); + } - // Enrich the context with the schema. - await sut.ExecuteAsync(options, x => Task.FromResult(null!)); + private static async Task ExecuteAsync(CachingGraphQLResolver resolver, ExecutionOptions options) + { + await resolver.ExecuteAsync(options, x => Task.FromResult(null!)); var actual = await new DocumentExecuter().ExecuteAsync(options); @@ -111,13 +103,6 @@ public abstract class GraphQLTestBase : IClassFixture return actual; } - private static Context BuildContext(string permissionId) - { - var permission = PermissionIds.ForApp(permissionId, TestApp.Default.Name, TestSchemas.DefaultId.Name).Id; - - return new Context(Mocks.FrontendUser(permission: permission), TestApp.Default); - } - protected CachingGraphQLResolver CreateSut(params ISchemaEntity[] schemas) { var appProvider = A.Fake(); @@ -127,9 +112,10 @@ public abstract class GraphQLTestBase : IClassFixture var serviceProvider = new ServiceCollection() - .AddLogging() - .AddMemoryCache() - .AddBackgroundCache() + .AddLogging(options => + { + options.AddDebug(); + }) .Configure(x => { x.CanCache = true; @@ -153,6 +139,8 @@ public abstract class GraphQLTestBase : IClassFixture A.Fake()) .AddSingleton( A.Fake()) + .AddMemoryCache() + .AddBackgroundCache() .AddSingleton(appProvider) .AddSingleton(assetQuery) .AddSingleton(commandBus) @@ -164,54 +152,19 @@ public abstract class GraphQLTestBase : IClassFixture return ActivatorUtilities.CreateInstance(serviceProvider); } - protected static string CreateQuery(string query, DomainId id = default, IEnrichedContentEntity? content = null) - { - query = query - .Replace('\'', '"') - .Replace('`', '"') - .Replace("", TestAsset.AllFields, StringComparison.Ordinal) - .Replace("", TestContent.AllFields, StringComparison.Ordinal) - .Replace("", TestContent.AllFlatFields, StringComparison.Ordinal); - - if (id != default) - { - query = query.Replace("", id.ToString(), StringComparison.Ordinal); - } - - if (query.Contains("", StringComparison.Ordinal) && content != null) - { - var data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id); - - // Json is not the same as the input format of graphql, therefore we need to convert it. - var dataJson = TestUtils.DefaultSerializer.Serialize(data, true); - - // Use properties without quotes. - dataJson = Regex.Replace(dataJson, "\"([^\"]+)\":", x => $"{x.Groups[1].Value}:"); - - // Use enum values whithout quotes. - dataJson = Regex.Replace(dataJson, "\"Enum([A-Za-z]+)\"", x => $"Enum{x.Groups[1].Value}"); - - query = query.Replace("", dataJson, StringComparison.Ordinal); - } - - return query; - } - - protected Context MatchsAssetContext() + protected static Context MatchsAssetContext() { return A.That.Matches(x => x.App == TestApp.Default && x.NoCleanup() && - x.NoAssetEnrichment() && - x.UserPrincipal == requestContext.UserPrincipal); + x.NoAssetEnrichment()); } - protected Context MatchsContentContext() + protected static Context MatchsContentContext() { return A.That.Matches(x => x.App == TestApp.Default && x.NoCleanup() && - x.NoEnrichment() && - x.UserPrincipal == requestContext.UserPrincipal); + x.NoEnrichment()); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs index 6f32b9eee..0f0aa8350 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs @@ -100,7 +100,7 @@ public static class TestAsset { id = asset.CreatedBy.Identifier, email = $"{asset.CreatedBy.Identifier}@email.com", - displayName = $"name_{asset.CreatedBy.Identifier}" + displayName = $"{asset.CreatedBy.Identifier}name" }, editToken = $"token_{asset.Id}", lastModified = asset.LastModified, 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 2899b9101..9b115f2ea 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,7 +7,6 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; @@ -149,7 +148,7 @@ public static class TestContent url flatData { myJson - myJsonValue: myJson(path: ""value"") + myJsonValue: myJson(path: 'value') myJson2 { __typename rootString, @@ -375,7 +374,7 @@ public static class TestContent { id = content.CreatedBy.Identifier, email = $"{content.CreatedBy.Identifier}@email.com", - displayName = $"name_{content.CreatedBy.Identifier}" + displayName = $"{content.CreatedBy.Identifier}name" }, editToken = $"token_{content.Id}", lastModified = content.LastModified, @@ -407,7 +406,7 @@ public static class TestContent { id = content.CreatedBy.Identifier, email = $"{content.CreatedBy.Identifier}@email.com", - displayName = $"name_{content.CreatedBy.Identifier}" + displayName = $"{content.CreatedBy.Identifier}name" }, editToken = $"token_{content.Id}", lastModified = content.LastModified, 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 new file mode 100644 index 000000000..4fed4bdc5 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs @@ -0,0 +1,86 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Text.Json; +using GraphQL; +using GraphQL.Execution; +using GraphQL.SystemTextJson; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Shared; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; + +public sealed class TestQuery +{ + private static readonly GraphQLSerializer Serializer = new GraphQLSerializer(TestUtils.DefaultOptions()); + + required public string Query { get; set; } + + public object? Variables { get; set; } + + public object? Args { get; set; } + + public string? Permission { get; set; } + + public string? OperationName { get; set; } + + public ExecutionOptions ToOptions(IServiceProvider services) + { + var query = Query; + + if (Args != null) + { + foreach (var property in Serialize(Args).EnumerateObject()) + { + query = query.Replace($"{{{property.Name}}}", property.Value.ToString(), StringComparison.Ordinal); + } + } + + var options = new ExecutionOptions + { + Query = query.Replace('\'', '\"') + }; + + if (OperationName != null) + { + options.OperationName = OperationName; + } + + if (Variables != null) + { + options.Variables = Serializer.ReadNode(Serialize(Variables))!; + } + + foreach (var listener in services.GetRequiredService>()) + { + options.Listeners.Add(listener); + } + + options.UserContext = ActivatorUtilities.CreateInstance(services, BuildContext(Permission)); + + return options; + + static Context BuildContext(string? permissionId) + { + if (permissionId == null) + { + return new Context(Mocks.FrontendUser(), TestApp.Default); + } + + var permission = PermissionIds.ForApp(permissionId, TestApp.Default.Name, TestSchemas.DefaultId.Name).Id; + + return new Context(Mocks.FrontendUser(permission: permission), TestApp.Default); + } + + static JsonElement Serialize(object value) + { + return JsonSerializer.SerializeToElement(value, TestUtils.DefaultOptions()); + } + } +}