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; this.value = value;
#pragma warning disable MA0143 // Primary constructor parameters should be readonly
contentValue = newContentValue; contentValue = newContentValue;
#pragma warning restore MA0143 // Primary constructor parameters should be readonly
contentField.MarkChanged(); contentField.MarkChanged();
isChanged = true; 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); 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, public async Task DeleteAppAsync(DomainId appId,
CancellationToken ct) 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, public async Task<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q,
CancellationToken ct) 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); 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, public Task<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q, SearchScope scope,
CancellationToken ct = default) 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 System.Runtime.CompilerServices;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -14,6 +15,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
public sealed class QueryAsStream : OperationBase 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, public async IAsyncEnumerable<Content> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,
[EnumeratorCancellation] CancellationToken ct) [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) private static FilterDefinition<MongoContentEntity> CreateFilter(DomainId appId, HashSet<DomainId>? schemaIds)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> 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); 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) public async Task<List<Schema>> QueryAllAsync(DomainId appId, CancellationToken ct = default)
{ {
using (Telemetry.Activities.StartActivity("MongoSchemaRepository/QueryAllAsync")) 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"; return "SchemasHash";
} }
async Task IDeleter.DeleteAppAsync(App app, Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct) 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) 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.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Infrastructure; 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); return Shard(app.Id).SearchAsync(app, query, scope, ct);
} }
public async Task DeleteAppAsync(App app, async Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct) CancellationToken ct)
{ {
if (Shard(app.Id) is IDeleter shard) 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); 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.Bson.Serialization.Attributes;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -57,10 +58,16 @@ public abstract class MongoTextIndexBase<T>(IMongoDatabase database, string shar
return $"TextIndex2{shardKey}"; return $"TextIndex2{shardKey}";
} }
async Task IDeleter.DeleteAppAsync(App app, Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct) 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, 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.Bson.Serialization;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Apps; 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;
using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Domain.Apps.Entities.Contents.Text.State;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -15,7 +18,10 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Text; 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() static MongoTextIndexerState()
{ {
@ -30,6 +36,8 @@ public sealed class MongoTextIndexerState(IMongoDatabase database) : MongoReposi
}); });
} }
int IDeleter.Order => -2000;
protected override string CollectionName() protected override string CollectionName()
{ {
return "TextIndexerState"; return "TextIndexerState";
@ -46,6 +54,20 @@ public sealed class MongoTextIndexerState(IMongoDatabase database) : MongoReposi
await Collection.DeleteManyAsync(filter, ct); 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, public async Task<Dictionary<UniqueContentId, TextContentState>> GetAsync(HashSet<UniqueContentId> ids,
CancellationToken ct = default) 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -14,9 +15,15 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Apps; 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 IEnumerable<IDeleter> deleters = deleters.OrderBy(x => x.Order).ToList();
private readonly AppsOptions options = options.Value;
private readonly HashSet<string> consumingTypes = private readonly HashSet<string> consumingTypes =
[ [
typeRegistry.GetName<IEvent, AppDeleted>(), typeRegistry.GetName<IEvent, AppDeleted>(),
@ -39,8 +46,8 @@ public sealed class AppPermanentDeleter(IEnumerable<IDeleter> deleters, IDomainO
switch (@event.Payload) switch (@event.Payload)
{ {
case AppDeleted appArchived: case AppDeleted appDeleted:
await OnArchiveAsync(appArchived); await OnDeleteAsync(appDeleted);
break; break;
case AppContributorRemoved appContributorRemoved: case AppContributorRemoved appContributorRemoved:
await OnAppContributorRemoved(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"); // The user can either remove the app itself or via a global setting for all apps.
if (!appDeleted.Permanent && !options.DeletePermanent)
// Bypass our normal app resolve process, so that we can also retrieve the deleted app. {
var app = factory.Create<AppDomainObject>(appArchived.AppId.Id); return;
}
await app.EnsureLoadedAsync(); using var activity = Telemetry.Activities.StartActivity("RemoveAppFromSystem");
// If the app does not exist, the version is lower than zero. var app = await GetAppAsync(appDeleted.AppId.Id);
if (app.Version < 0) if (app == null)
{ {
return; 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 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 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 public interface IContentRepository
{ {
IAsyncEnumerable<DomainId> StreamIds(DomainId appId, DomainId schemaId, SearchScope scope,
CancellationToken ct = default);
IAsyncEnumerable<Content> StreamScheduledWithoutDataAsync(Instant now, SearchScope scope, IAsyncEnumerable<Content> StreamScheduledWithoutDataAsync(Instant now, SearchScope scope,
CancellationToken ct = default); 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.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities; namespace Squidex.Domain.Apps.Entities;
@ -17,6 +18,12 @@ public interface IDeleter
Task DeleteAppAsync(App app, Task DeleteAppAsync(App app,
CancellationToken ct); CancellationToken ct);
Task DeleteSchemaAsync(App app, Schema schema,
CancellationToken ct)
{
return Task.CompletedTask;
}
Task DeleteContributorAsync(DomainId appId, string contributorId, Task DeleteContributorAsync(DomainId appId, string contributorId,
CancellationToken ct) 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; namespace Squidex.Domain.Apps.Entities.Rules.Runner;
public sealed class RuleRunnerJob( public sealed class RuleRunnerJob : IJobRunner
IAppProvider appProvider,
IEventFormatter eventFormatter,
IEventStore eventStore,
IRuleEventRepository ruleEventRepository,
IRuleService ruleService,
IRuleUsageTracker ruleUsageTracker,
ILogger<RuleRunnerJob> log)
: IJobRunner
{ {
public const string TaskName = "run-rule"; public const string TaskName = "run-rule";
public const string ArgRuleId = "ruleId"; public const string ArgRuleId = "ruleId";
public const string ArgSnapshot = "snapshots"; public const string ArgSnapshot = "snapshots";
private const int MaxErrors = 10; 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 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) public static DomainId? GetRunningRuleId(Job job)
{ {
if (job.TaskName != TaskName || job.Status != JobStatus.Started) 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 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; 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) 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; 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) 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))] [EventType(nameof(AppDeleted))]
public sealed class AppDeleted : AppEvent 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))] [EventType(nameof(SchemaDeleted))]
public sealed class SchemaDeleted : SchemaEvent 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.Linq.Expressions;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure.States; 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( public async Task ClearAsync(
CancellationToken ct = default) CancellationToken ct = default)
{ {
var database = collection.Database;
await database.RunCommandAsync<BsonDocument>("{ profile : 0 }", cancellationToken: ct); await database.RunCommandAsync<BsonDocument>("{ profile : 0 }", cancellationToken: ct);
await database.DropCollectionAsync(ProfilerDocument.CollectionName, ct); await database.DropCollectionAsync(ProfilerDocument.CollectionName, ct);
await database.RunCommandAsync<BsonDocument>("{ profile : 2 }", cancellationToken: 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() 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. /// Delete the app.
/// </summary> /// </summary>
/// <param name="app">The name of the app to delete.</param> /// <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="204">App deleted.</response>
/// <response code="404">App not found.</response> /// <response code="404">App not found.</response>
[HttpDelete] [HttpDelete]
@ -227,9 +228,9 @@ public sealed class AppsController(ICommandBus commandBus, IAppProvider appProvi
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ApiPermission(PermissionIds.AppDelete)] [ApiPermission(PermissionIds.AppDelete)]
[ApiCosts(0)] [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); 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Areas.Api.Config.OpenApi; 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> /// </summary>
/// <param name="app">The name of the app.</param> /// <param name="app">The name of the app.</param>
/// <param name="schema">The name of the schema to delete.</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="204">Schema deleted.</response>
/// <response code="404">Schema or app not found.</response> /// <response code="404">Schema or app not found.</response>
[HttpDelete] [HttpDelete]
@ -292,9 +293,9 @@ public sealed class SchemasController(ICommandBus commandBus, IAppProvider appPr
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ApiPermissionOrAnonymous(PermissionIds.AppSchemasDelete)] [ApiPermissionOrAnonymous(PermissionIds.AppSchemasDelete)]
[ApiCosts(1)] [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); 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 serialized = jsonSerializer.SerializeToBytes(scheme);
var options = new DistributedCacheEntryOptions var cacheOptions = new DistributedCacheEntryOptions
{ {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
}; };
await dynamicCache.SetAsync(CacheKey(id), serialized, options, ct); await dynamicCache.SetAsync(CacheKey(id), serialized, cacheOptions, ct);
return id; return id;
} }
@ -188,7 +188,7 @@ public sealed class DynamicSchemeProvider(
{ {
var scheme = new AuthenticationScheme(name, config.DisplayName, typeof(DynamicOpenIdConnectHandler)); var scheme = new AuthenticationScheme(name, config.DisplayName, typeof(DynamicOpenIdConnectHandler));
var options = new DynamicOpenIdConnectOptions var oidcOptions = new DynamicOpenIdConnectOptions
{ {
Events = new OidcHandler(new MyIdentityOptions Events = new OidcHandler(new MyIdentityOptions
{ {
@ -204,9 +204,9 @@ public sealed class DynamicSchemeProvider(
SignedOutRedirectUri = new PathString($"/signout-callback-{name}") 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) 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) public static void AddSquidexApps(this IServiceCollection services, IConfiguration config)
{ {
if (config.GetValue<bool>("apps:deletePermanent")) services.Configure<AppsOptions>(config,
{ "apps");
services.AddSingletonAs<AppPermanentDeleter>()
.As<IEventConsumer>();
}
services.AddSingletonAs<RolePermissionsProvider>() services.AddSingletonAs<RolePermissionsProvider>()
.AsSelf(); .AsSelf();
services.AddSingletonAs<AppPermanentDeleter>()
.As<IEventConsumer>();
services.AddSingletonAs<AppEventDeleter>() services.AddSingletonAs<AppEventDeleter>()
.As<IDeleter>(); .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.History;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Config.Domain; namespace Squidex.Config.Domain;
public static class SchemasServices 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>() services.AddSingletonAs<SchemasSearchSource>()
.As<ISearchSource>(); .As<ISearchSource>();

2
backend/src/Squidex/Startup.cs

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

11
backend/src/Squidex/appsettings.json

@ -249,6 +249,13 @@
"deletePermanent": false "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": { "contents": {
// True to enable memory caching. // True to enable memory caching.
// //
@ -701,7 +708,7 @@
"You are a bot to generate images.", "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." "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": { "text": {
@ -710,7 +717,7 @@
"Say hello to the user and explain him about your capabilities in a single, short sentence.", "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." "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_2 = clients_1.Add("3", "secret3");
var clients_3 = clients_2.Revoke("2"); var clients_3 = clients_2.Revoke("2");
Assert.Equal(new[] { "1", "3" }, clients_3.Keys); Assert.Equal(["1", "3"], clients_3.Keys);
} }
[Fact] [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_3 = contributors_2.Assign("3", Role.Developer);
var contributors_4 = contributors_3.Remove("2"); var contributors_4 = contributors_3.Remove("2");
Assert.Equal(new[] { "1", "3" }, contributors_4.Keys); Assert.Equal(["1", "3"], contributors_4.Keys);
} }
[Fact] [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_2 = roles_1.Add("role2");
var roles_3 = roles_2.Remove(firstRole); 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] [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"); 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] [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_1 = schema_0.SetFieldsInLists(FieldNames.Create("2"));
var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("2")); var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("2"));
Assert.Equal(new[] { "2" }, schema_1.FieldsInLists); Assert.Equal(["2"], schema_1.FieldsInLists);
Assert.Equal(new[] { "2" }, schema_2.FieldsInLists); Assert.Equal(["2"], schema_2.FieldsInLists);
Assert.Same(schema_1, schema_2); 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_1 = schema_0.SetFieldsInLists(FieldNames.Create("2", "1"));
var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("1", "2")); var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("1", "2"));
Assert.Equal(new[] { "2", "1" }, schema_1.FieldsInLists); Assert.Equal(["2", "1"], schema_1.FieldsInLists);
Assert.Equal(new[] { "1", "2" }, schema_2.FieldsInLists); Assert.Equal(["1", "2"], schema_2.FieldsInLists);
Assert.NotSame(schema_1, schema_2); Assert.NotSame(schema_1, schema_2);
} }
@ -382,8 +382,8 @@ public class SchemaTests
var schema_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2")); var schema_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2"));
var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("2")); var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("2"));
Assert.Equal(new[] { "2" }, schema_1.FieldsInReferences); Assert.Equal(["2"], schema_1.FieldsInReferences);
Assert.Equal(new[] { "2" }, schema_2.FieldsInReferences); Assert.Equal(["2"], schema_2.FieldsInReferences);
Assert.Same(schema_1, schema_2); 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_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2", "1"));
var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("1", "2")); var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("1", "2"));
Assert.Equal(new[] { "2", "1" }, schema_1.FieldsInReferences); Assert.Equal(["2", "1"], schema_1.FieldsInReferences);
Assert.Equal(new[] { "1", "2" }, schema_2.FieldsInReferences); Assert.Equal(["1", "2"], schema_2.FieldsInReferences);
Assert.NotSame(schema_1, schema_2); 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>>(); var log = A.Fake<ILogger<RuleService>>();
sut = new RuleService(Options.Create(new RuleOptions()), sut = new RuleService(Options.Create(new RuleOptions()),
new[] { ruleTriggerHandler }, [ruleTriggerHandler],
new[] { ruleActionHandler }, [ruleActionHandler],
eventEnricher, TestUtils.DefaultSerializer, log, typeRegistry) eventEnricher, TestUtils.DefaultSerializer, log, typeRegistry)
{ {
Clock = clock 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(); 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(); 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); dataSchema = schema.BuildDataSchema(LanguagesConfig.English.ToResolver(), ResolvedComponents.Empty);
sut = new ScriptingCompleter(new[] { scriptDescriptor1, scriptDescriptor2 }); sut = new ScriptingCompleter([scriptDescriptor1, scriptDescriptor2]);
} }
[Fact] [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)) A.CallTo(() => creator2.CreateEnrichedEventsAsync(envelope, default))
.Returns(enrichedEvent); .Returns(enrichedEvent);
var sut = new EventMessageWrapper(envelope, new[] { creator1, creator2 }); var sut = new EventMessageWrapper(envelope, [creator1, creator2]);
var actual = await sut.CreatePayloadAsync(); var actual = await sut.CreatePayloadAsync();
@ -54,7 +54,7 @@ public class EventMessageWrapperTests
A.CallTo(() => creator1.Handles(envelope.Payload)) A.CallTo(() => creator1.Handles(envelope.Payload))
.Returns(false); .Returns(false);
var sut = new EventMessageWrapper(envelope, new[] { creator1 }); var sut = new EventMessageWrapper(envelope, [creator1]);
Assert.Null(await sut.CreatePayloadAsync()); 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); await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -83,7 +83,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors); await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -94,7 +94,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("invalid"), errors); await sut.ValidateAsync(JsonValue.Create("invalid"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of objects." }); ["Invalid json type, expected array of objects."]);
} }
[Fact] [Fact]
@ -105,7 +105,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Object(), Object()), errors); await sut.ValidateAsync(CreateValue(Object(), Object()), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." }); ["Must have at least 3 item(s)."]);
} }
[Fact] [Fact]
@ -116,7 +116,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Object(), Object()), errors); await sut.ValidateAsync(CreateValue(Object(), Object()), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -127,7 +127,7 @@ public class ArrayFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Object("myString", "1"), Object("myString", "1")), errors); await sut.ValidateAsync(CreateValue(Object("myString", "1"), Object("myString", "1")), errors);
errors.Should().BeEquivalentTo( 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) 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); await sut.ValidateAsync(CreateValue(null), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -115,7 +115,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors, factory: factory); await sut.ValidateAsync(CreateValue(), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -126,7 +126,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(asset1, asset2), errors, factory: factory); await sut.ValidateAsync(CreateValue(asset1, asset2), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." }); ["Must have at least 3 item(s)."]);
} }
[Fact] [Fact]
@ -137,7 +137,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(asset1, asset2), errors, factory: factory); await sut.ValidateAsync(CreateValue(asset1, asset2), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -148,7 +148,7 @@ public class AssetsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(asset1, asset1), errors, factory: factory); await sut.ValidateAsync(CreateValue(asset1, asset1), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." }); ["Must not contain duplicate values."]);
} }
private static JsonValue CreateValue(params DomainId[]? ids) 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); await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -62,7 +62,7 @@ public class BooleanFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); await sut.ValidateAsync(JsonValue.Create("Invalid"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected boolean." }); ["Invalid json type, expected boolean."]);
} }
private static JsonValue CreateValue(bool? v) 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); await sut.ValidateAsync(null, errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -67,7 +67,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(id.ToString(), "componentField", default), errors, components: components); await sut.ValidateAsync(CreateValue(id.ToString(), "componentField", default), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "componentField: Field is required." }); ["componentField: Field is required."]);
} }
[Fact] [Fact]
@ -78,7 +78,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components); await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json object, expected object with 'schemaId' field." }); ["Invalid json object, expected object with 'schemaId' field."]);
} }
[Fact] [Fact]
@ -89,7 +89,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null, "field", 1), errors, components: components); await sut.ValidateAsync(CreateValue(null, "field", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid component. No 'schemaId' field found." }); ["Invalid component. No 'schemaId' field found."]);
} }
[Fact] [Fact]
@ -100,7 +100,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("invalid", "field", 1), errors, components: components); await sut.ValidateAsync(CreateValue("invalid", "field", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." }); ["Invalid component. Cannot find schema."]);
} }
[Fact] [Fact]
@ -111,7 +111,7 @@ public class ComponentFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(schemaId1.ToString(), "field", 1), errors, components: components); await sut.ValidateAsync(CreateValue(schemaId1.ToString(), "field", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." }); ["Invalid component. Cannot find schema."]);
} }
[Fact] [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); await sut.ValidateAsync(null, errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -77,7 +77,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(1, id.ToString(), "componentField", default), errors, components: components); await sut.ValidateAsync(CreateValue(1, id.ToString(), "componentField", default), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[1].componentField: Field is required." }); ["[1].componentField: Field is required."]);
} }
[Fact] [Fact]
@ -88,7 +88,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components); await sut.ValidateAsync(JsonValue.Create("Invalid"), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of objects." }); ["Invalid json type, expected array of objects."]);
} }
[Fact] [Fact]
@ -99,7 +99,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync((JsonValue)JsonValue.Array(JsonValue.Create("Invalid")), errors, components: components); await sut.ValidateAsync((JsonValue)JsonValue.Array(JsonValue.Create("Invalid")), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json object, expected object with 'schemaId' field." }); ["Invalid json object, expected object with 'schemaId' field."]);
} }
[Fact] [Fact]
@ -110,7 +110,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(1, null, "field", 1), errors, components: components); await sut.ValidateAsync(CreateValue(1, null, "field", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid component. No 'schemaId' field found." }); ["Invalid component. No 'schemaId' field found."]);
} }
[Fact] [Fact]
@ -121,7 +121,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(1, "invalid", "field", 1), errors, components: components); await sut.ValidateAsync(CreateValue(1, "invalid", "field", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." }); ["Invalid component. Cannot find schema."]);
} }
[Fact] [Fact]
@ -132,7 +132,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(1, schemaId1.ToString(), "field", 1), errors, components: components); await sut.ValidateAsync(CreateValue(1, schemaId1.ToString(), "field", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid component. Cannot find schema." }); ["Invalid component. Cannot find schema."]);
} }
[Fact] [Fact]
@ -143,7 +143,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components); await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." }); ["Must have at least 3 item(s)."]);
} }
[Fact] [Fact]
@ -154,7 +154,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components); await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -165,7 +165,7 @@ public class ComponentsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components); await sut.ValidateAsync(CreateValue(2, id.ToString(), "componentField", 1), errors, components: components);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not contain items with duplicate 'componentField' fields." }); ["Must not contain items with duplicate 'componentField' fields."]);
} }
[Fact] [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); await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -54,7 +54,7 @@ public class DateTimeFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); await sut.ValidateAsync(CreateValue(FutureDays(0)), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { $"Must be greater or equal to {sut.Properties.MinValue}." }); [$"Must be greater or equal to {sut.Properties.MinValue}."]);
} }
[Fact] [Fact]
@ -65,7 +65,7 @@ public class DateTimeFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(FutureDays(20)), errors); await sut.ValidateAsync(CreateValue(FutureDays(20)), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { $"Must be less or equal to {FutureDays(10)}." }); [$"Must be less or equal to {FutureDays(10)}."]);
} }
[Fact] [Fact]
@ -76,7 +76,7 @@ public class DateTimeFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); await sut.ValidateAsync(JsonValue.Create("Invalid"), errors);
errors.Should().BeEquivalentTo( 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] [Fact]
@ -87,7 +87,7 @@ public class DateTimeFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create(123), errors); await sut.ValidateAsync(JsonValue.Create(123), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected string." }); ["Invalid json type, expected string."]);
} }
private static Instant FutureDays(int days) 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); await sut.ValidateAsync(CreateValue(200, 0), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Latitude must be between -90 and 90." }); ["Latitude must be between -90 and 90."]);
} }
[Fact] [Fact]
@ -80,7 +80,7 @@ public class GeolocationFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(0, 200), errors); await sut.ValidateAsync(CreateValue(0, 200), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Longitude must be between -180 and 180." }); ["Longitude must be between -180 and 180."]);
} }
[Fact] [Fact]
@ -91,7 +91,7 @@ public class GeolocationFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Null, errors); await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
private static JsonValue CreateValue(double lat, double lon) 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); await sut.ValidateAsync(CreateValue(JsonValue.Null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
private static JsonValue CreateValue(JsonValue v) 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); await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -53,7 +53,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(5), errors); await sut.ValidateAsync(CreateValue(5), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must be greater or equal to 10." }); ["Must be greater or equal to 10."]);
} }
[Fact] [Fact]
@ -64,7 +64,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(20), errors); await sut.ValidateAsync(CreateValue(20), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must be less or equal to 10." }); ["Must be less or equal to 10."]);
} }
[Fact] [Fact]
@ -75,7 +75,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(20), errors); await sut.ValidateAsync(CreateValue(20), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Not an allowed value." }); ["Not an allowed value."]);
} }
[Fact] [Fact]
@ -86,7 +86,7 @@ public class NumberFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); await sut.ValidateAsync(JsonValue.Create("Invalid"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected number." }); ["Invalid json type, expected number."]);
} }
private static JsonValue CreateValue(double v) 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 DomainId ref2 = DomainId.NewGuid();
private readonly IValidatorsFactory factory; 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) public IEnumerable<IValidator> CreateValueValidators(ValidationContext context, IField field, ValidatorFactory createFieldValidator)
{ {
if (field is IField<ReferencesFieldProperties> references) if (field is IField<ReferencesFieldProperties> references)
@ -105,7 +112,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors, factory: factory); await sut.ValidateAsync(CreateValue(null), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -116,7 +123,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors, factory: factory); await sut.ValidateAsync(CreateValue(), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -127,7 +134,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, factory: factory); await sut.ValidateAsync(CreateValue(ref1, ref2), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." }); ["Must have at least 3 item(s)."]);
} }
[Fact] [Fact]
@ -138,7 +145,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref2), errors, factory: factory); await sut.ValidateAsync(CreateValue(ref1, ref2), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -149,7 +156,7 @@ public class ReferencesFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref1), errors, factory: factory); await sut.ValidateAsync(CreateValue(ref1, ref1), errors, factory: factory);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." }); ["Must not contain duplicate values."]);
} }
private static JsonValue CreateValue(params DomainId[]? ids) 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); await sut.ValidateAsync(CreateValue(string.Empty, "unknown"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid rich text." }); ["Invalid rich text."]);
} }
[Fact] [Fact]
@ -62,7 +62,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(null), errors); await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -73,7 +73,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(string.Empty), errors); await sut.ValidateAsync(CreateValue(string.Empty), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -84,7 +84,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("123"), errors); await sut.ValidateAsync(CreateValue("123"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 10 character(s)." }); ["Must have at least 10 character(s)."]);
} }
[Fact] [Fact]
@ -95,7 +95,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("12345678"), errors); await sut.ValidateAsync(CreateValue("12345678"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 5 character(s)." }); ["Must not have more than 5 character(s)."]);
} }
[Fact] [Fact]
@ -106,7 +106,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("123"), errors); await sut.ValidateAsync(CreateValue("123"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 10 text character(s)." }); ["Must have at least 10 text character(s)."]);
} }
[Fact] [Fact]
@ -117,7 +117,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("12345678"), errors); await sut.ValidateAsync(CreateValue("12345678"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 5 text character(s)." }); ["Must not have more than 5 text character(s)."]);
} }
[Fact] [Fact]
@ -128,7 +128,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("word1 word2 word3"), errors); await sut.ValidateAsync(CreateValue("word1 word2 word3"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 10 word(s)." }); ["Must have at least 10 word(s)."]);
} }
[Fact] [Fact]
@ -139,7 +139,7 @@ public class RichTextFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("word1 word2 word3 word4 word5 word6"), errors); await sut.ValidateAsync(CreateValue("word1 word2 word3 word4 word5 word6"), errors);
errors.Should().BeEquivalentTo( 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) 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); await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -53,7 +53,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(string.Empty), errors); await sut.ValidateAsync(CreateValue(string.Empty), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -64,7 +64,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("123"), errors); await sut.ValidateAsync(CreateValue("123"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 10 character(s)." }); ["Must have at least 10 character(s)."]);
} }
[Fact] [Fact]
@ -75,7 +75,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("12345678"), errors); await sut.ValidateAsync(CreateValue("12345678"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 5 character(s)." }); ["Must not have more than 5 character(s)."]);
} }
[Fact] [Fact]
@ -86,7 +86,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("123"), errors); await sut.ValidateAsync(CreateValue("123"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 10 text character(s)." }); ["Must have at least 10 text character(s)."]);
} }
[Fact] [Fact]
@ -97,7 +97,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("123456"), errors); await sut.ValidateAsync(CreateValue("123456"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 5 text character(s)." }); ["Must not have more than 5 text character(s)."]);
} }
[Fact] [Fact]
@ -108,7 +108,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("word1 word2 word3"), errors); await sut.ValidateAsync(CreateValue("word1 word2 word3"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 10 word(s)." }); ["Must have at least 10 word(s)."]);
} }
[Fact] [Fact]
@ -119,7 +119,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("word1 word2 word3 word4 word5 word6"), errors); await sut.ValidateAsync(CreateValue("word1 word2 word3 word4 word5 word6"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 5 word(s)." }); ["Must not have more than 5 word(s)."]);
} }
[Fact] [Fact]
@ -130,7 +130,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("Bar"), errors); await sut.ValidateAsync(CreateValue("Bar"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Not an allowed value." }); ["Not an allowed value."]);
} }
[Fact] [Fact]
@ -141,7 +141,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("abc"), errors); await sut.ValidateAsync(CreateValue("abc"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must follow the pattern." }); ["Must follow the pattern."]);
} }
[Fact] [Fact]
@ -152,7 +152,7 @@ public class StringFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("abc"), errors); await sut.ValidateAsync(CreateValue("abc"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Custom Error Message." }); ["Custom Error Message."]);
} }
private static JsonValue CreateValue(string? v) 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); await sut.ValidateAsync(CreateValue(null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -73,7 +73,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors); await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -84,7 +84,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync((JsonValue)JsonValue.Array(JsonValue.Null), errors); await sut.ValidateAsync((JsonValue)JsonValue.Array(JsonValue.Null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of strings." }); ["Invalid json type, expected array of strings."]);
} }
[Fact] [Fact]
@ -95,7 +95,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(string.Empty), errors); await sut.ValidateAsync(CreateValue(string.Empty), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of strings." }); ["Invalid json type, expected array of strings."]);
} }
[Fact] [Fact]
@ -106,7 +106,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.Create("invalid"), errors); await sut.ValidateAsync(JsonValue.Create("invalid"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Invalid json type, expected array of strings." }); ["Invalid json type, expected array of strings."]);
} }
[Fact] [Fact]
@ -117,7 +117,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors); await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 3 item(s)." }); ["Must have at least 3 item(s)."]);
} }
[Fact] [Fact]
@ -128,7 +128,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors); await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -139,7 +139,7 @@ public class TagsFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue("tag-1", "tag-2", null), errors); await sut.ValidateAsync(CreateValue("tag-1", "tag-2", null), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[1]: Not an allowed value." }); ["[1]: Not an allowed value."]);
} }
private static JsonValue CreateValue(params string?[]? ids) 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); await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Value must not be defined." }); ["Value must not be defined."]);
} }
[Fact] [Fact]
@ -55,7 +55,7 @@ public class UIFieldTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.True, errors); await sut.ValidateAsync(JsonValue.True, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Value must not be defined." }); ["Value must not be defined."]);
} }
[Fact] [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); await sut.ValidateAsync(50, errors);
errors.Should().BeEquivalentTo( 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); await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -101,7 +101,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(id), errors); await sut.ValidateAsync(CreateValue(id), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { $"[1]: Id {id} not found." }); [$"[1]: Id {id} not found."]);
} }
[Fact] [Fact]
@ -112,7 +112,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors);
errors.Should().BeEquivalentTo( 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] [Fact]
@ -123,7 +123,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors);
errors.Should().BeEquivalentTo( 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] [Fact]
@ -134,7 +134,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[1]: Not of expected type: Image." }); ["[1]: Not of expected type: Image."]);
} }
[Theory] [Theory]
@ -146,7 +146,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[2]: Width 800px must be greater than 1000px." }); ["[2]: Width 800px must be greater than 1000px."]);
} }
[Theory] [Theory]
@ -158,7 +158,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[2]: Width 800px must be less than 700px." }); ["[2]: Width 800px must be less than 700px."]);
} }
[Theory] [Theory]
@ -170,7 +170,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[2]: Height 600px must be greater than 800px." }); ["[2]: Height 600px must be greater than 800px."]);
} }
[Theory] [Theory]
@ -182,7 +182,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[2]: Height 600px must be less than 500px." }); ["[2]: Height 600px must be less than 500px."]);
} }
[Theory] [Theory]
@ -194,7 +194,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "[2]: Must have aspect ratio 1:1." }); ["[2]: Must have aspect ratio 1:1."]);
} }
[Fact] [Fact]
@ -205,7 +205,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Image1.Id), errors); await sut.ValidateAsync(CreateValue(Image1.Id), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2 item(s)." }); ["Must have at least 2 item(s)."]);
} }
[Fact] [Fact]
@ -216,7 +216,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Image1.Id, Image2.Id), errors); await sut.ValidateAsync(CreateValue(Image1.Id, Image2.Id), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -227,7 +227,7 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Image1.Id, Image1.Id), errors); await sut.ValidateAsync(CreateValue(Image1.Id, Image1.Id), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." }); ["Must not contain duplicate values."]);
} }
[Fact] [Fact]
@ -238,11 +238,10 @@ public class AssetsValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] [
{
"[1]: Must be an allowed extension.", "[1]: Must be an allowed extension.",
"[2]: Must be an allowed extension." "[2]: Must be an allowed extension."
}); ]);
} }
private static object CreateValue(params DomainId[] ids) 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); await sut.ValidateAsync(new List<int> { 2, 1, 4, 5 }, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] [
{
"[2]: Must be between 2 and 4.", "[2]: Must be between 2 and 4.",
"[4]: 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); await sut.ValidateAsync(null, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -60,7 +60,7 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 1 }, errors); await sut.ValidateAsync(new List<int> { 1 }, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have exactly 2 item(s)." }); ["Must have exactly 2 item(s)."]);
} }
[Fact] [Fact]
@ -71,7 +71,7 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 1 }, errors); await sut.ValidateAsync(new List<int> { 1 }, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2 item(s)." }); ["Must have at least 2 item(s)."]);
} }
[Fact] [Fact]
@ -82,7 +82,7 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 1, 2, 3, 4 }, errors); await sut.ValidateAsync(new List<int> { 1, 2, 3, 4 }, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 3 item(s)." }); ["Must not have more than 3 item(s)."]);
} }
[Fact] [Fact]
@ -93,6 +93,6 @@ public class CollectionValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(new List<int> { 1 }, errors); await sut.ValidateAsync(new List<int> { 1 }, errors);
errors.Should().BeEquivalentTo( 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); await sut.ValidateAsync(JsonValue.Null, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Value must not be defined." }); ["Value must not be defined."]);
} }
[Fact] [Fact]
@ -45,6 +45,6 @@ public class NoValueValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(JsonValue.True, errors); await sut.ValidateAsync(JsonValue.True, errors);
errors.Should().BeEquivalentTo( 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); await sut.ValidateAsync("foo", errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must follow the pattern." }); ["Must follow the pattern."]);
} }
[Fact] [Fact]
@ -63,7 +63,7 @@ public class PatternValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync("foo", errors); await sut.ValidateAsync("foo", errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Custom Error Message." }); ["Custom Error Message."]);
} }
[Fact] [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); 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( 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); await sut.ValidateAsync(1500, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must be exactly 2000." }); ["Must be exactly 2000."]);
} }
[Fact] [Fact]
@ -64,7 +64,7 @@ public class RangeValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(1500, errors); await sut.ValidateAsync(1500, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must be greater or equal to 2000." }); ["Must be greater or equal to 2000."]);
} }
[Fact] [Fact]
@ -75,6 +75,6 @@ public class RangeValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(1500, errors); await sut.ValidateAsync(1500, errors);
errors.Should().BeEquivalentTo( 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); await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -117,7 +117,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(), errors); await sut.ValidateAsync(CreateValue(), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -130,7 +130,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1), errors); await sut.ValidateAsync(CreateValue(ref1), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { $"[1]: Reference '{ref1}' not found." }); [$"[1]: Reference '{ref1}' not found."]);
} }
[Fact] [Fact]
@ -143,7 +143,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref2), errors); await sut.ValidateAsync(CreateValue(ref2), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { $"[1]: Reference '{ref2}' has invalid schema." }); [$"[1]: Reference '{ref2}' has invalid schema."]);
} }
[Fact] [Fact]
@ -156,7 +156,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref2), errors); await sut.ValidateAsync(CreateValue(ref2), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2 item(s)." }); ["Must have at least 2 item(s)."]);
} }
[Fact] [Fact]
@ -169,7 +169,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref2), errors); await sut.ValidateAsync(CreateValue(ref1, ref2), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2 item(s)." }); ["Must have at least 2 item(s)."]);
} }
[Fact] [Fact]
@ -182,7 +182,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref2), errors); await sut.ValidateAsync(CreateValue(ref1, ref2), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1 item(s)." }); ["Must not have more than 1 item(s)."]);
} }
[Fact] [Fact]
@ -195,7 +195,7 @@ public class ReferencesValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateValue(ref1, ref1), errors); await sut.ValidateAsync(CreateValue(ref1, ref1), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not contain duplicate values." }); ["Must not contain duplicate values."]);
} }
private static List<DomainId> CreateValue(params DomainId[] ids) 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); await sut.ValidateAsync(string.Empty, errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Field is required." }); ["Field is required."]);
} }
[Fact] [Fact]
@ -67,6 +67,6 @@ public class RequiredStringValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(null, errors); await sut.ValidateAsync(null, errors);
errors.Should().BeEquivalentTo( 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); await sut.ValidateAsync(null, errors);
errors.Should().BeEquivalentTo( 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); await sut.ValidateAsync(CreateString(4), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have exactly 2000 character(s)." }); ["Must have exactly 2000 character(s)."]);
} }
[Fact] [Fact]
@ -75,7 +75,7 @@ public class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateString(1500), errors); await sut.ValidateAsync(CreateString(1500), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2000 character(s)." }); ["Must have at least 2000 character(s)."]);
} }
[Fact] [Fact]
@ -86,7 +86,7 @@ public class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateString(1500), errors); await sut.ValidateAsync(CreateString(1500), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1000 character(s)." }); ["Must not have more than 1000 character(s)."]);
} }
[Fact] [Fact]
@ -97,7 +97,7 @@ public class StringLengthValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateString(1), errors); await sut.ValidateAsync(CreateString(1), errors);
errors.Should().BeEquivalentTo( 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) 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); await sut.ValidateAsync(CreateString(4), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have exactly 5 text character(s)." }); ["Must have exactly 5 text character(s)."]);
} }
[Fact] [Fact]
@ -82,7 +82,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateString(1500), errors); await sut.ValidateAsync(CreateString(1500), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2000 text character(s)." }); ["Must have at least 2000 text character(s)."]);
} }
[Fact] [Fact]
@ -93,7 +93,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateString(1500), errors); await sut.ValidateAsync(CreateString(1500), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1000 text character(s)." }); ["Must not have more than 1000 text character(s)."]);
} }
[Fact] [Fact]
@ -104,7 +104,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateString(1), errors); await sut.ValidateAsync(CreateString(1), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have between 2000 and 5000 text character(s)." }); ["Must have between 2000 and 5000 text character(s)."]);
} }
[Theory] [Theory]
@ -129,7 +129,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateSentence(4), errors); await sut.ValidateAsync(CreateSentence(4), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have exactly 5 word(s)." }); ["Must have exactly 5 word(s)."]);
} }
[Fact] [Fact]
@ -140,7 +140,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateSentence(1500), errors); await sut.ValidateAsync(CreateSentence(1500), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must have at least 2000 word(s)." }); ["Must have at least 2000 word(s)."]);
} }
[Fact] [Fact]
@ -151,7 +151,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateSentence(1500), errors); await sut.ValidateAsync(CreateSentence(1500), errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not have more than 1000 word(s)." }); ["Must not have more than 1000 word(s)."]);
} }
[Fact] [Fact]
@ -162,7 +162,7 @@ public class StringTextValidatorTests : IClassFixture<TranslationsFixture>
await sut.ValidateAsync(CreateSentence(1), errors); await sut.ValidateAsync(CreateSentence(1), errors);
errors.Should().BeEquivalentTo( 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) 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] [Fact]
public async Task Should_not_add_errors_if_value_is_invalid() 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); await sut.ValidateAsync(1, errors);
@ -28,7 +28,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact] [Fact]
public async Task Should_not_add_errors_if_value_is_null() 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); await sut.ValidateAsync(null, errors);
@ -38,7 +38,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact] [Fact]
public async Task Should_not_add_error_if_objects_contain_not_duplicates() 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[] await sut.ValidateAsync(new[]
{ {
@ -55,7 +55,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact] [Fact]
public async Task Should_not_add_error_if_objects_contain_unchecked_duplicates() 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[] await sut.ValidateAsync(new[]
{ {
@ -72,7 +72,7 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
[Fact] [Fact]
public async Task Should_add_error_if_objects_contain_duplicates() 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[] await sut.ValidateAsync(new[]
{ {
@ -84,13 +84,13 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
errors); errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] { "Must not contain items with duplicate 'myString' fields." }); ["Must not contain items with duplicate 'myString' fields."]);
} }
[Fact] [Fact]
public async Task Should_add_errors_if_objects_contain_multiple_duplicates() 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[] await sut.ValidateAsync(new[]
{ {
@ -104,10 +104,9 @@ public class UniqueObjectValuesValidatorTests : IClassFixture<TranslationsFixtur
errors); errors);
errors.Should().BeEquivalentTo( errors.Should().BeEquivalentTo(
new[] [
{
"Must not contain items with duplicate 'myString' fields.", "Must not contain items with duplicate 'myString' fields.",
"Must not contain items with duplicate 'myNumber' 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")); await sut.ValidateAsync("hello", errors, updater: c => c.Nested("property").Nested("iv"));
errors.Should().BeEquivalentTo( 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); 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")); await sut.ValidateAsync(12.5, errors, updater: c => c.Nested("property").Nested("iv"));
errors.Should().BeEquivalentTo( 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); 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); await sut.ValidateAsync(new[] { 1, 2, 2, 3 }, errors);
errors.Should().BeEquivalentTo( 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); await sut.DeleteAppAsync(App, CancellationToken);
var streamFilter = StreamFilter.Prefix($"[a-zA-Z0-9]-{AppId.Id}"); A.CallTo(() => eventStore.DeleteAsync(
A<StreamFilter>.That.Matches(x => x.Prefixes!.Contains($"([a-zA-Z0-9]+)-{AppId.Id}")),
A.CallTo(() => eventStore.DeleteAsync(streamFilter, A<CancellationToken>._)) CancellationToken))
.MustNotHaveHappened(); .MustHaveHappened();
} }
} }

