Browse Source

Permanent deletion (#1144)

* Delete permanent.

* Permanent deletion.
pull/1146/head
Sebastian Stehle 2 years ago
committed by GitHub
parent
commit
a7e44b2e98
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  2. 15
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  3. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  4. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs
  5. 27
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs
  6. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs
  7. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs
  8. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs
  9. 11
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs
  10. 24
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs
  11. 41
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs
  12. 13
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppsOptions.cs
  13. 1
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeleteApp.cs
  14. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs
  15. 35
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs
  16. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  17. 7
      backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs
  18. 35
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs
  19. 1
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs
  20. 2
      backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs
  21. 2
      backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs
  22. 95
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs
  23. 13
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasOptions.cs
  24. 1
      backend/src/Squidex.Domain.Apps.Events/Apps/AppDeleted.cs
  25. 1
      backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaDeleted.cs
  26. 1
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs
  27. 2
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs
  28. 12
      backend/src/Squidex.Infrastructure/Commands/CommandRequest.cs
  29. 5
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  30. 28
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/DeleteAppDto.cs
  31. 1
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  32. 28
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/DeleteSchemaDto.cs
  33. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  34. 10
      backend/src/Squidex/Areas/IdentityServer/Config/Dynamic/DynamicSchemeProvider.cs
  35. 10
      backend/src/Squidex/Config/Domain/AppsServices.cs
  36. 9
      backend/src/Squidex/Config/Domain/SchemasServices.cs
  37. 2
      backend/src/Squidex/Startup.cs
  38. 11
      backend/src/Squidex/appsettings.json
  39. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs
  40. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs
  41. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs
  42. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs
  43. 16
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  44. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  45. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs
  46. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs
  47. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/EventMessageWrapperTests.cs
  48. 12
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs
  49. 10
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs
  50. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs
  51. 12
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs
  52. 20
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs
  53. 10
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs
  54. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs
  55. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs
  56. 10
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs
  57. 19
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs
  58. 18
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/RichTextFieldTests.cs
  59. 22
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs
  60. 16
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs
  61. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs
  62. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs
  63. 31
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs
  64. 5
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs
  65. 10
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs
  66. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs
  67. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs
  68. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs
  69. 16
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs
  70. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs
  71. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs
  72. 8
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs
  73. 16
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringTextValidatorTests.cs
  74. 19
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueObjectValuesValidatorTests.cs
  75. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs
  76. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs
  77. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs
  78. 62
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs
  79. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs
  80. 17
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
  81. 18
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs
  82. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs
  83. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs
  84. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs
  85. 1
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs
  86. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs
  87. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs
  88. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs
  89. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs
  90. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs
  91. 10
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs
  92. 65
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs
  93. 24
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs
  94. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs
  95. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs
  96. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs
  97. 18
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs
  98. 12
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs
  99. 5
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs
  100. 39
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -40,7 +40,9 @@ public sealed class ContentFieldProperty(ContentFieldObject contentField, JsonVa
{
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;

15
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -127,6 +127,12 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
return queryScheduled.QueryAsync(now, ct);
}
public IAsyncEnumerable<DomainId> 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 sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
}
}
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<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q,
CancellationToken ct)
{

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -81,6 +81,12 @@ public partial class MongoContentRepository(
return GetCollection(scope).QueryScheduledWithoutDataAsync(now, ct);
}
public IAsyncEnumerable<DomainId> StreamIds(DomainId appId, DomainId schemaId, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).StreamIds(appId, schemaId, ct);
}
public Task<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q, SearchScope scope,
CancellationToken ct = default)
{

12
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs

@ -103,4 +103,16 @@ public sealed class MongoShardedContentRepository(IShardingStrategy sharding, Fu
}
}
}
public async IAsyncEnumerable<DomainId> 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;
}
}
}
}

27
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<Content> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,
[EnumeratorCancellation] CancellationToken ct)
{
@ -31,6 +38,26 @@ public sealed class QueryAsStream : OperationBase
}
}
public async IAsyncEnumerable<DomainId> 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<MongoContentEntity>.Projection.Include(x => x.Id);
using (var cursor = await Collection.Find(filter).Project<IdOnly>(projection).ToCursorAsync(ct))
{
while (await cursor.MoveNextAsync(ct))
{
foreach (var entity in cursor.Current)
{
yield return entity.Id;
}
}
}
}
private static FilterDefinition<MongoContentEntity> CreateFilter(DomainId appId, HashSet<DomainId>? schemaIds)
{
var filters = new List<FilterDefinition<MongoContentEntity>>

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs

@ -39,6 +39,12 @@ public sealed class MongoSchemaRepository(IMongoDatabase database) : MongoSnapsh
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<List<Schema>> QueryAllAsync(DomainId appId, CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoSchemaRepository/QueryAllAsync"))

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs

@ -31,10 +31,10 @@ public sealed class MongoSchemasHash(IMongoDatabase database) : MongoRepositoryB
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<Envelope<IEvent>> events)

12
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 sealed class MongoShardedTextIndex<T>(IShardingStrategy sharding, Func<st
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 sealed class MongoShardedTextIndex<T>(IShardingStrategy sharding, Func<st
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);
}
}
}

