From 86a5f999ed51843550c981617ee58418ecf28366 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 20 Jun 2020 20:56:42 +0200 Subject: [PATCH] Async fixes. (#537) * Async fixes. * More things. * More fixes. --- .../Assets/MongoAssetRepository.cs | 5 +- .../Operations/QueryContentsByQuery.cs | 7 +- .../Rules/MongoRuleEventRepository.cs | 5 +- .../Assets/Queries/AssetQueryParser.cs | 5 +- .../Assets/Queries/AssetQueryService.cs | 2 +- .../Assets/Queries/FilterTagTransformer.cs | 8 +- .../Contents/Counter/CounterJintExtension.cs | 5 +- .../Contents/Queries/ContentQueryParser.cs | 5 +- .../Contents/Queries/ContentQueryService.cs | 2 +- .../Contents/Queries/FilterTagTransformer.cs | 8 +- .../Contents/Queries/Steps/ConvertData.cs | 10 +- .../MongoXmlEntity.cs | 20 ++ .../MongoXmlRepository.cs | 51 ++++++ .../DefaultXmlRepository.cs | 53 ------ .../EventSourcing/CosmosDbSubscription.cs | 2 +- .../GetEventStoreSubscription.cs | 3 +- .../Queries/AsyncTransformVisitor.cs | 49 +++++ .../Queries/Optimizer.cs | 13 +- .../Queries/TransformVisitor.cs | 16 +- .../StringExtensions.cs | 2 +- .../Tasks/AsyncHelper.cs | 54 ++++++ .../Assets/AssetFoldersController.cs | 12 +- .../Users/UserManagementController.cs | 18 +- .../Config/IdentityServerExtensions.cs | 5 +- .../Config/IdentityServerServices.cs | 7 +- .../Controllers/Profile/ProfileController.cs | 16 +- .../Authentication/AuthenticationServices.cs | 5 - .../Squidex/Config/Domain/StoreServices.cs | 4 + .../Assets/Queries/AssetQueryParserTests.cs | 42 +++-- .../Assets/Queries/AssetQueryServiceTests.cs | 2 +- .../Queries/FilterTagTransformerTests.cs | 13 +- .../Contents/MongoDb/MongoDbQueryTests.cs | 91 +++++----- .../Queries/ContentQueryParserTests.cs | 47 ++--- .../Queries/ContentQueryServiceTests.cs | 2 +- .../Queries/FilterTagTransformerTests.cs | 10 +- .../DefaultXmlRepositoryTests.cs | 57 ------ .../Queries/QueryODataConversionTests.cs | 171 +++++++++--------- 37 files changed, 470 insertions(+), 357 deletions(-) create mode 100644 backend/src/Squidex.Domain.Users.MongoDb/MongoXmlEntity.cs create mode 100644 backend/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs delete mode 100644 backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs create mode 100644 backend/src/Squidex.Infrastructure/Queries/AsyncTransformVisitor.cs create mode 100644 backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs delete mode 100644 backend/tests/Squidex.Domain.Users.Tests/DefaultXmlRepositoryTests.cs diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 543b0b429..5833e3337 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -20,6 +20,7 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { @@ -84,9 +85,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets .QuerySort(query) .ToListAsync(); - await Task.WhenAll(assetItems, assetCount); + var (items, total) = await AsyncHelper.WhenAll(assetItems, assetCount); - return ResultList.Create(assetCount.Result, assetItems.Result); + return ResultList.Create(total, items); } catch (MongoQueryException ex) when (ex.Message.Contains("17406")) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs index 67201c5e5..c44e26ee5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs @@ -20,6 +20,7 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { @@ -87,14 +88,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations var contentCount = Collection.Find(filter).CountDocumentsAsync(); var contentItems = FindContentsAsync(query, filter); - await Task.WhenAll(contentItems, contentCount); + var (items, total) = await AsyncHelper.WhenAll(contentItems, contentCount); - foreach (var entity in contentItems.Result) + foreach (var entity in items) { entity.ParseData(schema.SchemaDef, converter); } - return ResultList.Create(contentCount.Result, contentItems.Result); + return ResultList.Create(total, items); } catch (MongoCommandException ex) when (ex.Code == 96) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs index 7ee3f9263..4f5fb1bde 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.MongoDb.Rules { @@ -71,9 +72,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules var taskForItems = Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync(); var taskForCount = Collection.Find(filter).CountDocumentsAsync(); - await Task.WhenAll(taskForItems, taskForCount); + var (items, total) = await AsyncHelper.WhenAll(taskForItems, taskForCount); - return ResultList.Create(taskForCount.Result, taskForItems.Result); + return ResultList.Create(total, items); } public async Task FindAsync(Guid id) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs index 800658466..a7aa7658d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.Extensions.Options; using Microsoft.OData; using Microsoft.OData.Edm; @@ -41,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries this.tagService = tagService; } - public virtual ClrQuery ParseQuery(Context context, Q q) + public virtual async ValueTask ParseQueryAsync(Context context, Q q) { Guard.NotNull(context, nameof(context)); @@ -71,7 +72,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries if (result.Filter != null) { - result.Filter = FilterTagTransformer.Transform(result.Filter, context.App.Id, tagService); + result.Filter = await FilterTagTransformer.TransformAsync(result.Filter, context.App.Id, tagService); } if (result.Sort.Count == 0) 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 77d59a537..916c5775a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs @@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries private async Task> QueryByQueryAsync(Context context, Guid? parentId, Q query) { - var parsedQuery = queryParser.ParseQuery(context, query); + var parsedQuery = await queryParser.ParseQueryAsync(context, query); return await assetRepository.QueryAsync(context.App.Id, parentId, parsedQuery); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs index 0ee4380ac..eb5eb25d1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries { - public sealed class FilterTagTransformer : TransformVisitor + public sealed class FilterTagTransformer : AsyncTransformVisitor { private readonly ITagService tagService; private readonly Guid appId; @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries this.tagService = tagService; } - public static FilterNode? Transform(FilterNode nodeIn, Guid appId, ITagService tagService) + public static ValueTask?> TransformAsync(FilterNode nodeIn, Guid appId, ITagService tagService) { Guard.NotNull(nodeIn, nameof(nodeIn)); Guard.NotNull(tagService, nameof(tagService)); @@ -33,11 +33,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries return nodeIn.Accept(new FilterTagTransformer(appId, tagService)); } - public override FilterNode? Visit(CompareFilter nodeIn) + public override async ValueTask?> Visit(CompareFilter nodeIn) { if (string.Equals(nodeIn.Path[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Value.Value is string stringValue) { - var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, HashSet.Of(stringValue))).Result; + var tagNames = await tagService.GetTagIdsAsync(appId, TagGroups.Assets, HashSet.Of(stringValue)); if (tagNames.TryGetValue(stringValue, out var normalized)) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs index 21a3f29ed..574ab7f87 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Infrastructure; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Contents.Counter { @@ -46,14 +47,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter { var grain = grainFactory.GetGrain(appId); - return Task.Run(() => grain.IncrementAsync(name)).Result; + return AsyncHelper.Sync(() => grain.IncrementAsync(name)); } private long Reset(Guid appId, string name, long value) { var grain = grainFactory.GetGrain(appId); - return Task.Run(() => grain.ResetAsync(name, value)).Result; + return AsyncHelper.Sync(() => grain.ResetAsync(name, value)); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index a6c486358..16f5b69bc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Microsoft.OData; @@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries this.options = options.Value; } - public virtual ClrQuery ParseQuery(Context context, ISchemaEntity schema, Q q) + public virtual ValueTask ParseQueryAsync(Context context, ISchemaEntity schema, Q q) { Guard.NotNull(context, nameof(context)); Guard.NotNull(schema, nameof(schema)); @@ -90,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries result.Take = options.MaxResults; } - return result; + return new ValueTask(result); } } 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 532a2f6c5..039456f88 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -208,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries private async Task> QueryByQueryAsync(Context context, ISchemaEntity schema, Q query) { - var parsedQuery = queryParser.ParseQuery(context, schema, query); + var parsedQuery = await queryParser.ParseQueryAsync(context, schema, query); return await QueryCoreAsync(context, schema, parsedQuery); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs index 21cc88623..bc79d824e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs @@ -16,7 +16,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.Contents.Queries { - public sealed class FilterTagTransformer : TransformVisitor + public sealed class FilterTagTransformer : AsyncTransformVisitor { private readonly ITagService tagService; private readonly ISchemaEntity schema; @@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries this.tagService = tagService; } - public static FilterNode? Transform(FilterNode nodeIn, Guid appId, ISchemaEntity schema, ITagService tagService) + public static ValueTask?> TransformAsync(FilterNode nodeIn, Guid appId, ISchemaEntity schema, ITagService tagService) { Guard.NotNull(nodeIn, nameof(nodeIn)); Guard.NotNull(tagService, nameof(tagService)); @@ -38,11 +38,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries return nodeIn.Accept(new FilterTagTransformer(appId, schema, tagService)); } - public override FilterNode? Visit(CompareFilter nodeIn) + public override async ValueTask?> Visit(CompareFilter nodeIn) { if (nodeIn.Value.Value is string stringValue && IsDataPath(nodeIn.Path) && IsTagField(nodeIn.Path)) { - var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Schemas(schema.Id), HashSet.Of(stringValue))).Result; + var tagNames = await tagService.GetTagIdsAsync(appId, TagGroups.Schemas(schema.Id), HashSet.Of(stringValue)); if (tagNames.TryGetValue(stringValue, out var normalized)) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs index 27383a6bc..698adb036 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs @@ -15,6 +15,7 @@ using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Infrastructure; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { @@ -72,12 +73,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps if (ids.Count > 0) { - var taskForAssets = QueryAssetIdsAsync(context, ids); - var taskForContents = QueryContentIdsAsync(context, ids); + var (assets, refContents) = await AsyncHelper.WhenAll( + QueryAssetIdsAsync(context, ids), + QueryContentIdsAsync(context, ids)); - await Task.WhenAll(taskForAssets, taskForContents); - - var foundIds = new HashSet(taskForAssets.Result.Union(taskForContents.Result)); + var foundIds = new HashSet(assets.Union(refContents)); return ValueReferencesConverter.CleanReferences(foundIds); } diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoXmlEntity.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoXmlEntity.cs new file mode 100644 index 000000000..fe3df1a1f --- /dev/null +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoXmlEntity.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoXmlEntity + { + [BsonId] + public string FriendlyName { get; set; } + + [BsonRequired] + public string Xml { get; set; } + } +} diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs new file mode 100644 index 000000000..df00fa621 --- /dev/null +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs @@ -0,0 +1,51 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.Repositories; +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoXmlRepository : IXmlRepository + { + private static readonly ReplaceOptions UpsertReplace = new ReplaceOptions { IsUpsert = true }; + private readonly IMongoCollection collection; + + public MongoXmlRepository(IMongoDatabase mongoDatabase) + { + Guard.NotNull(mongoDatabase, nameof(mongoDatabase)); + + collection = mongoDatabase.GetCollection("States_Repository"); + } + + public IReadOnlyCollection GetAllElements() + { + var documents = collection.Find(new BsonDocument()).ToList(); + + var elements = documents.Select(x => XElement.Parse(x.Xml)).ToList(); + + return elements; + } + + public void StoreElement(XElement element, string friendlyName) + { + var document = new MongoXmlEntity + { + FriendlyName = friendlyName + }; + + document.Xml = element.ToString(); + + collection.ReplaceOne(x => x.FriendlyName == friendlyName, document, UpsertReplace); + } + } +} diff --git a/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs b/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs deleted file mode 100644 index 05020f01b..000000000 --- a/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Xml.Linq; -using Microsoft.AspNetCore.DataProtection.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.States; - -namespace Squidex.Domain.Users -{ - public sealed class DefaultXmlRepository : IXmlRepository - { - private readonly ISnapshotStore store; - - [CollectionName("XmlRepository")] - public sealed class State - { - public string Xml { get; set; } - } - - public DefaultXmlRepository(ISnapshotStore store) - { - Guard.NotNull(store, nameof(store)); - - this.store = store; - } - - public IReadOnlyCollection GetAllElements() - { - var result = new List(); - - store.ReadAllAsync((state, version) => - { - result.Add(XElement.Parse(state.Xml)); - - return Task.CompletedTask; - }).Wait(); - - return result; - } - - public void StoreElement(XElement element, string friendlyName) - { - store.WriteAsync(friendlyName, new State { Xml = element.ToString() }, EtagVersion.Any, EtagVersion.Any).Wait(); - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs index bb0cc23f7..6f9c3b813 100644 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs +++ b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs @@ -38,7 +38,7 @@ namespace Squidex.Infrastructure.EventSourcing if (fromBeginning) { - hostName = $"squidex.{DateTime.UtcNow.Ticks.ToString()}"; + hostName = $"squidex.{DateTime.UtcNow.Ticks}"; } else { diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs index 90d25eec3..0d73af252 100644 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs +++ b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.ClientAPI.Exceptions; using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.EventSourcing { @@ -35,7 +36,7 @@ namespace Squidex.Infrastructure.EventSourcing this.position = projectionClient.ParsePositionOrNull(position); this.prefix = prefix; - var streamName = projectionClient.CreateProjectionAsync(streamFilter).Result; + var streamName = AsyncHelper.Sync(() => projectionClient.CreateProjectionAsync(streamFilter)); this.serializer = serializer; this.subscriber = subscriber; diff --git a/backend/src/Squidex.Infrastructure/Queries/AsyncTransformVisitor.cs b/backend/src/Squidex.Infrastructure/Queries/AsyncTransformVisitor.cs new file mode 100644 index 000000000..d635ba447 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Queries/AsyncTransformVisitor.cs @@ -0,0 +1,49 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Queries +{ + public abstract class AsyncTransformVisitor : FilterNodeVisitor?>, TValue> + { + public override ValueTask?> Visit(CompareFilter nodeIn) + { + return new ValueTask?>(nodeIn); + } + + public override async ValueTask?> Visit(LogicalFilter nodeIn) + { + var pruned = new List>(nodeIn.Filters.Count); + + foreach (var inner in nodeIn.Filters) + { + var transformed = await inner.Accept(this); + + if (transformed != null) + { + pruned.Add(transformed); + } + } + + return new LogicalFilter(nodeIn.Type, pruned); + } + + public override async ValueTask?> Visit(NegateFilter nodeIn) + { + var inner = await nodeIn.Filter.Accept(this); + + if (inner == null) + { + return inner; + } + + return new NegateFilter(inner); + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Queries/Optimizer.cs b/backend/src/Squidex.Infrastructure/Queries/Optimizer.cs index 23bfd513d..bbdecf551 100644 --- a/backend/src/Squidex.Infrastructure/Queries/Optimizer.cs +++ b/backend/src/Squidex.Infrastructure/Queries/Optimizer.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Linq; namespace Squidex.Infrastructure.Queries @@ -24,7 +25,17 @@ namespace Squidex.Infrastructure.Queries public override FilterNode? Visit(LogicalFilter nodeIn) { - var pruned = nodeIn.Filters.Select(x => x.Accept(this)!).NotNull().ToList(); + var pruned = new List>(nodeIn.Filters.Count); + + foreach (var filter in nodeIn.Filters) + { + var transformed = filter.Accept(this); + + if (transformed != null) + { + pruned.Add(transformed); + } + } if (pruned.Count == 1) { diff --git a/backend/src/Squidex.Infrastructure/Queries/TransformVisitor.cs b/backend/src/Squidex.Infrastructure/Queries/TransformVisitor.cs index 6ac548db6..c215374f1 100644 --- a/backend/src/Squidex.Infrastructure/Queries/TransformVisitor.cs +++ b/backend/src/Squidex.Infrastructure/Queries/TransformVisitor.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Linq; +using System.Collections.Generic; namespace Squidex.Infrastructure.Queries { @@ -18,9 +18,19 @@ namespace Squidex.Infrastructure.Queries public override FilterNode? Visit(LogicalFilter nodeIn) { - var inner = nodeIn.Filters.Select(x => x.Accept(this)!).NotNull().ToList(); + var pruned = new List>(nodeIn.Filters.Count); - return new LogicalFilter(nodeIn.Type, inner); + foreach (var inner in nodeIn.Filters) + { + var transformed = inner.Accept(this); + + if (transformed != null) + { + pruned.Add(transformed); + } + } + + return new LogicalFilter(nodeIn.Type, pruned); } public override FilterNode? Visit(NegateFilter nodeIn) diff --git a/backend/src/Squidex.Infrastructure/StringExtensions.cs b/backend/src/Squidex.Infrastructure/StringExtensions.cs index 08c04fbe7..a3acbab4e 100644 --- a/backend/src/Squidex.Infrastructure/StringExtensions.cs +++ b/backend/src/Squidex.Infrastructure/StringExtensions.cs @@ -19,7 +19,7 @@ namespace Squidex.Infrastructure private const char NullChar = (char)0; private static readonly Regex SlugRegex = new Regex("^[a-z0-9]+(\\-[a-z0-9]+)*$", RegexOptions.Compiled); - private static readonly Regex EmailRegex = new Regex("^[a-zA-Z0-9.!#$%&’*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$", RegexOptions.Compiled); + private static readonly Regex EmailRegex = new Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-||_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+([a-z]+|\d|-|\.{0,1}|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])?([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$", RegexOptions.Compiled); private static readonly Regex PropertyNameRegex = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled); private static readonly Dictionary LowerCaseDiacritics; diff --git a/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs b/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs new file mode 100644 index 000000000..ce347c372 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Tasks +{ + public static class AsyncHelper + { + private static readonly TaskFactory TaskFactory = new + TaskFactory(CancellationToken.None, + TaskCreationOptions.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + + public static async Task<(T1, T2)> WhenAll(Task task1, Task task2) + { + await Task.WhenAll(task1, task2); + + return (task1.Result, task2.Result); + } + + public static async Task<(T1, T2, T3)> WhenAll(Task task1, Task task2, Task task3) + { + await Task.WhenAll(task1, task2, task3); + + return (task1.Result, task2.Result, task3.Result); + } + + public static TResult Sync(Func> func) + { + return TaskFactory + .StartNew(func) + .Unwrap() + .GetAwaiter() + .GetResult(); + } + + public static void Sync(Func func) + { + TaskFactory + .StartNew(func) + .Unwrap() + .GetAwaiter() + .GetResult(); + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs index 9bf731ae7..ba46c2964 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs @@ -13,6 +13,7 @@ using Squidex.Areas.Api.Controllers.Assets.Models; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Tasks; using Squidex.Shared; using Squidex.Web; @@ -51,17 +52,16 @@ namespace Squidex.Areas.Api.Controllers.Assets [ApiCosts(1)] public async Task GetAssetFolders(string app, [FromQuery] Guid parentId) { - var assetFolders = assetQuery.QueryAssetFoldersAsync(Context, parentId); - var assetPath = assetQuery.FindAssetFolderAsync(parentId); - - await Task.WhenAll(assetFolders, assetPath); + var (folders, path) = await AsyncHelper.WhenAll( + assetQuery.QueryAssetFoldersAsync(Context, parentId), + assetQuery.FindAssetFolderAsync(parentId)); var response = Deferred.Response(() => { - return AssetFoldersDto.FromAssets(assetFolders.Result, assetPath.Result, Resources); + return AssetFoldersDto.FromAssets(folders, path, Resources); }); - Response.Headers[HeaderNames.ETag] = assetFolders.Result.ToEtag(); + Response.Headers[HeaderNames.ETag] = folders.ToEtag(); return Ok(response); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs index 888727701..331ff8254 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Users.Models; using Squidex.Domain.Users; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Validation; using Squidex.Shared; using Squidex.Web; @@ -22,12 +23,14 @@ namespace Squidex.Areas.Api.Controllers.Users { private readonly UserManager userManager; private readonly IUserFactory userFactory; + private readonly IUserEvents userEvents; - public UserManagementController(ICommandBus commandBus, UserManager userManager, IUserFactory userFactory) + public UserManagementController(ICommandBus commandBus, UserManager userManager, IUserFactory userFactory, IUserEvents userEvents) : base(commandBus) { this.userManager = userManager; this.userFactory = userFactory; + this.userEvents = userEvents; } [HttpGet] @@ -36,12 +39,11 @@ namespace Squidex.Areas.Api.Controllers.Users [ApiPermission(Permissions.AdminUsersRead)] public async Task GetUsers([FromQuery] string? query = null, [FromQuery] int skip = 0, [FromQuery] int take = 10) { - var taskForItems = userManager.QueryByEmailAsync(query, take, skip); - var taskForCount = userManager.CountByEmailAsync(query); + var (items, total) = await AsyncHelper.WhenAll( + userManager.QueryByEmailAsync(query, take, skip), + userManager.CountByEmailAsync(query)); - await Task.WhenAll(taskForItems, taskForCount); - - var response = UsersDto.FromResults(taskForItems.Result, taskForCount.Result, Resources); + var response = UsersDto.FromResults(items, total, Resources); return Ok(response); } @@ -72,6 +74,8 @@ namespace Squidex.Areas.Api.Controllers.Users { var user = await userManager.CreateAsync(userFactory, request.ToValues()); + userEvents.OnUserRegistered(user); + var response = UserDto.FromUser(user, Resources); return Ok(response); @@ -85,6 +89,8 @@ namespace Squidex.Areas.Api.Controllers.Users { var user = await userManager.UpdateAsync(id, request.ToValues()); + userEvents.OnUserUpdated(user); + var response = UserDto.FromUser(user, Resources); return Ok(response); diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs index aca245734..677361f1e 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs @@ -17,6 +17,7 @@ using Squidex.Config; using Squidex.Domain.Users; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Security; +using Squidex.Infrastructure.Tasks; using Squidex.Shared; namespace Squidex.Areas.IdentityServer.Config @@ -46,7 +47,7 @@ namespace Squidex.Areas.IdentityServer.Config var adminEmail = options.AdminEmail; var adminPass = options.AdminPassword; - Task.Run(async () => + AsyncHelper.Sync(async () => { if (userManager.SupportsQueryableUsers && !userManager.Users.Any()) { @@ -69,7 +70,7 @@ namespace Squidex.Areas.IdentityServer.Config .WriteProperty("status", "failed")); } } - }).Wait(); + }); } return services; diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs index 3d357a883..9646cf9fd 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs @@ -42,18 +42,21 @@ namespace Squidex.Areas.IdentityServer.Config services.AddIdentity() .AddDefaultTokenProviders(); + services.AddSingleton, PwnedPasswordValidator>(); + services.AddScoped, UserClaimsPrincipalFactoryWithEmail>(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); services.AddIdentityServer(options => { diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs index c98e2ac18..5dec1f7b9 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs @@ -21,6 +21,7 @@ using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Tasks; using Squidex.Shared.Identity; using Squidex.Shared.Users; @@ -232,11 +233,10 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile throw new DomainException("Cannot find user."); } - var taskForProviders = signInManager.GetExternalProvidersAsync(); - var taskForPassword = userManager.HasPasswordAsync(user.Identity); - var taskForLogins = userManager.GetLoginsAsync(user.Identity); - - await Task.WhenAll(taskForProviders, taskForPassword, taskForLogins); + var (providers, hasPassword, logins) = await AsyncHelper.WhenAll( + signInManager.GetExternalProvidersAsync(), + userManager.HasPasswordAsync(user.Identity), + userManager.GetLoginsAsync(user.Identity)); var result = new ProfileVM { @@ -244,10 +244,10 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile ClientSecret = user.ClientSecret()!, Email = user.Email, ErrorMessage = errorMessage, - ExternalLogins = taskForLogins.Result, - ExternalProviders = taskForProviders.Result, + ExternalLogins = logins, + ExternalProviders = providers, DisplayName = user.DisplayName()!, - HasPassword = taskForPassword.Result, + HasPassword = hasPassword, HasPasswordAuth = identityOptions.AllowPasswordAuth, IsHidden = user.IsHidden(), SuccessMessage = successMessage diff --git a/backend/src/Squidex/Config/Authentication/AuthenticationServices.cs b/backend/src/Squidex/Config/Authentication/AuthenticationServices.cs index 0e27dfb7e..b98f7549a 100644 --- a/backend/src/Squidex/Config/Authentication/AuthenticationServices.cs +++ b/backend/src/Squidex/Config/Authentication/AuthenticationServices.cs @@ -6,10 +6,8 @@ // ========================================================================== using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Users; namespace Squidex.Config.Authentication { @@ -19,9 +17,6 @@ namespace Squidex.Config.Authentication { var identityOptions = config.GetSection("identity").Get(); - services.AddSingletonAs() - .As(); - services.AddAuthentication() .AddSquidexCookies() .AddSquidexExternalGithubAuthentication(identityOptions) diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 4b8af4606..0b4e7b9dd 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -8,6 +8,7 @@ using System; using System.Linq; using IdentityServer4.Stores; +using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -102,6 +103,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As>().As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As().As(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryParserTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryParserTests.cs index 748554d69..bcaaa9560 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryParserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryParserTests.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Esprima.Ast; using FakeItEasy; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Tags; @@ -35,100 +37,100 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries } [Fact] - public void Should_use_existing_query() + public async Task Should_use_existing_query() { var clrQuery = new ClrQuery(); - var parsed = sut.ParseQuery(requestContext, Q.Empty.WithQuery(clrQuery)); + var parsed = await sut.ParseQueryAsync(requestContext, Q.Empty.WithQuery(clrQuery)); Assert.Same(parsed, clrQuery); } [Fact] - public void Should_throw_if_odata_query_is_invalid() + public async Task Should_throw_if_odata_query_is_invalid() { var query = Q.Empty.WithODataQuery("$filter=invalid"); - Assert.Throws(() => sut.ParseQuery(requestContext, query)); + await Assert.ThrowsAsync(() => sut.ParseQueryAsync(requestContext, query).AsTask()); } [Fact] - public void Should_throw_if_json_query_is_invalid() + public async Task Should_throw_if_json_query_is_invalid() { var query = Q.Empty.WithJsonQuery("invalid"); - Assert.Throws(() => sut.ParseQuery(requestContext, query)); + await Assert.ThrowsAsync(() => sut.ParseQueryAsync(requestContext, query).AsTask()); } [Fact] - public void Should_parse_odata_query() + public async Task Should_parse_odata_query() { var query = Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World"); - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending", parsed.ToString()); } [Fact] - public void Should_parse_odata_query_and_enrich_with_defaults() + public async Task Should_parse_odata_query_and_enrich_with_defaults() { var query = Q.Empty.WithODataQuery("$top=200&$filter=fileName eq 'ABC'"); - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_parse_json_query_and_enrich_with_defaults() + public async Task Should_parse_json_query_and_enrich_with_defaults() { var query = Q.Empty.WithJsonQuery(Json("{ 'filter': { 'path': 'fileName', 'op': 'eq', 'value': 'ABC' } }")); - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("Filter: fileName == 'ABC'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_parse_json_full_text_query_and_enrich_with_defaults() + public async Task Should_parse_json_full_text_query_and_enrich_with_defaults() { var query = Q.Empty.WithJsonQuery(Json("{ 'fullText': 'Hello' }")); - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("FullText: 'Hello'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_apply_default_page_size() + public async Task Should_apply_default_page_size() { var query = Q.Empty; - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_limit_number_of_assets() + public async Task Should_limit_number_of_assets() { var query = Q.Empty.WithODataQuery("$top=300&$skip=20"); - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("Skip: 20; Take: 200; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_denormalize_tags() + public async Task Should_denormalize_tags() { A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A>.That.Contains("name1"))) .Returns(new Dictionary { ["name1"] = "id1" }); var query = Q.Empty.WithODataQuery("$filter=tags eq 'name1'"); - var parsed = sut.ParseQuery(requestContext, query); + var parsed = await sut.ParseQueryAsync(requestContext, query); Assert.Equal("Filter: tags == 'id1'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs index 07dac3675..8662640f9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs @@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries { requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId)); - A.CallTo(() => queryParser.ParseQuery(requestContext, A._)) + A.CallTo(() => queryParser.ParseQueryAsync(requestContext, A._)) .Returns(new ClrQuery()); sut = new AssetQueryService(assetEnricher, assetRepository, assetFolderRepository, queryParser); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs index 00e08f0d5..db1e7ab50 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Tags; using Squidex.Infrastructure.Queries; @@ -20,37 +21,37 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries private readonly Guid appId = Guid.NewGuid(); [Fact] - public void Should_normalize_tags() + public async Task Should_normalize_tags() { A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A>.That.Contains("name1"))) .Returns(new Dictionary { ["name1"] = "id1" }); var source = ClrFilter.Eq("tags", "name1"); - var result = FilterTagTransformer.Transform(source, appId, tagService); + var result = await FilterTagTransformer.TransformAsync(source, appId, tagService); Assert.Equal("tags == 'id1'", result!.ToString()); } [Fact] - public void Should_not_fail_when_tags_not_found() + public async Task Should_not_fail_when_tags_not_found() { A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A>.That.Contains("name1"))) .Returns(new Dictionary()); var source = ClrFilter.Eq("tags", "name1"); - var result = FilterTagTransformer.Transform(source, appId, tagService); + var result = await FilterTagTransformer.TransformAsync(source, appId, tagService); Assert.Equal("tags == 'name1'", result!.ToString()); } [Fact] - public void Should_not_normalize_other_field() + public async Task Should_not_normalize_other_field() { var source = ClrFilter.Eq("other", "value"); - var result = FilterTagTransformer.Transform(source, appId, tagService); + var result = await FilterTagTransformer.TransformAsync(source, appId, tagService); Assert.Equal("other == 'value'", result!.ToString()); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs index 40497c03b..72a3d32ae 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs @@ -27,6 +27,9 @@ using Xunit; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; +#pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable IDE1006 // Naming Styles + namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { public class MongoDbQueryTests @@ -79,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_throw_exception_for_invalid_field() { - Assert.Throws(() => F(ClrFilter.Eq("data/invalid/iv", "Me"))); + Assert.Throws(() => _F(ClrFilter.Eq("data/invalid/iv", "Me"))); } [Fact] @@ -87,8 +90,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { var id = Guid.NewGuid(); - var i = F(ClrFilter.Eq("id", id)); - var o = C($"{{ '_id' : '{id}' }}"); + var i = _F(ClrFilter.Eq("id", id)); + var o = _C($"{{ '_id' : '{id}' }}"); Assert.Equal(o, i); } @@ -98,8 +101,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { var id = Guid.NewGuid().ToString(); - var i = F(ClrFilter.Eq("id", id)); - var o = C($"{{ '_id' : '{id}' }}"); + var i = _F(ClrFilter.Eq("id", id)); + var o = _C($"{{ '_id' : '{id}' }}"); Assert.Equal(o, i); } @@ -109,8 +112,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { var id = Guid.NewGuid(); - var i = F(ClrFilter.In("id", new List { id })); - var o = C($"{{ '_id' : {{ '$in' : ['{id}'] }} }}"); + var i = _F(ClrFilter.In("id", new List { id })); + var o = _C($"{{ '_id' : {{ '$in' : ['{id}'] }} }}"); Assert.Equal(o, i); } @@ -120,8 +123,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { var id = Guid.NewGuid().ToString(); - var i = F(ClrFilter.In("id", new List { id })); - var o = C($"{{ '_id' : {{ '$in' : ['{id}'] }} }}"); + var i = _F(ClrFilter.In("id", new List { id })); + var o = _C($"{{ '_id' : {{ '$in' : ['{id}'] }} }}"); Assert.Equal(o, i); } @@ -129,8 +132,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_lastModified() { - var i = F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); - var o = C("{ 'mt' : ISODate('1988-01-19T12:00:00Z') }"); + var i = _F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var o = _C("{ 'mt' : ISODate('1988-01-19T12:00:00Z') }"); Assert.Equal(o, i); } @@ -138,8 +141,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_lastModifiedBy() { - var i = F(ClrFilter.Eq("lastModifiedBy", "Me")); - var o = C("{ 'mb' : 'Me' }"); + var i = _F(ClrFilter.Eq("lastModifiedBy", "Me")); + var o = _C("{ 'mb' : 'Me' }"); Assert.Equal(o, i); } @@ -147,8 +150,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_created() { - var i = F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); - var o = C("{ 'ct' : ISODate('1988-01-19T12:00:00Z') }"); + var i = _F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var o = _C("{ 'ct' : ISODate('1988-01-19T12:00:00Z') }"); Assert.Equal(o, i); } @@ -156,8 +159,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_createdBy() { - var i = F(ClrFilter.Eq("createdBy", "Me")); - var o = C("{ 'cb' : 'Me' }"); + var i = _F(ClrFilter.Eq("createdBy", "Me")); + var o = _C("{ 'cb' : 'Me' }"); Assert.Equal(o, i); } @@ -165,8 +168,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_version() { - var i = F(ClrFilter.Eq("version", 0L)); - var o = C("{ 'vs' : NumberLong(0) }"); + var i = _F(ClrFilter.Eq("version", 0L)); + var o = _C("{ 'vs' : NumberLong(0) }"); Assert.Equal(o, i); } @@ -174,8 +177,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_version_and_list() { - var i = F(ClrFilter.In("version", new List { 0L, 2L, 5L })); - var o = C("{ 'vs' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }"); + var i = _F(ClrFilter.In("version", new List { 0L, 2L, 5L })); + var o = _C("{ 'vs' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }"); Assert.Equal(o, i); } @@ -183,8 +186,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_empty_test() { - var i = F(ClrFilter.Empty("data/firstName/iv")); - var o = C("{ '$or' : [{ 'do.1.iv' : { '$exists' : false } }, { 'do.1.iv' : null }, { 'do.1.iv' : '' }, { 'do.1.iv' : [] }] }"); + var i = _F(ClrFilter.Empty("data/firstName/iv")); + var o = _C("{ '$or' : [{ 'do.1.iv' : { '$exists' : false } }, { 'do.1.iv' : null }, { 'do.1.iv' : '' }, { 'do.1.iv' : [] }] }"); Assert.Equal(o, i); } @@ -192,8 +195,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_datetime_data() { - var i = F(ClrFilter.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); - var o = C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }"); + var i = _F(ClrFilter.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); + var o = _C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }"); Assert.Equal(o, i); } @@ -201,8 +204,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_underscore_field() { - var i = F(ClrFilter.Eq("data/dashed_field/iv", "Value")); - var o = C("{ 'do.8.iv' : 'Value' }"); + var i = _F(ClrFilter.Eq("data/dashed_field/iv", "Value")); + var o = _C("{ 'do.8.iv' : 'Value' }"); Assert.Equal(o, i); } @@ -210,8 +213,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_references_equals() { - var i = F(ClrFilter.Eq("data/friends/iv", "guid")); - var o = C("{ 'do.7.iv' : 'guid' }"); + var i = _F(ClrFilter.Eq("data/friends/iv", "guid")); + var o = _C("{ 'do.7.iv' : 'guid' }"); Assert.Equal(o, i); } @@ -219,8 +222,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_array_field() { - var i = F(ClrFilter.Eq("data/hobbies/iv/name", "PC")); - var o = C("{ 'do.9.iv.91' : 'PC' }"); + var i = _F(ClrFilter.Eq("data/hobbies/iv/name", "PC")); + var o = _C("{ 'do.9.iv.91' : 'PC' }"); Assert.Equal(o, i); } @@ -228,8 +231,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_assets_equals() { - var i = F(ClrFilter.Eq("data/pictures/iv", "guid")); - var o = C("{ 'do.6.iv' : 'guid' }"); + var i = _F(ClrFilter.Eq("data/pictures/iv", "guid")); + var o = _C("{ 'do.6.iv' : 'guid' }"); Assert.Equal(o, i); } @@ -237,8 +240,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_query_with_full_text() { - var i = Q(new ClrQuery { FullText = "Hello my World" }); - var o = C("{ '$text' : { '$search' : 'Hello my World' } }"); + var i = _Q(new ClrQuery { FullText = "Hello my World" }); + var o = _C("{ '$text' : { '$search' : 'Hello my World' } }"); Assert.Equal(o, i); } @@ -246,8 +249,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_orderby_with_single_field() { - var i = S(SortBuilder.Descending("data/age/iv")); - var o = C("{ 'do.4.iv' : -1 }"); + var i = _S(SortBuilder.Descending("data/age/iv")); + var o = _C("{ 'do.4.iv' : -1 }"); Assert.Equal(o, i); } @@ -255,8 +258,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb [Fact] public void Should_make_orderby_with_multiple_fields() { - var i = S(SortBuilder.Ascending("data/age/iv"), SortBuilder.Descending("data/firstName/en")); - var o = C("{ 'do.4.iv' : 1, 'do.1.en' : -1 }"); + var i = _S(SortBuilder.Ascending("data/age/iv"), SortBuilder.Descending("data/firstName/en")); + var o = _C("{ 'do.4.iv' : 1, 'do.1.en' : -1 }"); Assert.Equal(o, i); } @@ -285,17 +288,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb .MustHaveHappened(); } - private static string C(string value) + private static string _C(string value) { return value.Replace('\'', '"'); } - private string F(FilterNode filter) + private string _F(FilterNode filter) { - return Q(new ClrQuery { Filter = filter }); + return _Q(new ClrQuery { Filter = filter }); } - private string S(params SortNode[] sorts) + private string _S(params SortNode[] sorts) { var cursor = A.Fake>(); @@ -312,7 +315,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb return i; } - private string Q(ClrQuery query) + private string _Q(ClrQuery query) { var rendered = query.AdjustToModel(schemaDef).BuildFilter().Filter! diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs index e9d714c73..a3782d09e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs @@ -6,6 +6,9 @@ // ========================================================================== using System; +using System.Threading.Tasks; +using Esprima.Ast; +using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core; @@ -46,63 +49,63 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries } [Fact] - public void Should_use_existing_query() + public async Task Should_use_existing_query() { var clrQuery = new ClrQuery(); - var parsed = sut.ParseQuery(requestContext, schema, Q.Empty.WithQuery(clrQuery)); + var parsed = await sut.ParseQueryAsync(requestContext, schema, Q.Empty.WithQuery(clrQuery)); Assert.Same(parsed, clrQuery); } [Fact] - public void Should_throw_if_odata_query_is_invalid() + public async Task Should_throw_if_odata_query_is_invalid() { var query = Q.Empty.WithODataQuery("$filter=invalid"); - Assert.Throws(() => sut.ParseQuery(requestContext, schema, query)); + await Assert.ThrowsAsync(() => sut.ParseQueryAsync(requestContext, schema, query).AsTask()); } [Fact] - public void Should_throw_if_json_query_is_invalid() + public async Task Should_throw_if_json_query_is_invalid() { var query = Q.Empty.WithJsonQuery("invalid"); - Assert.Throws(() => sut.ParseQuery(requestContext, schema, query)); + await Assert.ThrowsAsync(() => sut.ParseQueryAsync(requestContext, schema, query).AsTask()); } [Fact] - public void Should_parse_odata_query() + public async Task Should_parse_odata_query() { var query = Q.Empty.WithODataQuery("$top=100&$orderby=data/firstName/iv asc&$search=Hello World"); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("FullText: 'Hello World'; Take: 100; Sort: data.firstName.iv Ascending", parsed.ToString()); } [Fact] - public void Should_parse_odata_query_and_enrich_with_defaults() + public async Task Should_parse_odata_query_and_enrich_with_defaults() { var query = Q.Empty.WithODataQuery("$top=200&$filter=data/firstName/iv eq 'ABC'"); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("Filter: data.firstName.iv == 'ABC'; Take: 200; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_parse_json_query_and_enrich_with_defaults() + public async Task Should_parse_json_query_and_enrich_with_defaults() { var query = Q.Empty.WithJsonQuery(Json("{ 'filter': { 'path': 'data.firstName.iv', 'op': 'eq', 'value': 'ABC' } }")); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("Filter: data.firstName.iv == 'ABC'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_convert_json_query_and_enrich_with_defaults() + public async Task Should_convert_json_query_and_enrich_with_defaults() { var query = Q.Empty.WithJsonQuery( new Query @@ -110,23 +113,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries Filter = new CompareFilter("data.firstName.iv", CompareOperator.Equals, JsonValue.Create("ABC")) }); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("Filter: data.firstName.iv == 'ABC'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_parse_json_full_text_query_and_enrich_with_defaults() + public async Task Should_parse_json_full_text_query_and_enrich_with_defaults() { var query = Q.Empty.WithJsonQuery(Json("{ 'fullText': 'Hello' }")); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("FullText: 'Hello'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_convert_json_full_text_query_and_enrich_with_defaults() + public async Task Should_convert_json_full_text_query_and_enrich_with_defaults() { var query = Q.Empty.WithJsonQuery( new Query @@ -134,27 +137,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries FullText = "Hello" }); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("FullText: 'Hello'; Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_apply_default_page_size() + public async Task Should_apply_default_page_size() { var query = Q.Empty; - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("Take: 30; Sort: lastModified Descending", parsed.ToString()); } [Fact] - public void Should_limit_number_of_contents() + public async Task Should_limit_number_of_contents() { var query = Q.Empty.WithODataQuery("$top=300&$skip=20"); - var parsed = sut.ParseQuery(requestContext, schema, query); + var parsed = await sut.ParseQueryAsync(requestContext, schema, query); Assert.Equal("Skip: 20; Take: 200; Sort: lastModified Descending", parsed.ToString()); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs index e8d84273e..990d923ee 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name)) .Returns(schema); - A.CallTo(() => queryParser.ParseQuery(A._, schema, A._)) + A.CallTo(() => queryParser.ParseQueryAsync(A._, schema, A._)) .Returns(new ClrQuery()); sut = new ContentQueryService( diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs index 4c69dbb17..803e2b228 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries var source = ClrFilter.Eq("data.tags2.iv", "name1"); - var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); + var result = FilterTagTransformer.TransformAsync(source, appId.Id, schema, tagService); Assert.Equal("data.tags2.iv == 'id1'", result!.ToString()); } @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries var source = ClrFilter.Eq("data.tags2.iv", "name1"); - var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); + var result = FilterTagTransformer.TransformAsync(source, appId.Id, schema, tagService); Assert.Equal("data.tags2.iv == 'name1'", result!.ToString()); } @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var source = ClrFilter.Eq("data.tags1.iv", "value"); - var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); + var result = FilterTagTransformer.TransformAsync(source, appId.Id, schema, tagService); Assert.Equal("data.tags1.iv == 'value'", result!.ToString()); @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var source = ClrFilter.Eq("data.string.iv", "value"); - var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); + var result = FilterTagTransformer.TransformAsync(source, appId.Id, schema, tagService); Assert.Equal("data.string.iv == 'value'", result!.ToString()); @@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var source = ClrFilter.Eq("no.data", "value"); - var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); + var result = FilterTagTransformer.TransformAsync(source, appId.Id, schema, tagService); Assert.Equal("no.data == 'value'", result!.ToString()); diff --git a/backend/tests/Squidex.Domain.Users.Tests/DefaultXmlRepositoryTests.cs b/backend/tests/Squidex.Domain.Users.Tests/DefaultXmlRepositoryTests.cs deleted file mode 100644 index ed86bcd93..000000000 --- a/backend/tests/Squidex.Domain.Users.Tests/DefaultXmlRepositoryTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; -using FakeItEasy; -using Squidex.Infrastructure; -using Squidex.Infrastructure.States; -using Xunit; - -namespace Squidex.Domain.Users -{ - public class DefaultXmlRepositoryTests - { - private readonly ISnapshotStore store = A.Fake>(); - private readonly DefaultXmlRepository sut; - - public DefaultXmlRepositoryTests() - { - sut = new DefaultXmlRepository(store); - } - - [Fact] - public void Should_write_new_item_to_store_with_friendly_name() - { - sut.StoreElement(new XElement("a"), "friendly-name"); - - A.CallTo(() => store.WriteAsync("friendly-name", A.That.Matches(x => x.Xml == ""), EtagVersion.Any, EtagVersion.Any)) - .MustHaveHappened(); - } - - [Fact] - public void Should_return_items_from_store() - { - A.CallTo(() => store.ReadAllAsync(A>._, A._)) - .Invokes((Func callback, CancellationToken ct) => - { - callback(new DefaultXmlRepository.State { Xml = "" }, EtagVersion.Any); - callback(new DefaultXmlRepository.State { Xml = "" }, EtagVersion.Any); - callback(new DefaultXmlRepository.State { Xml = "" }, EtagVersion.Any); - }); - - var result = sut.GetAllElements().ToList(); - - Assert.Equal("", result[0].ToString()); - Assert.Equal("", result[1].ToString()); - Assert.Equal("", result[2].ToString()); - } - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs index d6ee1821a..a0683d19f 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs @@ -9,6 +9,9 @@ using Microsoft.OData.Edm; using Squidex.Infrastructure.Queries.OData; using Xunit; +#pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable IDE1006 // Naming Styles + namespace Squidex.Infrastructure.Queries { public class QueryODataConversionTests @@ -65,8 +68,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/dateime")] public void Should_parse_filter_when_type_is_datetime(string field) { - var i = Q($"$filter={field} eq 1988-01-19T12:00:00Z"); - var o = C($"Filter: {field} == 1988-01-19T12:00:00Z"); + var i = _Q($"$filter={field} eq 1988-01-19T12:00:00Z"); + var o = _C($"Filter: {field} == 1988-01-19T12:00:00Z"); Assert.Equal(o, i); } @@ -74,8 +77,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_datetime_list() { - var i = Q("$filter=created in ('1988-01-19T12:00:00Z')"); - var o = C("Filter: created in [1988-01-19T12:00:00Z]"); + var i = _Q("$filter=created in ('1988-01-19T12:00:00Z')"); + var o = _C("Filter: created in [1988-01-19T12:00:00Z]"); Assert.Equal(o, i); } @@ -83,8 +86,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_datetime_and_and_value_is_date() { - var i = Q("$filter=created eq 1988-01-19"); - var o = C("Filter: created == 1988-01-19T00:00:00Z"); + var i = _Q("$filter=created eq 1988-01-19"); + var o = _C("Filter: created == 1988-01-19T00:00:00Z"); Assert.Equal(o, i); } @@ -96,8 +99,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/date")] public void Should_parse_filter_when_type_is_date(string field) { - var i = Q($"$filter={field} eq 1988-01-19"); - var o = C($"Filter: {field} == 1988-01-19T00:00:00Z"); + var i = _Q($"$filter={field} eq 1988-01-19"); + var o = _C($"Filter: {field} == 1988-01-19T00:00:00Z"); Assert.Equal(o, i); } @@ -105,8 +108,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_date_list() { - var i = Q("$filter=birthday in ('1988-01-19')"); - var o = C("Filter: birthday in [1988-01-19T00:00:00Z]"); + var i = _Q("$filter=birthday in ('1988-01-19')"); + var o = _C("Filter: birthday in [1988-01-19T00:00:00Z]"); Assert.Equal(o, i); } @@ -118,8 +121,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/guid")] public void Should_parse_filter_when_type_is_guid(string field) { - var i = Q($"$filter={field} eq B5FE25E3-B262-4B17-91EF-B3772A6B62BB"); - var o = C($"Filter: {field} == b5fe25e3-b262-4b17-91ef-b3772a6b62bb"); + var i = _Q($"$filter={field} eq B5FE25E3-B262-4B17-91EF-B3772A6B62BB"); + var o = _C($"Filter: {field} == b5fe25e3-b262-4b17-91ef-b3772a6b62bb"); Assert.Equal(o, i); } @@ -127,8 +130,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_guid_list() { - var i = Q("$filter=id in ('B5FE25E3-B262-4B17-91EF-B3772A6B62BB')"); - var o = C("Filter: id in [b5fe25e3-b262-4b17-91ef-b3772a6b62bb]"); + var i = _Q("$filter=id in ('B5FE25E3-B262-4B17-91EF-B3772A6B62BB')"); + var o = _C("Filter: id in [b5fe25e3-b262-4b17-91ef-b3772a6b62bb]"); Assert.Equal(o, i); } @@ -136,8 +139,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_null() { - var i = Q("$filter=firstName eq null"); - var o = C("Filter: firstName == null"); + var i = _Q("$filter=firstName eq null"); + var o = _C("Filter: firstName == null"); Assert.Equal(o, i); } @@ -149,8 +152,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/string")] public void Should_parse_filter_when_type_is_string(string field) { - var i = Q($"$filter={field} eq 'Dagobert'"); - var o = C($"Filter: {field} == 'Dagobert'"); + var i = _Q($"$filter={field} eq 'Dagobert'"); + var o = _C($"Filter: {field} == 'Dagobert'"); Assert.Equal(o, i); } @@ -158,8 +161,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_string_list() { - var i = Q("$filter=firstName in ('Dagobert')"); - var o = C("Filter: firstName in ['Dagobert']"); + var i = _Q("$filter=firstName in ('Dagobert')"); + var o = _C("Filter: firstName in ['Dagobert']"); Assert.Equal(o, i); } @@ -171,8 +174,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/boolean")] public void Should_parse_filter_when_type_is_boolean(string field) { - var i = Q($"$filter={field} eq true"); - var o = C($"Filter: {field} == True"); + var i = _Q($"$filter={field} eq true"); + var o = _C($"Filter: {field} == True"); Assert.Equal(o, i); } @@ -180,8 +183,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_boolean_list() { - var i = Q("$filter=isComicFigure in (true)"); - var o = C("Filter: isComicFigure in [True]"); + var i = _Q("$filter=isComicFigure in (true)"); + var o = _C("Filter: isComicFigure in [True]"); Assert.Equal(o, i); } @@ -193,8 +196,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/int")] public void Should_parse_filter_when_type_is_int32(string field) { - var i = Q($"$filter={field} eq 60"); - var o = C($"Filter: {field} == 60"); + var i = _Q($"$filter={field} eq 60"); + var o = _C($"Filter: {field} == 60"); Assert.Equal(o, i); } @@ -202,8 +205,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_int32_list() { - var i = Q("$filter=age in (60)"); - var o = C("Filter: age in [60]"); + var i = _Q("$filter=age in (60)"); + var o = _C("Filter: age in [60]"); Assert.Equal(o, i); } @@ -215,8 +218,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/long")] public void Should_parse_filter_when_type_is_int64(string field) { - var i = Q($"$filter={field} eq 31543143513456789"); - var o = C($"Filter: {field} == 31543143513456789"); + var i = _Q($"$filter={field} eq 31543143513456789"); + var o = _C($"Filter: {field} == 31543143513456789"); Assert.Equal(o, i); } @@ -224,8 +227,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_int64_list() { - var i = Q("$filter=incomeCents in (31543143513456789)"); - var o = C("Filter: incomeCents in [31543143513456789]"); + var i = _Q("$filter=incomeCents in (31543143513456789)"); + var o = _C("Filter: incomeCents in [31543143513456789]"); Assert.Equal(o, i); } @@ -237,8 +240,8 @@ namespace Squidex.Infrastructure.Queries [InlineData("properties/nested/double")] public void Should_parse_filter_when_type_is_double(string field) { - var i = Q($"$filter={field} eq 5634474356.1233"); - var o = C($"Filter: {field} == 5634474356.1233"); + var i = _Q($"$filter={field} eq 5634474356.1233"); + var o = _C($"Filter: {field} == 5634474356.1233"); Assert.Equal(o, i); } @@ -246,8 +249,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_when_type_is_double_list() { - var i = Q("$filter=incomeMio in (5634474356.1233)"); - var o = C("Filter: incomeMio in [5634474356.1233]"); + var i = _Q("$filter=incomeMio in (5634474356.1233)"); + var o = _C("Filter: incomeMio in [5634474356.1233]"); Assert.Equal(o, i); } @@ -255,8 +258,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_negation() { - var i = Q("$filter=not endswith(lastName, 'Duck')"); - var o = C("Filter: !(endsWith(lastName, 'Duck'))"); + var i = _Q("$filter=not endswith(lastName, 'Duck')"); + var o = _C("Filter: !(endsWith(lastName, 'Duck'))"); Assert.Equal(o, i); } @@ -264,8 +267,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_startswith() { - var i = Q("$filter=startswith(lastName, 'Duck')"); - var o = C("Filter: startsWith(lastName, 'Duck')"); + var i = _Q("$filter=startswith(lastName, 'Duck')"); + var o = _C("Filter: startsWith(lastName, 'Duck')"); Assert.Equal(o, i); } @@ -273,8 +276,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_endswith() { - var i = Q("$filter=endswith(lastName, 'Duck')"); - var o = C("Filter: endsWith(lastName, 'Duck')"); + var i = _Q("$filter=endswith(lastName, 'Duck')"); + var o = _C("Filter: endsWith(lastName, 'Duck')"); Assert.Equal(o, i); } @@ -282,8 +285,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_empty() { - var i = Q("$filter=empty(lastName)"); - var o = C("Filter: empty(lastName)"); + var i = _Q("$filter=empty(lastName)"); + var o = _C("Filter: empty(lastName)"); Assert.Equal(o, i); } @@ -291,8 +294,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_empty_to_true() { - var i = Q("$filter=empty(lastName) eq true"); - var o = C("Filter: empty(lastName)"); + var i = _Q("$filter=empty(lastName) eq true"); + var o = _C("Filter: empty(lastName)"); Assert.Equal(o, i); } @@ -300,8 +303,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_contains() { - var i = Q("$filter=contains(lastName, 'Duck')"); - var o = C("Filter: contains(lastName, 'Duck')"); + var i = _Q("$filter=contains(lastName, 'Duck')"); + var o = _C("Filter: contains(lastName, 'Duck')"); Assert.Equal(o, i); } @@ -309,8 +312,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_contains_to_true() { - var i = Q("$filter=contains(lastName, 'Duck') eq true"); - var o = C("Filter: contains(lastName, 'Duck')"); + var i = _Q("$filter=contains(lastName, 'Duck') eq true"); + var o = _C("Filter: contains(lastName, 'Duck')"); Assert.Equal(o, i); } @@ -318,8 +321,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_contains_to_false() { - var i = Q("$filter=contains(lastName, 'Duck') eq false"); - var o = C("Filter: !(contains(lastName, 'Duck'))"); + var i = _Q("$filter=contains(lastName, 'Duck') eq false"); + var o = _C("Filter: !(contains(lastName, 'Duck'))"); Assert.Equal(o, i); } @@ -327,8 +330,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_equals() { - var i = Q("$filter=age eq 1"); - var o = C("Filter: age == 1"); + var i = _Q("$filter=age eq 1"); + var o = _C("Filter: age == 1"); Assert.Equal(o, i); } @@ -336,8 +339,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_notequals() { - var i = Q("$filter=age ne 1"); - var o = C("Filter: age != 1"); + var i = _Q("$filter=age ne 1"); + var o = _C("Filter: age != 1"); Assert.Equal(o, i); } @@ -345,8 +348,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_lessthan() { - var i = Q("$filter=age lt 1"); - var o = C("Filter: age < 1"); + var i = _Q("$filter=age lt 1"); + var o = _C("Filter: age < 1"); Assert.Equal(o, i); } @@ -354,8 +357,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_lessthanorequal() { - var i = Q("$filter=age le 1"); - var o = C("Filter: age <= 1"); + var i = _Q("$filter=age le 1"); + var o = _C("Filter: age <= 1"); Assert.Equal(o, i); } @@ -363,8 +366,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_greaterthan() { - var i = Q("$filter=age gt 1"); - var o = C("Filter: age > 1"); + var i = _Q("$filter=age gt 1"); + var o = _C("Filter: age > 1"); Assert.Equal(o, i); } @@ -372,8 +375,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_greaterthanorequal() { - var i = Q("$filter=age ge 1"); - var o = C("Filter: age >= 1"); + var i = _Q("$filter=age ge 1"); + var o = _C("Filter: age >= 1"); Assert.Equal(o, i); } @@ -381,8 +384,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_conjunction_and_contains() { - var i = Q("$filter=contains(firstName, 'Sebastian') eq false and isComicFigure eq true"); - var o = C("Filter: (!(contains(firstName, 'Sebastian')) && isComicFigure == True)"); + var i = _Q("$filter=contains(firstName, 'Sebastian') eq false and isComicFigure eq true"); + var o = _C("Filter: (!(contains(firstName, 'Sebastian')) && isComicFigure == True)"); Assert.Equal(o, i); } @@ -390,8 +393,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_conjunction() { - var i = Q("$filter=age eq 1 and age eq 2"); - var o = C("Filter: (age == 1 && age == 2)"); + var i = _Q("$filter=age eq 1 and age eq 2"); + var o = _C("Filter: (age == 1 && age == 2)"); Assert.Equal(o, i); } @@ -399,8 +402,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_disjunction() { - var i = Q("$filter=age eq 1 or age eq 2"); - var o = C("Filter: (age == 1 || age == 2)"); + var i = _Q("$filter=age eq 1 or age eq 2"); + var o = _C("Filter: (age == 1 || age == 2)"); Assert.Equal(o, i); } @@ -408,8 +411,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_full_text_numbers() { - var i = Q("$search=\"33k\""); - var o = C("FullText: '33k'"); + var i = _Q("$search=\"33k\""); + var o = _C("FullText: '33k'"); Assert.Equal(o, i); } @@ -417,8 +420,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_full_text() { - var i = Q("$search=Duck"); - var o = C("FullText: 'Duck'"); + var i = _Q("$search=Duck"); + var o = _C("FullText: 'Duck'"); Assert.Equal(o, i); } @@ -426,8 +429,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_with_full_text_and_multiple_terms() { - var i = Q("$search=Dagobert or Donald"); - var o = C("FullText: 'Dagobert or Donald'"); + var i = _Q("$search=Dagobert or Donald"); + var o = _C("FullText: 'Dagobert or Donald'"); Assert.Equal(o, i); } @@ -435,8 +438,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_make_orderby_with_single_field() { - var i = Q("$orderby=age desc"); - var o = C("Sort: age Descending"); + var i = _Q("$orderby=age desc"); + var o = _C("Sort: age Descending"); Assert.Equal(o, i); } @@ -444,8 +447,8 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_make_orderby_with_multiple_field() { - var i = Q("$orderby=age, incomeMio desc"); - var o = C("Sort: age Ascending, incomeMio Descending"); + var i = _Q("$orderby=age, incomeMio desc"); + var o = _C("Sort: age Ascending, incomeMio Descending"); Assert.Equal(o, i); } @@ -453,18 +456,18 @@ namespace Squidex.Infrastructure.Queries [Fact] public void Should_parse_filter_and_take() { - var i = Q("$top=3&$skip=4"); - var o = C("Skip: 4; Take: 3"); + var i = _Q("$top=3&$skip=4"); + var o = _C("Skip: 4; Take: 3"); Assert.Equal(o, i); } - private static string C(string value) + private static string _C(string value) { return value.Replace('/', '.'); } - private static string? Q(string value) + private static string? _Q(string value) { var parser = EdmModel.ParseQuery(value);