62
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
@ -19,11 +20,12 @@ public class AppPermanentDeleterTests : GivenContext
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IDeleter deleter1 = A.Fake<IDeleter>(); private readonly IDeleter deleter1 = A.Fake<IDeleter>();
private readonly IDeleter deleter2 = A.Fake<IDeleter>(); private readonly IDeleter deleter2 = A.Fake<IDeleter>();
private readonly AppsOptions options = new AppsOptions();
private readonly AppPermanentDeleter sut; private readonly AppPermanentDeleter sut;
public AppPermanentDeleterTests() public AppPermanentDeleterTests()
{ {
sut = new AppPermanentDeleter(new[] { deleter1, deleter2 }, domainObjectFactory, TestUtils.TypeRegistry); sut = new AppPermanentDeleter([deleter1, deleter2], Options.Create(options), domainObjectFactory, TestUtils.TypeRegistry);
} }
[Fact] [Fact]
@ -97,19 +99,34 @@ public class AppPermanentDeleterTests : GivenContext
} }
[Fact] [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) await sut.On(Envelope.Create(new AppDeleted
.Returns(App); {
AppId = AppId,
Permanent = false
}));
A.CallTo(() => domainObjectFactory.Create<AppDomainObject>(App.Id)) A.CallTo(() => deleter1.DeleteAppAsync(App, default))
.Returns(domainObject); .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 await sut.On(Envelope.Create(new AppDeleted
{ {
AppId = AppId AppId = AppId,
Permanent = true
})); }));
A.CallTo(() => deleter1.DeleteAppAsync(App, default)) A.CallTo(() => deleter1.DeleteAppAsync(App, default))
@ -118,4 +135,33 @@ public class AppPermanentDeleterTests : GivenContext
A.CallTo(() => deleter2.DeleteAppAsync(App, default)) A.CallTo(() => deleter2.DeleteAppAsync(App, default))
.MustHaveHappened(); .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) .Returns(true)
.AssignsOutAndRefParametersLazily( .AssignsOutAndRefParametersLazily(
new Func<string, RefToken, object[]>((x, _) => new Func<string, RefToken, object[]>((x, _) =>
new[] { RefToken.User($"{x}_mapped") })); [RefToken.User($"{x}_mapped")]));
A.CallTo(() => mapping.TryMap("notfound", out mapped)) A.CallTo(() => mapping.TryMap("notfound", out mapped))
.Returns(false); .Returns(false);

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