11
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs

@ -9,6 +9,7 @@ using MongoDB.Bson;
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 @@ public abstract class MongoTextIndexBase<T>(IMongoDatabase database, string shar
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,

24
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 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Text;
public sealed class MongoTextIndexerState(IMongoDatabase database) : MongoRepositoryBase<TextContentState>(database), ITextIndexerState, IDeleter
public sealed class MongoTextIndexerState(
IMongoDatabase database,
IContentRepository contentRepository)
: MongoRepositoryBase<TextContentState>(database), ITextIndexerState, IDeleter
{
static MongoTextIndexerState()
{
@ -30,6 +36,8 @@ public sealed class MongoTextIndexerState(IMongoDatabase database) : MongoReposi
});
}
int IDeleter.Order => -2000;
protected override string CollectionName()
{
return "TextIndexerState";
@ -46,6 +54,20 @@ public sealed class MongoTextIndexerState(IMongoDatabase database) : MongoReposi
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<Dictionary<UniqueContentId, TextContentState>> GetAsync(HashSet<UniqueContentId> ids,
CancellationToken ct = default)
{

41
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 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Apps;
public sealed class AppPermanentDeleter(IEnumerable<IDeleter> deleters, IDomainObjectFactory factory, TypeRegistry typeRegistry) : IEventConsumer
public sealed class AppPermanentDeleter(
IEnumerable<IDeleter> deleters,
IOptions<AppsOptions> options,
IDomainObjectFactory factory,
TypeRegistry typeRegistry)
: IEventConsumer
{
private readonly IEnumerable<IDeleter> deleters = deleters.OrderBy(x => x.Order).ToList();
private readonly AppsOptions options = options.Value;
private readonly HashSet<string> consumingTypes =
[
typeRegistry.GetName<IEvent, AppDeleted>(),
@ -39,8 +46,8 @@ public sealed class AppPermanentDeleter(IEnumerable<IDeleter> deleters, IDomainO
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 @@ public sealed class AppPermanentDeleter(IEnumerable<IDeleter> deleters, IDomainO
}
}
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<AppDomainObject>(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 @@ public sealed class AppPermanentDeleter(IEnumerable<IDeleter> deleters, IDomainO
}
}
}
private async Task<AppDomainObject?> GetAppAsync(DomainId appId)
{
// Bypass our normal resolve process, so that we can also retrieve the deleted app.
var app = factory.Create<AppDomainObject>(appId);
await app.EnsureLoadedAsync();
// If the app does not exist, the version is lower than zero.
return app.Version < 0 ? null : app;
}
}

13
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; }
}

1
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; }
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs

@ -122,7 +122,7 @@ public sealed class DefaultAppLogStore(IRequestLogStore requestLogStore) : IAppL
}
finally
{
await writer.FlushAsync();
await writer.FlushAsync(ct);
}
}

35
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);
}
}
}

3
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<DomainId> StreamIds(DomainId appId, DomainId schemaId, SearchScope scope,
CancellationToken ct = default);
IAsyncEnumerable<Content> StreamScheduledWithoutDataAsync(Instant now, SearchScope scope,
CancellationToken ct = default);

7
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)
{

35
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs

@ -17,24 +17,41 @@ using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Rules.Runner;
public sealed class RuleRunnerJob(
IAppProvider appProvider,
IEventFormatter eventFormatter,
IEventStore eventStore,
IRuleEventRepository ruleEventRepository,
IRuleService ruleService,
IRuleUsageTracker ruleUsageTracker,
ILogger<RuleRunnerJob> 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<RuleRunnerJob> log;
public string Name => TaskName;
public RuleRunnerJob(
IAppProvider appProvider,
IEventFormatter eventFormatter,
IEventStore eventStore,
IRuleEventRepository ruleEventRepository,
IRuleService ruleService,
IRuleUsageTracker ruleUsageTracker,
ILogger<RuleRunnerJob> 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)

1
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; }
}

2
backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject;
public sealed partial class SchemaDomainObject
public partial class SchemaDomainObject
{
protected override Schema Apply(Schema snapshot, Envelope<IEvent> @event)
{

2
backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs

@ -22,7 +22,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject;
public sealed partial class SchemaDomainObject(DomainId id, IPersistenceFactory<Schema> persistence, ILogger<SchemaDomainObject> log) : DomainObject<Schema>(id, persistence, log)
public partial class SchemaDomainObject(DomainId id, IPersistenceFactory<Schema> persistence, ILogger<SchemaDomainObject> log) : DomainObject<Schema>(id, persistence, log)
{
protected override bool IsDeleted(Schema snapshot)
{

95
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<IDeleter> deleters,
IOptions<SchemasOptions> options,
IDomainObjectFactory factory,
TypeRegistry typeRegistry)
: IEventConsumer
{
private readonly IEnumerable<IDeleter> deleters = deleters.OrderBy(x => x.Order).ToList();
private readonly SchemasOptions options = options.Value;
private readonly HashSet<string> consumingTypes =
[
typeRegistry.GetName<IEvent, SchemaDeleted>(),
];
public StreamFilter EventsFilter { get; } = StreamFilter.Prefix("schema-");
public ValueTask<bool> HandlesAsync(StoredEvent @event)
{
return new ValueTask<bool>(consumingTypes.Contains(@event.Data.Type));
}
public async Task On(Envelope<IEvent> @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<SchemaDomainObject?> GetSchemaAsync(DomainId appId, DomainId schemaId)
{
// Bypass our normal resolve process, so that we can also retrieve the deleted schema.
var schema = factory.Create<SchemaDomainObject>(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;
}
}

13
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; }
}

1
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; }
}

1
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; }
}

