From a7e44b2e98916f5bb2ffebd06ead8023b07f33c8 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 24 Nov 2024 21:53:13 +0100 Subject: [PATCH] Permanent deletion (#1144) * Delete permanent. * Permanent deletion. --- .../ContentWrapper/ContentFieldProperty.cs | 2 + .../Contents/MongoContentCollection.cs | 15 ++ .../Contents/MongoContentRepository.cs | 6 + .../Contents/MongoShardedContentRepository.cs | 12 ++ .../Contents/Operations/QueryAsStream.cs | 27 ++++ .../Schemas/MongoSchemaRepository.cs | 6 + .../Schemas/MongoSchemasHash.cs | 4 +- .../Text/MongoShardedTextIndex.cs | 12 +- .../Text/MongoTextIndexBase.cs | 11 +- .../Text/MongoTextIndexerState.cs | 24 ++- .../Apps/AppPermanentDeleter.cs | 41 +++-- .../Apps/AppsOptions.cs | 13 ++ .../Apps/Commands/DeleteApp.cs | 1 + .../Apps/DefaultAppLogStore.cs | 2 +- .../Contents/ContentEventDeleter.cs | 35 +++++ .../Repositories/IContentRepository.cs | 3 + .../Squidex.Domain.Apps.Entities/IDeleter.cs | 7 + .../Rules/Runner/RuleRunnerJob.cs | 35 +++-- .../Schemas/Commands/DeleteSchema.cs | 1 + .../DomainObject/SchemaDomainObject.State.cs | 2 +- .../DomainObject/SchemaDomainObject.cs | 2 +- .../Schemas/SchemaPermanentDeleter.cs | 95 ++++++++++++ .../Schemas/SchemasOptions.cs | 13 ++ .../Apps/AppDeleted.cs | 1 + .../Schemas/SchemaDeleted.cs | 1 + .../MongoDb/MongoExtensions.cs | 1 - .../MongoDb/ProfilerCollection.cs | 2 - .../Commands/CommandRequest.cs | 12 +- .../Api/Controllers/Apps/AppsController.cs | 5 +- .../Controllers/Apps/Models/DeleteAppDto.cs | 28 ++++ .../Contents/ContentsController.cs | 1 - .../Schemas/Models/DeleteSchemaDto.cs | 28 ++++ .../Controllers/Schemas/SchemasController.cs | 5 +- .../Config/Dynamic/DynamicSchemeProvider.cs | 10 +- .../src/Squidex/Config/Domain/AppsServices.cs | 10 +- .../Squidex/Config/Domain/SchemasServices.cs | 9 +- backend/src/Squidex/Startup.cs | 2 +- backend/src/Squidex/appsettings.json | 11 +- .../Model/Apps/AppClientsTests.cs | 2 +- .../Model/Apps/AppContributorsTests.cs | 2 +- .../Model/Apps/RolesTests.cs | 2 +- .../Model/Contents/WorkflowsTests.cs | 2 +- .../Model/Schemas/SchemaTests.cs | 16 +- .../HandleRules/RuleServiceTests.cs | 4 +- .../Operations/Scripting/JintUserTests.cs | 2 +- .../Scripting/ScriptingCompleterTests.cs | 2 +- .../Subscriptions/EventMessageWrapperTests.cs | 4 +- .../ValidateContent/ArrayFieldTests.cs | 12 +- .../ValidateContent/AssetsFieldTests.cs | 10 +- .../ValidateContent/BooleanFieldTests.cs | 4 +- .../ValidateContent/ComponentFieldTests.cs | 12 +- .../ValidateContent/ComponentsFieldTests.cs | 20 +-- .../ValidateContent/DateTimeFieldTests.cs | 10 +- .../ValidateContent/GeolocationFieldTests.cs | 6 +- .../ValidateContent/JsonFieldTests.cs | 2 +- .../ValidateContent/NumberFieldTests.cs | 10 +- .../ValidateContent/ReferencesFieldTests.cs | 19 ++- .../ValidateContent/RichTextFieldTests.cs | 18 +-- .../ValidateContent/StringFieldTests.cs | 22 +-- .../ValidateContent/TagsFieldTests.cs | 16 +- .../ValidateContent/UIFieldTests.cs | 4 +- .../Validators/AllowedValuesValidatorTests.cs | 4 +- .../Validators/AssetsValidatorTests.cs | 31 ++-- .../CollectionItemValidatorTests.cs | 5 +- .../Validators/CollectionValidatorTests.cs | 10 +- .../Validators/NoValueValidatorTests.cs | 4 +- .../Validators/PatternValidatorTests.cs | 6 +- .../Validators/RangeValidatorTests.cs | 6 +- .../Validators/ReferencesValidatorTests.cs | 16 +- .../RequiredStringValidatorTests.cs | 4 +- .../Validators/RequiredValidatorTests.cs | 2 +- .../Validators/StringLengthValidatorTests.cs | 8 +- .../Validators/StringTextValidatorTests.cs | 16 +- .../UniqueObjectValuesValidatorTests.cs | 19 ++- .../Validators/UniqueValidatorTests.cs | 4 +- .../Validators/UniqueValuesValidatorTests.cs | 2 +- .../Apps/AppEventDeleterTests.cs | 8 +- .../Apps/AppPermanentDeleterTests.cs | 62 +++++++- .../Apps/BackupAppsTests.cs | 2 +- .../Apps/DomainObject/AppDomainObjectTests.cs | 17 ++- .../Assets/AssetUsageTrackerTests.cs | 18 +-- .../AssetCommandMiddlewareTests.cs | 2 +- .../Assets/FileTagAssetMetadataSourceTests.cs | 2 +- .../Assets/ImageAssetMetadataSourceTests.cs | 2 +- .../Assets/MongoDb/AssetQueryTests.cs | 1 - .../Assets/Queries/AssetEnricherTests.cs | 4 +- .../Assets/Queries/AssetQueryServiceTests.cs | 4 +- .../Assets/Queries/CalculateTokensTests.cs | 4 +- .../Assets/Queries/ConvertTagsTests.cs | 2 +- .../Queries/EnrichWithMetadataTextTests.cs | 4 +- .../Assets/Queries/ScriptAssetTests.cs | 10 +- .../Contents/ContentEventDeleterTests.cs | 65 ++++++++ .../Contents/ContentsSearchSourceTests.cs | 24 +-- .../Contents/Queries/CalculateTokensTests.cs | 4 +- .../Contents/Queries/ContentEnricherTests.cs | 6 +- .../Contents/Queries/EnrichWithSchemaTests.cs | 6 +- .../Queries/EnrichWithWorkflowsTests.cs | 18 +-- .../Contents/Queries/ScriptContentTests.cs | 12 +- .../Text/MongoTextIndexerStateFixture.cs | 5 +- .../Text/MongoTextIndexerStateTests.cs | 39 ++++- .../Jobs/DefaultJobsServiceTests.cs | 2 +- .../Rules/RuleEnqueuerTests.cs | 6 +- .../DomainObject/SchemaDomainObjectTests.cs | 14 +- .../Schemas/MongoDb/SchemasHashTests.cs | 21 ++- .../Schemas/SchemaPermanentDeleterTests.cs | 143 ++++++++++++++++++ .../Search/SearchManagerTests.cs | 2 +- ...vents_and_update_deleted_flag.verified.txt | 122 +++++++++++++++ ...e_events_with_permanent_flag.verified.txt} | 1 + ...vents_and_update_deleted_flag.verified.txt | 1 + ...te_events_with_permanent_flag.verified.txt | 38 +++++ .../CustomCommandMiddlewareRunnerTests.cs | 15 +- .../Commands/InMemoryCommandBusTests.cs | 6 +- .../Consume/EventConsumerManagerTests.cs | 2 +- .../Consume/EventConsumerWorkerTests.cs | 2 +- .../Json/ClaimsPrincipalConverterTests.cs | 15 +- .../Queries/PropertyPathTests.cs | 4 +- .../States/PersistenceBatchTests.cs | 14 +- .../States/PersistenceEventSourcingTests.cs | 4 +- 118 files changed, 1217 insertions(+), 349 deletions(-) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/AppsOptions.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasOptions.cs create mode 100644 backend/src/Squidex/Areas/Api/Controllers/Apps/Models/DeleteAppDto.cs create mode 100644 backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/DeleteSchemaDto.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt rename backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/{AppDomainObjectTests.DeleteApp_should_create_events_and_update_deleted_flag.verified.txt => AppDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt} (98%) create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs index 43e90a8aca..02ba8eb924 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs @@ -40,7 +40,9 @@ protected override JsValue? CustomValue { this.value = value; +#pragma warning disable MA0143 // Primary constructor parameters should be readonly contentValue = newContentValue; +#pragma warning restore MA0143 // Primary constructor parameters should be readonly contentField.MarkChanged(); isChanged = true; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index 28d2a31dba..4ea3e5dbe6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs @@ -127,6 +127,12 @@ public IAsyncEnumerable QueryScheduledWithoutDataAsync(Instant now, return queryScheduled.QueryAsync(now, ct); } + public IAsyncEnumerable StreamIds(DomainId appId, DomainId schemaId, + CancellationToken ct) + { + return queryAsStream.StreamAllIds(appId, schemaId, ct); + } + public async Task DeleteAppAsync(DomainId appId, CancellationToken ct) { @@ -136,6 +142,15 @@ public async Task DeleteAppAsync(DomainId appId, } } + public async Task DeleteSchemaAsync(DomainId schemaId, + CancellationToken ct) + { + using (Telemetry.Activities.StartActivity("MongoContentCollection/DeleteSchemaAsync")) + { + await Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedSchemaId, schemaId), ct); + } + } + public async Task> QueryAsync(App app, List schemas, Q q, CancellationToken ct) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 2dd3cf7d46..7a956dc8c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -81,6 +81,12 @@ public IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant now, Se return GetCollection(scope).QueryScheduledWithoutDataAsync(now, ct); } + public IAsyncEnumerable StreamIds(DomainId appId, DomainId schemaId, SearchScope scope, + CancellationToken ct = default) + { + return GetCollection(scope).StreamIds(appId, schemaId, ct); + } + public Task> QueryAsync(App app, List schemas, Q q, SearchScope scope, CancellationToken ct = default) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs index a413c81f2d..783aaecf6f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs @@ -103,4 +103,16 @@ public async IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant n } } } + + public async IAsyncEnumerable StreamIds(DomainId appId, DomainId schemaId, SearchScope scope, + [EnumeratorCancellation] CancellationToken ct = default) + { + foreach (var shard in Shards) + { + await foreach (var id in shard.StreamIds(appId, schemaId, scope, ct)) + { + yield return id; + } + } + } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs index 35ce6e1288..8af2f385ba 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Runtime.CompilerServices; +using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; @@ -14,6 +15,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; public sealed class QueryAsStream : OperationBase { + public sealed class IdOnly + { + [BsonElement("id")] + public DomainId Id { get; set; } + } + public async IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, [EnumeratorCancellation] CancellationToken ct) { @@ -31,6 +38,26 @@ public async IAsyncEnumerable StreamAll(DomainId appId, HashSet StreamAllIds(DomainId appId, DomainId schemaId, + [EnumeratorCancellation] CancellationToken ct) + { + var filter = CreateFilter(appId, [schemaId]); + + // Only query the ID from the database to improve performance. + var projection = Builders.Projection.Include(x => x.Id); + + using (var cursor = await Collection.Find(filter).Project(projection).ToCursorAsync(ct)) + { + while (await cursor.MoveNextAsync(ct)) + { + foreach (var entity in cursor.Current) + { + yield return entity.Id; + } + } + } + } + private static FilterDefinition CreateFilter(DomainId appId, HashSet? schemaIds) { var filters = new List> diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs index 4caf914524..e842caa59a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs @@ -39,6 +39,12 @@ Task IDeleter.DeleteAppAsync(App app, return Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedAppId, app.Id), ct); } + Task IDeleter.DeleteSchemaAsync(App app, Schema schema, + CancellationToken ct) + { + return Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedId, schema.Id), ct); + } + public async Task> QueryAllAsync(DomainId appId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoSchemaRepository/QueryAllAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs index ade1fb24bb..b6a052c947 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs @@ -31,10 +31,10 @@ protected override string CollectionName() return "SchemasHash"; } - async Task IDeleter.DeleteAppAsync(App app, + Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { - await Collection.DeleteManyAsync(Filter.Eq(x => x.AppId, app.Id), ct); + return Collection.DeleteManyAsync(Filter.Eq(x => x.AppId, app.Id), ct); } public Task On(IEnumerable> events) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs index ac7eea0762..330aae00ef 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Infrastructure; @@ -46,7 +47,7 @@ public async Task ExecuteAsync(IndexCommand[] commands, return Shard(app.Id).SearchAsync(app, query, scope, ct); } - public async Task DeleteAppAsync(App app, + async Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { if (Shard(app.Id) is IDeleter shard) @@ -54,4 +55,13 @@ public async Task DeleteAppAsync(App app, await shard.DeleteAppAsync(app, ct); } } + + async Task IDeleter.DeleteSchemaAsync(App app, Schema schema, + CancellationToken ct) + { + if (Shard(app.Id) is IDeleter shard) + { + await shard.DeleteSchemaAsync(app, schema, ct); + } + } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs index b015a4d3a4..7ac90caca5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs @@ -9,6 +9,7 @@ using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Infrastructure; @@ -57,10 +58,16 @@ protected override string CollectionName() return $"TextIndex2{shardKey}"; } - async Task IDeleter.DeleteAppAsync(App app, + Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { - await Collection.DeleteManyAsync(Filter.Eq(x => x.AppId, app.Id), ct); + return Collection.DeleteManyAsync(Filter.Eq(x => x.AppId, app.Id), ct); + } + + Task IDeleter.DeleteSchemaAsync(App app, Schema schema, + CancellationToken ct) + { + return Collection.DeleteManyAsync(Filter.Eq(x => x.SchemaId, schema.Id), ct); } public async virtual Task ExecuteAsync(IndexCommand[] commands, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs index 68d4196fd0..bfc93ae81d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs @@ -8,6 +8,9 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Infrastructure; @@ -15,7 +18,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Text; -public sealed class MongoTextIndexerState(IMongoDatabase database) : MongoRepositoryBase(database), ITextIndexerState, IDeleter +public sealed class MongoTextIndexerState( + IMongoDatabase database, + IContentRepository contentRepository) + : MongoRepositoryBase(database), ITextIndexerState, IDeleter { static MongoTextIndexerState() { @@ -30,6 +36,8 @@ static MongoTextIndexerState() }); } + int IDeleter.Order => -2000; + protected override string CollectionName() { return "TextIndexerState"; @@ -46,6 +54,20 @@ async Task IDeleter.DeleteAppAsync(App app, await Collection.DeleteManyAsync(filter, ct); } + async Task IDeleter.DeleteSchemaAsync(App app, Schema schema, + CancellationToken ct) + { + var ids = contentRepository.StreamIds(app.Id, schema.Id, SearchScope.All, ct).Batch(1000, ct); + + await foreach (var batch in ids.WithCancellation(ct)) + { + var filter = + Filter.In(x => x.UniqueContentId, batch.Select(x => new UniqueContentId(app.Id, x))); + + await Collection.DeleteManyAsync(filter, ct); + } + } + public async Task> GetAsync(HashSet ids, CancellationToken ct = default) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs index 48bf28c885..9ddfc3cdf1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure; @@ -14,9 +15,15 @@ namespace Squidex.Domain.Apps.Entities.Apps; -public sealed class AppPermanentDeleter(IEnumerable deleters, IDomainObjectFactory factory, TypeRegistry typeRegistry) : IEventConsumer +public sealed class AppPermanentDeleter( + IEnumerable deleters, + IOptions options, + IDomainObjectFactory factory, + TypeRegistry typeRegistry) + : IEventConsumer { private readonly IEnumerable deleters = deleters.OrderBy(x => x.Order).ToList(); + private readonly AppsOptions options = options.Value; private readonly HashSet consumingTypes = [ typeRegistry.GetName(), @@ -39,8 +46,8 @@ public async Task On(Envelope @event) switch (@event.Payload) { - case AppDeleted appArchived: - await OnArchiveAsync(appArchived); + case AppDeleted appDeleted: + await OnDeleteAsync(appDeleted); break; case AppContributorRemoved appContributorRemoved: await OnAppContributorRemoved(appContributorRemoved); @@ -63,17 +70,18 @@ private async Task OnAppContributorRemoved(AppContributorRemoved appContributorR } } - private async Task OnArchiveAsync(AppDeleted appArchived) + private async Task OnDeleteAsync(AppDeleted appDeleted) { - using var activity = Telemetry.Activities.StartActivity("RemoveAppFromSystem"); - - // Bypass our normal app resolve process, so that we can also retrieve the deleted app. - var app = factory.Create(appArchived.AppId.Id); + // The user can either remove the app itself or via a global setting for all apps. + if (!appDeleted.Permanent && !options.DeletePermanent) + { + return; + } - await app.EnsureLoadedAsync(); + using var activity = Telemetry.Activities.StartActivity("RemoveAppFromSystem"); - // If the app does not exist, the version is lower than zero. - if (app.Version < 0) + var app = await GetAppAsync(appDeleted.AppId.Id); + if (app == null) { return; } @@ -86,4 +94,15 @@ private async Task OnArchiveAsync(AppDeleted appArchived) } } } + + private async Task GetAppAsync(DomainId appId) + { + // Bypass our normal resolve process, so that we can also retrieve the deleted app. + var app = factory.Create(appId); + + await app.EnsureLoadedAsync(); + + // If the app does not exist, the version is lower than zero. + return app.Version < 0 ? null : app; + } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppsOptions.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppsOptions.cs new file mode 100644 index 0000000000..5cdf2ed41b --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppsOptions.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Entities.Apps; + +public sealed class AppsOptions +{ + public bool DeletePermanent { get; set; } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeleteApp.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeleteApp.cs index 03a02402b0..434df342a0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeleteApp.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeleteApp.cs @@ -9,4 +9,5 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands; public sealed class DeleteApp : AppCommand { + public bool Permanent { get; set; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs index 814103071e..98e894741a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs @@ -122,7 +122,7 @@ public async Task ReadLogAsync(DomainId appId, DateTime fromDate, DateTime toDat } finally { - await writer.FlushAsync(); + await writer.FlushAsync(ct); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs new file mode 100644 index 0000000000..d04a401a8d --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities.Contents; + +public sealed class ContentEventDeleter(IContentRepository contentRepository, IEventStore eventStore) : IDeleter +{ + public int Order => -1000; + + public Task DeleteAppAsync(App app, CancellationToken ct) + { + return Task.CompletedTask; + } + + public async Task DeleteSchemAsync(App app, Schema schema, + CancellationToken ct) + { + await foreach (var id in contentRepository.StreamIds(app.Id, schema.Id, SearchScope.All, ct)) + { + var streamFilter = StreamFilter.Prefix($"content-{DomainId.Combine(app.Id, id)}"); + + await eventStore.DeleteAsync(streamFilter, ct); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index 0d4ee612c1..4414b39623 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -17,6 +17,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories; public interface IContentRepository { + IAsyncEnumerable StreamIds(DomainId appId, DomainId schemaId, SearchScope scope, + CancellationToken ct = default); + IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant now, SearchScope scope, CancellationToken ct = default); diff --git a/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs index c712201501..3bb0abc677 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities; @@ -17,6 +18,12 @@ public interface IDeleter Task DeleteAppAsync(App app, CancellationToken ct); + Task DeleteSchemaAsync(App app, Schema schema, + CancellationToken ct) + { + return Task.CompletedTask; + } + Task DeleteContributorAsync(DomainId appId, string contributorId, CancellationToken ct) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs index 1373f7f41c..ccac502e83 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs @@ -17,24 +17,41 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner; -public sealed class RuleRunnerJob( - IAppProvider appProvider, - IEventFormatter eventFormatter, - IEventStore eventStore, - IRuleEventRepository ruleEventRepository, - IRuleService ruleService, - IRuleUsageTracker ruleUsageTracker, - ILogger log) - : IJobRunner +public sealed class RuleRunnerJob : IJobRunner { public const string TaskName = "run-rule"; public const string ArgRuleId = "ruleId"; public const string ArgSnapshot = "snapshots"; private const int MaxErrors = 10; + private readonly IAppProvider appProvider; + private readonly IEventFormatter eventFormatter; + private readonly IEventStore eventStore; + private readonly IRuleEventRepository ruleEventRepository; + private readonly IRuleService ruleService; + private readonly IRuleUsageTracker ruleUsageTracker; + private readonly ILogger log; public string Name => TaskName; + public RuleRunnerJob( + IAppProvider appProvider, + IEventFormatter eventFormatter, + IEventStore eventStore, + IRuleEventRepository ruleEventRepository, + IRuleService ruleService, + IRuleUsageTracker ruleUsageTracker, + ILogger log) + { + this.appProvider = appProvider; + this.eventStore = eventStore; + this.eventFormatter = eventFormatter; + this.ruleEventRepository = ruleEventRepository; + this.ruleService = ruleService; + this.ruleUsageTracker = ruleUsageTracker; + this.log = log; + } + public static DomainId? GetRunningRuleId(Job job) { if (job.TaskName != TaskName || job.Status != JobStatus.Started) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs index b16685b4d7..cd77653fed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs @@ -9,4 +9,5 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands; public sealed class DeleteSchema : SchemaCommand { + public bool Permanent { get; set; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs index 78409c8fe6..39292325b0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs @@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject; -public sealed partial class SchemaDomainObject +public partial class SchemaDomainObject { protected override Schema Apply(Schema snapshot, Envelope @event) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs index 436de849a0..475d82bc35 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject; -public sealed partial class SchemaDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log) : DomainObject(id, persistence, log) +public partial class SchemaDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log) : DomainObject(id, persistence, log) { protected override bool IsDeleted(Schema snapshot) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs new file mode 100644 index 0000000000..988c7797dc --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs @@ -0,0 +1,95 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Options; +using Squidex.Domain.Apps.Entities.Schemas.DomainObject; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Entities.Schemas; + +public sealed class SchemaPermanentDeleter( + IAppProvider appProvider, + IEnumerable deleters, + IOptions options, + IDomainObjectFactory factory, + TypeRegistry typeRegistry) + : IEventConsumer +{ + private readonly IEnumerable deleters = deleters.OrderBy(x => x.Order).ToList(); + private readonly SchemasOptions options = options.Value; + private readonly HashSet consumingTypes = + [ + typeRegistry.GetName(), + ]; + + public StreamFilter EventsFilter { get; } = StreamFilter.Prefix("schema-"); + + public ValueTask HandlesAsync(StoredEvent @event) + { + return new ValueTask(consumingTypes.Contains(@event.Data.Type)); + } + + public async Task On(Envelope @event) + { + if (@event.Headers.Restored()) + { + return; + } + + switch (@event.Payload) + { + case SchemaDeleted schemaDeleted: + await OnDeleteAsync(schemaDeleted); + break; + } + } + + private async Task OnDeleteAsync(SchemaDeleted schemaDeleted) + { + // The user can either remove the app itself or via a global setting for all apps. + if (!schemaDeleted.Permanent && !options.DeletePermanent) + { + return; + } + + using var activity = Telemetry.Activities.StartActivity("RemoveAppFromSystem"); + + var app = await appProvider.GetAppAsync(schemaDeleted.AppId.Id); + if (app == null) + { + return; + } + + var schema = await GetSchemaAsync(app.Id, schemaDeleted.SchemaId.Id); + if (schema == null) + { + return; + } + + foreach (var deleter in deleters) + { + using (Telemetry.Activities.StartActivity(deleter.GetType().Name)) + { + await deleter.DeleteSchemaAsync(app, schema.Snapshot, default); + } + } + } + + private async Task GetSchemaAsync(DomainId appId, DomainId schemaId) + { + // Bypass our normal resolve process, so that we can also retrieve the deleted schema. + var schema = factory.Create(DomainId.Combine(appId, schemaId)); + + await schema.EnsureLoadedAsync(); + // If the app does not exist, the version is lower than zero. + return schema.Version < 0 ? null : schema; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasOptions.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasOptions.cs new file mode 100644 index 0000000000..2042979b30 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasOptions.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Entities.Schemas; + +public sealed class SchemasOptions +{ + public bool DeletePermanent { get; set; } +} diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppDeleted.cs b/backend/src/Squidex.Domain.Apps.Events/Apps/AppDeleted.cs index 58b7c4f76f..7579b372cf 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Apps/AppDeleted.cs +++ b/backend/src/Squidex.Domain.Apps.Events/Apps/AppDeleted.cs @@ -12,4 +12,5 @@ namespace Squidex.Domain.Apps.Events.Apps; [EventType(nameof(AppDeleted))] public sealed class AppDeleted : AppEvent { + public bool Permanent { get; set; } } diff --git a/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaDeleted.cs b/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaDeleted.cs index 59be4592c3..aecd91835b 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaDeleted.cs +++ b/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaDeleted.cs @@ -12,4 +12,5 @@ namespace Squidex.Domain.Apps.Events.Schemas; [EventType(nameof(SchemaDeleted))] public sealed class SchemaDeleted : SchemaEvent { + public bool Permanent { get; set; } } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index 56663770cb..0b964fc968 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -9,7 +9,6 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs index 30a34cd779..839740dbcb 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs @@ -27,8 +27,6 @@ public async Task> GetQueriesAsync(string collec public async Task ClearAsync( CancellationToken ct = default) { - var database = collection.Database; - await database.RunCommandAsync("{ profile : 0 }", cancellationToken: ct); await database.DropCollectionAsync(ProfilerDocument.CollectionName, ct); await database.RunCommandAsync("{ profile : 2 }", cancellationToken: ct); diff --git a/backend/src/Squidex.Infrastructure/Commands/CommandRequest.cs b/backend/src/Squidex.Infrastructure/Commands/CommandRequest.cs index 27bded5eaa..5da8b73a34 100644 --- a/backend/src/Squidex.Infrastructure/Commands/CommandRequest.cs +++ b/backend/src/Squidex.Infrastructure/Commands/CommandRequest.cs @@ -26,18 +26,18 @@ public static CommandRequest Create(IAggregateCommand command) public void ApplyContext() { - var culture = GetCulture(Culture); + var currentCulture = GetCulture(Culture); - if (culture != null) + if (currentCulture != null) { - CultureInfo.CurrentCulture = culture; + CultureInfo.CurrentCulture = currentCulture; } - var uiCulture = GetCulture(CultureUI); + var currentUICulture = GetCulture(CultureUI); - if (uiCulture != null) + if (currentUICulture != null) { - CultureInfo.CurrentUICulture = uiCulture; + CultureInfo.CurrentUICulture = currentUICulture; } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 5af2fcdff4..f0c32978da 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -220,6 +220,7 @@ public async Task DeleteImage(string app) /// Delete the app. /// /// The name of the app to delete. + /// The request parameters. /// App deleted. /// App not found. [HttpDelete] @@ -227,9 +228,9 @@ public async Task DeleteImage(string app) [ProducesResponseType(StatusCodes.Status204NoContent)] [ApiPermission(PermissionIds.AppDelete)] [ApiCosts(0)] - public async Task DeleteApp(string app) + public async Task DeleteApp(string app, DeleteAppDto request) { - var command = new DeleteApp(); + var command = request.ToCommand(); await CommandBus.PublishAsync(command, HttpContext.RequestAborted); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/DeleteAppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/DeleteAppDto.cs new file mode 100644 index 0000000000..6da39eec43 --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/DeleteAppDto.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Mvc; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Infrastructure.Reflection; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Apps.Models; + +[OpenApiRequest] +public sealed class DeleteAppDto +{ + /// + /// True to delete the app permanently. + /// + [FromQuery(Name = "permanent")] + public bool Permanent { get; set; } + + public DeleteApp ToCommand() + { + return SimpleMapper.Map(this, new DeleteApp()); + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index cb317f86e7..af42473a34 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Areas.Api.Config.OpenApi; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/DeleteSchemaDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/DeleteSchemaDto.cs new file mode 100644 index 0000000000..77b83d21a4 --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/DeleteSchemaDto.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Mvc; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Reflection; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models; + +[OpenApiRequest] +public sealed class DeleteSchemaDto +{ + /// + /// True to delete the schema and the contents permanently. + /// + [FromQuery(Name = "permanent")] + public bool Permanent { get; set; } + + public DeleteSchema ToCommand() + { + return SimpleMapper.Map(this, new DeleteSchema()); + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 2510b602ef..9db7689efd 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -285,6 +285,7 @@ public async Task UnpublishSchema(string app, string schema) /// /// The name of the app. /// The name of the schema to delete. + /// The request parameters. /// Schema deleted. /// Schema or app not found. [HttpDelete] @@ -292,9 +293,9 @@ public async Task UnpublishSchema(string app, string schema) [ProducesResponseType(StatusCodes.Status204NoContent)] [ApiPermissionOrAnonymous(PermissionIds.AppSchemasDelete)] [ApiCosts(1)] - public async Task DeleteSchema(string app, string schema) + public async Task DeleteSchema(string app, string schema, DeleteSchemaDto request) { - var command = new DeleteSchema(); + var command = request.ToCommand(); await CommandBus.PublishAsync(command, HttpContext.RequestAborted); diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/Dynamic/DynamicSchemeProvider.cs b/backend/src/Squidex/Areas/IdentityServer/Config/Dynamic/DynamicSchemeProvider.cs index e1a199cfe4..398fc33293 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/Dynamic/DynamicSchemeProvider.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/Dynamic/DynamicSchemeProvider.cs @@ -43,12 +43,12 @@ public async Task AddTemporarySchemeAsync(AuthScheme scheme, var serialized = jsonSerializer.SerializeToBytes(scheme); - var options = new DistributedCacheEntryOptions + var cacheOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) }; - await dynamicCache.SetAsync(CacheKey(id), serialized, options, ct); + await dynamicCache.SetAsync(CacheKey(id), serialized, cacheOptions, ct); return id; } @@ -188,7 +188,7 @@ private SchemeResult CreateScheme(string name, AuthScheme config) { var scheme = new AuthenticationScheme(name, config.DisplayName, typeof(DynamicOpenIdConnectHandler)); - var options = new DynamicOpenIdConnectOptions + var oidcOptions = new DynamicOpenIdConnectOptions { Events = new OidcHandler(new MyIdentityOptions { @@ -204,9 +204,9 @@ private SchemeResult CreateScheme(string name, AuthScheme config) SignedOutRedirectUri = new PathString($"/signout-callback-{name}") }; - configure.PostConfigure(name, options); + configure.PostConfigure(name, oidcOptions); - return new SchemeResult(scheme, options); + return new SchemeResult(scheme, oidcOptions); } public IDisposable? OnChange(Action listener) diff --git a/backend/src/Squidex/Config/Domain/AppsServices.cs b/backend/src/Squidex/Config/Domain/AppsServices.cs index 3d0c40edfa..423ed10e6d 100644 --- a/backend/src/Squidex/Config/Domain/AppsServices.cs +++ b/backend/src/Squidex/Config/Domain/AppsServices.cs @@ -22,15 +22,15 @@ public static class AppsServices { public static void AddSquidexApps(this IServiceCollection services, IConfiguration config) { - if (config.GetValue("apps:deletePermanent")) - { - services.AddSingletonAs() - .As(); - } + services.Configure(config, + "apps"); services.AddSingletonAs() .AsSelf(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/SchemasServices.cs b/backend/src/Squidex/Config/Domain/SchemasServices.cs index 432421dafc..648154c97f 100644 --- a/backend/src/Squidex/Config/Domain/SchemasServices.cs +++ b/backend/src/Squidex/Config/Domain/SchemasServices.cs @@ -9,13 +9,20 @@ using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Search; +using Squidex.Infrastructure.EventSourcing; namespace Squidex.Config.Domain; public static class SchemasServices { - public static void AddSquidexSchemas(this IServiceCollection services) + public static void AddSquidexSchemas(this IServiceCollection services, IConfiguration config) { + services.Configure(config, + "schemas"); + + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/backend/src/Squidex/Startup.cs b/backend/src/Squidex/Startup.cs index 3fd347deda..e7709c876f 100644 --- a/backend/src/Squidex/Startup.cs +++ b/backend/src/Squidex/Startup.cs @@ -55,7 +55,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSquidexOpenApiSettings(); services.AddSquidexQueries(config); services.AddSquidexRules(config); - services.AddSquidexSchemas(); + services.AddSquidexSchemas(config); services.AddSquidexSearch(); services.AddSquidexSerializers(); services.AddSquidexStoreServices(config); diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index 22e038e41a..10f5f86e6b 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -249,6 +249,13 @@ "deletePermanent": false }, + "schemas": { + // True to delete schemas and the content permanently. + // + // This process can take a while and is executed in the background. + "deletePermanent": false + }, + "contents": { // True to enable memory caching. // @@ -701,7 +708,7 @@ "You are a bot to generate images.", "Say hello to the user and explain him the user about your capabilities in a single, short sentence." ], - "tools": ["dall-e"] + "tools": [ "dall-e" ] }, "text": { @@ -710,7 +717,7 @@ "Say hello to the user and explain him about your capabilities in a single, short sentence.", "When you are asked to generate content such as articles, add placeholders for image, describe and use the following pattern: {description}. {description} is the generated image description." ], - "tools": ["none"] + "tools": [ "none" ] } } }, diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs index 3277166078..66c404ff39 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs @@ -94,7 +94,7 @@ public void Should_revoke_client() var clients_2 = clients_1.Add("3", "secret3"); var clients_3 = clients_2.Revoke("2"); - Assert.Equal(new[] { "1", "3" }, clients_3.Keys); + Assert.Equal(["1", "3"], clients_3.Keys); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs index 4592d8bceb..b3aa4036d3 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs @@ -51,7 +51,7 @@ public void Should_remove_contributor() var contributors_3 = contributors_2.Assign("3", Role.Developer); var contributors_4 = contributors_3.Remove("2"); - Assert.Equal(new[] { "1", "3" }, contributors_4.Keys); + Assert.Equal(["1", "3"], contributors_4.Keys); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs index e66b512447..a474b2caff 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs @@ -96,7 +96,7 @@ public void Should_remove_role() var roles_2 = roles_1.Add("role2"); var roles_3 = roles_2.Remove(firstRole); - Assert.Equal(new[] { "role1", "role2" }, roles_3.Custom.Select(x => x.Name)); + Assert.Equal(["role1", "role2"], roles_3.Custom.Select(x => x.Name)); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs index 11da807432..b391d46e83 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs @@ -40,7 +40,7 @@ public void Should_add_new_workflow_with_default_states() var workflows_1 = workflows_0.Add(id, "1"); - Assert.Equal(workflows_1[id].Steps.Keys, new[] { Status.Archived, Status.Draft, Status.Published }); + Assert.Equal(workflows_1[id].Steps.Keys, [Status.Archived, Status.Draft, Status.Published]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index a1710bf3de..c1b5e3044e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -354,8 +354,8 @@ public void Should_set_list_fields() var schema_1 = schema_0.SetFieldsInLists(FieldNames.Create("2")); var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("2")); - Assert.Equal(new[] { "2" }, schema_1.FieldsInLists); - Assert.Equal(new[] { "2" }, schema_2.FieldsInLists); + Assert.Equal(["2"], schema_1.FieldsInLists); + Assert.Equal(["2"], schema_2.FieldsInLists); Assert.Same(schema_1, schema_2); } @@ -365,8 +365,8 @@ public void Should_also_set_list_fields_if_reordered() var schema_1 = schema_0.SetFieldsInLists(FieldNames.Create("2", "1")); var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("1", "2")); - Assert.Equal(new[] { "2", "1" }, schema_1.FieldsInLists); - Assert.Equal(new[] { "1", "2" }, schema_2.FieldsInLists); + Assert.Equal(["2", "1"], schema_1.FieldsInLists); + Assert.Equal(["1", "2"], schema_2.FieldsInLists); Assert.NotSame(schema_1, schema_2); } @@ -382,8 +382,8 @@ public void Should_set_reference_fields() var schema_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2")); var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("2")); - Assert.Equal(new[] { "2" }, schema_1.FieldsInReferences); - Assert.Equal(new[] { "2" }, schema_2.FieldsInReferences); + Assert.Equal(["2"], schema_1.FieldsInReferences); + Assert.Equal(["2"], schema_2.FieldsInReferences); Assert.Same(schema_1, schema_2); } @@ -393,8 +393,8 @@ public void Should_also_set_reference_fields_if_reordered() var schema_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2", "1")); var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("1", "2")); - Assert.Equal(new[] { "2", "1" }, schema_1.FieldsInReferences); - Assert.Equal(new[] { "1", "2" }, schema_2.FieldsInReferences); + Assert.Equal(["2", "1"], schema_1.FieldsInReferences); + Assert.Equal(["1", "2"], schema_2.FieldsInReferences); Assert.NotSame(schema_1, schema_2); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs index b30e08086c..b005110369 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs @@ -81,8 +81,8 @@ public RuleServiceTests() var log = A.Fake>(); sut = new RuleService(Options.Create(new RuleOptions()), - new[] { ruleTriggerHandler }, - new[] { ruleActionHandler }, + [ruleTriggerHandler], + [ruleActionHandler], eventEnricher, TestUtils.DefaultSerializer, log, typeRegistry) { Clock = clock diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs index 123fb7cbfd..930455a17c 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs @@ -111,7 +111,7 @@ private static void AssertUser(ClaimsIdentity identity, string id, bool isClient { var engine = new Engine(); - engine.SetValue("user", JintUser.Create(engine, new ClaimsPrincipal(new[] { identity }))); + engine.SetValue("user", JintUser.Create(engine, new ClaimsPrincipal([identity]))); return engine.Evaluate(script).ToObject(); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs index d305e411b0..024861e25e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs @@ -28,7 +28,7 @@ public ScriptingCompleterTests() dataSchema = schema.BuildDataSchema(LanguagesConfig.English.ToResolver(), ResolvedComponents.Empty); - sut = new ScriptingCompleter(new[] { scriptDescriptor1, scriptDescriptor2 }); + sut = new ScriptingCompleter([scriptDescriptor1, scriptDescriptor2]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/EventMessageWrapperTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/EventMessageWrapperTests.cs index a2480f24d3..cb957172a0 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/EventMessageWrapperTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/EventMessageWrapperTests.cs @@ -37,7 +37,7 @@ public async Task Should_return_event_from_first_creator() A.CallTo(() => creator2.CreateEnrichedEventsAsync(envelope, default)) .Returns(enrichedEvent); - var sut = new EventMessageWrapper(envelope, new[] { creator1, creator2 }); + var sut = new EventMessageWrapper(envelope, [creator1, creator2]); var actual = await sut.CreatePayloadAsync(); @@ -54,7 +54,7 @@ public async Task Should_not_invoke_creator_if_it_does_not_handle_event() A.CallTo(() => creator1.Handles(envelope.Payload)) .Returns(false); - var sut = new EventMessageWrapper(envelope, new[] { creator1 }); + var sut = new EventMessageWrapper(envelope, [creator1]); Assert.Null(await sut.CreatePayloadAsync()); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs index 635db8a48f..6933faf31f 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs @@ -72,7 +72,7 @@ public async Task Should_add_error_if_items_are_required_and_null() await sut.ValidateAsync(CreateValue(null), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -83,7 +83,7 @@ public async Task Should_add_error_if_items_are_required_and_empty() await sut.ValidateAsync(CreateValue(), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -94,7 +94,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("invalid"), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected array of objects." }); + ["Invalid json type, expected array of objects."]); } [Fact] @@ -105,7 +105,7 @@ public async Task Should_add_error_if_value_has_not_enough_items() await sut.ValidateAsync(CreateValue(Object(), Object()), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 3 item(s)." }); + ["Must have at least 3 item(s)."]); } [Fact] @@ -116,7 +116,7 @@ public async Task Should_add_error_if_value_has_too_much_items() await sut.ValidateAsync(CreateValue(Object(), Object()), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -127,7 +127,7 @@ public async Task Should_add_error_if_value_has_duplicates() await sut.ValidateAsync(CreateValue(Object("myString", "1"), Object("myString", "1")), errors); errors.Should().BeEquivalentTo( - new[] { "Must not contain items with duplicate 'myString' fields." }); + ["Must not contain items with duplicate 'myString' fields."]); } private static JsonValue CreateValue(params JsonObject[]? objects) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs index 7c951a7cb8..7fa9bc904d 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs @@ -104,7 +104,7 @@ public async Task Should_add_error_if_assets_are_required_and_null() await sut.ValidateAsync(CreateValue(null), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -115,7 +115,7 @@ public async Task Should_add_error_if_assets_are_required_and_empty() await sut.ValidateAsync(CreateValue(), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -126,7 +126,7 @@ public async Task Should_add_error_if_value_has_not_enough_items() await sut.ValidateAsync(CreateValue(asset1, asset2), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Must have at least 3 item(s)." }); + ["Must have at least 3 item(s)."]); } [Fact] @@ -137,7 +137,7 @@ public async Task Should_add_error_if_value_has_too_much_items() await sut.ValidateAsync(CreateValue(asset1, asset2), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -148,7 +148,7 @@ public async Task Should_add_error_if_values_contains_duplicate() await sut.ValidateAsync(CreateValue(asset1, asset1), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Must not contain duplicate values." }); + ["Must not contain duplicate values."]); } private static JsonValue CreateValue(params DomainId[]? ids) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs index f2654ceae2..14af771478 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs @@ -51,7 +51,7 @@ public async Task Should_add_error_if_boolean_is_required() await sut.ValidateAsync(CreateValue(null), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -62,7 +62,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected boolean." }); + ["Invalid json type, expected boolean."]); } private static JsonValue CreateValue(bool? v) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs index 8a8f0f1679..968df8a9f7 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs @@ -56,7 +56,7 @@ public async Task Should_add_error_if_component_is_required() await sut.ValidateAsync(null, errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -67,7 +67,7 @@ public async Task Should_add_error_if_component_value_is_required() await sut.ValidateAsync(CreateValue(id.ToString(), "componentField", default), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "componentField: Field is required." }); + ["componentField: Field is required."]); } [Fact] @@ -78,7 +78,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid json object, expected object with 'schemaId' field." }); + ["Invalid json object, expected object with 'schemaId' field."]); } [Fact] @@ -89,7 +89,7 @@ public async Task Should_add_error_if_value_has_no_discriminator() await sut.ValidateAsync(CreateValue(null, "field", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid component. No 'schemaId' field found." }); + ["Invalid component. No 'schemaId' field found."]); } [Fact] @@ -100,7 +100,7 @@ public async Task Should_add_error_if_value_has_invalid_discriminator_format() await sut.ValidateAsync(CreateValue("invalid", "field", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid component. Cannot find schema." }); + ["Invalid component. Cannot find schema."]); } [Fact] @@ -111,7 +111,7 @@ public async Task Should_add_error_if_value_has_invalid_discriminator_schema() await sut.ValidateAsync(CreateValue(schemaId1.ToString(), "field", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid component. Cannot find schema." }); + ["Invalid component. Cannot find schema."]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs index 18a26c0fe1..e31f38d448 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs @@ -66,7 +66,7 @@ public async Task Should_add_error_if_components_are_required() await sut.ValidateAsync(null, errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -77,7 +77,7 @@ public async Task Should_add_error_if_components_value_is_required() await sut.ValidateAsync(CreateValue(1, id.ToString(), "componentField", default), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "[1].componentField: Field is required." }); + ["[1].componentField: Field is required."]); } [Fact] @@ -88,7 +88,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected array of objects." }); + ["Invalid json type, expected array of objects."]); } [Fact] @@ -99,7 +99,7 @@ public async Task Should_add_error_if_component_is_not_valid() await sut.ValidateAsync((JsonValue)JsonValue.Array(JsonValue.Create("Invalid")), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid json object, expected object with 'schemaId' field." }); + ["Invalid json object, expected object with 'schemaId' field."]); } [Fact] @@ -110,7 +110,7 @@ public async Task Should_add_error_if_component_has_no_discriminator() await sut.ValidateAsync(CreateValue(1, null, "field", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid component. No 'schemaId' field found." }); + ["Invalid component. No 'schemaId' field found."]); } [Fact] @@ -121,7 +121,7 @@ public async Task Should_add_error_if_value_has_invalid_discriminator_format() await sut.ValidateAsync(CreateValue(1, "invalid", "field", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid component. Cannot find schema." }); + ["Invalid component. Cannot find schema."]); } [Fact] @@ -132,7 +132,7 @@ public async Task Should_add_error_if_value_has_invalid_discriminator_schema() await sut.ValidateAsync(CreateValue(1, schemaId1.ToString(), "field", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Invalid component. Cannot find schema." }); + ["Invalid component. Cannot find schema."]); } [Fact] @@ -143,7 +143,7 @@ public async Task Should_add_error_if_value_has_not_enough_components() await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Must have at least 3 item(s)." }); + ["Must have at least 3 item(s)."]); } [Fact] @@ -154,7 +154,7 @@ public async Task Should_add_error_if_value_has_too_much_components() await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -165,7 +165,7 @@ public async Task Should_add_error_if_value_has_duplicates() await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components); errors.Should().BeEquivalentTo( - new[] { "Must not contain items with duplicate 'componentField' fields." }); + ["Must not contain items with duplicate 'componentField' fields."]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs index 351ba087c0..2a03c262b4 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs @@ -43,7 +43,7 @@ public async Task Should_add_error_if_datetime_is_required() await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -54,7 +54,7 @@ public async Task Should_add_error_if_datetime_is_less_than_min() await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); errors.Should().BeEquivalentTo( - new[] { $"Must be greater or equal to {sut.Properties.MinValue}." }); + [$"Must be greater or equal to {sut.Properties.MinValue}."]); } [Fact] @@ -65,7 +65,7 @@ public async Task Should_add_error_if_datetime_is_greater_than_max() await sut.ValidateAsync(CreateValue(FutureDays(20)), errors); errors.Should().BeEquivalentTo( - new[] { $"Must be less or equal to {FutureDays(10)}." }); + [$"Must be less or equal to {FutureDays(10)}."]); } [Fact] @@ -76,7 +76,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); errors.Should().BeEquivalentTo( - new[] { "The value string does not match the required number from the format string \"uuuu\". Value being parsed: '^Invalid'. (^ indicates error position.)" }); + ["The value string does not match the required number from the format string \"uuuu\". Value being parsed: '^Invalid'. (^ indicates error position.)"]); } [Fact] @@ -87,7 +87,7 @@ public async Task Should_add_error_if_value_is_another_type() await sut.ValidateAsync(JsonValue.Create(123), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected string." }); + ["Invalid json type, expected string."]); } private static Instant FutureDays(int days) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs index f6a749d893..a3dfb6d32a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs @@ -69,7 +69,7 @@ public async Task Should_add_error_if_geolocation_has_invalid_latitude() await sut.ValidateAsync(CreateValue(200, 0), errors); errors.Should().BeEquivalentTo( - new[] { "Latitude must be between -90 and 90." }); + ["Latitude must be between -90 and 90."]); } [Fact] @@ -80,7 +80,7 @@ public async Task Should_add_error_if_geolocation_has_invalid_longitude() await sut.ValidateAsync(CreateValue(0, 200), errors); errors.Should().BeEquivalentTo( - new[] { "Longitude must be between -180 and 180." }); + ["Longitude must be between -180 and 180."]); } [Fact] @@ -91,7 +91,7 @@ public async Task Should_add_error_if_geolocation_is_required() await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } private static JsonValue CreateValue(double lat, double lon) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs index bef20ad8af..211940c8da 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs @@ -41,7 +41,7 @@ public async Task Should_add_error_if_json_is_required() await sut.ValidateAsync(CreateValue(JsonValue.Null), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } private static JsonValue CreateValue(JsonValue v) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs index 04f449a41b..fd8b04b7fa 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs @@ -42,7 +42,7 @@ public async Task Should_add_error_if_number_is_required() await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -53,7 +53,7 @@ public async Task Should_add_error_if_number_is_less_than_min() await sut.ValidateAsync(CreateValue(5), errors); errors.Should().BeEquivalentTo( - new[] { "Must be greater or equal to 10." }); + ["Must be greater or equal to 10."]); } [Fact] @@ -64,7 +64,7 @@ public async Task Should_add_error_if_number_is_greater_than_max() await sut.ValidateAsync(CreateValue(20), errors); errors.Should().BeEquivalentTo( - new[] { "Must be less or equal to 10." }); + ["Must be less or equal to 10."]); } [Fact] @@ -75,7 +75,7 @@ public async Task Should_add_error_if_number_is_not_allowed() await sut.ValidateAsync(CreateValue(20), errors); errors.Should().BeEquivalentTo( - new[] { "Not an allowed value." }); + ["Not an allowed value."]); } [Fact] @@ -86,7 +86,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected number." }); + ["Invalid json type, expected number."]); } private static JsonValue CreateValue(double v) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs index b7beb4123a..be5a177574 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs @@ -23,8 +23,15 @@ public class ReferencesFieldTests : IClassFixture private readonly DomainId ref2 = DomainId.NewGuid(); private readonly IValidatorsFactory factory; - private sealed class CustomFactory(DomainId schemaId) : IValidatorsFactory + private sealed class CustomFactory : IValidatorsFactory { + private readonly DomainId schemaId; + + public CustomFactory(DomainId schemaId) + { + this.schemaId = schemaId; + } + public IEnumerable CreateValueValidators(ValidationContext context, IField field, ValidatorFactory createFieldValidator) { if (field is IField references) @@ -105,7 +112,7 @@ public async Task Should_add_error_if_references_are_required_and_null() await sut.ValidateAsync(CreateValue(null), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -116,7 +123,7 @@ public async Task Should_add_error_if_references_are_required_and_empty() await sut.ValidateAsync(CreateValue(), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -127,7 +134,7 @@ public async Task Should_add_error_if_value_has_not_enough_items() await sut.ValidateAsync(CreateValue(ref1, ref2), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Must have at least 3 item(s)." }); + ["Must have at least 3 item(s)."]); } [Fact] @@ -138,7 +145,7 @@ public async Task Should_add_error_if_value_has_too_much_items() await sut.ValidateAsync(CreateValue(ref1, ref2), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -149,7 +156,7 @@ public async Task Should_add_error_if_reference_contains_duplicate_values() await sut.ValidateAsync(CreateValue(ref1, ref1), errors, factory: factory); errors.Should().BeEquivalentTo( - new[] { "Must not contain duplicate values." }); + ["Must not contain duplicate values."]); } private static JsonValue CreateValue(params DomainId[]? ids) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/RichTextFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/RichTextFieldTests.cs index 7500f4b3ff..3cd210431b 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/RichTextFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/RichTextFieldTests.cs @@ -51,7 +51,7 @@ public async Task Should_not_add_error_if_rich_text_is_invalid() await sut.ValidateAsync(CreateValue(string.Empty, "unknown"), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid rich text." }); + ["Invalid rich text."]); } [Fact] @@ -62,7 +62,7 @@ public async Task Should_add_error_if_string_is_required_but_null() await sut.ValidateAsync(CreateValue(null), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -73,7 +73,7 @@ public async Task Should_add_error_if_string_is_required_but_empty() await sut.ValidateAsync(CreateValue(string.Empty), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -84,7 +84,7 @@ public async Task Should_add_error_if_string_is_shorter_than_min_length() await sut.ValidateAsync(CreateValue("123"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 10 character(s)." }); + ["Must have at least 10 character(s)."]); } [Fact] @@ -95,7 +95,7 @@ public async Task Should_add_error_if_string_is_longer_than_max_length() await sut.ValidateAsync(CreateValue("12345678"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 5 character(s)." }); + ["Must not have more than 5 character(s)."]); } [Fact] @@ -106,7 +106,7 @@ public async Task Should_add_error_if_string_is_shorter_than_min_characters() await sut.ValidateAsync(CreateValue("123"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 10 text character(s)." }); + ["Must have at least 10 text character(s)."]); } [Fact] @@ -117,7 +117,7 @@ public async Task Should_add_error_if_string_is_longer_than_max_characters() await sut.ValidateAsync(CreateValue("12345678"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 5 text character(s)." }); + ["Must not have more than 5 text character(s)."]); } [Fact] @@ -128,7 +128,7 @@ public async Task Should_add_error_if_string_is_shorter_than_min_words() await sut.ValidateAsync(CreateValue("word1 word2 word3"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 10 word(s)." }); + ["Must have at least 10 word(s)."]); } [Fact] @@ -139,7 +139,7 @@ public async Task Should_add_error_if_string_is_longer_than_max_words() await sut.ValidateAsync(CreateValue("word1 word2 word3 word4 word5 word6"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 5 word(s)." }); + ["Must not have more than 5 word(s)."]); } private static JsonValue CreateValue(string? v, string? type = null) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs index 5eabb065f2..d3c8896864 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs @@ -42,7 +42,7 @@ public async Task Should_add_error_if_string_is_required_but_null() await sut.ValidateAsync(CreateValue(null), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -53,7 +53,7 @@ public async Task Should_add_error_if_string_is_required_but_empty() await sut.ValidateAsync(CreateValue(string.Empty), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -64,7 +64,7 @@ public async Task Should_add_error_if_string_is_shorter_than_min_length() await sut.ValidateAsync(CreateValue("123"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 10 character(s)." }); + ["Must have at least 10 character(s)."]); } [Fact] @@ -75,7 +75,7 @@ public async Task Should_add_error_if_string_is_longer_than_max_length() await sut.ValidateAsync(CreateValue("12345678"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 5 character(s)." }); + ["Must not have more than 5 character(s)."]); } [Fact] @@ -86,7 +86,7 @@ public async Task Should_add_error_if_string_is_shorter_than_min_characters() await sut.ValidateAsync(CreateValue("123"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 10 text character(s)." }); + ["Must have at least 10 text character(s)."]); } [Fact] @@ -97,7 +97,7 @@ public async Task Should_add_error_if_string_is_longer_than_max_characters() await sut.ValidateAsync(CreateValue("123456"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 5 text character(s)." }); + ["Must not have more than 5 text character(s)."]); } [Fact] @@ -108,7 +108,7 @@ public async Task Should_add_error_if_string_is_shorter_than_min_words() await sut.ValidateAsync(CreateValue("word1 word2 word3"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 10 word(s)." }); + ["Must have at least 10 word(s)."]); } [Fact] @@ -119,7 +119,7 @@ public async Task Should_add_error_if_string_is_longer_than_max_words() await sut.ValidateAsync(CreateValue("word1 word2 word3 word4 word5 word6"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 5 word(s)." }); + ["Must not have more than 5 word(s)."]); } [Fact] @@ -130,7 +130,7 @@ public async Task Should_add_error_if_string_not_allowed() await sut.ValidateAsync(CreateValue("Bar"), errors); errors.Should().BeEquivalentTo( - new[] { "Not an allowed value." }); + ["Not an allowed value."]); } [Fact] @@ -141,7 +141,7 @@ public async Task Should_add_error_if_number_is_not_valid_pattern() await sut.ValidateAsync(CreateValue("abc"), errors); errors.Should().BeEquivalentTo( - new[] { "Must follow the pattern." }); + ["Must follow the pattern."]); } [Fact] @@ -152,7 +152,7 @@ public async Task Should_add_error_if_number_is_not_valid_pattern_with_message() await sut.ValidateAsync(CreateValue("abc"), errors); errors.Should().BeEquivalentTo( - new[] { "Custom Error Message." }); + ["Custom Error Message."]); } private static JsonValue CreateValue(string? v) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs index c4d160b5d6..46db81a6cb 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs @@ -62,7 +62,7 @@ public async Task Should_add_error_if_tags_are_required_but_null() await sut.ValidateAsync(CreateValue(null), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -73,7 +73,7 @@ public async Task Should_add_error_if_tags_are_required_but_empty() await sut.ValidateAsync(CreateValue(), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -84,7 +84,7 @@ public async Task Should_add_error_if_tag_value_is_null() await sut.ValidateAsync((JsonValue)JsonValue.Array(JsonValue.Null), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected array of strings." }); + ["Invalid json type, expected array of strings."]); } [Fact] @@ -95,7 +95,7 @@ public async Task Should_add_error_if_tag_value_is_empty() await sut.ValidateAsync(CreateValue(string.Empty), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected array of strings." }); + ["Invalid json type, expected array of strings."]); } [Fact] @@ -106,7 +106,7 @@ public async Task Should_add_error_if_value_is_not_valid() await sut.ValidateAsync(JsonValue.Create("invalid"), errors); errors.Should().BeEquivalentTo( - new[] { "Invalid json type, expected array of strings." }); + ["Invalid json type, expected array of strings."]); } [Fact] @@ -117,7 +117,7 @@ public async Task Should_add_error_if_value_has_not_enough_items() await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 3 item(s)." }); + ["Must have at least 3 item(s)."]); } [Fact] @@ -128,7 +128,7 @@ public async Task Should_add_error_if_value_has_too_much_items() await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -139,7 +139,7 @@ public async Task Should_add_error_if_value_contains_an_not_allowed_values() await sut.ValidateAsync(CreateValue("tag-1", "tag-2", null), errors); errors.Should().BeEquivalentTo( - new[] { "[1]: Not an allowed value." }); + ["[1]: Not an allowed value."]); } private static JsonValue CreateValue(params string?[]? ids) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs index 8f3f981bb0..e5a19d3dde 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs @@ -44,7 +44,7 @@ public async Task Should_add_error_if_value_is_json_null() await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( - new[] { "Value must not be defined." }); + ["Value must not be defined."]); } [Fact] @@ -55,7 +55,7 @@ public async Task Should_add_error_if_value_is_valid() await sut.ValidateAsync(JsonValue.True, errors); errors.Should().BeEquivalentTo( - new[] { "Value must not be defined." }); + ["Value must not be defined."]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs index 9ab88b03a9..b44d84cc94 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs @@ -42,6 +42,6 @@ public async Task Should_add_error_if_value_is_not_allowed() await sut.ValidateAsync(50, errors); errors.Should().BeEquivalentTo( - new[] { "Not an allowed value." }); + ["Not an allowed value."]); } -} +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs index 25a72fca84..59c2eb5598 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs @@ -88,7 +88,7 @@ public async Task Should_add_error_if_references_are_required() await sut.ValidateAsync(CreateValue(), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -101,7 +101,7 @@ public async Task Should_add_error_if_asset_are_not_valid() await sut.ValidateAsync(CreateValue(id), errors); errors.Should().BeEquivalentTo( - new[] { $"[1]: Id {id} not found." }); + [$"[1]: Id {id} not found."]); } [Fact] @@ -112,7 +112,7 @@ public async Task Should_add_error_if_document_is_too_small() await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( - new[] { "[1]: Size of 4 kB must be greater than 5 kB." }); + ["[1]: Size of 4 kB must be greater than 5 kB."]); } [Fact] @@ -123,7 +123,7 @@ public async Task Should_add_error_if_document_is_too_big() await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( - new[] { "[2]: Size of 8 kB must be less than 5 kB." }); + ["[2]: Size of 8 kB must be less than 5 kB."]); } [Fact] @@ -134,7 +134,7 @@ public async Task Should_add_error_if_document_is_not_an_image() await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( - new[] { "[1]: Not of expected type: Image." }); + ["[1]: Not of expected type: Image."]); } [Theory] @@ -146,7 +146,7 @@ public async Task Should_add_error_if_asset_width_is_too_small(DomainId videoOrI await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( - new[] { "[2]: Width 800px must be greater than 1000px." }); + ["[2]: Width 800px must be greater than 1000px."]); } [Theory] @@ -158,7 +158,7 @@ public async Task Should_add_error_if_asset_width_is_too_big(DomainId videoOrIma await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( - new[] { "[2]: Width 800px must be less than 700px." }); + ["[2]: Width 800px must be less than 700px."]); } [Theory] @@ -170,7 +170,7 @@ public async Task Should_add_error_if_asset_height_is_too_small(DomainId videoOr await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( - new[] { "[2]: Height 600px must be greater than 800px." }); + ["[2]: Height 600px must be greater than 800px."]); } [Theory] @@ -182,7 +182,7 @@ public async Task Should_add_error_if_asset_height_is_too_big(DomainId videoOrIm await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( - new[] { "[2]: Height 600px must be less than 500px." }); + ["[2]: Height 600px must be less than 500px."]); } [Theory] @@ -194,7 +194,7 @@ public async Task Should_add_error_if_asset_has_invalid_aspect_ratio(DomainId vi await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( - new[] { "[2]: Must have aspect ratio 1:1." }); + ["[2]: Must have aspect ratio 1:1."]); } [Fact] @@ -205,7 +205,7 @@ public async Task Should_add_error_if_value_has_not_enough_items() await sut.ValidateAsync(CreateValue(Image1.Id), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2 item(s)." }); + ["Must have at least 2 item(s)."]); } [Fact] @@ -216,7 +216,7 @@ public async Task Should_add_error_if_value_has_too_much_items() await sut.ValidateAsync(CreateValue(Image1.Id, Image2.Id), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -227,7 +227,7 @@ public async Task Should_add_error_if_reference_contains_duplicate_values() await sut.ValidateAsync(CreateValue(Image1.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( - new[] { "Must not contain duplicate values." }); + ["Must not contain duplicate values."]); } [Fact] @@ -238,11 +238,10 @@ public async Task Should_add_error_if_image_has_invalid_extension() await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( - new[] - { + [ "[1]: Must be an allowed extension.", "[2]: Must be an allowed extension." - }); + ]); } private static object CreateValue(params DomainId[] ids) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs index 988643ed17..e93e3dea4c 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs @@ -42,10 +42,9 @@ public async Task Should_add_error_if_at_least_one_item_is_not_valid() await sut.ValidateAsync(new List { 2, 1, 4, 5 }, errors); errors.Should().BeEquivalentTo( - new[] - { + [ "[2]: Must be between 2 and 4.", "[4]: Must be between 2 and 4." - }); + ]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs index a2dd93b11a..6d9c1a980a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs @@ -49,7 +49,7 @@ public async Task Should_add_error_if_value_is_null() await sut.ValidateAsync(null, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -60,7 +60,7 @@ public async Task Should_add_error_if_collection_has_not_exact_number_of_items() await sut.ValidateAsync(new List { 1 }, errors); errors.Should().BeEquivalentTo( - new[] { "Must have exactly 2 item(s)." }); + ["Must have exactly 2 item(s)."]); } [Fact] @@ -71,7 +71,7 @@ public async Task Should_add_error_if_collection_has_too_few_items() await sut.ValidateAsync(new List { 1 }, errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2 item(s)." }); + ["Must have at least 2 item(s)."]); } [Fact] @@ -82,7 +82,7 @@ public async Task Should_add_error_if_collection_has_too_many_items() await sut.ValidateAsync(new List { 1, 2, 3, 4 }, errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 3 item(s)." }); + ["Must not have more than 3 item(s)."]); } [Fact] @@ -93,6 +93,6 @@ public async Task Should_add_error_if_collection_count_is_not_in_range() await sut.ValidateAsync(new List { 1 }, errors); errors.Should().BeEquivalentTo( - new[] { "Must have between 2 and 5 item(s)." }); + ["Must have between 2 and 5 item(s)."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs index 2020798cff..8ebf32778c 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs @@ -34,7 +34,7 @@ public async Task Should_add_error_if_value_is_json_null() await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( - new[] { "Value must not be defined." }); + ["Value must not be defined."]); } [Fact] @@ -45,6 +45,6 @@ public async Task Should_add_error_if_value_is_valid() await sut.ValidateAsync(JsonValue.True, errors); errors.Should().BeEquivalentTo( - new[] { "Value must not be defined." }); + ["Value must not be defined."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs index 6c2ce96849..bbae0f4aec 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs @@ -52,7 +52,7 @@ public async Task Should_add_error_with_default_message_if_value_is_not_valid() await sut.ValidateAsync("foo", errors); errors.Should().BeEquivalentTo( - new[] { "Must follow the pattern." }); + ["Must follow the pattern."]); } [Fact] @@ -63,7 +63,7 @@ public async Task Should_add_error_with_custom_message_if_value_is_not_valid() await sut.ValidateAsync("foo", errors); errors.Should().BeEquivalentTo( - new[] { "Custom Error Message." }); + ["Custom Error Message."]); } [Fact] @@ -74,6 +74,6 @@ public async Task Should_add_timeout_error_if_regex_is_too_slow() await sut.ValidateAsync("https://archiverbx.blob.core.windows.net/static/C:/Users/USR/Documents/Projects/PROJ/static/images/full/1234567890.jpg", errors); errors.Should().BeEquivalentTo( - new[] { "Regex is too slow." }); + ["Regex is too slow."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs index c28c142bf2..934f314437 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs @@ -53,7 +53,7 @@ public async Task Should_add_error_if_value_is_not_equal_to_min_and_max() await sut.ValidateAsync(1500, errors); errors.Should().BeEquivalentTo( - new[] { "Must be exactly 2000." }); + ["Must be exactly 2000."]); } [Fact] @@ -64,7 +64,7 @@ public async Task Should_add_error_if_value_is_smaller_than_min() await sut.ValidateAsync(1500, errors); errors.Should().BeEquivalentTo( - new[] { "Must be greater or equal to 2000." }); + ["Must be greater or equal to 2000."]); } [Fact] @@ -75,6 +75,6 @@ public async Task Should_add_error_if_value_is_greater_than_max() await sut.ValidateAsync(1500, errors); errors.Should().BeEquivalentTo( - new[] { "Must be less or equal to 1000." }); + ["Must be less or equal to 1000."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs index d4008e7f29..2f8fd90007 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs @@ -104,7 +104,7 @@ public async Task Should_add_error_if_references_are_required() await sut.ValidateAsync(CreateValue(), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -117,7 +117,7 @@ public async Task Should_add_error_if_references_are_published_required() await sut.ValidateAsync(CreateValue(), errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -130,7 +130,7 @@ public async Task Should_add_error_if_references_are_not_valid() await sut.ValidateAsync(CreateValue(ref1), errors); errors.Should().BeEquivalentTo( - new[] { $"[1]: Reference '{ref1}' not found." }); + [$"[1]: Reference '{ref1}' not found."]); } [Fact] @@ -143,7 +143,7 @@ public async Task Should_add_error_if_reference_schema_is_not_valid() await sut.ValidateAsync(CreateValue(ref2), errors); errors.Should().BeEquivalentTo( - new[] { $"[1]: Reference '{ref2}' has invalid schema." }); + [$"[1]: Reference '{ref2}' has invalid schema."]); } [Fact] @@ -156,7 +156,7 @@ public async Task Should_add_error_if_value_has_not_enough_items() await sut.ValidateAsync(CreateValue(ref2), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2 item(s)." }); + ["Must have at least 2 item(s)."]); } [Fact] @@ -169,7 +169,7 @@ public async Task Should_add_error_if_value_has_not_enough_published_items() await sut.ValidateAsync(CreateValue(ref1, ref2), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2 item(s)." }); + ["Must have at least 2 item(s)."]); } [Fact] @@ -182,7 +182,7 @@ public async Task Should_add_error_if_value_has_too_much_items() await sut.ValidateAsync(CreateValue(ref1, ref2), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1 item(s)." }); + ["Must not have more than 1 item(s)."]); } [Fact] @@ -195,7 +195,7 @@ public async Task Should_add_error_if_reference_contains_duplicate_values() await sut.ValidateAsync(CreateValue(ref1, ref1), errors); errors.Should().BeEquivalentTo( - new[] { "Must not contain duplicate values." }); + ["Must not contain duplicate values."]); } private static List CreateValue(params DomainId[] ids) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs index 8be7d32898..047b1c1442 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs @@ -56,7 +56,7 @@ public async Task Should_add_error_if_empty_strings_are_not_allowed() await sut.ValidateAsync(string.Empty, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } [Fact] @@ -67,6 +67,6 @@ public async Task Should_add_error_if_value_is_null() await sut.ValidateAsync(null, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs index 4afcb1332a..8efaa7d8c2 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs @@ -52,6 +52,6 @@ public async Task Should_add_error_if_value_is_null() await sut.ValidateAsync(null, errors); errors.Should().BeEquivalentTo( - new[] { "Field is required." }); + ["Field is required."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs index 170e0e489a..9e8f9d99aa 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs @@ -64,7 +64,7 @@ public async Task Should_add_error_if_value_has_not_exact_number_of_characters() await sut.ValidateAsync(CreateString(4), errors); errors.Should().BeEquivalentTo( - new[] { "Must have exactly 2000 character(s)." }); + ["Must have exactly 2000 character(s)."]); } [Fact] @@ -75,7 +75,7 @@ public async Task Should_add_error_if_value_is_smaller_than_min() await sut.ValidateAsync(CreateString(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2000 character(s)." }); + ["Must have at least 2000 character(s)."]); } [Fact] @@ -86,7 +86,7 @@ public async Task Should_add_error_if_value_is_greater_than_max() await sut.ValidateAsync(CreateString(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1000 character(s)." }); + ["Must not have more than 1000 character(s)."]); } [Fact] @@ -97,7 +97,7 @@ public async Task Should_add_error_if_collection_count_is_not_in_range() await sut.ValidateAsync(CreateString(1), errors); errors.Should().BeEquivalentTo( - new[] { "Must have between 2000 and 5000 character(s)." }); + ["Must have between 2000 and 5000 character(s)."]); } private static string CreateString(int size) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringTextValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringTextValidatorTests.cs index 9a1f92b0b2..77c2335999 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringTextValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringTextValidatorTests.cs @@ -71,7 +71,7 @@ public async Task Should_add_error_if_value_has_not_exact_number_of_characters() await sut.ValidateAsync(CreateString(4), errors); errors.Should().BeEquivalentTo( - new[] { "Must have exactly 5 text character(s)." }); + ["Must have exactly 5 text character(s)."]); } [Fact] @@ -82,7 +82,7 @@ public async Task Should_add_error_if_value_is_smaller_than_min_characters() await sut.ValidateAsync(CreateString(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2000 text character(s)." }); + ["Must have at least 2000 text character(s)."]); } [Fact] @@ -93,7 +93,7 @@ public async Task Should_add_error_if_value_is_greater_than_max_characters() await sut.ValidateAsync(CreateString(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1000 text character(s)." }); + ["Must not have more than 1000 text character(s)."]); } [Fact] @@ -104,7 +104,7 @@ public async Task Should_add_error_if_collection_count_is_not_in_character_range await sut.ValidateAsync(CreateString(1), errors); errors.Should().BeEquivalentTo( - new[] { "Must have between 2000 and 5000 text character(s)." }); + ["Must have between 2000 and 5000 text character(s)."]); } [Theory] @@ -129,7 +129,7 @@ public async Task Should_add_error_if_value_has_not_exact_number_of_words() await sut.ValidateAsync(CreateSentence(4), errors); errors.Should().BeEquivalentTo( - new[] { "Must have exactly 5 word(s)." }); + ["Must have exactly 5 word(s)."]); } [Fact] @@ -140,7 +140,7 @@ public async Task Should_add_error_if_value_is_smaller_than_min_words() await sut.ValidateAsync(CreateSentence(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must have at least 2000 word(s)." }); + ["Must have at least 2000 word(s)."]); } [Fact] @@ -151,7 +151,7 @@ public async Task Should_add_error_if_value_is_greater_than_max_words() await sut.ValidateAsync(CreateSentence(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must not have more than 1000 word(s)." }); + ["Must not have more than 1000 word(s)."]); } [Fact] @@ -162,7 +162,7 @@ public async Task Should_add_error_if_collection_count_is_not_in_word_range() await sut.ValidateAsync(CreateSentence(1), errors); errors.Should().BeEquivalentTo( - new[] { "Must have between 2000 and 5000 word(s)." }); + ["Must have between 2000 and 5000 word(s)."]); } private static string CreateString(int size) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueObjectValuesValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueObjectValuesValidatorTests.cs index db3ce344e0..d545b23e98 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueObjectValuesValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueObjectValuesValidatorTests.cs @@ -18,7 +18,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture c.Nested("property").Nested("iv")); errors.Should().BeEquivalentTo( - new[] { "property.iv: Another content with the same value exists." }); + ["property.iv: Another content with the same value exists."]); Assert.Equal("Data.property.iv == 'hello'", filter); } @@ -88,7 +88,7 @@ public async Task Should_add_error_if_other_content_with_double_value_found() await sut.ValidateAsync(12.5, errors, updater: c => c.Nested("property").Nested("iv")); errors.Should().BeEquivalentTo( - new[] { "property.iv: Another content with the same value exists." }); + ["property.iv: Another content with the same value exists."]); Assert.Equal("Data.property.iv == 12.5", filter); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs index 518389e824..57ac30e04f 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs @@ -52,6 +52,6 @@ public async Task Should_add_error_if_array_contains_duplicates() await sut.ValidateAsync(new[] { 1, 2, 2, 3 }, errors); errors.Should().BeEquivalentTo( - new[] { "Must not contain duplicate values." }); + ["Must not contain duplicate values."]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs index 850f21228f..92117dd2e8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs @@ -33,9 +33,9 @@ public async Task Should_remove_events_from_streams() { await sut.DeleteAppAsync(App, CancellationToken); - var streamFilter = StreamFilter.Prefix($"[a-zA-Z0-9]-{AppId.Id}"); - - A.CallTo(() => eventStore.DeleteAsync(streamFilter, A._)) - .MustNotHaveHappened(); + A.CallTo(() => eventStore.DeleteAsync( + A.That.Matches(x => x.Prefixes!.Contains($"([a-zA-Z0-9]+)-{AppId.Id}")), + CancellationToken)) + .MustHaveHappened(); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs index 7ac3a76413..3952db043e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -19,11 +20,12 @@ public class AppPermanentDeleterTests : GivenContext private readonly IDomainObjectFactory domainObjectFactory = A.Fake(); private readonly IDeleter deleter1 = A.Fake(); private readonly IDeleter deleter2 = A.Fake(); + private readonly AppsOptions options = new AppsOptions(); private readonly AppPermanentDeleter sut; public AppPermanentDeleterTests() { - sut = new AppPermanentDeleter(new[] { deleter1, deleter2 }, domainObjectFactory, TestUtils.TypeRegistry); + sut = new AppPermanentDeleter([deleter1, deleter2], Options.Create(options), domainObjectFactory, TestUtils.TypeRegistry); } [Fact] @@ -97,19 +99,34 @@ await sut.On(Envelope.Create(new AppContributorRemoved } [Fact] - public async Task Should_call_deleters_when_app_deleted() + public async Task Should_call_deleters_when_app_deleted_and_enabled_globally() { - var domainObject = A.Fake(); + options.DeletePermanent = true; + SetupDomainObject(); - A.CallTo(() => domainObject.Snapshot) - .Returns(App); + await sut.On(Envelope.Create(new AppDeleted + { + AppId = AppId, + Permanent = false + })); - A.CallTo(() => domainObjectFactory.Create(App.Id)) - .Returns(domainObject); + A.CallTo(() => deleter1.DeleteAppAsync(App, default)) + .MustHaveHappened(); + + A.CallTo(() => deleter2.DeleteAppAsync(App, default)) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_call_deleters_when_app_deleted_and_enabled_per_event() + { + options.DeletePermanent = false; + SetupDomainObject(); await sut.On(Envelope.Create(new AppDeleted { - AppId = AppId + AppId = AppId, + Permanent = true })); A.CallTo(() => deleter1.DeleteAppAsync(App, default)) @@ -118,4 +135,33 @@ await sut.On(Envelope.Create(new AppDeleted A.CallTo(() => deleter2.DeleteAppAsync(App, default)) .MustHaveHappened(); } + + [Fact] + public async Task Should_not_call_deleters_when_app_not_deleted_permanently_and_not_enabled_globally() + { + options.DeletePermanent = false; + + await sut.On(Envelope.Create(new AppDeleted + { + AppId = AppId, + Permanent = false + })); + + A.CallTo(() => deleter1.DeleteAppAsync(App, default)) + .MustNotHaveHappened(); + + A.CallTo(() => deleter2.DeleteAppAsync(App, default)) + .MustNotHaveHappened(); + } + + private void SetupDomainObject() + { + var domainObject = A.Fake(); + + A.CallTo(() => domainObject.Snapshot) + .Returns(App); + + A.CallTo(() => domainObjectFactory.Create(App.Id)) + .Returns(domainObject); + } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs index 5170ee4fb4..4257259fa8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs @@ -333,7 +333,7 @@ private IUserMapping CreateUserMapping() .Returns(true) .AssignsOutAndRefParametersLazily( new Func((x, _) => - new[] { RefToken.User($"{x}_mapped") })); + [RefToken.User($"{x}_mapped")])); A.CallTo(() => mapping.TryMap("notfound", out mapped)) .Returns(false); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs index fec0cc6a6e..c214dfdfa6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs @@ -502,7 +502,7 @@ public async Task UpdateRole_should_create_events_and_update_role() } [Fact] - public async Task DeleteApp_should_create_events_and_update_deleted_flag() + public async Task Delete_should_create_events_and_update_deleted_flag() { var command = new DeleteApp(); @@ -516,6 +516,21 @@ public async Task DeleteApp_should_create_events_and_update_deleted_flag() .MustHaveHappened(); } + [Fact] + public async Task Delete_should_create_events_with_permanent_flag() + { + var command = new DeleteApp { Permanent = true }; + + await ExecuteCreateAsync(); + + var actual = await PublishAsync(sut, command); + + await VerifySutAsync(actual, None.Value); + + A.CallTo(() => billingManager.UnsubscribeAsync(command.Actor.Identifier, A._, default)) + .MustHaveHappened(); + } + private Task ExecuteCreateAsync() { return PublishAsync(sut, new CreateApp { Name = AppId.Name, AppId = AppId.Id }); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs index 6b35e68fab..d5504986a6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs @@ -75,7 +75,7 @@ public async Task Should_increase_usage_if_for_event(AssetEvent @event, long siz Envelope.Create(@event) .SetTimestamp(Instant.FromDateTimeUtc(DateTime.UtcNow.Date.AddDays(13))); - await sut.On(new[] { envelope }); + await sut.On([envelope]); A.CallTo(() => assetUsageTracker.TrackAsync(AppId.Id, date, sizeDiff, countDiff, default)) .MustHaveHappened(); @@ -104,7 +104,7 @@ public async Task Should_write_tags_when_asset_created() A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { envelope }); + await sut.On([envelope]); update.Should().BeEquivalentTo(new Dictionary { @@ -151,7 +151,7 @@ public async Task Should_group_tags_by_app() A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { envelope1, envelope2 }); + await sut.On([envelope1, envelope2]); update.Should().BeEquivalentTo(new Dictionary { @@ -202,7 +202,7 @@ public async Task Should_merge_tags_with_previous_event_on_annotate() A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { envelope1, envelope2 }); + await sut.On([envelope1, envelope2]); update.Should().BeEquivalentTo(new Dictionary { @@ -250,8 +250,8 @@ public async Task Should_merge_tags_with_previous_event_on_annotate_from_other_b A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { envelope1 }); - await sut.On(new[] { envelope2 }); + await sut.On([envelope1]); + await sut.On([envelope2]); update.Should().BeEquivalentTo(new Dictionary { @@ -290,7 +290,7 @@ public async Task Should_merge_tags_with_previous_event_on_delete() A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { Envelope.Create(@event1), Envelope.Create(@event2) }); + await sut.On([Envelope.Create(@event1), Envelope.Create(@event2)]); update.Should().BeEquivalentTo(new Dictionary { @@ -325,7 +325,7 @@ public async Task Should_merge_tags_with_stored_state_if_previous_event_not_in_c A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { envelope }); + await sut.On([envelope]); update.Should().BeEquivalentTo(new Dictionary { @@ -357,7 +357,7 @@ public async Task Should_merge_tags_with_asset_if_previous_tags_not_in_store() A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A>._, default)) .Invokes(x => { update = x.GetArgument>(2); }); - await sut.On(new[] { envelope }); + await sut.On([envelope]); update.Should().BeEquivalentTo(new Dictionary { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs index 3015bb94fe..c8475815cb 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs @@ -47,7 +47,7 @@ public AssetCommandMiddlewareTests() assetEnricher, assetFileStore, assetQuery, - ApiContextProvider, new[] { assetMetadataSource }); + ApiContextProvider, [assetMetadataSource]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs index 0a702c3c03..34e2b7b004 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs @@ -99,7 +99,7 @@ public void Should_format_video() var formatted = sut.Format(source); - Assert.Equal(new[] { "128x55pt", "00:10:12" }, formatted); + Assert.Equal(["128x55pt", "00:10:12"], formatted); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs index 8ce3607023..e201680371 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs @@ -167,7 +167,7 @@ public void Should_format_image() var formatted = sut.Format(source); - Assert.Equal(new[] { "128x55px" }, formatted); + Assert.Equal(["128x55px"], formatted); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs index 258ce42318..2409fb732b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs @@ -15,7 +15,6 @@ using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Validation; -using static Squidex.Infrastructure.MongoDb.MongoQueryTests; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs index 0af565d65f..112e86260b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs @@ -19,7 +19,7 @@ public async Task Should_only_invoke_pre_enrich_for_empty_assets() var step1 = A.Fake(); var step2 = A.Fake(); - var sut = new AssetEnricher(new[] { step1, step2 }); + var sut = new AssetEnricher([step1, step2]); await sut.EnrichAsync(assets, ApiContext, CancellationToken); @@ -44,7 +44,7 @@ public async Task Should_invoke_steps() var step1 = A.Fake(); var step2 = A.Fake(); - var sut = new AssetEnricher(new[] { step1, step2 }); + var sut = new AssetEnricher([step1, step2]); await sut.EnrichAsync(source, ApiContext, CancellationToken); 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 aaa38acf0d..3633ceb108 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 @@ -217,7 +217,7 @@ public async Task Should_find_asset_folder_with_path() var actual = await sut.FindAssetFolderAsync(AppId.Id, folder1.Id, CancellationToken); - Assert.Equal(actual, new[] { folder1 }); + Assert.Equal(actual, [folder1]); } [Fact] @@ -238,7 +238,7 @@ public async Task Should_resolve_folder_path_from_child() var actual = await sut.FindAssetFolderAsync(AppId.Id, folder3.Id, CancellationToken); - Assert.Equal(actual, new[] { folder1, folder2, folder3 }); + Assert.Equal(actual, [folder1, folder2, folder3]); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs index 8003bb80a7..d950bfe6c1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs @@ -38,7 +38,7 @@ public async Task Should_compute_ui_tokens() { var asset = CreateAsset(); - await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); + await sut.EnrichAsync(ApiContext, [asset], CancellationToken); Assert.NotNull(asset.EditToken); @@ -51,7 +51,7 @@ public async Task Should_also_compute_ui_tokens_for_frontend() { var asset = CreateAsset(); - await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken); + await sut.EnrichAsync(FrontendContext, [asset], CancellationToken); Assert.NotNull(asset.EditToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs index 4ab2997a43..f5f2969d1a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs @@ -84,7 +84,7 @@ public async Task Should_enrich_multiple_assets_with_tag_names() ["id3"] = "name3" }); - await sut.EnrichAsync(ApiContext, new[] { asset1, asset2 }, CancellationToken); + await sut.EnrichAsync(ApiContext, [asset1, asset2], CancellationToken); Assert.Equal(["name1", "name2"], asset1.TagNames); Assert.Equal(["name2", "name3"], asset2.TagNames); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs index a966bb8f30..ffdf245943 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs @@ -56,10 +56,10 @@ public async Task Should_enrich_asset_with_metadata() }; A.CallTo(() => assetMetadataSource1.Format(asset)) - .Returns(new[] { "metadata1" }); + .Returns(["metadata1"]); A.CallTo(() => assetMetadataSource2.Format(asset)) - .Returns(new[] { "metadata2", "metadata3" }); + .Returns(["metadata2", "metadata3"]); await sut.EnrichAsync(FrontendContext, Enumerable.Repeat(asset, 1), CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs index 4e056f4ffd..185485db00 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs @@ -28,7 +28,7 @@ public async Task Should_not_call_script_engine_if_no_script_configured() { var asset = CreateAsset(); - await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); + await sut.EnrichAsync(ApiContext, [asset], CancellationToken); A.CallTo(() => scriptEngine.ExecuteAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -41,7 +41,7 @@ public async Task Should_not_call_script_engine_for_frontend_user() var asset = CreateAsset(); - await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken); + await sut.EnrichAsync(FrontendContext, [asset], CancellationToken); A.CallTo(() => scriptEngine.ExecuteAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -54,7 +54,7 @@ public async Task Should_not_call_script_engine_if_disabled_and_user_has_permiss var asset = CreateAsset(); - await sut.EnrichAsync(ContextWithNoScript(), new[] { asset }, CancellationToken); + await sut.EnrichAsync(ContextWithNoScript(), [asset], CancellationToken); A.CallTo(() => scriptEngine.ExecuteAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -67,7 +67,7 @@ public async Task Should_call_script_engine() var asset = CreateAsset(); - await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); + await sut.EnrichAsync(ApiContext, [asset], CancellationToken); A.CallTo(() => scriptEngine.ExecuteAsync( A.That.Matches(x => @@ -88,7 +88,7 @@ public async Task Should_call_script_engine_with_pre_query_script() var asset = CreateAsset(); - await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); + await sut.EnrichAsync(ApiContext, [asset], CancellationToken); A.CallTo(() => scriptEngine.ExecuteAsync( A.That.Matches(x => diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs new file mode 100644 index 0000000000..fa160e3e88 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities.Contents; + +public class ContentEventDeleterTests : GivenContext +{ + private readonly IContentRepository contentRepository = A.Fake(); + private readonly IEventStore eventStore = A.Fake(); + private readonly ContentEventDeleter sut; + + public ContentEventDeleterTests() + { + sut = new ContentEventDeleter(contentRepository, eventStore); + } + + [Fact] + public void Should_run_at_beginning() + { + var order = sut.Order; + + Assert.Equal(-1000, order); + } + + [Fact] + public async Task Should_do_nothing_when_app_deleted() + { + await sut.DeleteAppAsync(App, CancellationToken); + + A.CallTo(eventStore) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_remove_events_from_streams() + { + var id1 = DomainId.NewGuid(); + var id2 = DomainId.NewGuid(); + var ids = new[] { id1, id2 }; + + A.CallTo(() => contentRepository.StreamIds(App.Id, Schema.Id, SearchScope.All, CancellationToken)) + .Returns(ids.ToAsyncEnumerable()); + + await sut.DeleteSchemAsync(App, Schema, CancellationToken); + + A.CallTo(() => eventStore.DeleteAsync( + A.That.Matches(x => x.Prefixes!.Contains($"content-{AppId.Id}--{id1}")), + CancellationToken)) + .MustHaveHappened(); + + A.CallTo(() => eventStore.DeleteAsync( + A.That.Matches(x => x.Prefixes!.Contains($"content-{AppId.Id}--{id2}")), + CancellationToken)) + .MustHaveHappened(); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs index 9972c9963b..df1c05bdab 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs @@ -63,11 +63,11 @@ public async Task Should_return_content_with_multiple_invariant_reference_fields .AddField("field2", new ContentFieldData() .AddInvariant("world")), - ReferenceFields = new[] - { + ReferenceFields = + [ Fields.String(1, "field1", Partitioning.Invariant), Fields.String(2, "field2", Partitioning.Invariant) - }, + ], SchemaId = schemaId1 }; @@ -84,10 +84,10 @@ public async Task Should_return_content_with_invariant_reference_field() .AddField("field", new ContentFieldData() .AddInvariant("hello")), - ReferenceFields = new[] - { + ReferenceFields = + [ Fields.String(1, "field", Partitioning.Invariant) - }, + ], SchemaId = schemaId1 }; @@ -105,10 +105,10 @@ public async Task Should_return_content_with_localized_reference_field() .AddField("field", new ContentFieldData() .AddLocalized("en", "hello")), - ReferenceFields = new[] - { + ReferenceFields = + [ Fields.String(1, "field", Partitioning.Language) - }, + ], SchemaId = schemaId1 }; @@ -130,10 +130,10 @@ public async Task Should_return_content_with_invariant_field_and_reference_data( .AddField("field", new ContentFieldData() .AddLocalized("en", "resolved")), - ReferenceFields = new[] - { + ReferenceFields = + [ Fields.String(1, "field", Partitioning.Language) - }, + ], SchemaId = schemaId1 }; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs index 7939088740..a26a87c1f7 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs @@ -29,7 +29,7 @@ public async Task Should_compute_ui_tokens() { var content = CreateContent(); - await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken); Assert.NotNull(content.EditToken); @@ -42,7 +42,7 @@ public async Task Should_also_compute_ui_tokens_for_frontend() { var content = CreateContent(); - await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken); Assert.NotNull(content.EditToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs index ecc2d78bdd..e654a5c873 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs @@ -35,7 +35,7 @@ public async Task Should_only_invoke_pre_enrich_for_empty_contents() var step1 = A.Fake(); var step2 = A.Fake(); - var sut = new ContentEnricher(new[] { step1, step2 }, AppProvider); + var sut = new ContentEnricher([step1, step2], AppProvider); await sut.EnrichAsync(source, ApiContext, CancellationToken); @@ -60,7 +60,7 @@ public async Task Should_invoke_steps() var step1 = A.Fake(); var step2 = A.Fake(); - var sut = new ContentEnricher(new[] { step1, step2 }, AppProvider); + var sut = new ContentEnricher([step1, step2], AppProvider); await sut.EnrichAsync(source, false, ApiContext, CancellationToken); @@ -85,7 +85,7 @@ public async Task Should_provide_and_cache_schema() var step1 = new ResolveSchema(); var step2 = new ResolveSchema(); - var sut = new ContentEnricher(new[] { step1, step2 }, AppProvider); + var sut = new ContentEnricher([step1, step2], AppProvider); await sut.EnrichAsync(source, false, ApiContext, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs index 494cbb6977..4737844f15 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs @@ -25,7 +25,7 @@ public async Task Should_enrich_with_reference_fields() { var content = CreateContent(); - await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken); Assert.NotNull(content.ReferenceFields); } @@ -35,7 +35,7 @@ public async Task Should_not_enrich_with_reference_fields_if_not_frontend() { var content = CreateContent(); - await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken); Assert.Null(content.ReferenceFields); } @@ -45,7 +45,7 @@ public async Task Should_enrich_with_schema_names() { var content = CreateContent(); - await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken); Assert.Equal("my-schema", content.SchemaDisplayName); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs index bc0f1b30db..6ee7c86d6a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs @@ -36,7 +36,7 @@ public async Task Should_enrich_content_with_next_statuses() A.CallTo(() => workflow.GetNextAsync(content, content.Status, FrontendContext.UserPrincipal)) .Returns(nexts); - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.Equal(nexts, content.NextStatuses); } @@ -46,7 +46,7 @@ public async Task Should_enrich_content_with_next_statuses_if_draft_singleton() { var content = CreateContent() with { IsSingleton = true, Status = Status.Draft }; - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, default); + await sut.EnrichAsync(FrontendContext, [content], null!, default); Assert.Equal(Status.Published, content.NextStatuses?.Single().Status); @@ -59,7 +59,7 @@ public async Task Should_enrich_content_with_next_statuses_if_published_singleto { var content = CreateContent() with { IsSingleton = true }; - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.Empty(content.NextStatuses!); @@ -75,7 +75,7 @@ public async Task Should_enrich_content_with_status_color() A.CallTo(() => workflow.GetInfoAsync(content, content.Status)) .Returns(new StatusInfo(Status.Published, StatusColors.Published)); - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.Equal(StatusColors.Published, content.StatusColor); } @@ -88,7 +88,7 @@ public async Task Should_enrich_content_with_new_status_color() A.CallTo(() => workflow.GetInfoAsync(content, content.NewStatus!.Value)) .Returns(new StatusInfo(Status.Published, StatusColors.Archived)); - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.Equal(StatusColors.Archived, content.NewStatusColor); } @@ -101,7 +101,7 @@ public async Task Should_enrich_content_with_scheduled_status_color() A.CallTo(() => workflow.GetInfoAsync(content, content.ScheduleJob.Status)) .Returns(new StatusInfo(Status.Published, StatusColors.Archived)); - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.Equal(StatusColors.Archived, content.ScheduledStatusColor); } @@ -114,7 +114,7 @@ public async Task Should_enrich_content_with_default_color_if_not_found() A.CallTo(() => workflow.GetInfoAsync(content, content.Status)) .Returns(ValueTask.FromResult(null!)); - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.Equal(StatusColors.Draft, content.StatusColor); } @@ -127,7 +127,7 @@ public async Task Should_enrich_content_with_can_update() A.CallTo(() => workflow.CanUpdateAsync(content, content.Status, FrontendContext.UserPrincipal)) .Returns(true); - await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken); Assert.True(content.CanUpdate); } @@ -137,7 +137,7 @@ public async Task Should_not_enrich_content_with_can_update_if_disabled_in_conte { var content = CreateContent(); - await sut.EnrichAsync(ApiContext.Clone(b => b.WithResolveFlow(false)), new[] { content }, null!, CancellationToken); + await sut.EnrichAsync(ApiContext.Clone(b => b.WithResolveFlow(false)), [content], null!, CancellationToken); Assert.False(content.CanUpdate); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs index 26f30ff4c4..57397af134 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs @@ -31,7 +31,7 @@ public async Task Should_not_call_script_engine_if_no_script_configured() { var content = CreateContent(); - await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken); A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -47,7 +47,7 @@ public async Task Should_not_call_script_engine_for_frontend_user() var content = CreateContent(); - await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken); A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -63,7 +63,7 @@ public async Task Should_not_call_script_engine_if_disabled_and_user_has_permiss var content = CreateContent(); - await sut.EnrichAsync(ContextWithNoScript(), new[] { content }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ContextWithNoScript(), [content], SchemaProvider(), CancellationToken); A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -80,7 +80,7 @@ public async Task Should_call_script_engine() var contentBefore = CreateContent(); var contentData = contentBefore.Data; - await sut.EnrichAsync(ApiContext, new[] { contentBefore }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ApiContext, [contentBefore], SchemaProvider(), CancellationToken); Assert.NotSame(contentBefore.Data, contentData); @@ -109,7 +109,7 @@ public async Task Should_call_script_engine_with_pre_query_script() var contentBefore = CreateContent(); var contentData = contentBefore.Data; - await sut.EnrichAsync(ApiContext, new[] { contentBefore }, SchemaProvider(), CancellationToken); + await sut.EnrichAsync(ApiContext, [contentBefore], SchemaProvider(), CancellationToken); Assert.NotSame(contentBefore.Data, contentData); @@ -160,7 +160,7 @@ public async Task Should_make_test_with_pre_query_script() var sut2 = new ScriptContent(realScriptEngine); - await sut2.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); + await sut2.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken); Assert.Equal(JsonValue.Create(123), content.Data["test"]!["iv"]); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs index 12d8935e24..d36c8a04c6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.MongoDb.Text; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.MongoDb; @@ -14,6 +15,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text; public sealed class MongoTextIndexerStateFixture : IAsyncLifetime { + public IContentRepository ContentRepository { get; } = A.Fake(); + public MongoTextIndexerState State { get; } public MongoTextIndexerStateFixture() @@ -23,7 +26,7 @@ public MongoTextIndexerStateFixture() var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["mongoDb:configuration"]!); var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["mongodb:database"]!); - State = new MongoTextIndexerState(mongoDatabase); + State = new MongoTextIndexerState(mongoDatabase, ContentRepository); } public Task InitializeAsync() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs index 11f9a8772a..001b73e417 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs @@ -8,6 +8,7 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Infrastructure; @@ -34,7 +35,6 @@ await _.State.SetAsync( ]); var actual = await _.State.GetAsync(HashSet.Of(id1, id2)); - actual.Should().BeEquivalentTo(new Dictionary { [id1] = new TextContentState { UniqueContentId = id1, State = TextState.Stage0_Draft__Stage1_None }, @@ -63,10 +63,12 @@ await _.State.SetAsync( } [Fact] - public async Task Should_remove_by_app_state() + public async Task Should_remove_by_app() { var appId1 = DomainId.NewGuid(); var appId2 = DomainId.NewGuid(); + var app2 = new App { Id = appId2 }; + var id1 = new UniqueContentId(appId1, DomainId.NewGuid()); var id2 = new UniqueContentId(appId1, DomainId.NewGuid()); var id3 = new UniqueContentId(appId2, DomainId.NewGuid()); @@ -78,10 +80,41 @@ await _.State.SetAsync( new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } ]); - await ((IDeleter)_.State).DeleteAppAsync(new App { Id = appId1 }, default); + await ((IDeleter)_.State).DeleteAppAsync(app2, default); var actual = await _.State.GetAsync(HashSet.Of(id1, id2, id3)); + actual.Should().BeEquivalentTo(new Dictionary + { + [id3] = new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } + }); + } + + [Fact] + public async Task Should_remove_by_schema() + { + var appId = DomainId.NewGuid(); + var app = new App { Id = appId }; + + var schemaId = DomainId.NewGuid(); + var schema = new Schema { Id = schemaId }; + + var id1 = new UniqueContentId(appId, DomainId.NewGuid()); + var id2 = new UniqueContentId(appId, DomainId.NewGuid()); + var id3 = new UniqueContentId(appId, DomainId.NewGuid()); + await _.State.SetAsync( + [ + new TextContentState { UniqueContentId = id1, State = TextState.Stage0_Draft__Stage1_None }, + new TextContentState { UniqueContentId = id2, State = TextState.Stage0_Published__Stage1_Draft }, + new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } + ]); + + A.CallTo(() => _.ContentRepository.StreamIds(appId, schemaId, SearchScope.All, default)) + .Returns(new[] { id1.ContentId, id2.ContentId }.ToAsyncEnumerable()); + + await ((IDeleter)_.State).DeleteSchemaAsync(app, schema, default); + + var actual = await _.State.GetAsync(HashSet.Of(id1, id2, id3)); actual.Should().BeEquivalentTo(new Dictionary { [id3] = new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Jobs/DefaultJobsServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Jobs/DefaultJobsServiceTests.cs index c845d6d573..ae581b64e3 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Jobs/DefaultJobsServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Jobs/DefaultJobsServiceTests.cs @@ -36,7 +36,7 @@ public DefaultJobsServiceTests() A.CallTo(() => runner2.Name) .Returns("job2"); - sut = new DefaultJobService(messaging, new[] { runner1, runner2 }, state.PersistenceFactory); + sut = new DefaultJobService(messaging, [runner1, runner2], state.PersistenceFactory); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs index abd8d8c911..fa49a22c55 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs @@ -186,7 +186,7 @@ public async Task Should_handle_event_with_successful_job() A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) .Invokes(x => writes = x.GetArgument>(0)?.ToArray()); - await sut.On(new[] { @event }); + await sut.On([@event]); Assert.Equal(new[] { new RuleEventWrite(job, job.Created) }, writes); @@ -214,7 +214,7 @@ public async Task Should_handle_event_with_failed_job() A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) .Invokes(x => writes = x.GetArgument>(0)?.ToArray()); - await sut.On(new[] { @event }); + await sut.On([@event]); Assert.Equal(new[] { new RuleEventWrite(job) }, writes); @@ -237,7 +237,7 @@ public async Task Should_not_handle_restored_event() SetupRules(@event, job, default); - await sut.On(new[] { @event.SetRestored(true) }); + await sut.On([@event.SetRestored(true)]); A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) .MustNotHaveHappened(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs index d379db3fde..3ef6a4e03c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs @@ -19,9 +19,6 @@ public class SchemaDomainObjectTests : HandlerTestBase { private readonly string fieldName = "age"; private readonly string arrayName = "array"; - private readonly NamedId fieldId = NamedId.Of(1L, "age"); - private readonly NamedId arrayId = NamedId.Of(1L, "array"); - private readonly NamedId nestedId = NamedId.Of(2L, "age"); private readonly SchemaDomainObject sut; protected override DomainId Id @@ -244,6 +241,17 @@ public async Task Delete_should_create_events_and_update_deleted_flag() await VerifySutAsync(actual, None.Value); } + [Fact] + public async Task Delete_should_create_events_with_permanent_flag() + { + var command = new DeleteSchema { Permanent = true }; + + await ExecuteCreateAsync(); + var actual = await PublishAsync(sut, command); + + await VerifySutAsync(actual, None.Value); + } + [Fact] public async Task Reorder_should_create_events_and_reorder_fields() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs index e1f33ff27e..746a947c91 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs @@ -18,9 +18,14 @@ namespace Squidex.Domain.Apps.Entities.Schemas.MongoDb; [Trait("Category", "Dependencies")] -public class SchemasHashTests(SchemasHashFixture fixture) : GivenContext, IClassFixture +public class SchemasHashTests : GivenContext, IClassFixture { - public SchemasHashFixture _ { get; } = fixture; + public SchemasHashFixture _ { get; } + + public SchemasHashTests(SchemasHashFixture fixture) + { + _ = fixture; + } [Fact] public async Task Should_compute_cache_independent_from_order() @@ -28,8 +33,8 @@ public async Task Should_compute_cache_independent_from_order() var schema1 = Schema.WithId(DomainId.NewGuid(), "my-schema"); var schema2 = Schema.WithId(DomainId.NewGuid(), "my-schema") with { Version = 3 }; - var hash1 = await _.SchemasHash.ComputeHashAsync(App, new[] { schema1, schema2 }, CancellationToken); - var hash2 = await _.SchemasHash.ComputeHashAsync(App, new[] { schema2, schema1 }, CancellationToken); + var hash1 = await _.SchemasHash.ComputeHashAsync(App, [schema1, schema2], CancellationToken); + var hash2 = await _.SchemasHash.ComputeHashAsync(App, [schema2, schema1], CancellationToken); Assert.NotNull(hash1); Assert.NotNull(hash2); @@ -44,10 +49,10 @@ public async Task Should_compute_cache_independent_from_db() var timestamp = SystemClock.Instance.GetCurrentInstant().WithoutMs(); - var computedHash = await _.SchemasHash.ComputeHashAsync(App, new[] { schema1, schema2 }, CancellationToken); + var computedHash = await _.SchemasHash.ComputeHashAsync(App, [schema1, schema2], CancellationToken); - await _.SchemasHash.On(new[] - { + await _.SchemasHash.On( + [ Envelope.Create(new SchemaCreated { AppId = AppId, @@ -59,7 +64,7 @@ await _.SchemasHash.On(new[] AppId = AppId, SchemaId = schema2.NamedId() }).SetEventStreamNumber(schema2.Version).SetTimestamp(timestamp) - }); + ]); var (dbTime, dbHash) = await _.SchemasHash.GetCurrentHashAsync(App, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs new file mode 100644 index 0000000000..26f295e94b --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs @@ -0,0 +1,143 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Options; +using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities.Schemas.DomainObject; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities.Schemas; + +public class SchemaPermanentDeleterTests : GivenContext +{ + private readonly IDomainObjectFactory domainObjectFactory = A.Fake(); + private readonly IDeleter deleter1 = A.Fake(); + private readonly IDeleter deleter2 = A.Fake(); + private readonly SchemasOptions options = new SchemasOptions(); + private readonly SchemaPermanentDeleter sut; + + public SchemaPermanentDeleterTests() + { + sut = new SchemaPermanentDeleter(AppProvider, [deleter1, deleter2], Options.Create(options), domainObjectFactory, TestUtils.TypeRegistry); + } + + [Fact] + public void Should_return_assets_filter_for_events_filter() + { + Assert.Equal(StreamFilter.Prefix("schema-"), sut.EventsFilter); + } + + [Fact] + public async Task Should_do_nothing_on_clear() + { + await ((IEventConsumer)sut).ClearAsync(); + } + + [Fact] + public void Should_return_type_name_for_name() + { + Assert.Equal(nameof(SchemaPermanentDeleter), ((IEventConsumer)sut).Name); + } + + [Fact] + public async Task Should_handle_delete_event() + { + var eventType = TestUtils.TypeRegistry.GetName(); + + var storedEvent = + new StoredEvent("stream", "1", 1, + new EventData(eventType, [], "payload")); + + Assert.True(await sut.HandlesAsync(storedEvent)); + } + + [Fact] + public async Task Should_not_handle_creation_event() + { + var eventType = TestUtils.TypeRegistry.GetName(); + + var storedEvent = + new StoredEvent("stream", "1", 1, + new EventData(eventType, [], "payload")); + + Assert.False(await sut.HandlesAsync(storedEvent)); + } + + [Fact] + public async Task Should_call_deleters_when_schema_deleted_and_enabled_globally() + { + options.DeletePermanent = true; + SetupDomainObject(); + + await sut.On(Envelope.Create(new SchemaDeleted + { + AppId = AppId, + SchemaId = SchemaId, + Permanent = false + })); + + A.CallTo(() => deleter1.DeleteSchemaAsync(App, Schema, default)) + .MustHaveHappened(); + + A.CallTo(() => deleter2.DeleteSchemaAsync(App, Schema, default)) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_call_deleters_when_schema_deleted_and_enabled_per_event() + { + options.DeletePermanent = false; + SetupDomainObject(); + + await sut.On(Envelope.Create(new SchemaDeleted + { + AppId = AppId, + SchemaId = SchemaId, + Permanent = true + })); + + A.CallTo(() => deleter1.DeleteSchemaAsync(App, Schema, default)) + .MustHaveHappened(); + + A.CallTo(() => deleter2.DeleteSchemaAsync(App, Schema, default)) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_not_call_deleters_when_app_not_deleted_permanently_and_not_enabled_globally() + { + options.DeletePermanent = false; + + await sut.On(Envelope.Create(new SchemaDeleted + { + AppId = AppId, + SchemaId = SchemaId, + Permanent = false + })); + + A.CallTo(() => deleter1.DeleteSchemaAsync(App, Schema, default)) + .MustNotHaveHappened(); + + A.CallTo(() => deleter2.DeleteSchemaAsync(App, Schema, default)) + .MustNotHaveHappened(); + } + + private void SetupDomainObject() + { + var domainObject = A.Fake(); + + A.CallTo(() => domainObject.Snapshot) + .Returns(Schema); + + A.CallTo(() => domainObjectFactory.Create(DomainId.Combine(App.Id, Schema.Id))) + .Returns(domainObject); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs index 8ded2b5d9a..dd0f1dab5f 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Search/SearchManagerTests.cs @@ -19,7 +19,7 @@ public class SearchManagerTests : GivenContext public SearchManagerTests() { - sut = new SearchManager(new[] { source1, source2 }, log); + sut = new SearchManager([source1, source2], log); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt new file mode 100644 index 0000000000..644c1917a7 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt @@ -0,0 +1,122 @@ +{ + sut: { + UniqueId: Guid_1, + Snapshot: { + Name: my-app, + Contributors: { + me: Owner + }, + Roles: { + All: [ + { + Name: Owner, + Permissions: [ + { + Id: * + } + ], + IsDefault: true + }, + { + Name: Reader, + Properties: { + ui.api.hide: true + }, + Permissions: [ + { + Id: assets.read + }, + { + Id: contents.*.read + } + ], + IsDefault: true + }, + { + Name: Editor, + Properties: { + ui.api.hide: true + }, + Permissions: [ + { + Id: assets + }, + { + Id: contents.* + }, + { + Id: roles.read + }, + { + Id: workflows.read + } + ], + IsDefault: true + }, + { + Name: Developer, + Permissions: [ + { + Id: assets + }, + { + Id: contents.* + }, + { + Id: roles.read + }, + { + Id: rules + }, + { + Id: schemas + }, + { + Id: workflows + } + ], + IsDefault: true + } + ] + }, + Settings: { + HideScheduler: true, + HideDateTimeModeButton: false + }, + AssetScripts: {}, + Languages: { + Master: en, + AllKeys: [ + en + ], + Values: { + en: { + IsOptional: false + } + } + }, + IsDeleted: true, + Id: Guid_1, + CreatedBy: subject:me, + LastModifiedBy: subject:me, + Version: 3, + UniqueId: Guid_1 + }, + Version: 3, + ObjectState: Deleted + }, + events: [ + { + Headers: { + AggregateId: Guid_1, + EventId: Guid_2 + }, + Payload: { + Permanent: false, + AppId: Guid_1,my-app, + Actor: subject:me, + FromRule: false + } + } + ] +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.DeleteApp_should_create_events_and_update_deleted_flag.verified.txt b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt similarity index 98% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.DeleteApp_should_create_events_and_update_deleted_flag.verified.txt rename to backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt index d16f8c8100..959f48fcbf 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.DeleteApp_should_create_events_and_update_deleted_flag.verified.txt +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/AppDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt @@ -112,6 +112,7 @@ EventId: Guid_2 }, Payload: { + Permanent: true, AppId: Guid_1,my-app, Actor: subject:me, FromRule: false diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt index 9959d343d9..2cbbf7bcb8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_and_update_deleted_flag.verified.txt @@ -27,6 +27,7 @@ EventId: Guid_3 }, Payload: { + Permanent: false, SchemaId: Guid_2,my-schema, AppId: Guid_1,my-app, Actor: subject:me, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt new file mode 100644 index 0000000000..dfcebcf3c7 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Verify/SchemaDomainObjectTests.Delete_should_create_events_with_permanent_flag.verified.txt @@ -0,0 +1,38 @@ +{ + sut: { + UniqueId: Guid_1--Guid_2, + Snapshot: { + Name: my-schema, + IsPublished: false, + FieldCollection: {}, + Scripts: {}, + Properties: { + ValidateOnPublish: false + }, + AppId: Guid_1,my-app, + IsDeleted: true, + UniqueId: Guid_1--Guid_2, + Id: Guid_2, + CreatedBy: subject:me, + LastModifiedBy: subject:me, + Version: 1 + }, + Version: 1, + ObjectState: Deleted + }, + events: [ + { + Headers: { + AggregateId: Guid_1--Guid_2, + EventId: Guid_3 + }, + Payload: { + Permanent: true, + SchemaId: Guid_2,my-schema, + AppId: Guid_1,my-app, + Actor: subject:me, + FromRule: false + } + } + ] +} \ No newline at end of file diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/CustomCommandMiddlewareRunnerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/CustomCommandMiddlewareRunnerTests.cs index 7aced4f9df..3019db807c 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/CustomCommandMiddlewareRunnerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/CustomCommandMiddlewareRunnerTests.cs @@ -16,8 +16,15 @@ public sealed class Command : ICommand public long ExpectedVersion { get; set; } } - public sealed class CustomMiddleware(int value) : ICustomCommandMiddleware + public sealed class CustomMiddleware : ICustomCommandMiddleware { + private readonly int value; + + public CustomMiddleware(int value) + { + this.value = value; + } + public Task HandleAsync(CommandContext context, NextDelegate next, CancellationToken ct) { @@ -36,12 +43,12 @@ public async Task Should_run_extensions_in_right_order() var command = new Command(); var context = new CommandContext(command, A.Fake()); - var sut = new CustomCommandMiddlewareRunner(new[] - { + var sut = new CustomCommandMiddlewareRunner( + [ new CustomMiddleware(10), new CustomMiddleware(12), new CustomMiddleware(14) - }); + ]); var isNextCalled = false; diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/InMemoryCommandBusTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/InMemoryCommandBusTests.cs index 3f6b8b114e..445c290f48 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/InMemoryCommandBusTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/InMemoryCommandBusTests.cs @@ -67,7 +67,7 @@ public async Task Should_not_set_succeeded_if_handler_returns_false() { var handler = new NonHandledHandler(); - var sut = new InMemoryCommandBus(new ICommandMiddleware[] { handler }); + var sut = new InMemoryCommandBus([handler]); var context = await sut.PublishAsync(command, default); @@ -80,7 +80,7 @@ public async Task Should_set_succeeded_if_handler_marks_completed() { var handler = new HandledHandler(); - var sut = new InMemoryCommandBus(new ICommandMiddleware[] { handler }); + var sut = new InMemoryCommandBus([handler]); var context = await sut.PublishAsync(command, default); @@ -93,7 +93,7 @@ public async Task Should_throw_and_call_all_handlers_if_first_handler_fails() { var handler = new ThrowHandledHandler(); - var sut = new InMemoryCommandBus(new ICommandMiddleware[] { handler }); + var sut = new InMemoryCommandBus([handler]); await Assert.ThrowsAsync(async () => await sut.PublishAsync(command, default)); diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs index eff0995c88..2a2f2848c7 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerManagerTests.cs @@ -30,7 +30,7 @@ public EventConsumerManagerTests() A.CallTo(() => consumer2.Name) .Returns(consumerName2); - sut = new EventConsumerManager(persistenceFactory, new[] { consumer1, consumer2 }, messaging); + sut = new EventConsumerManager(persistenceFactory, [consumer1, consumer2], messaging); } [Fact] diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerWorkerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerWorkerTests.cs index 57f0a4577e..e748c82617 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerWorkerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerWorkerTests.cs @@ -25,7 +25,7 @@ public EventConsumerWorkerTests() A.CallTo(() => consumer1.Name).Returns("1"); A.CallTo(() => consumer2.Name).Returns("2"); - sut = new EventConsumerWorker(new[] { consumer1, consumer2 }, factory); + sut = new EventConsumerWorker([consumer1, consumer2], factory); } [Fact] diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs index 2f385db85c..a8de8e0873 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs @@ -16,23 +16,20 @@ public class ClaimsPrincipalConverterTests public void Should_serialize_and_deserialize() { var value = new ClaimsPrincipal( - new[] - { + [ new ClaimsIdentity( - new[] - { + [ new Claim("email", "me@email.com"), new Claim("username", "me@email.com") - }, + ], "Cookie"), new ClaimsIdentity( - new[] - { + [ new Claim("user_id", "12345"), new Claim("login", "me") - }, + ], "Google") - }); + ]); var serialized = value.SerializeAndDeserialize(); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Queries/PropertyPathTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Queries/PropertyPathTests.cs index 93e07dd639..655686a5df 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Queries/PropertyPathTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Queries/PropertyPathTests.cs @@ -12,7 +12,7 @@ public class PropertyPathTests [Fact] public void Should_create() { - var path = new PropertyPath(new[] { "path", "to", "property" }); + var path = new PropertyPath(["path", "to", "property"]); Assert.Equal(new[] { "path", "to", "property" }, path.ToArray()); } @@ -20,7 +20,7 @@ public void Should_create() [Fact] public void Should_convert_to_string() { - var path = new PropertyPath(new[] { "path", "to", "property" }); + var path = new PropertyPath(["path", "to", "property"]); Assert.Equal("path.to.property", path.ToString()); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs index 3f520a1873..bf6519f5c4 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs @@ -46,7 +46,7 @@ public async Task Should_read_from_preloaded_events() [key2] = [event2_1, event2_2] }); - await bulk.LoadAsync(new[] { key1, key2 }); + await bulk.LoadAsync([key1, key2]); var persistedEvents1 = Save.Events(); var persistence1 = bulk.WithEventSourcing(None.Type, key1, persistedEvents1.Write); @@ -58,8 +58,8 @@ public async Task Should_read_from_preloaded_events() await persistence2.ReadAsync(); - Assert.Equal(persistedEvents1.ToArray(), new[] { event1_1, event1_2 }); - Assert.Equal(persistedEvents2.ToArray(), new[] { event2_1, event2_2 }); + Assert.Equal(persistedEvents1.ToArray(), [event1_1, event1_2]); + Assert.Equal(persistedEvents2.ToArray(), [event2_1, event2_2]); } [Fact] @@ -69,7 +69,7 @@ public async Task Should_provide_empty_events_if_nothing_loaded() var bulk = sut.WithBatchContext(None.Type); - await bulk.LoadAsync(new[] { key }); + await bulk.LoadAsync([key]); var persistedEvents = Save.Events(); var persistence = bulk.WithEventSourcing(None.Type, key, persistedEvents.Write); @@ -98,7 +98,7 @@ public async Task Should_write_batched() var bulk = sut.WithBatchContext(None.Type); - await bulk.LoadAsync(new[] { key1, key2 }); + await bulk.LoadAsync([key1, key2]); var persistedEvents1 = Save.Events(); var persistence1 = bulk.WithEventSourcing(None.Type, key1, persistedEvents1.Write); @@ -130,7 +130,7 @@ public async Task Should_write_each_id_only_once_if_same_id_requested_twice() var bulk = sut.WithBatchContext(None.Type); - await bulk.LoadAsync(new[] { key1, key2 }); + await bulk.LoadAsync([key1, key2]); var persistedEvents1_1 = Save.Events(); var persistence1_1 = bulk.WithEventSourcing(None.Type, key1, persistedEvents1_1.Write); @@ -162,7 +162,7 @@ public async Task Should_write_each_id_only_once_if_same_persistence_written_twi var bulk = sut.WithBatchContext(None.Type); - await bulk.LoadAsync(new[] { key1, key2 }); + await bulk.LoadAsync([key1, key2]); var persistedEvents1 = Save.Events(); var persistence1 = bulk.WithEventSourcing(None.Type, key1, persistedEvents1.Write); diff --git a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs index 02068790bb..5830aa553e 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs @@ -41,7 +41,7 @@ public async Task Should_read_from_store() await persistence.ReadAsync(); - Assert.Equal(persistedEvents.ToArray(), new[] { event1, event2 }); + Assert.Equal(persistedEvents.ToArray(), [event1, event2]); } [Fact] @@ -57,7 +57,7 @@ public async Task Should_read_until_stopped() await persistence.ReadAsync(); - Assert.Equal(persistedEvents.ToArray(), new[] { event1 }); + Assert.Equal(persistedEvents.ToArray(), [event1]); } [Fact]