@ -502,7 +502,7 @@ public class AppDomainObjectTests : HandlerTestBase<App>
} }
[Fact] [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(); var command = new DeleteApp();
@ -516,6 +516,21 @@ public class AppDomainObjectTests : HandlerTestBase<App>
.MustHaveHappened(); .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() private Task ExecuteCreateAsync()
{ {
return PublishAsync(sut, new CreateApp { Name = AppId.Name, AppId = AppId.Id }); 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) Envelope.Create<IEvent>(@event)
.SetTimestamp(Instant.FromDateTimeUtc(DateTime.UtcNow.Date.AddDays(13))); .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)) A.CallTo(() => assetUsageTracker.TrackAsync(AppId.Id, date, sizeDiff, countDiff, default))
.MustHaveHappened(); .MustHaveHappened();
@ -104,7 +104,7 @@ public class AssetUsageTrackerTests : GivenContext
A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .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> 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)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .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> 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)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .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> 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)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); });
await sut.On(new[] { envelope1 }); await sut.On([envelope1]);
await sut.On(new[] { envelope2 }); await sut.On([envelope2]);
update.Should().BeEquivalentTo(new Dictionary<string, int> 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)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .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> 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)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .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> 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)) A.CallTo(() => tagService.UpdateAsync(AppId.Id, TagGroups.Assets, A<Dictionary<string, int>>._, default))
.Invokes(x => { update = x.GetArgument<Dictionary<string, int>>(2); }); .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> 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, assetEnricher,
assetFileStore, assetFileStore,
assetQuery, assetQuery,
ApiContextProvider, new[] { assetMetadataSource }); ApiContextProvider, [assetMetadataSource]);
} }
[Fact] [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); var formatted = sut.Format(source);
Assert.Equal(new[] { "128x55pt", "00:10:12" }, formatted); Assert.Equal(["128x55pt", "00:10:12"], formatted);
} }
[Fact] [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); var formatted = sut.Format(source);
Assert.Equal(new[] { "128x55px" }, formatted); Assert.Equal(["128x55px"], formatted);
} }
[Fact] [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.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using static Squidex.Infrastructure.MongoDb.MongoQueryTests;
using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter;
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; 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 step1 = A.Fake<IAssetEnricherStep>();
var step2 = 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); await sut.EnrichAsync(assets, ApiContext, CancellationToken);
@ -44,7 +44,7 @@ public class AssetEnricherTests : GivenContext
var step1 = A.Fake<IAssetEnricherStep>(); var step1 = A.Fake<IAssetEnricherStep>();
var step2 = 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); 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); var actual = await sut.FindAssetFolderAsync(AppId.Id, folder1.Id, CancellationToken);
Assert.Equal(actual, new[] { folder1 }); Assert.Equal(actual, [folder1]);
} }
[Fact] [Fact]
@ -238,7 +238,7 @@ public class AssetQueryServiceTests : GivenContext
var actual = await sut.FindAssetFolderAsync(AppId.Id, folder3.Id, CancellationToken); var actual = await sut.FindAssetFolderAsync(AppId.Id, folder3.Id, CancellationToken);
Assert.Equal(actual, new[] { folder1, folder2, folder3 }); Assert.Equal(actual, [folder1, folder2, folder3]);
} }
[Fact] [Fact]

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