1
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs

@ -9,7 +9,6 @@ using System.Globalization;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using Squidex.Infrastructure.States;

2
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs

@ -27,8 +27,6 @@ public sealed class ProfilerCollection(IMongoDatabase database)
public async Task ClearAsync(
CancellationToken ct = default)
{
var database = collection.Database;
await database.RunCommandAsync<BsonDocument>("{ profile : 0 }", cancellationToken: ct);
await database.DropCollectionAsync(ProfilerDocument.CollectionName, ct);
await database.RunCommandAsync<BsonDocument>("{ profile : 2 }", cancellationToken: ct);

12
backend/src/Squidex.Infrastructure/Commands/CommandRequest.cs

@ -26,18 +26,18 @@ public sealed class CommandRequest(IAggregateCommand command, string culture, st
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;
}
}

5
backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -220,6 +220,7 @@ public sealed class AppsController(ICommandBus commandBus, IAppProvider appProvi
/// Delete the app.
/// </summary>
/// <param name="app">The name of the app to delete.</param>
/// <param name="request">The request parameters.</param>
/// <response code="204">App deleted.</response>
/// <response code="404">App not found.</response>
[HttpDelete]
@ -227,9 +228,9 @@ public sealed class AppsController(ICommandBus commandBus, IAppProvider appProvi
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ApiPermission(PermissionIds.AppDelete)]
[ApiCosts(0)]
public async Task<IActionResult> DeleteApp(string app)
public async Task<IActionResult> DeleteApp(string app, DeleteAppDto request)
{
var command = new DeleteApp();
var command = request.ToCommand();
await CommandBus.PublishAsync(command, HttpContext.RequestAborted);

28
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
{
/// <summary>
/// True to delete the app permanently.
/// </summary>
[FromQuery(Name = "permanent")]
public bool Permanent { get; set; }
public DeleteApp ToCommand()
{
return SimpleMapper.Map(this, new DeleteApp());
}
}

1
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;

28
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
{
/// <summary>
/// True to delete the schema and the contents permanently.
/// </summary>
[FromQuery(Name = "permanent")]
public bool Permanent { get; set; }
public DeleteSchema ToCommand()
{
return SimpleMapper.Map(this, new DeleteSchema());
}
}

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -285,6 +285,7 @@ public sealed class SchemasController(ICommandBus commandBus, IAppProvider appPr
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="schema">The name of the schema to delete.</param>
/// <param name="request">The request parameters.</param>
/// <response code="204">Schema deleted.</response>
/// <response code="404">Schema or app not found.</response>
[HttpDelete]
@ -292,9 +293,9 @@ public sealed class SchemasController(ICommandBus commandBus, IAppProvider appPr
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ApiPermissionOrAnonymous(PermissionIds.AppSchemasDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteSchema(string app, string schema)
public async Task<IActionResult> DeleteSchema(string app, string schema, DeleteSchemaDto request)
{
var command = new DeleteSchema();
var command = request.ToCommand();
await CommandBus.PublishAsync(command, HttpContext.RequestAborted);

10
backend/src/Squidex/Areas/IdentityServer/Config/Dynamic/DynamicSchemeProvider.cs

@ -43,12 +43,12 @@ public sealed class DynamicSchemeProvider(
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 @@ public sealed class DynamicSchemeProvider(
{
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 @@ public sealed class DynamicSchemeProvider(
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<DynamicOpenIdConnectOptions, string?> listener)

10
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<bool>("apps:deletePermanent"))
{
services.AddSingletonAs<AppPermanentDeleter>()
.As<IEventConsumer>();
}
services.Configure<AppsOptions>(config,
"apps");
services.AddSingletonAs<RolePermissionsProvider>()
.AsSelf();
services.AddSingletonAs<AppPermanentDeleter>()
.As<IEventConsumer>();
services.AddSingletonAs<AppEventDeleter>()
.As<IDeleter>();

9
backend/src/Squidex/Config/Domain/SchemasServices.cs

@ -9,13 +9,20 @@ using Squidex.AI;
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<SchemasOptions>(config,
"schemas");
services.AddSingletonAs<SchemaPermanentDeleter>()
.As<IEventConsumer>();
services.AddSingletonAs<SchemasSearchSource>()
.As<ISearchSource>();

2
backend/src/Squidex/Startup.cs

@ -55,7 +55,7 @@ public sealed class Startup(IConfiguration config)
services.AddSquidexOpenApiSettings();
services.AddSquidexQueries(config);
services.AddSquidexRules(config);
services.AddSquidexSchemas();
services.AddSquidexSchemas(config);
services.AddSquidexSearch();
services.AddSquidexSerializers();
services.AddSquidexStoreServices(config);

11
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: <IMG>{description}</IMG>. {description} is the generated image description."
],
"tools": ["none"]
"tools": [ "none" ]
}
}
},

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs

@ -94,7 +94,7 @@ public class AppClientsTests
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]

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs

@ -51,7 +51,7 @@ public class AppContributorsTests
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]

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs

@ -96,7 +96,7 @@ public class RolesTests
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]

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs

@ -40,7 +40,7 @@ public class WorkflowsTests
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]

16
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs

@ -354,8 +354,8 @@ public class SchemaTests
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 class SchemaTests
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 class SchemaTests
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 class SchemaTests
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);
}

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs

@ -81,8 +81,8 @@ public class RuleServiceTests
var log = A.Fake<ILogger<RuleService>>();
sut = new RuleService(Options.Create(new RuleOptions()),
new[] { ruleTriggerHandler },
new[] { ruleActionHandler },
[ruleTriggerHandler],
[ruleActionHandler],
eventEnricher, TestUtils.DefaultSerializer, log, typeRegistry)
{
Clock = clock

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs

@ -111,7 +111,7 @@ public class JintUserTests
{
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();
}

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs

@ -28,7 +28,7 @@ public class ScriptingCompleterTests
dataSchema = schema.BuildDataSchema(LanguagesConfig.English.ToResolver(), ResolvedComponents.Empty);
sut = new ScriptingCompleter(new[] { scriptDescriptor1, scriptDescriptor2 });
sut = new ScriptingCompleter([scriptDescriptor1, scriptDescriptor2]);
}
[Fact]

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/EventMessageWrapperTests.cs

@ -37,7 +37,7 @@ public class EventMessageWrapperTests
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 class EventMessageWrapperTests
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());

12
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs

@ -72,7 +72,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -83,7 +83,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -94,7 +94,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
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 class ArrayFieldTests : IClassFixture<TranslationsFixture>
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 class ArrayFieldTests : IClassFixture<TranslationsFixture>
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 class ArrayFieldTests : IClassFixture<TranslationsFixture>
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)

10
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs

@ -104,7 +104,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors, factory: factory);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -115,7 +115,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors, factory: factory);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -126,7 +126,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
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 class AssetsFieldTests : IClassFixture<TranslationsFixture>
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 class AssetsFieldTests : IClassFixture<TranslationsFixture>
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)

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs

@ -51,7 +51,7 @@ public class BooleanFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -62,7 +62,7 @@ public class BooleanFieldTests : IClassFixture<TranslationsFixture>
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)

12
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs

@ -56,7 +56,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(null, errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -67,7 +67,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentFieldTests : IClassFixture<TranslationsFixture>
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]

20
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs

@ -66,7 +66,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(null, errors, components: components);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -77,7 +77,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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 class ComponentsFieldTests : IClassFixture<TranslationsFixture>
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]

10
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs

@ -43,7 +43,7 @@ public class DateTimeFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -54,7 +54,7 @@ public class DateTimeFieldTests : IClassFixture<TranslationsFixture>
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 class DateTimeFieldTests : IClassFixture<TranslationsFixture>
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 class DateTimeFieldTests : IClassFixture<TranslationsFixture>
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 class DateTimeFieldTests : IClassFixture<TranslationsFixture>
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)

6
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs

@ -69,7 +69,7 @@ public class GeolocationFieldTests : IClassFixture<TranslationsFixture>
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 class GeolocationFieldTests : IClassFixture<TranslationsFixture>
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 class GeolocationFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
private static JsonValue CreateValue(double lat, double lon)

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs

@ -41,7 +41,7 @@ public class JsonFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(JsonValue.Null), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
private static JsonValue CreateValue(JsonValue v)

10
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs

@ -42,7 +42,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -53,7 +53,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
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 class NumberFieldTests : IClassFixture<TranslationsFixture>
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 class NumberFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(20), errors);
errors.Should().BeEquivalentTo(
new[] { "Not an allowed value." });
["Not an allowed value."]);
}
[Fact]
@ -86,7 +86,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
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)

19
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs

@ -23,8 +23,15 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
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<IValidator> CreateValueValidators(ValidationContext context, IField field, ValidatorFactory createFieldValidator)
{
if (field is IField<ReferencesFieldProperties> references)
@ -105,7 +112,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors, factory: factory);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -116,7 +123,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors, factory: factory);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -127,7 +134,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
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 class ReferencesFieldTests : IClassFixture<TranslationsFixture>
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 class ReferencesFieldTests : IClassFixture<TranslationsFixture>
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)

18
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/RichTextFieldTests.cs