@ -38,7 +38,7 @@ public class CalculateTokensTests : GivenContext
{ {
var asset = CreateAsset(); var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); await sut.EnrichAsync(ApiContext, [asset], CancellationToken);
Assert.NotNull(asset.EditToken); Assert.NotNull(asset.EditToken);
@ -51,7 +51,7 @@ public class CalculateTokensTests : GivenContext
{ {
var asset = CreateAsset(); var asset = CreateAsset();
await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken); await sut.EnrichAsync(FrontendContext, [asset], CancellationToken);
Assert.NotNull(asset.EditToken); 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" ["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(["name1", "name2"], asset1.TagNames);
Assert.Equal(["name2", "name3"], asset2.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)) A.CallTo(() => assetMetadataSource1.Format(asset))
.Returns(new[] { "metadata1" }); .Returns(["metadata1"]);
A.CallTo(() => assetMetadataSource2.Format(asset)) A.CallTo(() => assetMetadataSource2.Format(asset))
.Returns(new[] { "metadata2", "metadata3" }); .Returns(["metadata2", "metadata3"]);
await sut.EnrichAsync(FrontendContext, Enumerable.Repeat(asset, 1), CancellationToken); 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(); 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>._)) A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -41,7 +41,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset(); 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>._)) A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -54,7 +54,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset(); 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>._)) A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -67,7 +67,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset(); var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); await sut.EnrichAsync(ApiContext, [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync( A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x => A<AssetScriptVars>.That.Matches(x =>
@ -88,7 +88,7 @@ public class ScriptAssetTests : GivenContext
var asset = CreateAsset(); var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken); await sut.EnrichAsync(ApiContext, [asset], CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync( A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x => 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", .AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddInvariant("world")), .AddInvariant("world")),
ReferenceFields = new[] ReferenceFields =
{ [
Fields.String(1, "field1", Partitioning.Invariant), Fields.String(1, "field1", Partitioning.Invariant),
Fields.String(2, "field2", Partitioning.Invariant) Fields.String(2, "field2", Partitioning.Invariant)
}, ],
SchemaId = schemaId1 SchemaId = schemaId1
}; };
@ -84,10 +84,10 @@ public class ContentsSearchSourceTests : GivenContext
.AddField("field", .AddField("field",
new ContentFieldData() new ContentFieldData()
.AddInvariant("hello")), .AddInvariant("hello")),
ReferenceFields = new[] ReferenceFields =
{ [
Fields.String(1, "field", Partitioning.Invariant) Fields.String(1, "field", Partitioning.Invariant)
}, ],
SchemaId = schemaId1 SchemaId = schemaId1
}; };
@ -105,10 +105,10 @@ public class ContentsSearchSourceTests : GivenContext
.AddField("field", .AddField("field",
new ContentFieldData() new ContentFieldData()
.AddLocalized("en", "hello")), .AddLocalized("en", "hello")),
ReferenceFields = new[] ReferenceFields =
{ [
Fields.String(1, "field", Partitioning.Language) Fields.String(1, "field", Partitioning.Language)
}, ],
SchemaId = schemaId1 SchemaId = schemaId1
}; };
@ -130,10 +130,10 @@ public class ContentsSearchSourceTests : GivenContext
.AddField("field", .AddField("field",
new ContentFieldData() new ContentFieldData()
.AddLocalized("en", "resolved")), .AddLocalized("en", "resolved")),
ReferenceFields = new[] ReferenceFields =
{ [
Fields.String(1, "field", Partitioning.Language) Fields.String(1, "field", Partitioning.Language)
}, ],
SchemaId = schemaId1 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(); var content = CreateContent();
await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken);
Assert.NotNull(content.EditToken); Assert.NotNull(content.EditToken);
@ -42,7 +42,7 @@ public class CalculateTokensTests : GivenContext
{ {
var content = CreateContent(); var content = CreateContent();
await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken);
Assert.NotNull(content.EditToken); 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 step1 = A.Fake<IContentEnricherStep>();
var step2 = 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); await sut.EnrichAsync(source, ApiContext, CancellationToken);
@ -60,7 +60,7 @@ public class ContentEnricherTests : GivenContext
var step1 = A.Fake<IContentEnricherStep>(); var step1 = A.Fake<IContentEnricherStep>();
var step2 = 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); await sut.EnrichAsync(source, false, ApiContext, CancellationToken);
@ -85,7 +85,7 @@ public class ContentEnricherTests : GivenContext
var step1 = new ResolveSchema(); var step1 = new ResolveSchema();
var step2 = 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); 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(); var content = CreateContent();
await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(FrontendContext, [content], SchemaProvider(), CancellationToken);
Assert.NotNull(content.ReferenceFields); Assert.NotNull(content.ReferenceFields);
} }
@ -35,7 +35,7 @@ public class EnrichWithSchemaTests : GivenContext
{ {
var content = CreateContent(); var content = CreateContent();
await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken);
Assert.Null(content.ReferenceFields); Assert.Null(content.ReferenceFields);
} }
@ -45,7 +45,7 @@ public class EnrichWithSchemaTests : GivenContext
{ {
var content = CreateContent(); var content = CreateContent();
await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(ApiContext, [content], SchemaProvider(), CancellationToken);
Assert.Equal("my-schema", content.SchemaDisplayName); 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)) A.CallTo(() => workflow.GetNextAsync(content, content.Status, FrontendContext.UserPrincipal))
.Returns(nexts); .Returns(nexts);
await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken);
Assert.Equal(nexts, content.NextStatuses); Assert.Equal(nexts, content.NextStatuses);
} }
@ -46,7 +46,7 @@ public class EnrichWithWorkflowsTests : GivenContext
{ {
var content = CreateContent() with { IsSingleton = true, Status = Status.Draft }; 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); Assert.Equal(Status.Published, content.NextStatuses?.Single().Status);
@ -59,7 +59,7 @@ public class EnrichWithWorkflowsTests : GivenContext
{ {
var content = CreateContent() with { IsSingleton = true }; 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!); Assert.Empty(content.NextStatuses!);
@ -75,7 +75,7 @@ public class EnrichWithWorkflowsTests : GivenContext
A.CallTo(() => workflow.GetInfoAsync(content, content.Status)) A.CallTo(() => workflow.GetInfoAsync(content, content.Status))
.Returns(new StatusInfo(Status.Published, StatusColors.Published)); .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); Assert.Equal(StatusColors.Published, content.StatusColor);
} }
@ -88,7 +88,7 @@ public class EnrichWithWorkflowsTests : GivenContext
A.CallTo(() => workflow.GetInfoAsync(content, content.NewStatus!.Value)) A.CallTo(() => workflow.GetInfoAsync(content, content.NewStatus!.Value))
.Returns(new StatusInfo(Status.Published, StatusColors.Archived)); .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); Assert.Equal(StatusColors.Archived, content.NewStatusColor);
} }
@ -101,7 +101,7 @@ public class EnrichWithWorkflowsTests : GivenContext
A.CallTo(() => workflow.GetInfoAsync(content, content.ScheduleJob.Status)) A.CallTo(() => workflow.GetInfoAsync(content, content.ScheduleJob.Status))
.Returns(new StatusInfo(Status.Published, StatusColors.Archived)); .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); Assert.Equal(StatusColors.Archived, content.ScheduledStatusColor);
} }
@ -114,7 +114,7 @@ public class EnrichWithWorkflowsTests : GivenContext
A.CallTo(() => workflow.GetInfoAsync(content, content.Status)) A.CallTo(() => workflow.GetInfoAsync(content, content.Status))
.Returns(ValueTask.FromResult<StatusInfo?>(null!)); .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); Assert.Equal(StatusColors.Draft, content.StatusColor);
} }
@ -127,7 +127,7 @@ public class EnrichWithWorkflowsTests : GivenContext
A.CallTo(() => workflow.CanUpdateAsync(content, content.Status, FrontendContext.UserPrincipal)) A.CallTo(() => workflow.CanUpdateAsync(content, content.Status, FrontendContext.UserPrincipal))
.Returns(true); .Returns(true);
await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); await sut.EnrichAsync(FrontendContext, [content], null!, CancellationToken);
Assert.True(content.CanUpdate); Assert.True(content.CanUpdate);
} }
@ -137,7 +137,7 @@ public class EnrichWithWorkflowsTests : GivenContext
{ {
var content = CreateContent(); 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); 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(); 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>._)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -47,7 +47,7 @@ public class ScriptContentTests : GivenContext
var content = CreateContent(); 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>._)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -63,7 +63,7 @@ public class ScriptContentTests : GivenContext
var content = CreateContent(); 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>._)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -80,7 +80,7 @@ public class ScriptContentTests : GivenContext
var contentBefore = CreateContent(); var contentBefore = CreateContent();
var contentData = contentBefore.Data; var contentData = contentBefore.Data;
await sut.EnrichAsync(ApiContext, new[] { contentBefore }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(ApiContext, [contentBefore], SchemaProvider(), CancellationToken);
Assert.NotSame(contentBefore.Data, contentData); Assert.NotSame(contentBefore.Data, contentData);
@ -109,7 +109,7 @@ public class ScriptContentTests : GivenContext
var contentBefore = CreateContent(); var contentBefore = CreateContent();
var contentData = contentBefore.Data; var contentData = contentBefore.Data;
await sut.EnrichAsync(ApiContext, new[] { contentBefore }, SchemaProvider(), CancellationToken); await sut.EnrichAsync(ApiContext, [contentBefore], SchemaProvider(), CancellationToken);
Assert.NotSame(contentBefore.Data, contentData); Assert.NotSame(contentBefore.Data, contentData);
@ -160,7 +160,7 @@ public class ScriptContentTests : GivenContext
var sut2 = new ScriptContent(realScriptEngine); 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"]); 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.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.MongoDb.Text; using Squidex.Domain.Apps.Entities.MongoDb.Text;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
@ -14,6 +15,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text;
public sealed class MongoTextIndexerStateFixture : IAsyncLifetime public sealed class MongoTextIndexerStateFixture : IAsyncLifetime
{ {
public IContentRepository ContentRepository { get; } = A.Fake<IContentRepository>();
public MongoTextIndexerState State { get; } public MongoTextIndexerState State { get; }
public MongoTextIndexerStateFixture() public MongoTextIndexerStateFixture()
@ -23,7 +26,7 @@ public sealed class MongoTextIndexerStateFixture : IAsyncLifetime
var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["mongoDb:configuration"]!); var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["mongoDb:configuration"]!);
var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["mongodb:database"]!); var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["mongodb:database"]!);
State = new MongoTextIndexerState(mongoDatabase); State = new MongoTextIndexerState(mongoDatabase, ContentRepository);
} }
public Task InitializeAsync() 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 #pragma warning disable SA1300 // Element should begin with upper-case letter
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Domain.Apps.Entities.Contents.Text.State;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -34,7 +35,6 @@ public class MongoTextIndexerStateTests(MongoTextIndexerStateFixture fixture) :
]); ]);
var actual = await _.State.GetAsync(HashSet.Of(id1, id2)); var actual = await _.State.GetAsync(HashSet.Of(id1, id2));
actual.Should().BeEquivalentTo(new Dictionary<UniqueContentId, TextContentState> actual.Should().BeEquivalentTo(new Dictionary<UniqueContentId, TextContentState>
{ {
[id1] = new TextContentState { UniqueContentId = id1, State = TextState.Stage0_Draft__Stage1_None }, [id1] = new TextContentState { UniqueContentId = id1, State = TextState.Stage0_Draft__Stage1_None },
@ -63,10 +63,12 @@ public class MongoTextIndexerStateTests(MongoTextIndexerStateFixture fixture) :
} }
[Fact] [Fact]
public async Task Should_remove_by_app_state() public async Task Should_remove_by_app()
{ {
var appId1 = DomainId.NewGuid(); var appId1 = DomainId.NewGuid();
var appId2 = DomainId.NewGuid(); var appId2 = DomainId.NewGuid();
var app2 = new App { Id = appId2 };
var id1 = new UniqueContentId(appId1, DomainId.NewGuid()); var id1 = new UniqueContentId(appId1, DomainId.NewGuid());
var id2 = new UniqueContentId(appId1, DomainId.NewGuid()); var id2 = new UniqueContentId(appId1, DomainId.NewGuid());
var id3 = new UniqueContentId(appId2, 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 } 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)); 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> actual.Should().BeEquivalentTo(new Dictionary<UniqueContentId, TextContentState>
{ {
[id3] = new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } [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