@ -51,7 +51,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(string.Empty, "unknown"), errors);
errors.Should().BeEquivalentTo(
new[] { "Invalid rich text." });
["Invalid rich text."]);
}
[Fact]
@ -62,7 +62,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -73,7 +73,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(string.Empty), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -84,7 +84,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
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 class RichTextFieldTests : IClassFixture<TranslationsFixture>
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 class RichTextFieldTests : IClassFixture<TranslationsFixture>
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 class RichTextFieldTests : IClassFixture<TranslationsFixture>
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 class RichTextFieldTests : IClassFixture<TranslationsFixture>
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 class RichTextFieldTests : IClassFixture<TranslationsFixture>
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)

22
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs

@ -42,7 +42,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -53,7 +53,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(string.Empty), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -64,7 +64,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
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 class StringFieldTests : IClassFixture<TranslationsFixture>
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 class StringFieldTests : IClassFixture<TranslationsFixture>
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 class StringFieldTests : IClassFixture<TranslationsFixture>
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 class StringFieldTests : IClassFixture<TranslationsFixture>
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 class StringFieldTests : IClassFixture<TranslationsFixture>
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 class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("Bar"), errors);
errors.Should().BeEquivalentTo(
new[] { "Not an allowed value." });
["Not an allowed value."]);
}
[Fact]
@ -141,7 +141,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("abc"), errors);
errors.Should().BeEquivalentTo(
new[] { "Must follow the pattern." });
["Must follow the pattern."]);
}
[Fact]
@ -152,7 +152,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("abc"), errors);
errors.Should().BeEquivalentTo(
new[] { "Custom Error Message." });
["Custom Error Message."]);
}
private static JsonValue CreateValue(string? v)

16
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs

@ -62,7 +62,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -73,7 +73,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -84,7 +84,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
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 class TagsFieldTests : IClassFixture<TranslationsFixture>
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 class TagsFieldTests : IClassFixture<TranslationsFixture>
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 class TagsFieldTests : IClassFixture<TranslationsFixture>
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 class TagsFieldTests : IClassFixture<TranslationsFixture>
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 class TagsFieldTests : IClassFixture<TranslationsFixture>
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)

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs

@ -44,7 +44,7 @@ public class UIFieldTests : IClassFixture<TranslationsFixture>
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 class UIFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.True, errors);
errors.Should().BeEquivalentTo(
new[] { "Value must not be defined." });
["Value must not be defined."]);
}
[Fact]

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AllowedValuesValidatorTests.cs

@ -42,6 +42,6 @@ public class AllowedValuesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(50, errors);
errors.Should().BeEquivalentTo(
new[] { "Not an allowed value." });
["Not an allowed value."]);
}
}
}

31
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs

@ -88,7 +88,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -101,7 +101,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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 class AssetsValidatorTests : IClassFixture<TranslationsFixture>
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)

5
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs

@ -42,10 +42,9 @@ public class CollectionItemValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 2, 1, 4, 5 }, errors);
errors.Should().BeEquivalentTo(
new[]
{
[
"[2]: Must be between 2 and 4.",
"[4]: Must be between 2 and 4."
});
]);
}
}

10
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs

@ -49,7 +49,7 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(null, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -60,7 +60,7 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 1 }, errors);
errors.Should().BeEquivalentTo(
new[] { "Must have exactly 2 item(s)." });
["Must have exactly 2 item(s)."]);
}
[Fact]
@ -71,7 +71,7 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 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 class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 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 class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 1 }, errors);
errors.Should().BeEquivalentTo(
new[] { "Must have between 2 and 5 item(s)." });
["Must have between 2 and 5 item(s)."]);
}
}

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/NoValueValidatorTests.cs

@ -34,7 +34,7 @@ public class NoValueValidatorTests : IClassFixture<TranslationsFixture>
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 class NoValueValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.True, errors);
errors.Should().BeEquivalentTo(
new[] { "Value must not be defined." });
["Value must not be defined."]);
}
}

6
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs

@ -52,7 +52,7 @@ public class PatternValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync("foo", errors);
errors.Should().BeEquivalentTo(
new[] { "Must follow the pattern." });
["Must follow the pattern."]);
}
[Fact]
@ -63,7 +63,7 @@ public class PatternValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync("foo", errors);
errors.Should().BeEquivalentTo(
new[] { "Custom Error Message." });
["Custom Error Message."]);
}
[Fact]
@ -74,6 +74,6 @@ public class PatternValidatorTests : IClassFixture<TranslationsFixture>
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."]);
}
}

6
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs

@ -53,7 +53,7 @@ public class RangeValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(1500, errors);
errors.Should().BeEquivalentTo(
new[] { "Must be exactly 2000." });
["Must be exactly 2000."]);
}
[Fact]
@ -64,7 +64,7 @@ public class RangeValidatorTests : IClassFixture<TranslationsFixture>
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 class RangeValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(1500, errors);
errors.Should().BeEquivalentTo(
new[] { "Must be less or equal to 1000." });
["Must be less or equal to 1000."]);
}
}

16
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs

@ -104,7 +104,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -117,7 +117,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -130,7 +130,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
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 class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
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 class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
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 class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
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 class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
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 class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref1), errors);
errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." });
["Must not contain duplicate values."]);
}
private static List<DomainId> CreateValue(params DomainId[] ids)

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredStringValidatorTests.cs

@ -56,7 +56,7 @@ public class RequiredStringValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(string.Empty, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
[Fact]
@ -67,6 +67,6 @@ public class RequiredStringValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(null, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
}

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RequiredValidatorTests.cs

@ -52,6 +52,6 @@ public class RequiredValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(null, errors);
errors.Should().BeEquivalentTo(
new[] { "Field is required." });
["Field is required."]);
}
}

8
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs

@ -64,7 +64,7 @@ public class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
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 class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
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 class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
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 class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
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)

16
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringTextValidatorTests.cs

@ -71,7 +71,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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 class StringTextValidatorTests : IClassFixture<TranslationsFixture>
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)

19
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueObjectValuesValidatorTests.cs

@ -18,7 +18,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact]
public async Task Should_not_add_errors_if_value_is_invalid()
{
var sut = new UniqueObjectValuesValidator(new[] { "myString" });
var sut = new UniqueObjectValuesValidator(["myString"]);
await sut.ValidateAsync(1, errors);
@ -28,7 +28,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact]
public async Task Should_not_add_errors_if_value_is_null()
{
var sut = new UniqueObjectValuesValidator(new[] { "myString" });
var sut = new UniqueObjectValuesValidator(["myString"]);
await sut.ValidateAsync(null, errors);
@ -38,7 +38,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact]
public async Task Should_not_add_error_if_objects_contain_not_duplicates()
{
var sut = new UniqueObjectValuesValidator(new[] { "myString" });
var sut = new UniqueObjectValuesValidator(["myString"]);
await sut.ValidateAsync(new[]
{
@ -55,7 +55,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact]
public async Task Should_not_add_error_if_objects_contain_unchecked_duplicates()
{
var sut = new UniqueObjectValuesValidator(new[] { "myString" });
var sut = new UniqueObjectValuesValidator(["myString"]);
await sut.ValidateAsync(new[]
{
@ -72,7 +72,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact]
public async Task Should_add_error_if_objects_contain_duplicates()
{
var sut = new UniqueObjectValuesValidator(new[] { "myString" });
var sut = new UniqueObjectValuesValidator(["myString"]);
await sut.ValidateAsync(new[]
{
@ -84,13 +84,13 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
errors);
errors.Should().BeEquivalentTo(
new[] { "Must not contain items with duplicate 'myString' fields." });
["Must not contain items with duplicate 'myString' fields."]);
}
[Fact]
public async Task Should_add_errors_if_objects_contain_multiple_duplicates()
{
var sut = new UniqueObjectValuesValidator(new[] { "myString", "myNumber" });
var sut = new UniqueObjectValuesValidator(["myString", "myNumber"]);
await sut.ValidateAsync(new[]
{
@ -104,10 +104,9 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
errors);
errors.Should().BeEquivalentTo(
new[]
{
[
"Must not contain items with duplicate 'myString' fields.",
"Must not contain items with duplicate 'myNumber' fields."
});
]);
}
}

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs

@ -73,7 +73,7 @@ public class UniqueValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync("hello", 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 == 'hello'", filter);
}
@ -88,7 +88,7 @@ public class UniqueValidatorTests : IClassFixture<TranslationsFixture>
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);
}

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValuesValidatorTests.cs

@ -52,6 +52,6 @@ public class UniqueValuesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new[] { 1, 2, 2, 3 }, errors);
errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." });
["Must not contain duplicate values."]);
}
}

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs

@ -33,9 +33,9 @@ public class AppEventDeleterTests : GivenContext
{
await sut.DeleteAppAsync(App, CancellationToken);
var streamFilter = StreamFilter.Prefix($"[a-zA-Z0-9]-{AppId.Id}");
A.CallTo(() => eventStore.DeleteAsync(streamFilter, A<CancellationToken>._))
.MustNotHaveHappened();
A.CallTo(() => eventStore.DeleteAsync(
A<StreamFilter>.That.Matches(x => x.Prefixes!.Contains($"([a-zA-Z0-9]+)-{AppId.Id}")),
CancellationToken))
.MustHaveHappened();
}
}

62
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<IDomainObjectFactory>();
private readonly IDeleter deleter1 = A.Fake<IDeleter>();
private readonly IDeleter deleter2 = A.Fake<IDeleter>();
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 @@ public class AppPermanentDeleterTests : GivenContext
}
[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<AppDomainObject>();
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<AppDomainObject>(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 @@ public class AppPermanentDeleterTests : GivenContext
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<AppDomainObject>();
A.CallTo(() => domainObject.Snapshot)
.Returns(App);
A.CallTo(() => domainObjectFactory.Create<AppDomainObject>(App.Id))
.Returns(domainObject);
}
}

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs

@ -333,7 +333,7 @@ public class BackupAppsTests : GivenContext
.Returns(true)
.AssignsOutAndRefParametersLazily(
new Func<string, RefToken, object[]>((x, _) =>
new[] { RefToken.User($"{x}_mapped") }));
[RefToken.User($"{x}_mapped")]));
A.CallTo(() => mapping.TryMap("notfound", out mapped))
.Returns(false);

17
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs

@ -502,7 +502,7 @@ public class AppDomainObjectTests : HandlerTestBase<App>
}
[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 class AppDomainObjectTests : HandlerTestBase<App>
.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<App>._, default))
.MustHaveHappened();
}
private Task ExecuteCreateAsync()
{
return PublishAsync(sut, new CreateApp { Name = AppId.Name, AppId = AppId.Id });

18
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs

@ -75,7 +75,7 @@ public class AssetUsageTrackerTests : GivenContext
Envelope.Create<IEvent>(@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 class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope });
await sut.On([envelope]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{
@ -151,7 +151,7 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope1, envelope2 });
await sut.On([envelope1, envelope2]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{
@ -202,7 +202,7 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope1, envelope2 });
await sut.On([envelope1, envelope2]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{
@ -250,8 +250,8 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope1 });
await sut.On(new[] { envelope2 });
await sut.On([envelope1]);
await sut.On([envelope2]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{
@ -290,7 +290,7 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { Envelope.Create<IEvent>(@event1), Envelope.Create<IEvent>(@event2) });
await sut.On([Envelope.Create<IEvent>(@event1), Envelope.Create<IEvent>(@event2)]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{
@ -325,7 +325,7 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope });
await sut.On([envelope]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{
@ -357,7 +357,7 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope });
await sut.On([envelope]);
update.Should().BeEquivalentTo(new Dictionary<string, int>
{

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs

@ -47,7 +47,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<Asset>
assetEnricher,
assetFileStore,
assetQuery,
ApiContextProvider, new[] { assetMetadataSource });
ApiContextProvider, [assetMetadataSource]);
}
[Fact]

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs

@ -99,7 +99,7 @@ public class FileTagAssetMetadataSourceTests : GivenContext
var formatted = sut.Format(source);
Assert.Equal(new[] { "128x55pt", "00:10:12" }, formatted);
Assert.Equal(["128x55pt", "00:10:12"], formatted);
}
[Fact]

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs

@ -167,7 +167,7 @@ public class ImageAssetMetadataSourceTests : GivenContext
var formatted = sut.Format(source);
Assert.Equal(new[] { "128x55px" }, formatted);
Assert.Equal(["128x55px"], formatted);
}
[Fact]

1
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs

@ -15,7 +15,6 @@ using Squidex.Infrastructure;
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;

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs

@ -19,7 +19,7 @@ public class AssetEnricherTests : GivenContext
var step1 = A.Fake<IAssetEnricherStep>();
var step2 = A.Fake<IAssetEnricherStep>();
var sut = new AssetEnricher(new[] { step1, step2 });
var sut = new AssetEnricher([step1, step2]);
await sut.EnrichAsync(assets, ApiContext, CancellationToken);
@ -44,7 +44,7 @@ public class AssetEnricherTests : GivenContext
var step1 = A.Fake<IAssetEnricherStep>();
var step2 = A.Fake<IAssetEnricherStep>();
var sut = new AssetEnricher(new[] { step1, step2 });
var sut = new AssetEnricher([step1, step2]);
await sut.EnrichAsync(source, ApiContext, CancellationToken);

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs

@ -217,7 +217,7 @@ public class AssetQueryServiceTests : GivenContext
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 class AssetQueryServiceTests : GivenContext
var actual = await sut.FindAssetFolderAsync(AppId.Id, folder3.Id, CancellationToken);
Assert.Equal(actual, new[] { folder1, folder2, folder3 });
Assert.Equal(actual, [folder1, folder2, folder3]);
}
[Fact]

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs

@ -38,7 +38,7 @@ public class CalculateTokensTests : GivenContext
{
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 class CalculateTokensTests : GivenContext
{
var asset = CreateAsset();
await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken);
await sut.EnrichAsync(FrontendContext, [asset], CancellationToken);
Assert.NotNull(asset.EditToken);

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs

@ -84,7 +84,7 @@ public class ConvertTagsTests : GivenContext
["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);

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs

@ -56,10 +56,10 @@ public class EnrichWithMetadataTextTests : GivenContext
};
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);

10
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs

@ -28,7 +28,7 @@ public class ScriptAssetTests : GivenContext
{
var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
await sut.EnrichAsync(ApiContext, [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
@ -41,7 +41,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset();
await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken);
await sut.EnrichAsync(FrontendContext, [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
@ -54,7 +54,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset();
await sut.EnrichAsync(ContextWithNoScript(), new[] { asset }, CancellationToken);
await sut.EnrichAsync(ContextWithNoScript(), [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
@ -67,7 +67,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
await sut.EnrichAsync(ApiContext, [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x =>
@ -88,7 +88,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
await sut.EnrichAsync(ApiContext, [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x =>

65
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<IContentRepository>();
private readonly IEventStore eventStore = A.Fake<IEventStore>();
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<StreamFilter>.That.Matches(x => x.Prefixes!.Contains($"content-{AppId.Id}--{id1}")),
CancellationToken))
.MustHaveHappened();
A.CallTo(() => eventStore.DeleteAsync(
A<StreamFilter>.That.Matches(x => x.Prefixes!.Contains($"content-{AppId.Id}--{id2}")),
CancellationToken))
.MustHaveHappened();
}
}

24
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs

@ -63,11 +63,11 @@ public class ContentsSearchSourceTests : GivenContext
.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 class ContentsSearchSourceTests : GivenContext
.AddField("field",
new ContentFieldData()
.AddInvariant("hello")),
ReferenceFields = new[]
{
ReferenceFields =
[
Fields.String(1, "field", Partitioning.Invariant)
},
],
SchemaId = schemaId1
};
@ -105,10 +105,10 @@ public class ContentsSearchSourceTests : GivenContext
.AddField("field",
new ContentFieldData()
.AddLocalized("en", "hello")),
ReferenceFields = new[]
{
ReferenceFields =
[
Fields.String(1, "field", Partitioning.Language)
},
],
SchemaId = schemaId1
};
@ -130,10 +130,10 @@ public class ContentsSearchSourceTests : GivenContext
.AddField("field",
new ContentFieldData()
.AddLocalized("en", "resolved")),
ReferenceFields = new[]
{
ReferenceFields =
[
Fields.String(1, "field", Partitioning.Language)
},
],
SchemaId = schemaId1
};

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs

@ -29,7 +29,7 @@ public class CalculateTokensTests : GivenContext
{
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 class CalculateTokensTests : GivenContext
{
var content = CreateContent();
await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken);
await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken);
Assert.NotNull(content.EditToken);

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs

@ -35,7 +35,7 @@ public class ContentEnricherTests : GivenContext
var step1 = A.Fake<IContentEnricherStep>();
var step2 = A.Fake<IContentEnricherStep>();
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 class ContentEnricherTests : GivenContext
var step1 = A.Fake<IContentEnricherStep>();
var step2 = A.Fake<IContentEnricherStep>();
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 class ContentEnricherTests : GivenContext
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);

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs

@ -25,7 +25,7 @@ public class EnrichWithSchemaTests : GivenContext
{
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 class EnrichWithSchemaTests : GivenContext
{
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 class EnrichWithSchemaTests : GivenContext
{
var content = CreateContent();
await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken);
await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken);
Assert.Equal("my-schema", content.SchemaDisplayName);
}

18
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs

@ -36,7 +36,7 @@ public class EnrichWithWorkflowsTests : GivenContext
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 class EnrichWithWorkflowsTests : GivenContext
{
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 class EnrichWithWorkflowsTests : GivenContext
{
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 class EnrichWithWorkflowsTests : GivenContext
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 class EnrichWithWorkflowsTests : GivenContext
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 class EnrichWithWorkflowsTests : GivenContext
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 class EnrichWithWorkflowsTests : GivenContext
A.CallTo(() => workflow.GetInfoAsync(content, content.Status))
.Returns(ValueTask.FromResult<StatusInfo?>(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 class EnrichWithWorkflowsTests : GivenContext
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 class EnrichWithWorkflowsTests : GivenContext
{
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);

12
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs

@ -31,7 +31,7 @@ public class ScriptContentTests : GivenContext
{
var content = CreateContent();
await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken);
await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
@ -47,7 +47,7 @@ public class ScriptContentTests : GivenContext
var content = CreateContent();
await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken);
await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
@ -63,7 +63,7 @@ public class ScriptContentTests : GivenContext
var content = CreateContent();
await sut.EnrichAsync(ContextWithNoScript(), new[] { content }, SchemaProvider(), CancellationToken);
await sut.EnrichAsync(ContextWithNoScript(), [content], SchemaProvider(), CancellationToken);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
@ -80,7 +80,7 @@ public class ScriptContentTests : GivenContext
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 class ScriptContentTests : GivenContext
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 class ScriptContentTests : GivenContext
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"]);
}

5
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<IContentRepository>();
public MongoTextIndexerState State { get; }
public MongoTextIndexerStateFixture()
@ -23,7 +26,7 @@ public sealed class MongoTextIndexerStateFixture : IAsyncLifetime
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()

39
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 @@ public class MongoTextIndexerStateTests(MongoTextIndexerStateFixture fixture) :
]);
var actual = await _.State.GetAsync(HashSet.Of(id1, id2));
actual.Should().BeEquivalentTo(new Dictionary<UniqueContentId, TextContentState>
{
[id1] = new TextContentState { UniqueContentId = id1, State = TextState.Stage0_Draft__Stage1_None },
@ -63,10 +63,12 @@ public class MongoTextIndexerStateTests(MongoTextIndexerStateFixture fixture) :
}
[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 @@ public class MongoTextIndexerStateTests(MongoTextIndexerStateFixture fixture) :
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<UniqueContentId, TextContentState>
{
[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<UniqueContentId, TextContentState>
{
[id3] = new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save