Browse Source

Asset query scripts (#962)

* First implementation.

* Tests simplified.

* API test and UI fixes
pull/965/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
a4f84bc310
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetScripts.cs
  2. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs
  3. 20
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  4. 1
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasIndexDefinition.cs
  5. 1
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs
  6. 51
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs
  7. 16
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs
  8. 41
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs
  9. 15
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ValidationExtensions.cs
  10. 124
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs
  11. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricher.cs
  12. 20
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricherStep.cs
  13. 52
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/CalculateTokens.cs
  14. 56
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ConvertTags.cs
  15. 42
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichForCaching.cs
  16. 66
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichWithMetadataText.cs
  17. 107
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs
  18. 72
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  19. 32
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs
  20. 25
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs
  21. 66
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs
  22. 28
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  23. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs
  24. 2
      backend/src/Squidex.Web/Pipeline/AppResolver.cs
  25. 1
      backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs
  26. 10
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs
  27. 10
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAssetScriptsDto.cs
  28. 16
      backend/src/Squidex/Config/Domain/AssetServices.cs
  29. 2
      backend/src/Squidex/Squidex.csproj
  30. 60
      backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs
  31. 117
      backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderTests.cs
  32. 13
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs
  33. 15
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs
  34. 208
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppSettingsSearchSourceTests.cs
  35. 80
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs
  36. 13
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUsageDeleterTests.cs
  37. 102
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs
  38. 17
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppImageStoreTests.cs
  39. 26
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs
  40. 30
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs
  41. 57
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs
  42. 72
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppClientsTests.cs
  43. 79
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppContributorsTests.cs
  44. 40
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppLanguagesTests.cs
  45. 81
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppRolesTests.cs
  46. 96
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs
  47. 36
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppWorkflowTests.cs
  48. 189
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs
  49. 21
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/RestrictAppsCommandMiddlewareTests.cs
  50. 17
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs
  51. 15
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs
  52. 20
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetPermanentDeleterTests.cs
  53. 13
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetTagsDeleterTests.cs
  54. 48
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs
  55. 21
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs
  56. 41
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs
  57. 34
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsSearchSourceTests.cs
  58. 120
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/BackupAssetsTests.cs
  59. 83
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DefaultAssetFileStoreTests.cs
  60. 44
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs
  61. 52
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetDomainObjectTests.cs
  62. 14
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetFolderDomainObjectTests.cs
  63. 28
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetsBulkUpdateCommandMiddlewareTests.cs
  64. 27
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetFolderTests.cs
  65. 38
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetTests.cs
  66. 25
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/ScriptingExtensionsTests.cs
  67. 31
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs
  68. 197
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs
  69. 36
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetLoaderTests.cs
  70. 43
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryParserTests.cs
  71. 90
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs
  72. 66
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs
  73. 107
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs
  74. 66
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichForCachingTests.cs
  75. 68
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs
  76. 107
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs
  77. 18
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs
  78. 40
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs
  79. 34
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupServiceTests.cs
  80. 19
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/DefaultBackupArchiveStoreTests.cs
  81. 47
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/UserMappingTests.cs
  82. 145
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageGateTests.cs
  83. 31
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageNotifierWorkerTest.cs
  84. 11
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentsLoaderTests.cs
  85. 25
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/DomainObject/CommentsCommandMiddlewareTests.cs
  86. 39
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/WatchingServiceTests.cs
  87. 54
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BackupContentsTests.cs
  88. 29
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs
  89. 24
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentSchedulerProcessTests.cs
  90. 55
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs
  91. 28
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Counter/CounterJintExtensionTests.cs
  92. 34
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Counter/CounterServiceTests.cs
  93. 41
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs
  94. 19
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentCommandMiddlewareTests.cs
  95. 106
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs
  96. 70
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs
  97. 37
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs
  98. 43
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
  99. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs
  100. 33
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs

4
backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetScripts.cs

@ -20,4 +20,8 @@ public sealed record AssetScripts
public string? Move { get; init; } public string? Move { get; init; }
public string? Delete { get; init; } public string? Delete { get; init; }
public string? Query { get; init; }
public string? QueryPre { get; init; }
} }

7
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs

@ -7,16 +7,11 @@
namespace Squidex.Domain.Apps.Core.Scripting; namespace Squidex.Domain.Apps.Core.Scripting;
public struct ScriptOptions public record struct ScriptOptions
{ {
public bool CanReject { get; set; } public bool CanReject { get; set; }
public bool CanDisallow { get; set; } public bool CanDisallow { get; set; }
public bool AsContext { get; set; } public bool AsContext { get; set; }
public override readonly string ToString()
{
return $"CanReject={CanReject}, CanDisallow={CanDisallow}, AsContext={AsContext}";
}
} }

20
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -37,41 +37,45 @@ public sealed class ContentValidator
this.partitionResolver = partitionResolver; this.partitionResolver = partitionResolver;
} }
public ValueTask ValidateInputPartialAsync(ContentData data) public ValueTask ValidateInputPartialAsync(ContentData data,
CancellationToken ct = default)
{ {
Guard.NotNull(data); Guard.NotNull(data);
ValidateInputCore(data, true); ValidateInputCore(data, true);
return context.Root.CompleteAsync(); return context.Root.CompleteAsync(ct);
} }
public ValueTask ValidateInputAsync(ContentData data) public ValueTask ValidateInputAsync(ContentData data,
CancellationToken ct = default)
{ {
Guard.NotNull(data); Guard.NotNull(data);
ValidateInputCore(data, false); ValidateInputCore(data, false);
return context.Root.CompleteAsync(); return context.Root.CompleteAsync(ct);
} }
public ValueTask ValidateInputAndContentAsync(ContentData data) public ValueTask ValidateInputAndContentAsync(ContentData data,
CancellationToken ct = default)
{ {
Guard.NotNull(data); Guard.NotNull(data);
ValidateInputCore(data, false); ValidateInputCore(data, false);
ValidateContentCore(data); ValidateContentCore(data);
return context.Root.CompleteAsync(); return context.Root.CompleteAsync(ct);
} }
public ValueTask ValidateContentAsync(ContentData data) public ValueTask ValidateContentAsync(ContentData data,
CancellationToken ct = default)
{ {
Guard.NotNull(data); Guard.NotNull(data);
ValidateContentCore(data); ValidateContentCore(data);
return context.Root.CompleteAsync(); return context.Root.CompleteAsync(ct);
} }
private void ValidateInputCore(ContentData data, bool partial) private void ValidateInputCore(ContentData data, bool partial)

1
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasIndexDefinition.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Net;
using System.Net.Http.Json; using System.Net.Http.Json;
using Squidex.Hosting.Configuration; using Squidex.Hosting.Configuration;

1
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs

@ -8,6 +8,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.Queries;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;

51
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs

@ -70,16 +70,16 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
if (Version > EtagVersion.Empty && !IsDeleted(Snapshot)) if (Version > EtagVersion.Empty && !IsDeleted(Snapshot))
{ {
await UpdateCore(c.AsUpdate(), operation); await UpdateCore(c.AsUpdate(), operation, ct);
} }
else else
{ {
await CreateCore(c.AsCreate(), operation); await CreateCore(c.AsCreate(), operation, ct);
} }
if (Is.OptionalChange(Snapshot.ParentId, c.ParentId)) if (Is.OptionalChange(Snapshot.ParentId, c.ParentId))
{ {
await MoveCore(c.AsMove(c.ParentId.Value), operation); await MoveCore(c.AsMove(c.ParentId.Value), operation, ct);
} }
return Snapshot; return Snapshot;
@ -90,11 +90,11 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await CreateCore(c, operation); await CreateCore(c, operation, ct);
if (Is.Change(Snapshot.ParentId, c.ParentId)) if (Is.Change(Snapshot.ParentId, c.ParentId))
{ {
await MoveCore(c.AsMove(), operation); await MoveCore(c.AsMove(), operation, ct);
} }
return Snapshot; return Snapshot;
@ -105,7 +105,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await AnnotateCore(c, operation); await AnnotateCore(c, operation, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -115,7 +115,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await UpdateCore(c, operation); await UpdateCore(c, operation, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -125,7 +125,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await MoveCore(c, operation); await MoveCore(c, operation, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -135,7 +135,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation); await DeleteCore(c, operation, ct);
}, ct); }, ct);
case DeleteAsset delete: case DeleteAsset delete:
@ -143,7 +143,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation); await DeleteCore(c, operation, ct);
}, ct); }, ct);
default: default:
@ -152,16 +152,17 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
} }
} }
private async Task CreateCore(CreateAsset create, AssetOperation operation) private async Task CreateCore(CreateAsset create, AssetOperation operation,
CancellationToken ct)
{ {
if (!create.OptimizeValidation) if (!create.OptimizeValidation)
{ {
await operation.MustMoveToValidFolder(create.ParentId); await operation.MustMoveToValidFolder(create.ParentId, ct);
} }
if (!create.DoNotScript) if (!create.DoNotScript)
{ {
await operation.ExecuteCreateScriptAsync(create); await operation.ExecuteCreateScriptAsync(create, ct);
} }
if (create.Tags != null) if (create.Tags != null)
@ -172,11 +173,12 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
Create(create); Create(create);
} }
private async Task AnnotateCore(AnnotateAsset annotate, AssetOperation operation) private async Task AnnotateCore(AnnotateAsset annotate, AssetOperation operation,
CancellationToken ct)
{ {
if (!annotate.DoNotScript) if (!annotate.DoNotScript)
{ {
await operation.ExecuteAnnotateScriptAsync(annotate); await operation.ExecuteAnnotateScriptAsync(annotate, ct);
} }
if (annotate.Tags != null) if (annotate.Tags != null)
@ -187,41 +189,44 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
Annotate(annotate); Annotate(annotate);
} }
private async Task UpdateCore(UpdateAsset update, AssetOperation operation) private async Task UpdateCore(UpdateAsset update, AssetOperation operation,
CancellationToken ct)
{ {
if (!update.DoNotScript) if (!update.DoNotScript)
{ {
await operation.ExecuteUpdateScriptAsync(update); await operation.ExecuteUpdateScriptAsync(update, ct);
} }
Update(update); Update(update);
} }
private async Task MoveCore(MoveAsset move, AssetOperation operation) private async Task MoveCore(MoveAsset move, AssetOperation operation,
CancellationToken ct)
{ {
if (!move.OptimizeValidation) if (!move.OptimizeValidation)
{ {
await operation.MustMoveToValidFolder(move.ParentId); await operation.MustMoveToValidFolder(move.ParentId, ct);
} }
if (!move.DoNotScript) if (!move.DoNotScript)
{ {
await operation.ExecuteMoveScriptAsync(move); await operation.ExecuteMoveScriptAsync(move, ct);
} }
Move(move); Move(move);
} }
private async Task DeleteCore(DeleteAsset delete, AssetOperation operation) private async Task DeleteCore(DeleteAsset delete, AssetOperation operation,
CancellationToken ct)
{ {
if (delete.CheckReferrers) if (delete.CheckReferrers)
{ {
await operation.CheckReferrersAsync(); await operation.CheckReferrersAsync(ct);
} }
if (!delete.DoNotScript) if (!delete.DoNotScript)
{ {
await operation.ExecuteDeleteScriptAsync(delete); await operation.ExecuteDeleteScriptAsync(delete, ct);
} }
Delete(delete); Delete(delete);

16
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs

@ -56,7 +56,7 @@ public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDo
case CreateAssetFolder create: case CreateAssetFolder create:
return CreateReturnAsync(create, async (c, ct) => return CreateReturnAsync(create, async (c, ct) =>
{ {
await CreateCore(c, c); await CreateCore(c, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -64,7 +64,7 @@ public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDo
case MoveAssetFolder move: case MoveAssetFolder move:
return UpdateReturnAsync(move, async (c, ct) => return UpdateReturnAsync(move, async (c, ct) =>
{ {
await MoveCore(c); await MoveCore(c, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -89,7 +89,8 @@ public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDo
} }
} }
private async Task CreateCore(CreateAssetFolder create, CreateAssetFolder c) private async Task CreateCore(CreateAssetFolder c,
CancellationToken ct)
{ {
var operation = await AssetFolderOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetFolderOperation.CreateAsync(serviceProvider, c, () => Snapshot);
@ -97,19 +98,20 @@ public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDo
if (!c.OptimizeValidation) if (!c.OptimizeValidation)
{ {
await operation.MustMoveToValidFolder(c.ParentId); await operation.MustMoveToValidFolder(c.ParentId, ct);
} }
Create(create); Create(c);
} }
private async Task MoveCore(MoveAssetFolder c) private async Task MoveCore(MoveAssetFolder c,
CancellationToken ct)
{ {
var operation = await AssetFolderOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await AssetFolderOperation.CreateAsync(serviceProvider, c, () => Snapshot);
if (!c.OptimizeValidation) if (!c.OptimizeValidation)
{ {
await operation.MustMoveToValidFolder(c.ParentId); await operation.MustMoveToValidFolder(c.ParentId, ct);
} }
Move(c); Move(c);

41
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs

@ -21,7 +21,8 @@ public static class ScriptingExtensions
CanReject = true CanReject = true
}; };
public static async Task ExecuteCreateScriptAsync(this AssetOperation operation, CreateAsset create) public static async Task ExecuteCreateScriptAsync(this AssetOperation operation, CreateAsset create,
CancellationToken ct)
{ {
var script = operation.App.AssetScripts?.Create; var script = operation.App.AssetScripts?.Create;
@ -30,7 +31,7 @@ public static class ScriptingExtensions
return; return;
} }
var parentPath = await GetPathAsync(operation, create.ParentId); var parentPath = await GetPathAsync(operation, create.ParentId, ct);
// Script vars are just wrappers over dictionaries for better performance. // Script vars are just wrappers over dictionaries for better performance.
var vars = new AssetScriptVars var vars = new AssetScriptVars
@ -51,10 +52,11 @@ public static class ScriptingExtensions
Operation = "Create" Operation = "Create"
}; };
await ExecuteScriptAsync(operation, script, vars); await ExecuteScriptAsync(operation, script, vars, ct);
} }
public static Task ExecuteUpdateScriptAsync(this AssetOperation operation, UpdateAsset update) public static Task ExecuteUpdateScriptAsync(this AssetOperation operation, UpdateAsset update,
CancellationToken ct)
{ {
var script = operation.App.AssetScripts?.Update; var script = operation.App.AssetScripts?.Update;
@ -79,10 +81,11 @@ public static class ScriptingExtensions
Operation = "Update" Operation = "Update"
}; };
return ExecuteScriptAsync(operation, script, vars); return ExecuteScriptAsync(operation, script, vars, ct);
} }
public static Task ExecuteAnnotateScriptAsync(this AssetOperation operation, AnnotateAsset annotate) public static Task ExecuteAnnotateScriptAsync(this AssetOperation operation, AnnotateAsset annotate,
CancellationToken ct)
{ {
var script = operation.App.AssetScripts?.Annotate; var script = operation.App.AssetScripts?.Annotate;
@ -106,10 +109,11 @@ public static class ScriptingExtensions
Operation = "Annotate" Operation = "Annotate"
}; };
return ExecuteScriptAsync(operation, script, vars); return ExecuteScriptAsync(operation, script, vars, ct);
} }
public static async Task ExecuteMoveScriptAsync(this AssetOperation operation, MoveAsset move) public static async Task ExecuteMoveScriptAsync(this AssetOperation operation, MoveAsset move,
CancellationToken ct)
{ {
var script = operation.App.AssetScripts?.Move; var script = operation.App.AssetScripts?.Move;
@ -118,7 +122,7 @@ public static class ScriptingExtensions
return; return;
} }
var parentPath = await GetPathAsync(operation, move.ParentId); var parentPath = await GetPathAsync(operation, move.ParentId, ct);
// Script vars are just wrappers over dictionaries for better performance. // Script vars are just wrappers over dictionaries for better performance.
var vars = new AssetScriptVars var vars = new AssetScriptVars
@ -131,10 +135,11 @@ public static class ScriptingExtensions
Operation = "Move" Operation = "Move"
}; };
await ExecuteScriptAsync(operation, script, vars); await ExecuteScriptAsync(operation, script, vars, ct);
} }
public static Task ExecuteDeleteScriptAsync(this AssetOperation operation, DeleteAsset delete) public static Task ExecuteDeleteScriptAsync(this AssetOperation operation, DeleteAsset delete,
CancellationToken ct)
{ {
var script = operation.App.AssetScripts?.Delete; var script = operation.App.AssetScripts?.Delete;
@ -153,14 +158,15 @@ public static class ScriptingExtensions
Operation = "Delete" Operation = "Delete"
}; };
return ExecuteScriptAsync(operation, script, vars); return ExecuteScriptAsync(operation, script, vars, ct);
} }
private static async Task ExecuteScriptAsync(AssetOperation operation, string script, AssetScriptVars vars) private static async Task ExecuteScriptAsync(AssetOperation operation, string script, AssetScriptVars vars,
CancellationToken ct)
{ {
var snapshot = operation.Snapshot; var snapshot = operation.Snapshot;
var parentPath = await GetPathAsync(operation, snapshot.ParentId); var parentPath = await GetPathAsync(operation, snapshot.ParentId, ct);
// Script vars are just wrappers over dictionaries for better performance. // Script vars are just wrappers over dictionaries for better performance.
var asset = new AssetEntityScriptVars var asset = new AssetEntityScriptVars
@ -186,10 +192,11 @@ public static class ScriptingExtensions
var scriptEngine = operation.Resolve<IScriptEngine>(); var scriptEngine = operation.Resolve<IScriptEngine>();
await scriptEngine.ExecuteAsync(vars, script, Options); await scriptEngine.ExecuteAsync(vars, script, Options, ct);
} }
private static async Task<Array> GetPathAsync(AssetOperation operation, DomainId parentId) private static async Task<Array> GetPathAsync(AssetOperation operation, DomainId parentId,
CancellationToken ct)
{ {
if (parentId == default) if (parentId == default)
{ {
@ -197,7 +204,7 @@ public static class ScriptingExtensions
} }
var assetQuery = operation.Resolve<IAssetQueryService>(); var assetQuery = operation.Resolve<IAssetQueryService>();
var assetPath = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId); var assetPath = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId, ct);
return assetPath.Select(x => new { id = x.Id, folderName = x.FolderName }).ToArray(); return assetPath.Select(x => new { id = x.Id, folderName = x.FolderName }).ToArray();
} }

15
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ValidationExtensions.cs

@ -26,7 +26,8 @@ public static class ValidationExtensions
operation.ThrowOnErrors(); operation.ThrowOnErrors();
} }
public static async Task MustMoveToValidFolder(this AssetOperation operation, DomainId parentId) public static async Task MustMoveToValidFolder(this AssetOperation operation, DomainId parentId,
CancellationToken ct)
{ {
// If moved to root folder or not moved at all, we can just skip the validation. // If moved to root folder or not moved at all, we can just skip the validation.
if (parentId == DomainId.Empty || parentId == operation.Snapshot.ParentId) if (parentId == DomainId.Empty || parentId == operation.Snapshot.ParentId)
@ -36,7 +37,7 @@ public static class ValidationExtensions
var assetQuery = operation.Resolve<IAssetQueryService>(); var assetQuery = operation.Resolve<IAssetQueryService>();
var path = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId); var path = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId, ct);
if (path.Count == 0) if (path.Count == 0)
{ {
@ -46,7 +47,8 @@ public static class ValidationExtensions
operation.ThrowOnErrors(); operation.ThrowOnErrors();
} }
public static async Task MustMoveToValidFolder(this AssetFolderOperation operation, DomainId parentId) public static async Task MustMoveToValidFolder(this AssetFolderOperation operation, DomainId parentId,
CancellationToken ct)
{ {
// If moved to root folder or not moved at all, we can just skip the validation. // If moved to root folder or not moved at all, we can just skip the validation.
if (parentId == DomainId.Empty || parentId == operation.Snapshot.ParentId) if (parentId == DomainId.Empty || parentId == operation.Snapshot.ParentId)
@ -56,7 +58,7 @@ public static class ValidationExtensions
var assetQuery = operation.Resolve<IAssetQueryService>(); var assetQuery = operation.Resolve<IAssetQueryService>();
var path = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId); var path = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId, ct);
if (path.Count == 0) if (path.Count == 0)
{ {
@ -77,11 +79,12 @@ public static class ValidationExtensions
operation.ThrowOnErrors(); operation.ThrowOnErrors();
} }
public static async Task CheckReferrersAsync(this AssetOperation operation) public static async Task CheckReferrersAsync(this AssetOperation operation,
CancellationToken ct)
{ {
var contentRepository = operation.Resolve<IContentRepository>(); var contentRepository = operation.Resolve<IContentRepository>();
var hasReferrer = await contentRepository.HasReferrersAsync(operation.App.Id, operation.CommandId, SearchScope.All, default); var hasReferrer = await contentRepository.HasReferrersAsync(operation.App.Id, operation.CommandId, SearchScope.All, ct);
if (hasReferrer) if (hasReferrer)
{ {

124
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs

@ -5,32 +5,18 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Text;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public sealed class AssetEnricher : IAssetEnricher public sealed class AssetEnricher : IAssetEnricher
{ {
private readonly ITagService tagService; private readonly IEnumerable<IAssetEnricherStep> steps;
private readonly IEnumerable<IAssetMetadataSource> assetMetadataSources;
private readonly IRequestCache requestCache; public AssetEnricher(IEnumerable<IAssetEnricherStep> steps)
private readonly IUrlGenerator urlGenerator;
private readonly IJsonSerializer serializer;
public AssetEnricher(ITagService tagService, IEnumerable<IAssetMetadataSource> assetMetadataSources, IRequestCache requestCache,
IUrlGenerator urlGenerator, IJsonSerializer serializer)
{ {
this.tagService = tagService; this.steps = steps;
this.assetMetadataSources = assetMetadataSources;
this.requestCache = requestCache;
this.urlGenerator = urlGenerator;
this.serializer = serializer;
} }
public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset, Context context, public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset, Context context,
@ -52,108 +38,42 @@ public sealed class AssetEnricher : IAssetEnricher
using (Telemetry.Activities.StartActivity("AssetEnricher/EnrichAsync")) using (Telemetry.Activities.StartActivity("AssetEnricher/EnrichAsync"))
{ {
var results = assets.Select(x => SimpleMapper.Map(x, new AssetEntity())).ToList(); var results = new List<AssetEntity>();
foreach (var asset in results) if (context.App != null)
{ {
requestCache.AddDependency(asset.UniqueId, asset.Version); foreach (var step in steps)
{
await step.EnrichAsync(context, ct);
}
} }
if (!context.ShouldSkipAssetEnrichment()) if (!assets.Any())
{ {
await EnrichTagsAsync(results, ct); return results;
EnrichWithMetadataText(results);
EnrichWithEditTokens(results);
} }
return results; foreach (var asset in assets)
}
}
private void EnrichWithEditTokens(IEnumerable<IEnrichedAssetEntity> assets)
{
var url = urlGenerator.Root();
foreach (var asset in assets)
{
// We have to use these short names here because they are later read like this.
var token = new
{ {
a = asset.AppId.Name, var result = SimpleMapper.Map(asset, new AssetEntity());
i = asset.Id.ToString(),
u = url
};
var json = serializer.Serialize(token); results.Add(result);
asset.EditToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
}
}
private void EnrichWithMetadataText(List<AssetEntity> results)
{
var sb = new StringBuilder();
void Append(string? text)
{
if (!string.IsNullOrWhiteSpace(text))
{
sb.AppendIfNotEmpty(", ");
sb.Append(text);
} }
}
foreach (var asset in results)
{
sb.Clear();
foreach (var source in assetMetadataSources) if (context.App != null)
{ {
foreach (var metadata in source.Format(asset)) foreach (var step in steps)
{ {
Append(metadata); ct.ThrowIfCancellationRequested();
}
}
Append(asset.FileSize.ToReadableSize());
asset.MetadataText = sb.ToString();
}
}
private async Task EnrichTagsAsync(List<AssetEntity> assets,
CancellationToken ct)
{
foreach (var group in assets.GroupBy(x => x.AppId.Id))
{
ct.ThrowIfCancellationRequested();
var tagsById = await CalculateTagsAsync(group, ct);
foreach (var asset in group) using (Telemetry.Activities.StartActivity(step.ToString()!))
{
asset.TagNames = new HashSet<string>();
if (asset.Tags != null)
{
foreach (var id in asset.Tags)
{ {
if (tagsById.TryGetValue(id, out var name)) await step.EnrichAsync(context, results, ct);
{
asset.TagNames.Add(name);
}
} }
} }
} }
}
}
private async Task<Dictionary<string, string>> CalculateTagsAsync(IGrouping<DomainId, IAssetEntity> group, return results;
CancellationToken ct) }
{
var uniqueIds = group.Where(x => x.Tags != null).SelectMany(x => x.Tags).ToHashSet();
return await tagService.GetTagNamesAsync(group.Key, TagGroups.Assets, uniqueIds, ct);
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricher.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public interface IAssetEnricher public interface IAssetEnricher
{ {

20
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricherStep.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public interface IAssetEnricherStep
{
Task EnrichAsync(Context context, IEnumerable<AssetEntity> assets,
CancellationToken ct);
Task EnrichAsync(Context context,
CancellationToken ct)
{
return Task.CompletedTask;
}
}

52
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/CalculateTokens.cs

@ -0,0 +1,52 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Text;
using Squidex.Domain.Apps.Core;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
public sealed class CalculateTokens : IAssetEnricherStep
{
private readonly IJsonSerializer serializer;
private readonly IUrlGenerator urlGenerator;
public CalculateTokens(IUrlGenerator urlGenerator, IJsonSerializer serializer)
{
this.serializer = serializer;
this.urlGenerator = urlGenerator;
}
public Task EnrichAsync(Context context, IEnumerable<AssetEntity> assets,
CancellationToken ct)
{
if (context.ShouldSkipAssetEnrichment())
{
return Task.CompletedTask;
}
var url = urlGenerator.Root();
foreach (var asset in assets)
{
// We have to use these short names here because they are later read like this.
var token = new
{
a = asset.AppId.Name,
i = asset.Id.ToString(),
u = url
};
var json = serializer.Serialize(token);
asset.EditToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
}
return Task.CompletedTask;
}
}

56
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ConvertTags.cs

@ -0,0 +1,56 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
public sealed class ConvertTags : IAssetEnricherStep
{
private readonly ITagService tagService;
public ConvertTags(ITagService tagService)
{
this.tagService = tagService;
}
public async Task EnrichAsync(Context context, IEnumerable<AssetEntity> assets,
CancellationToken ct)
{
if (context.ShouldSkipAssetEnrichment())
{
return;
}
var tagsById = await CalculateTagsAsync(context.App.Id, assets, ct);
foreach (var asset in assets)
{
asset.TagNames = new HashSet<string>();
if (asset.Tags != null)
{
foreach (var id in asset.Tags)
{
if (tagsById.TryGetValue(id, out var name))
{
asset.TagNames.Add(name);
}
}
}
}
}
private async Task<Dictionary<string, string>> CalculateTagsAsync(DomainId appId, IEnumerable<IAssetEntity> assets,
CancellationToken ct)
{
var uniqueIds = assets.Where(x => x.Tags != null).SelectMany(x => x.Tags).ToHashSet();
return await tagService.GetTagNamesAsync(appId, TagGroups.Assets, uniqueIds, ct);
}
}

42
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichForCaching.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure.Caching;
namespace Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
public sealed class EnrichForCaching : IAssetEnricherStep
{
private readonly IRequestCache requestCache;
public EnrichForCaching(IRequestCache requestCache)
{
this.requestCache = requestCache;
}
public Task EnrichAsync(Context context,
CancellationToken ct)
{
context.AddCacheHeaders(requestCache);
return Task.CompletedTask;
}
public Task EnrichAsync(Context context, IEnumerable<AssetEntity> assets,
CancellationToken ct)
{
requestCache.AddDependency(context.App.Id, context.App.Version);
foreach (var asset in assets)
{
requestCache.AddDependency(asset.UniqueId, asset.Version);
}
return Task.CompletedTask;
}
}

66
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichWithMetadataText.cs

@ -0,0 +1,66 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool;
namespace Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
public sealed class EnrichWithMetadataText : IAssetEnricherStep
{
private readonly IEnumerable<IAssetMetadataSource> assetMetadataSources;
public EnrichWithMetadataText(IEnumerable<IAssetMetadataSource> assetMetadataSources)
{
this.assetMetadataSources = assetMetadataSources;
}
public Task EnrichAsync(Context context, IEnumerable<AssetEntity> assets,
CancellationToken ct)
{
if (context.ShouldSkipAssetEnrichment())
{
return Task.CompletedTask;
}
var sb = DefaultPools.StringBuilder.Get();
try
{
void Append(string? text)
{
if (!string.IsNullOrWhiteSpace(text))
{
sb.AppendIfNotEmpty(", ");
sb.Append(text);
}
}
foreach (var asset in assets)
{
sb.Clear();
foreach (var source in assetMetadataSources)
{
foreach (var metadata in source.Format(asset))
{
Append(metadata);
}
}
Append(asset.FileSize.ToReadableSize());
asset.MetadataText = sb.ToString();
}
}
finally
{
DefaultPools.StringBuilder.Return(sb);
}
return Task.CompletedTask;
}
}

107
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs

@ -0,0 +1,107 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Scripting;
namespace Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
public sealed class ScriptAsset : IAssetEnricherStep
{
private readonly IScriptEngine scriptEngine;
public ScriptAsset(IScriptEngine scriptEngine)
{
this.scriptEngine = scriptEngine;
}
public async Task EnrichAsync(Context context, IEnumerable<AssetEntity> assets,
CancellationToken ct)
{
if (!ShouldEnrich(context))
{
return;
}
var script = context.App.AssetScripts.Query;
if (string.IsNullOrWhiteSpace(script))
{
return;
}
// Script vars are just wrappers over dictionaries for better performance.
var vars = new AssetScriptVars
{
AppId = context.App.Id,
AppName = context.App.Name,
User = context.UserPrincipal
};
var preScript = context.App.AssetScripts.QueryPre;
if (!string.IsNullOrWhiteSpace(preScript))
{
var options = new ScriptOptions
{
AsContext = true
};
await scriptEngine.ExecuteAsync(vars, preScript, options, ct);
}
foreach (var asset in assets)
{
await ScriptAsync(vars, script, asset, ct);
}
}
private async Task ScriptAsync(AssetScriptVars sharedVars, string script, AssetEntity asset,
CancellationToken ct)
{
// Script vars are just wrappers over dictionaries for better performance.
var vars = new AssetScriptVars
{
AssetId = asset.Id,
Asset = new AssetEntityScriptVars
{
Metadata = asset.Metadata,
FileHash = asset.FileHash,
FileName = asset.FileName,
FileSize = asset.FileSize,
FileSlug = asset.Slug,
FileVersion = asset.FileVersion,
IsProtected = asset.IsProtected,
MimeType = asset.MimeType,
ParentId = asset.ParentId,
ParentPath = null,
Tags = asset.Tags
}
};
foreach (var (key, value) in sharedVars)
{
if (!vars.ContainsKey(key))
{
vars[key] = value;
}
}
var options = new ScriptOptions
{
AsContext = true,
CanDisallow = true,
CanReject = true
};
await scriptEngine.ExecuteAsync(vars, script, options, ct);
}
private static bool ShouldEnrich(Context context)
{
return !context.IsFrontendClient;
}
}

72
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs

@ -75,20 +75,20 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (Version <= EtagVersion.Empty || IsDeleted(Snapshot)) if (Version <= EtagVersion.Empty || IsDeleted(Snapshot))
{ {
await CreateCore(c.AsCreate(), operation); await CreateCore(c.AsCreate(), operation, ct);
} }
else if (c.Patch) else if (c.Patch)
{ {
await PatchCore(c.AsUpdate(), operation); await PatchCore(c.AsUpdate(), operation, ct);
} }
else else
{ {
await UpdateCore(c.AsUpdate(), operation); await UpdateCore(c.AsUpdate(), operation, ct);
} }
if (Is.OptionalChange(operation.Snapshot.EditingStatus(), c.Status)) if (Is.OptionalChange(operation.Snapshot.EditingStatus(), c.Status))
{ {
await ChangeCore(c.AsChange(c.Status.Value), operation); await ChangeCore(c.AsChange(c.Status.Value), operation, ct);
} }
return Snapshot; return Snapshot;
@ -99,7 +99,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await CreateCore(c, operation); await CreateCore(c, operation, ct);
if (operation.Schema.SchemaDef.Type == SchemaType.Singleton) if (operation.Schema.SchemaDef.Type == SchemaType.Singleton)
{ {
@ -107,7 +107,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
} }
else if (Is.OptionalChange(Snapshot.Status, c.Status)) else if (Is.OptionalChange(Snapshot.Status, c.Status))
{ {
await ChangeCore(c.AsChange(c.Status.Value), operation); await ChangeCore(c.AsChange(c.Status.Value), operation, ct);
} }
return Snapshot; return Snapshot;
@ -118,7 +118,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await ValidateCore(operation); await ValidateCore(operation, ct);
return true; return true;
}, ct); }, ct);
@ -148,7 +148,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await PatchCore(c, operation); await PatchCore(c, operation, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -158,7 +158,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await UpdateCore(c, operation); await UpdateCore(c, operation, ct);
return Snapshot; return Snapshot;
}, ct); }, ct);
@ -186,7 +186,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await ChangeCore(c, operation); await ChangeCore(c, operation, ct);
} }
} }
catch (Exception) catch (Exception)
@ -209,7 +209,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation); await DeleteCore(c, operation, ct);
}, ct); }, ct);
case DeleteContent deleteContent: case DeleteContent deleteContent:
@ -217,7 +217,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{ {
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot); var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation); await DeleteCore(c, operation, ct);
}, ct); }, ct);
default: default:
@ -226,7 +226,8 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
} }
} }
private async Task CreateCore(CreateContent c, ContentOperation operation) private async Task CreateCore(CreateContent c, ContentOperation operation,
CancellationToken ct)
{ {
operation.MustNotCreateComponent(); operation.MustNotCreateComponent();
operation.MustNotCreateSingleton(); operation.MustNotCreateSingleton();
@ -235,27 +236,28 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
await operation.ValidateInputAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished()); await operation.ValidateInputAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished(), ct);
} }
var status = await operation.GetInitialStatusAsync(); var status = await operation.GetInitialStatusAsync();
if (!c.DoNotScript) if (!c.DoNotScript)
{ {
c.Data = await operation.ExecuteCreateScriptAsync(c.Data, status); c.Data = await operation.ExecuteCreateScriptAsync(c.Data, status, ct);
} }
operation.GenerateDefaultValues(c.Data); operation.GenerateDefaultValues(c.Data);
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
await operation.ValidateContentAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished()); await operation.ValidateContentAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished(), ct);
} }
Create(c, status); Create(c, status);
} }
private async Task ChangeCore(ChangeContentStatus c, ContentOperation operation) private async Task ChangeCore(ChangeContentStatus c, ContentOperation operation,
CancellationToken ct)
{ {
operation.MustHavePermission(PermissionIds.AppContentsChangeStatus); operation.MustHavePermission(PermissionIds.AppContentsChangeStatus);
operation.MustNotChangeSingleton(c.Status); operation.MustNotChangeSingleton(c.Status);
@ -276,7 +278,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (!c.DoNotScript) if (!c.DoNotScript)
{ {
var newData = await operation.ExecuteChangeScriptAsync(c.Status, GetChange(c.Status)); var newData = await operation.ExecuteChangeScriptAsync(c.Status, GetChange(c.Status), ct);
if (!newData.Equals(Snapshot.Data)) if (!newData.Equals(Snapshot.Data))
{ {
@ -297,25 +299,26 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (c.CheckReferrers && Snapshot.IsPublished()) if (c.CheckReferrers && Snapshot.IsPublished())
{ {
await operation.CheckReferrersAsync(); await operation.CheckReferrersAsync(ct);
} }
if (!c.DoNotValidate && await operation.ShouldValidateAsync(c.Status)) if (!c.DoNotValidate && await operation.ShouldValidateAsync(c.Status))
{ {
await operation.ValidateContentAndInputAsync(Snapshot.Data, c.OptimizeValidation, true); await operation.ValidateContentAndInputAsync(Snapshot.Data, c.OptimizeValidation, true, ct);
} }
ChangeStatus(c); ChangeStatus(c);
} }
private async Task UpdateCore(UpdateContent c, ContentOperation operation) private async Task UpdateCore(UpdateContent c, ContentOperation operation,
CancellationToken ct)
{ {
operation.MustHavePermission(PermissionIds.AppContentsUpdate); operation.MustHavePermission(PermissionIds.AppContentsUpdate);
operation.MustHaveData(c.Data); operation.MustHaveData(c.Data);
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
await operation.ValidateInputAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished()); await operation.ValidateInputAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished(), ct);
} }
if (!c.DoNotValidateWorkflow) if (!c.DoNotValidateWorkflow)
@ -332,25 +335,26 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (!c.DoNotScript) if (!c.DoNotScript)
{ {
newData = await operation.ExecuteUpdateScriptAsync(newData); newData = await operation.ExecuteUpdateScriptAsync(newData, ct);
} }
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished()); await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished(), ct);
} }
Update(c, newData); Update(c, newData);
} }
private async Task PatchCore(UpdateContent c, ContentOperation operation) private async Task PatchCore(UpdateContent c, ContentOperation operation,
CancellationToken ct)
{ {
operation.MustHavePermission(PermissionIds.AppContentsUpdate); operation.MustHavePermission(PermissionIds.AppContentsUpdate);
operation.MustHaveData(c.Data); operation.MustHaveData(c.Data);
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
await operation.ValidateInputPartialAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished()); await operation.ValidateInputPartialAsync(c.Data, c.OptimizeValidation, Snapshot.IsPublished(), ct);
} }
if (!c.DoNotValidateWorkflow) if (!c.DoNotValidateWorkflow)
@ -367,12 +371,12 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (!c.DoNotScript) if (!c.DoNotScript)
{ {
newData = await operation.ExecuteUpdateScriptAsync(newData); newData = await operation.ExecuteUpdateScriptAsync(newData, ct);
} }
if (!c.DoNotValidate) if (!c.DoNotValidate)
{ {
await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished()); await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished(), ct);
} }
Update(c, newData); Update(c, newData);
@ -388,11 +392,12 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
} }
} }
private async Task ValidateCore(ContentOperation operation) private async Task ValidateCore(ContentOperation operation,
CancellationToken ct)
{ {
operation.MustHavePermission(PermissionIds.AppContentsRead); operation.MustHavePermission(PermissionIds.AppContentsRead);
await operation.ValidateContentAndInputAsync(Snapshot.Data, false, Snapshot.IsPublished()); await operation.ValidateContentAndInputAsync(Snapshot.Data, false, Snapshot.IsPublished(), ct);
} }
private async Task CreateDraftCore(CreateContentDraft c, ContentOperation operation) private async Task CreateDraftCore(CreateContentDraft c, ContentOperation operation)
@ -413,19 +418,20 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
DeleteDraft(c); DeleteDraft(c);
} }
private async Task DeleteCore(DeleteContent c, ContentOperation operation) private async Task DeleteCore(DeleteContent c, ContentOperation operation,
CancellationToken ct)
{ {
operation.MustHavePermission(PermissionIds.AppContentsDelete); operation.MustHavePermission(PermissionIds.AppContentsDelete);
operation.MustNotDeleteSingleton(); operation.MustNotDeleteSingleton();
if (!c.DoNotScript) if (!c.DoNotScript)
{ {
await operation.ExecuteDeleteScriptAsync(c.Permanent); await operation.ExecuteDeleteScriptAsync(c.Permanent, ct);
} }
if (c.CheckReferrers) if (c.CheckReferrers)
{ {
await operation.CheckReferrersAsync(); await operation.CheckReferrersAsync(ct);
} }
Delete(c); Delete(c);

32
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs

@ -19,7 +19,8 @@ public static class ScriptingExtensions
CanReject = true CanReject = true
}; };
public static Task<ContentData> ExecuteCreateScriptAsync(this ContentOperation operation, ContentData data, Status status) public static Task<ContentData> ExecuteCreateScriptAsync(this ContentOperation operation, ContentData data, Status status,
CancellationToken ct)
{ {
var script = operation.SchemaDef.Scripts.Create; var script = operation.SchemaDef.Scripts.Create;
@ -40,10 +41,11 @@ public static class ScriptingExtensions
StatusOld = default StatusOld = default
}); });
return TransformAsync(operation, script, vars); return TransformAsync(operation, script, vars, ct);
} }
public static Task<ContentData> ExecuteUpdateScriptAsync(this ContentOperation operation, ContentData data) public static Task<ContentData> ExecuteUpdateScriptAsync(this ContentOperation operation, ContentData data,
CancellationToken ct)
{ {
var script = operation.SchemaDef.Scripts.Update; var script = operation.SchemaDef.Scripts.Update;
@ -64,10 +66,11 @@ public static class ScriptingExtensions
StatusOld = default StatusOld = default
}); });
return TransformAsync(operation, script, vars); return TransformAsync(operation, script, vars, ct);
} }
public static Task<ContentData> ExecuteChangeScriptAsync(this ContentOperation operation, Status status, StatusChange change) public static Task<ContentData> ExecuteChangeScriptAsync(this ContentOperation operation, Status status, StatusChange change,
CancellationToken ct)
{ {
var script = operation.SchemaDef.Scripts.Change; var script = operation.SchemaDef.Scripts.Change;
@ -89,10 +92,11 @@ public static class ScriptingExtensions
Validate = Validate(operation, status) Validate = Validate(operation, status)
}); });
return TransformAsync(operation, script, vars); return TransformAsync(operation, script, vars, ct);
} }
public static Task ExecuteDeleteScriptAsync(this ContentOperation operation, bool permanent) public static Task ExecuteDeleteScriptAsync(this ContentOperation operation, bool permanent,
CancellationToken ct)
{ {
var script = operation.SchemaDef.Scripts.Delete; var script = operation.SchemaDef.Scripts.Delete;
@ -114,17 +118,19 @@ public static class ScriptingExtensions
StatusOld = default StatusOld = default
}); });
return ExecuteAsync(operation, script, vars); return ExecuteAsync(operation, script, vars, ct);
} }
private static async Task<ContentData> TransformAsync(ContentOperation operation, string script, ContentScriptVars vars) private static async Task<ContentData> TransformAsync(ContentOperation operation, string script, ContentScriptVars vars,
CancellationToken ct)
{ {
return await operation.Resolve<IScriptEngine>().TransformAsync(vars, script, Options); return await operation.Resolve<IScriptEngine>().TransformAsync(vars, script, Options, ct);
} }
private static async Task ExecuteAsync(ContentOperation operation, string script, ContentScriptVars vars) private static async Task ExecuteAsync(ContentOperation operation, string script, ContentScriptVars vars,
CancellationToken ct)
{ {
await operation.Resolve<IScriptEngine>().ExecuteAsync(vars, script, Options); await operation.Resolve<IScriptEngine>().ExecuteAsync(vars, script, Options, ct);
} }
private static Action Validate(ContentOperation operation, Status status) private static Action Validate(ContentOperation operation, Status status)
@ -135,7 +141,7 @@ public static class ScriptingExtensions
{ {
var snapshot = operation.Snapshot; var snapshot = operation.Snapshot;
operation.ValidateContentAndInputAsync(snapshot.Data, false, snapshot.IsPublished() || status == Status.Published).Wait(); operation.ValidateContentAndInputAsync(snapshot.Data, false, snapshot.IsPublished() || status == Status.Published, default).Wait();
} }
catch (AggregateException ex) when (ex.InnerException != null) catch (AggregateException ex) when (ex.InnerException != null)
{ {

25
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs

@ -47,38 +47,42 @@ public static class ValidationExtensions
operation.ThrowOnErrors(); operation.ThrowOnErrors();
} }
public static async Task ValidateInputAsync(this ContentOperation operation, ContentData data, bool optimize, bool published) public static async Task ValidateInputAsync(this ContentOperation operation, ContentData data, bool optimize, bool published,
CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
await validator.ValidateInputAsync(data); await validator.ValidateInputAsync(data, ct);
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static async Task ValidateInputPartialAsync(this ContentOperation operation, ContentData data, bool optimize, bool published) public static async Task ValidateInputPartialAsync(this ContentOperation operation, ContentData data, bool optimize, bool published,
CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
await validator.ValidateInputPartialAsync(data); await validator.ValidateInputPartialAsync(data, ct);
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static async Task ValidateContentAsync(this ContentOperation operation, ContentData data, bool optimize, bool published) public static async Task ValidateContentAsync(this ContentOperation operation, ContentData data, bool optimize, bool published,
CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
await validator.ValidateContentAsync(data); await validator.ValidateContentAsync(data, ct);
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
public static async Task ValidateContentAndInputAsync(this ContentOperation operation, ContentData data, bool optimize, bool published) public static async Task ValidateContentAndInputAsync(this ContentOperation operation, ContentData data, bool optimize, bool published,
CancellationToken ct)
{ {
var validator = GetValidator(operation, optimize, published); var validator = GetValidator(operation, optimize, published);
await validator.ValidateInputAndContentAsync(data); await validator.ValidateInputAndContentAsync(data, ct);
operation.AddErrors(validator.Errors).ThrowOnErrors(); operation.AddErrors(validator.Errors).ThrowOnErrors();
} }
@ -88,11 +92,12 @@ public static class ValidationExtensions
data.GenerateDefaultValues(operation.Schema.SchemaDef, operation.Partition()); data.GenerateDefaultValues(operation.Schema.SchemaDef, operation.Partition());
} }
public static async Task CheckReferrersAsync(this ContentOperation operation) public static async Task CheckReferrersAsync(this ContentOperation operation,
CancellationToken ct)
{ {
var contentRepository = operation.Resolve<IContentRepository>(); var contentRepository = operation.Resolve<IContentRepository>();
var hasReferrer = await contentRepository.HasReferrersAsync(operation.App.Id, operation.CommandId, SearchScope.All, default); var hasReferrer = await contentRepository.HasReferrersAsync(operation.App.Id, operation.CommandId, SearchScope.All, ct);
if (hasReferrer) if (hasReferrer)
{ {

66
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs

@ -58,52 +58,54 @@ public sealed class ContentEnricher : IContentEnricher
} }
} }
if (contents.Any()) if (!contents.Any())
{ {
foreach (var content in contents) return results;
{ }
var result = SimpleMapper.Map(content, new ContentEntity());
foreach (var content in contents)
{
var result = SimpleMapper.Map(content, new ContentEntity());
if (cloneData) if (cloneData)
{
using (Telemetry.Activities.StartActivity("ContentEnricher/CloneData"))
{ {
using (Telemetry.Activities.StartActivity("ContentEnricher/CloneData")) result.Data = result.Data.Clone();
{
result.Data = result.Data.Clone();
}
} }
results.Add(result);
} }
if (context.App != null) results.Add(result);
{ }
var schemaCache = new Dictionary<DomainId, Task<(ISchemaEntity, ResolvedComponents)>>();
Task<(ISchemaEntity, ResolvedComponents)> GetSchema(DomainId id) if (context.App != null)
{
var schemaCache = new Dictionary<DomainId, Task<(ISchemaEntity, ResolvedComponents)>>();
Task<(ISchemaEntity, ResolvedComponents)> GetSchema(DomainId id)
{
return schemaCache.GetOrAdd(id, async x =>
{ {
return schemaCache.GetOrAdd(id, async x => var schema = await appProvider.GetSchemaAsync(context.App.Id, x, false, ct);
if (schema == null)
{ {
var schema = await appProvider.GetSchemaAsync(context.App.Id, x, false, ct); throw new DomainObjectNotFoundException(x.ToString());
}
if (schema == null) var components = await appProvider.GetComponentsAsync(schema, ct);
{
throw new DomainObjectNotFoundException(x.ToString());
}
var components = await appProvider.GetComponentsAsync(schema, ct); return (schema, components);
});
}
return (schema, components); foreach (var step in steps)
}); {
} ct.ThrowIfCancellationRequested();
foreach (var step in steps) using (Telemetry.Activities.StartActivity(step.ToString()!))
{ {
ct.ThrowIfCancellationRequested(); await step.EnrichAsync(context, results, GetSchema, ct);
using (Telemetry.Activities.StartActivity(step.ToString()!))
{
await step.EnrichAsync(context, results, GetSchema, ct);
}
} }
} }
} }

28
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs

@ -36,25 +36,27 @@ public sealed class ResolveAssets : IContentEnricherStep
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas, public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas,
CancellationToken ct) CancellationToken ct)
{ {
if (ShouldEnrich(context)) if (!ShouldEnrich(context))
{ {
var ids = new HashSet<DomainId>(); return;
}
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) var ids = new HashSet<DomainId>();
{
var (schema, components) = await schemas(group.Key);
AddAssetIds(ids, schema, components, group); foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
} {
var (schema, components) = await schemas(group.Key);
var assets = await GetAssetsAsync(context, ids, ct); AddAssetIds(ids, schema, components, group);
}
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) var assets = await GetAssetsAsync(context, ids, ct);
{
var (schema, components) = await schemas(group.Key);
ResolveAssetsUrls(schema, components, group, assets); foreach (var group in contents.GroupBy(x => x.SchemaId.Id))
} {
var (schema, components) = await schemas(group.Key);
ResolveAssetsUrls(schema, components, group, assets);
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs

@ -37,6 +37,7 @@ public sealed class ScriptContent : IContentEnricherStep
continue; continue;
} }
// Script vars are just wrappers over dictionaries for better performance.
var vars = new ContentScriptVars var vars = new ContentScriptVars
{ {
AppId = schema.AppId.Id, AppId = schema.AppId.Id,
@ -68,6 +69,7 @@ public sealed class ScriptContent : IContentEnricherStep
private async Task TransformAsync(ContentScriptVars sharedVars, string script, ContentEntity content, private async Task TransformAsync(ContentScriptVars sharedVars, string script, ContentEntity content,
CancellationToken ct) CancellationToken ct)
{ {
// Script vars are just wrappers over dictionaries for better performance.
var vars = new ContentScriptVars var vars = new ContentScriptVars
{ {
ContentId = content.Id, ContentId = content.Id,
@ -87,7 +89,9 @@ public sealed class ScriptContent : IContentEnricherStep
var options = new ScriptOptions var options = new ScriptOptions
{ {
AsContext = true AsContext = true,
CanDisallow = true,
CanReject = true
}; };
content.Data = await scriptEngine.TransformAsync(vars, script, options, ct); content.Data = await scriptEngine.TransformAsync(vars, script, options, ct);

2
backend/src/Squidex.Web/Pipeline/AppResolver.cs

@ -44,7 +44,7 @@ public sealed class AppResolver : IAsyncActionFilter
var isFrontend = user.IsInClient(DefaultClients.Frontend); var isFrontend = user.IsInClient(DefaultClients.Frontend);
var app = await appProvider.GetAppAsync(appName, !isFrontend, default); var app = await appProvider.GetAppAsync(appName, !isFrontend, context.HttpContext.RequestAborted);
if (app == null) if (app == null)
{ {

1
backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs

@ -107,6 +107,7 @@ public static class OpenApiServices
}; };
settings.AllowReferencesWithProperties = true; settings.AllowReferencesWithProperties = true;
settings.DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull;
settings.FlattenInheritanceHierarchy = flatten; settings.FlattenInheritanceHierarchy = flatten;
settings.SchemaNameGenerator = new SchemaNameGenerator(); settings.SchemaNameGenerator = new SchemaNameGenerator();
settings.SchemaProcessors.Add(new DiscriminatorProcessor(typeRegistry)); settings.SchemaProcessors.Add(new DiscriminatorProcessor(typeRegistry));

10
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs

@ -13,6 +13,16 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models;
public sealed class AssetScriptsDto : Resource public sealed class AssetScriptsDto : Resource
{ {
/// <summary>
/// The script that is executed for each asset when querying assets.
/// </summary>
public string? Query { get; set; }
/// <summary>
/// The script that is executed for all assets when querying assets.
/// </summary>
public string? QueryPre { get; set; }
/// <summary> /// <summary>
/// The script that is executed when creating an asset. /// The script that is executed when creating an asset.
/// </summary> /// </summary>

10
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAssetScriptsDto.cs

@ -13,6 +13,16 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models;
public sealed class UpdateAssetScriptsDto public sealed class UpdateAssetScriptsDto
{ {
/// <summary>
/// The script that is executed for each asset when querying assets.
/// </summary>
public string? Query { get; set; }
/// <summary>
/// The script that is executed for all assets when querying assets.
/// </summary>
public string? QueryPre { get; set; }
/// <summary> /// <summary>
/// The script that is executed when creating an asset. /// The script that is executed when creating an asset.
/// </summary> /// </summary>

16
backend/src/Squidex/Config/Domain/AssetServices.cs

@ -11,6 +11,7 @@ using Squidex.Assets;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Queries; using Squidex.Domain.Apps.Entities.Assets.Queries;
using Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
using Squidex.Hosting; using Squidex.Hosting;
@ -72,6 +73,21 @@ public static class AssetServices
services.AddSingletonAs<AssetEnricher>() services.AddSingletonAs<AssetEnricher>()
.As<IAssetEnricher>(); .As<IAssetEnricher>();
services.AddSingletonAs<CalculateTokens>()
.As<IAssetEnricherStep>();
services.AddSingletonAs<ConvertTags>()
.As<IAssetEnricherStep>();
services.AddSingletonAs<EnrichForCaching>()
.As<IAssetEnricherStep>();
services.AddSingletonAs<EnrichWithMetadataText>()
.As<IAssetEnricherStep>();
services.AddSingletonAs<ScriptAsset>()
.As<IAssetEnricherStep>();
services.AddSingletonAs<AssetQueryService>() services.AddSingletonAs<AssetQueryService>()
.As<IAssetQueryService>(); .As<IAssetQueryService>();

2
backend/src/Squidex/Squidex.csproj

@ -57,7 +57,7 @@
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="2.1.1" /> <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="2.1.1" />
<PackageReference Include="NJsonSchema" Version="10.8.0" /> <PackageReference Include="NJsonSchema" Version="10.8.0" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="13.18.0" /> <PackageReference Include="NSwag.AspNetCore" Version="13.18.2" />
<PackageReference Include="OpenCover" Version="4.7.1221" PrivateAssets="all" /> <PackageReference Include="OpenCover" Version="4.7.1221" PrivateAssets="all" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc7" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc7" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc7" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc7" />

60
backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs

@ -12,10 +12,8 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities; namespace Squidex.Domain.Apps.Entities;
public class AppProviderExtensionsTests public class AppProviderExtensionsTests : GivenContext
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly NamedId<DomainId> componentId1 = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly NamedId<DomainId> componentId1 = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly NamedId<DomainId> componentId2 = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly NamedId<DomainId> componentId2 = NamedId.Of(DomainId.NewGuid(), "my-schema");
@ -23,13 +21,13 @@ public class AppProviderExtensionsTests
[Fact] [Fact]
public async Task Should_do_nothing_if_no_component_found() public async Task Should_do_nothing_if_no_component_found()
{ {
var schema = Mocks.Schema(appId, schemaId); var schema = Mocks.Schema(AppId, schemaId);
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Empty(components); Assert.Empty(components);
A.CallTo(() => appProvider.GetSchemaAsync(A<DomainId>._, A<DomainId>._, false, A<CancellationToken>._)) A.CallTo(() => AppProvider.GetSchemaAsync(A<DomainId>._, A<DomainId>._, false, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -37,39 +35,39 @@ public class AppProviderExtensionsTests
public async Task Should_resolve_self_as_component() public async Task Should_resolve_self_as_component()
{ {
var schema = var schema =
Mocks.Schema(appId, schemaId, Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name) new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
SchemaId = schemaId.Id SchemaId = schemaId.Id
})); }));
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components); Assert.Single(components);
Assert.Same(schema.SchemaDef, components[schemaId.Id]); Assert.Same(schema.SchemaDef, components[schemaId.Id]);
A.CallTo(() => appProvider.GetSchemaAsync(A<DomainId>._, A<DomainId>._, false, A<CancellationToken>._)) A.CallTo(() => AppProvider.GetSchemaAsync(A<DomainId>._, A<DomainId>._, false, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
[Fact] [Fact]
public async Task Should_resolve_from_component() public async Task Should_resolve_from_component()
{ {
var component = Mocks.Schema(appId, componentId1); var component = Mocks.Schema(AppId, componentId1);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId1.Id, false, default)) A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken))
.Returns(component); .Returns(component);
var schema = var schema =
Mocks.Schema(appId, schemaId, Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name) new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
SchemaId = componentId1.Id SchemaId = componentId1.Id
})); }));
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components); Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]); Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -78,20 +76,20 @@ public class AppProviderExtensionsTests
[Fact] [Fact]
public async Task Should_resolve_from_components() public async Task Should_resolve_from_components()
{ {
var component = Mocks.Schema(appId, componentId1); var component = Mocks.Schema(AppId, componentId1);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId1.Id, false, default)) A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken))
.Returns(component); .Returns(component);
var schema = var schema =
Mocks.Schema(appId, schemaId, Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name) new Schema(schemaId.Name)
.AddComponents(1, "1", Partitioning.Invariant, new ComponentsFieldProperties .AddComponents(1, "1", Partitioning.Invariant, new ComponentsFieldProperties
{ {
SchemaId = componentId1.Id SchemaId = componentId1.Id
})); }));
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components); Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]); Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -100,13 +98,13 @@ public class AppProviderExtensionsTests
[Fact] [Fact]
public async Task Should_resolve_from_array() public async Task Should_resolve_from_array()
{ {
var component = Mocks.Schema(appId, componentId1); var component = Mocks.Schema(AppId, componentId1);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId1.Id, false, default)) A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken))
.Returns(component); .Returns(component);
var schema = var schema =
Mocks.Schema(appId, schemaId, Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name) new Schema(schemaId.Name)
.AddArray(1, "1", Partitioning.Invariant, a => a .AddArray(1, "1", Partitioning.Invariant, a => a
.AddComponent(2, "2", new ComponentFieldProperties .AddComponent(2, "2", new ComponentFieldProperties
@ -114,7 +112,7 @@ public class AppProviderExtensionsTests
SchemaId = componentId1.Id SchemaId = componentId1.Id
}))); })));
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components); Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]); Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -124,25 +122,25 @@ public class AppProviderExtensionsTests
public async Task Should_resolve_self_referencing_component() public async Task Should_resolve_self_referencing_component()
{ {
var component = var component =
Mocks.Schema(appId, componentId1, Mocks.Schema(AppId, componentId1,
new Schema(componentId1.Name) new Schema(componentId1.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
SchemaId = componentId1.Id SchemaId = componentId1.Id
})); }));
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId1.Id, false, default)) A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken))
.Returns(component); .Returns(component);
var schema = var schema =
Mocks.Schema(appId, schemaId, Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name) new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
SchemaId = componentId1.Id SchemaId = componentId1.Id
})); }));
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components); Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]); Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -152,7 +150,7 @@ public class AppProviderExtensionsTests
public async Task Should_resolve_component_of_component() public async Task Should_resolve_component_of_component()
{ {
var component1 = var component1 =
Mocks.Schema(appId, componentId1, Mocks.Schema(AppId, componentId1,
new Schema(componentId1.Name) new Schema(componentId1.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
@ -160,28 +158,28 @@ public class AppProviderExtensionsTests
})); }));
var component2 = var component2 =
Mocks.Schema(appId, componentId2, Mocks.Schema(AppId, componentId2,
new Schema(componentId2.Name) new Schema(componentId2.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
SchemaId = componentId2.Id SchemaId = componentId2.Id
})); }));
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId1.Id, false, default)) A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken))
.Returns(component1); .Returns(component1);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId2.Id, false, default)) A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId2.Id, false, CancellationToken))
.Returns(component2); .Returns(component2);
var schema = var schema =
Mocks.Schema(appId, schemaId, Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name) new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{ {
SchemaId = componentId1.Id SchemaId = componentId1.Id
})); }));
var components = await appProvider.GetComponentsAsync(schema); var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Equal(2, components.Count); Assert.Equal(2, components.Count);
Assert.Same(component1.SchemaDef, components[componentId1.Id]); Assert.Same(component1.SchemaDef, components[componentId1.Id]);

117
backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderTests.cs

@ -20,55 +20,42 @@ using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Entities; namespace Squidex.Domain.Apps.Entities;
public class AppProviderTests public class AppProviderTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IAppsIndex indexForApps = A.Fake<IAppsIndex>(); private readonly IAppsIndex indexForApps = A.Fake<IAppsIndex>();
private readonly IRulesIndex indexForRules = A.Fake<IRulesIndex>(); private readonly IRulesIndex indexForRules = A.Fake<IRulesIndex>();
private readonly ISchemasIndex indexForSchemas = A.Fake<ISchemasIndex>(); private readonly ISchemasIndex indexForSchemas = A.Fake<ISchemasIndex>();
private readonly ITeamsIndex indexForTeams = A.Fake<ITeamsIndex>(); private readonly ITeamsIndex indexForTeams = A.Fake<ITeamsIndex>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly IAppEntity app;
private readonly AppProvider sut; private readonly AppProvider sut;
public AppProviderTests() public AppProviderTests()
{ {
ct = cts.Token;
app = Mocks.App(appId);
sut = new AppProvider(indexForApps, indexForRules, indexForSchemas, indexForTeams, new AsyncLocalCache()); sut = new AppProvider(indexForApps, indexForRules, indexForSchemas, indexForTeams, new AsyncLocalCache());
} }
[Fact] [Fact]
public async Task Should_get_app_with_schema_from_index() public async Task Should_get_app_with_schema_from_index()
{ {
var schema = Mocks.Schema(app.NamedId(), schemaId); A.CallTo(() => indexForApps.GetAppAsync(AppId.Id, false, CancellationToken))
.Returns(App);
A.CallTo(() => indexForApps.GetAppAsync(app.Id, false, ct))
.Returns(app);
A.CallTo(() => indexForSchemas.GetSchemaAsync(app.Id, schema.Id, false, ct)) A.CallTo(() => indexForSchemas.GetSchemaAsync(AppId.Id, SchemaId.Id, false, CancellationToken))
.Returns(schema); .Returns(Schema);
var actual = await sut.GetAppWithSchemaAsync(app.Id, schemaId.Id, false, ct); var actual = await sut.GetAppWithSchemaAsync(AppId.Id, SchemaId.Id, false, CancellationToken);
Assert.Equal(schema, actual.Item2); Assert.Equal(Schema, actual.Item2);
} }
[Fact] [Fact]
public async Task Should_get_team_apps_from_index() public async Task Should_get_team_apps_from_index()
{ {
var team = Mocks.Team(DomainId.NewGuid()); A.CallTo(() => indexForApps.GetAppsForTeamAsync(TeamId, CancellationToken))
.Returns(new List<IAppEntity> { App });
A.CallTo(() => indexForApps.GetAppsForTeamAsync(team.Id, ct)) var actual = await sut.GetTeamAppsAsync(TeamId, CancellationToken);
.Returns(new List<IAppEntity> { app });
var actual = await sut.GetTeamAppsAsync(team.Id, ct); Assert.Equal(App, actual.Single());
Assert.Equal(app, actual.Single());
} }
[Fact] [Fact]
@ -76,99 +63,89 @@ public class AppProviderTests
{ {
var permissions = new PermissionSet("*"); var permissions = new PermissionSet("*");
A.CallTo(() => indexForApps.GetAppsForUserAsync("user1", permissions, ct)) A.CallTo(() => indexForApps.GetAppsForUserAsync("user1", permissions, CancellationToken))
.Returns(new List<IAppEntity> { app }); .Returns(new List<IAppEntity> { App });
var actual = await sut.GetUserAppsAsync("user1", permissions, ct); var actual = await sut.GetUserAppsAsync("user1", permissions, CancellationToken);
Assert.Equal(app, actual.Single()); Assert.Equal(App, actual.Single());
} }
[Fact] [Fact]
public async Task Should_get_app_from_index() public async Task Should_get_app_from_index()
{ {
A.CallTo(() => indexForApps.GetAppAsync(app.Id, false, ct)) A.CallTo(() => indexForApps.GetAppAsync(AppId.Id, false, CancellationToken))
.Returns(app); .Returns(App);
var actual = await sut.GetAppAsync(app.Id, false, ct); var actual = await sut.GetAppAsync(AppId.Id, false, CancellationToken);
Assert.Equal(app, actual); Assert.Equal(App, actual);
} }
[Fact] [Fact]
public async Task Should_get_app_by_name_from_index() public async Task Should_get_app_by_name_from_index()
{ {
A.CallTo(() => indexForApps.GetAppAsync(app.Name, false, ct)) A.CallTo(() => indexForApps.GetAppAsync(AppId.Name, false, CancellationToken))
.Returns(app); .Returns(App);
var actual = await sut.GetAppAsync(app.Name, false, ct); var actual = await sut.GetAppAsync(AppId.Name, false, CancellationToken);
Assert.Equal(app, actual); Assert.Equal(App, actual);
} }
[Fact] [Fact]
public async Task Should_get_team_from_index() public async Task Should_get_team_from_index()
{ {
var team = Mocks.Team(DomainId.NewGuid()); A.CallTo(() => indexForTeams.GetTeamAsync(TeamId, CancellationToken))
.Returns(Team);
A.CallTo(() => indexForTeams.GetTeamAsync(team.Id, ct))
.Returns(team);
var actual = await sut.GetTeamAsync(team.Id, ct); var actual = await sut.GetTeamAsync(TeamId, CancellationToken);
Assert.Equal(team, actual); Assert.Equal(Team, actual);
} }
[Fact] [Fact]
public async Task Should_get_teams_from_index() public async Task Should_get_teams_from_index()
{ {
var team = Mocks.Team(DomainId.NewGuid()); A.CallTo(() => indexForTeams.GetTeamsAsync("user1", CancellationToken))
.Returns(new List<ITeamEntity> { Team });
A.CallTo(() => indexForTeams.GetTeamsAsync("user1", ct)) var actual = await sut.GetUserTeamsAsync("user1", CancellationToken);
.Returns(new List<ITeamEntity> { team });
var actual = await sut.GetUserTeamsAsync("user1", ct); Assert.Equal(Team, actual.Single());
Assert.Equal(team, actual.Single());
} }
[Fact] [Fact]
public async Task Should_get_schema_from_index() public async Task Should_get_schema_from_index()
{ {
var schema = Mocks.Schema(app.NamedId(), schemaId); A.CallTo(() => indexForSchemas.GetSchemaAsync(AppId.Id, SchemaId.Id, false, CancellationToken))
.Returns(Schema);
A.CallTo(() => indexForSchemas.GetSchemaAsync(app.Id, schema.Id, false, ct))
.Returns(schema);
var actual = await sut.GetSchemaAsync(app.Id, schema.Id, false, ct); var actual = await sut.GetSchemaAsync(AppId.Id, SchemaId.Id, false, CancellationToken);
Assert.Equal(schema, actual); Assert.Equal(Schema, actual);
} }
[Fact] [Fact]
public async Task Should_get_schema_by_name_from_index() public async Task Should_get_schema_by_name_from_index()
{ {
var schema = Mocks.Schema(app.NamedId(), schemaId); A.CallTo(() => indexForSchemas.GetSchemaAsync(AppId.Id, SchemaId.Name, false, CancellationToken))
.Returns(Schema);
A.CallTo(() => indexForSchemas.GetSchemaAsync(app.Id, schemaId.Name, false, ct)) var actual = await sut.GetSchemaAsync(AppId.Id, SchemaId.Name, false, CancellationToken);
.Returns(schema);
var actual = await sut.GetSchemaAsync(app.Id, schemaId.Name, false, ct); Assert.Equal(Schema, actual);
Assert.Equal(schema, actual);
} }
[Fact] [Fact]
public async Task Should_get_schemas_from_index() public async Task Should_get_schemas_from_index()
{ {
var schema = Mocks.Schema(app.NamedId(), schemaId); A.CallTo(() => indexForSchemas.GetSchemasAsync(AppId.Id, CancellationToken))
.Returns(new List<ISchemaEntity> { Schema });
A.CallTo(() => indexForSchemas.GetSchemasAsync(app.Id, ct))
.Returns(new List<ISchemaEntity> { schema });
var actual = await sut.GetSchemasAsync(app.Id, ct); var actual = await sut.GetSchemasAsync(AppId.Id, CancellationToken);
Assert.Equal(schema, actual.Single()); Assert.Equal(Schema, actual.Single());
} }
[Fact] [Fact]
@ -176,10 +153,10 @@ public class AppProviderTests
{ {
var rule = new RuleEntity(); var rule = new RuleEntity();
A.CallTo(() => indexForRules.GetRulesAsync(app.Id, ct)) A.CallTo(() => indexForRules.GetRulesAsync(AppId.Id, CancellationToken))
.Returns(new List<IRuleEntity> { rule }); .Returns(new List<IRuleEntity> { rule });
var actual = await sut.GetRulesAsync(app.Id, ct); var actual = await sut.GetRulesAsync(AppId.Id, CancellationToken);
Assert.Equal(rule, actual.Single()); Assert.Equal(rule, actual.Single());
} }
@ -189,10 +166,10 @@ public class AppProviderTests
{ {
var rule = new RuleEntity { Id = DomainId.NewGuid() }; var rule = new RuleEntity { Id = DomainId.NewGuid() };
A.CallTo(() => indexForRules.GetRulesAsync(app.Id, ct)) A.CallTo(() => indexForRules.GetRulesAsync(AppId.Id, CancellationToken))
.Returns(new List<IRuleEntity> { rule }); .Returns(new List<IRuleEntity> { rule });
var actual = await sut.GetRuleAsync(app.Id, rule.Id, ct); var actual = await sut.GetRuleAsync(AppId.Id, rule.Id, CancellationToken);
Assert.Equal(rule, actual); Assert.Equal(rule, actual);
} }

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

@ -6,22 +6,17 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class AppEventDeleterTests public class AppEventDeleterTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IEventStore eventStore = A.Fake<IEventStore>(); private readonly IEventStore eventStore = A.Fake<IEventStore>();
private readonly AppEventDeleter sut; private readonly AppEventDeleter sut;
public AppEventDeleterTests() public AppEventDeleterTests()
{ {
ct = cts.Token;
sut = new AppEventDeleter(eventStore); sut = new AppEventDeleter(eventStore);
} }
@ -36,11 +31,9 @@ public class AppEventDeleterTests
[Fact] [Fact]
public async Task Should_remove_events_from_streams() public async Task Should_remove_events_from_streams()
{ {
var app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app")); await sut.DeleteAppAsync(App, CancellationToken);
await sut.DeleteAppAsync(app, ct);
A.CallTo(() => eventStore.DeleteAsync($"^[a-zA-Z0-9]-{app.Id}", A<CancellationToken>._)) A.CallTo(() => eventStore.DeleteAsync($"^[a-zA-Z0-9]-{AppId.Id}", A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
} }

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

@ -9,13 +9,12 @@ 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;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class AppPermanentDeleterTests 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>();
@ -90,24 +89,22 @@ public class AppPermanentDeleterTests
[Fact] [Fact]
public async Task Should_call_deleters_when_contributor_removed() public async Task Should_call_deleters_when_contributor_removed()
{ {
var app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app"));
await sut.On(Envelope.Create(new AppContributorRemoved await sut.On(Envelope.Create(new AppContributorRemoved
{ {
AppId = app.NamedId(), ContributorId = "user1" AppId = AppId, ContributorId = "user1"
})); }));
A.CallTo(() => deleter1.DeleteContributorAsync(app.Id, "user1", default)) A.CallTo(() => deleter1.DeleteContributorAsync(AppId.Id, "user1", default))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => deleter2.DeleteContributorAsync(app.Id, "user1", default)) A.CallTo(() => deleter2.DeleteContributorAsync(AppId.Id, "user1", default))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_call_deleters_when_app_deleted() public async Task Should_call_deleters_when_app_deleted()
{ {
var app = new AppDomainObject.State { Id = DomainId.NewGuid(), Name = "my-app" }; var app = new AppDomainObject.State { Id = AppId.Id, Name = AppId.Name };
var domainObject = A.Fake<AppDomainObject>(); var domainObject = A.Fake<AppDomainObject>();
@ -119,7 +116,7 @@ public class AppPermanentDeleterTests
await sut.On(Envelope.Create(new AppDeleted await sut.On(Envelope.Create(new AppDeleted
{ {
AppId = app.NamedId() AppId = AppId
})); }));
A.CallTo(() => deleter1.DeleteAppAsync(app, default)) A.CallTo(() => deleter1.DeleteAppAsync(app, default))

208
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppSettingsSearchSourceTests.cs

@ -5,20 +5,16 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public sealed class AppSettingsSearchSourceTests public sealed class AppSettingsSearchSourceTests : GivenContext
{ {
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AppSettingsSearchSource sut; private readonly AppSettingsSearchSource sut;
public AppSettingsSearchSourceTests() public AppSettingsSearchSourceTests()
@ -29,24 +25,20 @@ public sealed class AppSettingsSearchSourceTests
[Fact] [Fact]
public async Task Should_return_empty_if_nothing_matching() public async Task Should_return_empty_if_nothing_matching()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("xyz", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("xyz", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_dashboard_actual_if_matching_and_permission_given() public async Task Should_return_dashboard_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppUsage, appId.Name); var requestContext = SetupContext(PermissionIds.AppUsage);
var ctx = ContextWithPermission(permission.Id);
A.CallTo(() => urlGenerator.DashboardUI(appId)) A.CallTo(() => urlGenerator.DashboardUI(AppId))
.Returns("dashboard-url"); .Returns("dashboard-url");
var actual = await sut.SearchAsync("dashboard", ctx, default); var actual = await sut.SearchAsync("dashboard", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -54,26 +46,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_dashboard_actual_if_user_has_no_permission() public async Task Should_not_return_dashboard_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("assets", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_languages_actual_if_matching_and_permission_given() public async Task Should_return_languages_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppLanguagesRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppLanguagesRead);
var ctx = ContextWithPermission(permission.Id);
A.CallTo(() => urlGenerator.LanguagesUI(appId)) A.CallTo(() => urlGenerator.LanguagesUI(AppId))
.Returns("languages-url"); .Returns("languages-url");
var actual = await sut.SearchAsync("languages", ctx, default); var actual = await sut.SearchAsync("languages", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -81,36 +69,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_languages_actual_if_user_has_no_permission() public async Task Should_not_return_languages_result_if_user_has_no_permission()
{
var ctx = ContextWithPermission();
var actual = await sut.SearchAsync("assets", ctx, default);
Assert.Empty(actual);
}
[Fact]
public async Task Should_not_return_patterns_actual_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("patterns", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_schemas_actual_if_matching_and_permission_given() public async Task Should_return_schemas_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppSchemasRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppSchemasRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.SchemasUI(AppId))
A.CallTo(() => urlGenerator.SchemasUI(appId))
.Returns("schemas-url"); .Returns("schemas-url");
var actual = await sut.SearchAsync("schemas", ctx, default); var actual = await sut.SearchAsync("schemas", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -118,26 +92,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_schemas_actual_if_user_has_no_permission() public async Task Should_not_return_schemas_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("schemas", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("schemas", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_assets_actual_if_matching_and_permission_given() public async Task Should_return_assets_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppAssetsRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppAssetsRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.AssetsUI(AppId, A<string?>._))
A.CallTo(() => urlGenerator.AssetsUI(appId, A<string?>._))
.Returns("assets-url"); .Returns("assets-url");
var actual = await sut.SearchAsync("assets", ctx, default); var actual = await sut.SearchAsync("assets", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -145,26 +115,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_assets_actual_if_user_has_no_permission() public async Task Should_not_return_assets_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("assets", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_backups_actual_if_matching_and_permission_given() public async Task Should_return_backups_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppBackupsRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppBackupsRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.BackupsUI(AppId))
A.CallTo(() => urlGenerator.BackupsUI(appId))
.Returns("backups-url"); .Returns("backups-url");
var actual = await sut.SearchAsync("backups", ctx, default); var actual = await sut.SearchAsync("backups", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -172,26 +138,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_backups_actual_if_user_has_no_permission() public async Task Should_not_return_backups_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("backups", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("backups", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_clients_actual_if_matching_and_permission_given() public async Task Should_return_clients_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppClientsRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppClientsRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.ClientsUI(AppId))
A.CallTo(() => urlGenerator.ClientsUI(appId))
.Returns("clients-url"); .Returns("clients-url");
var actual = await sut.SearchAsync("clients", ctx, default); var actual = await sut.SearchAsync("clients", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -199,26 +161,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_clients_actual_if_user_has_no_permission() public async Task Should_not_return_clients_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("clients", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("clients", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_contributors_actual_if_matching_and_permission_given() public async Task Should_return_contributors_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppContributorsRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppContributorsRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.ContributorsUI(AppId))
A.CallTo(() => urlGenerator.ContributorsUI(appId))
.Returns("contributors-url"); .Returns("contributors-url");
var actual = await sut.SearchAsync("contributors", ctx, default); var actual = await sut.SearchAsync("contributors", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -226,26 +184,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_contributors_clients_actual_if_user_has_no_permission() public async Task Should_not_contributors_clients_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("contributors", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("contributors", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_subscription_actual_if_matching_and_permission_given() public async Task Should_return_subscription_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppPlansRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppPlansRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.PlansUI(AppId))
A.CallTo(() => urlGenerator.PlansUI(appId))
.Returns("subscription-url"); .Returns("subscription-url");
var actual = await sut.SearchAsync("subscription", ctx, default); var actual = await sut.SearchAsync("subscription", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -253,26 +207,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_subscription_clients_actual_if_user_has_no_permission() public async Task Should_not_subscription_clients_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("subscription", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("subscription", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_roles_actual_if_matching_and_permission_given() public async Task Should_return_roles_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppRolesRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppRolesRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.RolesUI(AppId))
A.CallTo(() => urlGenerator.RolesUI(appId))
.Returns("roles-url"); .Returns("roles-url");
var actual = await sut.SearchAsync("roles", ctx, default); var actual = await sut.SearchAsync("roles", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -280,26 +230,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_roles_clients_actual_if_user_has_no_permission() public async Task Should_not_roles_clients_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("roles", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("roles", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_rules_actual_if_matching_and_permission_given() public async Task Should_return_rules_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppRulesRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppRulesRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.RulesUI(AppId))
A.CallTo(() => urlGenerator.RulesUI(appId))
.Returns("rules-url"); .Returns("rules-url");
var actual = await sut.SearchAsync("rules", ctx, default); var actual = await sut.SearchAsync("rules", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -307,26 +253,22 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_rules_actual_if_user_has_no_permission() public async Task Should_not_return_rules_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("assets", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
[Fact] [Fact]
public async Task Should_return_workflows_actual_if_matching_and_permission_given() public async Task Should_return_workflows_result_if_matching_and_permission_given()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppWorkflowsRead, appId.Name); var requestContext = SetupContext(PermissionIds.AppWorkflowsRead);
var ctx = ContextWithPermission(permission.Id); A.CallTo(() => urlGenerator.WorkflowsUI(AppId))
A.CallTo(() => urlGenerator.WorkflowsUI(appId))
.Returns("workflows-url"); .Returns("workflows-url");
var actual = await sut.SearchAsync("workflows", ctx, default); var actual = await sut.SearchAsync("workflows", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -334,25 +276,15 @@ public sealed class AppSettingsSearchSourceTests
} }
[Fact] [Fact]
public async Task Should_not_return_workflows_actual_if_user_has_no_permission() public async Task Should_not_return_workflows_result_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("workflows", ApiContext, CancellationToken);
var actual = await sut.SearchAsync("workflows", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
} }
private Context ContextWithPermission(string? permission = null) private Context SetupContext(string permission)
{ {
var claimsIdentity = new ClaimsIdentity(); return CreateContext(false, PermissionIds.ForApp(permission, AppId.Name).Id);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
if (permission != null)
{
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
}
return new Context(claimsPrincipal, Mocks.App(appId));
} }
} }

80
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs

@ -8,27 +8,21 @@
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.TestHelpers; using Squidex.Infrastructure.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public sealed class AppUISettingsTests public sealed class AppUISettingsTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly TestState<AppUISettings.State> state; private readonly TestState<AppUISettings.State> state;
private readonly DomainId appId = DomainId.NewGuid();
private readonly string userId = Guid.NewGuid().ToString(); private readonly string userId = Guid.NewGuid().ToString();
private readonly string stateId; private readonly string stateId;
private readonly AppUISettings sut; private readonly AppUISettings sut;
public AppUISettingsTests() public AppUISettingsTests()
{ {
ct = cts.Token; stateId = $"{AppId.Id}_{userId}";
stateId = $"{appId}_{userId}";
state = new TestState<AppUISettings.State>(stateId); state = new TestState<AppUISettings.State>(stateId);
sut = new AppUISettings(state.PersistenceFactory); sut = new AppUISettings(state.PersistenceFactory);
@ -45,87 +39,85 @@ public sealed class AppUISettingsTests
[Fact] [Fact]
public async Task Should_delete_contributor_state() public async Task Should_delete_contributor_state()
{ {
await ((IDeleter)sut).DeleteContributorAsync(appId, userId, ct); await ((IDeleter)sut).DeleteContributorAsync(AppId.Id, userId, CancellationToken);
A.CallTo(() => state.Persistence.DeleteAsync(ct)) A.CallTo(() => state.Persistence.DeleteAsync(CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_delete_app_and_contributors() public async Task Should_delete_app_and_contributors()
{ {
var app = Mocks.App(NamedId.Of(appId, "my-app")); A.CallTo(() => App.Contributors)
A.CallTo(() => app.Contributors)
.Returns(Contributors.Empty.Assign(userId, Role.Owner)); .Returns(Contributors.Empty.Assign(userId, Role.Owner));
var rootState = new TestState<AppUISettings.State>(appId, state.PersistenceFactory); var rootState = new TestState<AppUISettings.State>(AppId.Id, state.PersistenceFactory);
await ((IDeleter)sut).DeleteAppAsync(app, ct); await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
A.CallTo(() => state.Persistence.DeleteAsync(ct)) A.CallTo(() => state.Persistence.DeleteAsync(CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => rootState.Persistence.DeleteAsync(ct)) A.CallTo(() => rootState.Persistence.DeleteAsync(CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_set_setting() public async Task Should_set_setting()
{ {
await sut.SetAsync(appId, userId, new JsonObject().Add("key", 42), ct); await sut.SetAsync(AppId.Id, userId, new JsonObject().Add("key", 42), CancellationToken);
var actual = await sut.GetAsync(appId, userId, ct); var actual = await sut.GetAsync(AppId.Id, userId, CancellationToken);
var expected = var expected =
new JsonObject().Add("key", 42); new JsonObject().Add("key", 42);
Assert.Equal(expected.ToString(), actual.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_set_root_value() public async Task Should_set_root_value()
{ {
await sut.SetAsync(appId, userId, "key", 42, ct); await sut.SetAsync(AppId.Id, userId, "key", 42, CancellationToken);
var actual = await sut.GetAsync(appId, userId, ct); var actual = await sut.GetAsync(AppId.Id, userId, CancellationToken);
var expected = var expected =
new JsonObject().Add("key", 42); new JsonObject().Add("key", 42);
Assert.Equal(expected.ToString(), actual.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_remove_root_value() public async Task Should_remove_root_value()
{ {
await sut.SetAsync(appId, userId, "key", 42, ct); await sut.SetAsync(AppId.Id, userId, "key", 42, CancellationToken);
await sut.RemoveAsync(appId, userId, "key", ct); await sut.RemoveAsync(AppId.Id, userId, "key", CancellationToken);
await sut.RemoveAsync(appId, userId, "key", ct); await sut.RemoveAsync(AppId.Id, userId, "key", CancellationToken);
var actual = await sut.GetAsync(appId, userId, ct); var actual = await sut.GetAsync(AppId.Id, userId, CancellationToken);
var expected = new JsonObject(); var expected = new JsonObject();
Assert.Equal(expected.ToString(), actual.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, CancellationToken))
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
[Fact] [Fact]
public async Task Should_set_nested_value() public async Task Should_set_nested_value()
{ {
await sut.SetAsync(appId, userId, "root.nested", 42, ct); await sut.SetAsync(AppId.Id, userId, "root.nested", 42, CancellationToken);
var actual = await sut.GetAsync(appId, userId, ct); var actual = await sut.GetAsync(AppId.Id, userId, CancellationToken);
var expected = var expected =
new JsonObject().Add("root", new JsonObject().Add("root",
@ -133,17 +125,17 @@ public sealed class AppUISettingsTests
Assert.Equal(expected.ToString(), actual.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_not_write_state_if_value_not_changed() public async Task Should_not_write_state_if_value_not_changed()
{ {
await sut.SetAsync(appId, userId, "root.nested", 42, ct); await sut.SetAsync(AppId.Id, userId, "root.nested", 42, CancellationToken);
await sut.SetAsync(appId, userId, "root.nested", 42, ct); await sut.SetAsync(AppId.Id, userId, "root.nested", 42, CancellationToken);
var actual = await sut.GetAsync(appId, userId, ct); var actual = await sut.GetAsync(AppId.Id, userId, CancellationToken);
var expected = var expected =
new JsonObject().Add("root", new JsonObject().Add("root",
@ -151,19 +143,19 @@ public sealed class AppUISettingsTests
Assert.Equal(expected.ToString(), actual.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
[Fact] [Fact]
public async Task Should_remove_nested_value() public async Task Should_remove_nested_value()
{ {
await sut.SetAsync(appId, userId, "root.nested", 42, ct); await sut.SetAsync(AppId.Id, userId, "root.nested", 42, CancellationToken);
await sut.RemoveAsync(appId, userId, "root.nested", ct); await sut.RemoveAsync(AppId.Id, userId, "root.nested", CancellationToken);
await sut.RemoveAsync(appId, userId, "key", ct); await sut.RemoveAsync(AppId.Id, userId, "key", CancellationToken);
var actual = await sut.GetAsync(appId, userId, ct); var actual = await sut.GetAsync(AppId.Id, userId, CancellationToken);
var expected = var expected =
new JsonObject().Add("root", new JsonObject().Add("root",
@ -171,22 +163,22 @@ public sealed class AppUISettingsTests
Assert.Equal(expected.ToString(), actual.ToString()); Assert.Equal(expected.ToString(), actual.ToString());
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, CancellationToken))
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
[Fact] [Fact]
public async Task Should_throw_exception_if_nested_not_an_object() public async Task Should_throw_exception_if_nested_not_an_object()
{ {
await sut.SetAsync(appId, userId, "root.nested", 42, ct); await sut.SetAsync(AppId.Id, userId, "root.nested", 42, CancellationToken);
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.SetAsync(appId, userId, "root.nested.value", 42, ct)); await Assert.ThrowsAsync<InvalidOperationException>(() => sut.SetAsync(AppId.Id, userId, "root.nested.value", 42, CancellationToken));
} }
[Fact] [Fact]
public async Task Should_do_nothing_if_deleting_and_nested_not_found() public async Task Should_do_nothing_if_deleting_and_nested_not_found()
{ {
await sut.RemoveAsync(appId, userId, "root.nested", ct); await sut.RemoveAsync(AppId.Id, userId, "root.nested", CancellationToken);
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, A<CancellationToken>._)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -195,7 +187,7 @@ public sealed class AppUISettingsTests
[Fact] [Fact]
public async Task Should_do_nothing_if_deleting_and_key_not_found() public async Task Should_do_nothing_if_deleting_and_key_not_found()
{ {
await sut.RemoveAsync(appId, userId, "root", ct); await sut.RemoveAsync(AppId.Id, userId, "root", CancellationToken);
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, A<CancellationToken>._)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<AppUISettings.State>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();

13
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUsageDeleterTests.cs

@ -6,22 +6,17 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.UsageTracking; using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class AppUsageDeleterTests public class AppUsageDeleterTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IApiUsageTracker usageTracker = A.Fake<IApiUsageTracker>(); private readonly IApiUsageTracker usageTracker = A.Fake<IApiUsageTracker>();
private readonly AppUsageDeleter sut; private readonly AppUsageDeleter sut;
public AppUsageDeleterTests() public AppUsageDeleterTests()
{ {
ct = cts.Token;
sut = new AppUsageDeleter(usageTracker); sut = new AppUsageDeleter(usageTracker);
} }
@ -36,11 +31,9 @@ public class AppUsageDeleterTests
[Fact] [Fact]
public async Task Should_remove_events_from_streams() public async Task Should_remove_events_from_streams()
{ {
var app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app")); await sut.DeleteAppAsync(App, CancellationToken);
await sut.DeleteAppAsync(app, ct);
A.CallTo(() => usageTracker.DeleteAsync(app.Id.ToString(), ct)) A.CallTo(() => usageTracker.DeleteAsync(AppId.Id.ToString(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

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

@ -10,6 +10,7 @@ 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.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -18,22 +19,16 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class BackupAppsTests public class BackupAppsTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly Rebuilder rebuilder = A.Fake<Rebuilder>(); private readonly Rebuilder rebuilder = A.Fake<Rebuilder>();
private readonly IAppsIndex appsIndex = A.Fake<IAppsIndex>(); private readonly IAppsIndex appsIndex = A.Fake<IAppsIndex>();
private readonly IAppUISettings appUISettings = A.Fake<IAppUISettings>(); private readonly IAppUISettings appUISettings = A.Fake<IAppUISettings>();
private readonly IAppImageStore appImageStore = A.Fake<IAppImageStore>(); private readonly IAppImageStore appImageStore = A.Fake<IAppImageStore>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly RefToken actor = RefToken.User("123");
private readonly BackupApps sut; private readonly BackupApps sut;
public BackupAppsTests() public BackupAppsTests()
{ {
ct = cts.Token;
sut = new BackupApps(rebuilder, appImageStore, appsIndex, appUISettings); sut = new BackupApps(rebuilder, appImageStore, appsIndex, appUISettings);
} }
@ -46,62 +41,56 @@ public class BackupAppsTests
[Fact] [Fact]
public async Task Should_reserve_app_name() public async Task Should_reserve_app_name()
{ {
const string appName = "my-app";
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => appsIndex.ReserveAsync(appId, appName, A<CancellationToken>._)) A.CallTo(() => appsIndex.ReserveAsync(AppId.Id, AppId.Name, A<CancellationToken>._))
.Returns("Reservation"); .Returns("Reservation");
await sut.RestoreEventAsync(Envelope.Create(new AppCreated await sut.RestoreEventAsync(Envelope.Create(new AppCreated
{ {
Name = appName Name = AppId.Name
}), context, ct); }), context, CancellationToken);
A.CallTo(() => appsIndex.ReserveAsync(appId, appName, A<CancellationToken>._)) A.CallTo(() => appsIndex.ReserveAsync(AppId.Id, AppId.Name, A<CancellationToken>._))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_complete_reservation_with_previous_token() public async Task Should_complete_reservation_with_previous_token()
{ {
const string appName = "my-app";
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => appsIndex.ReserveAsync(appId, appName, ct)) A.CallTo(() => appsIndex.ReserveAsync(AppId.Id, AppId.Name, CancellationToken))
.Returns("Reservation"); .Returns("Reservation");
await sut.RestoreEventAsync(Envelope.Create(new AppCreated await sut.RestoreEventAsync(Envelope.Create(new AppCreated
{ {
Name = appName Name = AppId.Name
}), context, ct); }), context, CancellationToken);
await sut.CompleteRestoreAsync(context, appName); await sut.CompleteRestoreAsync(context, AppId.Name);
A.CallTo(() => appsIndex.RemoveReservationAsync("Reservation", default)) A.CallTo(() => appsIndex.RemoveReservationAsync("Reservation", default))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => rebuilder.InsertManyAsync<AppDomainObject, AppDomainObject.State>(A<IEnumerable<DomainId>>.That.Is(appId), 1, default)) A.CallTo(() => rebuilder.InsertManyAsync<AppDomainObject, AppDomainObject.State>(A<IEnumerable<DomainId>>.That.Is(AppId.Id), 1, default))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_cleanup_reservation_with_previous_token() public async Task Should_cleanup_reservation_with_previous_token()
{ {
const string appName = "my-app";
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => appsIndex.ReserveAsync(appId, appName, ct)) A.CallTo(() => appsIndex.ReserveAsync(AppId.Id, AppId.Name, CancellationToken))
.Returns("Reservation"); .Returns("Reservation");
await sut.RestoreEventAsync(Envelope.Create(new AppCreated await sut.RestoreEventAsync(Envelope.Create(new AppCreated
{ {
Name = appName Name = AppId.Name
}), context, ct); }), context, CancellationToken);
await sut.CleanupRestoreErrorAsync(appId); await sut.CleanupRestoreErrorAsync(AppId.Id);
A.CallTo(() => appsIndex.RemoveReservationAsync("Reservation", default)) A.CallTo(() => appsIndex.RemoveReservationAsync("Reservation", default))
.MustHaveHappened(); .MustHaveHappened();
@ -110,25 +99,23 @@ public class BackupAppsTests
[Fact] [Fact]
public async Task Should_throw_exception_if_no_reservation_token_returned() public async Task Should_throw_exception_if_no_reservation_token_returned()
{ {
const string appName = "my-app";
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => appsIndex.ReserveAsync(appId, appName, ct)) A.CallTo(() => appsIndex.ReserveAsync(AppId.Id, AppId.Name, CancellationToken))
.Returns(Task.FromResult<string?>(null)); .Returns(Task.FromResult<string?>(null));
var @event = Envelope.Create(new AppCreated var @event = Envelope.Create(new AppCreated
{ {
Name = appName Name = AppId.Name
}); });
await Assert.ThrowsAsync<BackupRestoreException>(() => sut.RestoreEventAsync(@event, context, ct)); await Assert.ThrowsAsync<BackupRestoreException>(() => sut.RestoreEventAsync(@event, context, CancellationToken));
} }
[Fact] [Fact]
public async Task Should_not_cleanup_reservation_if_no_reservation_token_hold() public async Task Should_not_cleanup_reservation_if_no_reservation_token_hold()
{ {
await sut.CleanupRestoreErrorAsync(appId); await sut.CleanupRestoreErrorAsync(AppId.Id);
A.CallTo(() => appsIndex.RemoveReservationAsync("Reservation", A<CancellationToken>._)) A.CallTo(() => appsIndex.RemoveReservationAsync("Reservation", A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -141,12 +128,12 @@ public class BackupAppsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => appUISettings.GetAsync(appId, null, ct)) A.CallTo(() => appUISettings.GetAsync(AppId.Id, null, CancellationToken))
.Returns(settings); .Returns(settings);
await sut.BackupAsync(context, ct); await sut.BackupAsync(context, CancellationToken);
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, settings, ct)) A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, settings, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -157,12 +144,12 @@ public class BackupAppsTests
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => context.Reader.ReadJsonAsync<JsonObject>(A<string>._, ct)) A.CallTo(() => context.Reader.ReadJsonAsync<JsonObject>(A<string>._, CancellationToken))
.Returns(settings); .Returns(settings);
await sut.RestoreAsync(context, ct); await sut.RestoreAsync(context, CancellationToken);
A.CallTo(() => appUISettings.SetAsync(appId, null, settings, ct)) A.CallTo(() => appUISettings.SetAsync(AppId.Id, null, settings, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -176,7 +163,7 @@ public class BackupAppsTests
ContributorId = "found" ContributorId = "found"
}); });
var actual = await sut.RestoreEventAsync(@event, context, ct); var actual = await sut.RestoreEventAsync(@event, context, CancellationToken);
Assert.True(actual); Assert.True(actual);
Assert.Equal("found_mapped", @event.Payload.ContributorId); Assert.Equal("found_mapped", @event.Payload.ContributorId);
@ -192,7 +179,7 @@ public class BackupAppsTests
ContributorId = "unknown" ContributorId = "unknown"
}); });
var actual = await sut.RestoreEventAsync(@event, context, ct); var actual = await sut.RestoreEventAsync(@event, context, CancellationToken);
Assert.False(actual); Assert.False(actual);
Assert.Equal("unknown", @event.Payload.ContributorId); Assert.Equal("unknown", @event.Payload.ContributorId);
@ -208,7 +195,7 @@ public class BackupAppsTests
ContributorId = "found" ContributorId = "found"
}); });
var actual = await sut.RestoreEventAsync(@event, context, ct); var actual = await sut.RestoreEventAsync(@event, context, CancellationToken);
Assert.True(actual); Assert.True(actual);
Assert.Equal("found_mapped", @event.Payload.ContributorId); Assert.Equal("found_mapped", @event.Payload.ContributorId);
@ -224,7 +211,7 @@ public class BackupAppsTests
ContributorId = "unknown" ContributorId = "unknown"
}); });
var actual = await sut.RestoreEventAsync(@event, context, ct); var actual = await sut.RestoreEventAsync(@event, context, CancellationToken);
Assert.False(actual); Assert.False(actual);
Assert.Equal("unknown", @event.Payload.ContributorId); Assert.Equal("unknown", @event.Payload.ContributorId);
@ -237,13 +224,13 @@ public class BackupAppsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => context.Writer.OpenBlobAsync(A<string>._, ct)) A.CallTo(() => context.Writer.OpenBlobAsync(A<string>._, CancellationToken))
.Returns(imageStream); .Returns(imageStream);
A.CallTo(() => appImageStore.DownloadAsync(appId, imageStream, ct)) A.CallTo(() => appImageStore.DownloadAsync(AppId.Id, imageStream, CancellationToken))
.Throws(new AssetNotFoundException("Image")); .Throws(new AssetNotFoundException("Image"));
await sut.BackupEventAsync(Envelope.Create(new AppImageUploaded()), context, ct); await sut.BackupEventAsync(Envelope.Create(new AppImageUploaded()), context, CancellationToken);
} }
[Fact] [Fact]
@ -253,12 +240,12 @@ public class BackupAppsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => context.Writer.OpenBlobAsync(A<string>._, ct)) A.CallTo(() => context.Writer.OpenBlobAsync(A<string>._, CancellationToken))
.Returns(imageStream); .Returns(imageStream);
await sut.BackupEventAsync(Envelope.Create(new AppImageUploaded()), context, ct); await sut.BackupEventAsync(Envelope.Create(new AppImageUploaded()), context, CancellationToken);
A.CallTo(() => appImageStore.DownloadAsync(appId, imageStream, ct)) A.CallTo(() => appImageStore.DownloadAsync(AppId.Id, imageStream, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -269,12 +256,12 @@ public class BackupAppsTests
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => context.Reader.OpenBlobAsync(A<string>._, ct)) A.CallTo(() => context.Reader.OpenBlobAsync(A<string>._, CancellationToken))
.Returns(imageStream); .Returns(imageStream);
await sut.RestoreEventAsync(Envelope.Create(new AppImageUploaded()), context, ct); await sut.RestoreEventAsync(Envelope.Create(new AppImageUploaded()), context, CancellationToken);
A.CallTo(() => appImageStore.UploadAsync(appId, imageStream, ct)) A.CallTo(() => appImageStore.UploadAsync(AppId.Id, imageStream, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -285,30 +272,31 @@ public class BackupAppsTests
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => context.Reader.OpenBlobAsync(A<string>._, ct)) A.CallTo(() => context.Reader.OpenBlobAsync(A<string>._, CancellationToken))
.Returns(imageStream); .Returns(imageStream);
A.CallTo(() => appImageStore.UploadAsync(appId, imageStream, ct)) A.CallTo(() => appImageStore.UploadAsync(AppId.Id, imageStream, CancellationToken))
.Throws(new AssetAlreadyExistsException("Image")); .Throws(new AssetAlreadyExistsException("Image"));
await sut.RestoreEventAsync(Envelope.Create(new AppImageUploaded()), context, ct); await sut.RestoreEventAsync(Envelope.Create(new AppImageUploaded()), context, CancellationToken);
} }
private BackupContext CreateBackupContext() private BackupContext CreateBackupContext()
{ {
return new BackupContext(appId, CreateUserMapping(), A.Fake<IBackupWriter>()); return new BackupContext(AppId.Id, CreateUserMapping(), A.Fake<IBackupWriter>());
} }
private RestoreContext CreateRestoreContext() private RestoreContext CreateRestoreContext()
{ {
return new RestoreContext(appId, CreateUserMapping(), A.Fake<IBackupReader>(), DomainId.NewGuid()); return new RestoreContext(AppId.Id, CreateUserMapping(), A.Fake<IBackupReader>(), DomainId.NewGuid());
} }
private IUserMapping CreateUserMapping() private IUserMapping CreateUserMapping()
{ {
var mapping = A.Fake<IUserMapping>(); var mapping = A.Fake<IUserMapping>();
A.CallTo(() => mapping.Initiator).Returns(actor); A.CallTo(() => mapping.Initiator)
.Returns(User);
RefToken mapped; RefToken mapped;

17
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppImageStoreTests.cs

@ -8,14 +8,13 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure; using Squidex.Domain.Apps.Entities.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class DefaultAppImageStoreTests public class DefaultAppImageStoreTests : GivenContext
{ {
private readonly IAssetStore assetStore = A.Fake<IAssetStore>(); private readonly IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly string fileNameDefault; private readonly string fileNameDefault;
private readonly string fileNameFolder; private readonly string fileNameFolder;
private readonly AssetOptions options = new AssetOptions(); private readonly AssetOptions options = new AssetOptions();
@ -23,8 +22,8 @@ public class DefaultAppImageStoreTests
public DefaultAppImageStoreTests() public DefaultAppImageStoreTests()
{ {
fileNameDefault = appId.ToString(); fileNameDefault = AppId.Id.ToString();
fileNameFolder = $"{appId}/thumbnail"; fileNameFolder = $"{AppId.Id}/thumbnail";
sut = new DefaultAppImageStore(assetStore, Options.Create(options)); sut = new DefaultAppImageStore(assetStore, Options.Create(options));
} }
@ -40,9 +39,9 @@ public class DefaultAppImageStoreTests
var fileName = GetFileName(folderPerApp); var fileName = GetFileName(folderPerApp);
await sut.UploadAsync(appId, stream); await sut.UploadAsync(AppId.Id, stream, CancellationToken);
A.CallTo(() => assetStore.UploadAsync(fileName, stream, true, default)) A.CallTo(() => assetStore.UploadAsync(fileName, stream, true, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -57,9 +56,9 @@ public class DefaultAppImageStoreTests
var fileName = GetFileName(folderPerApp); var fileName = GetFileName(folderPerApp);
await sut.DownloadAsync(appId, stream); await sut.DownloadAsync(AppId.Id, stream, CancellationToken);
A.CallTo(() => assetStore.DownloadAsync(fileName, stream, default, default)) A.CallTo(() => assetStore.DownloadAsync(fileName, stream, default, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }

26
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs

@ -7,23 +7,17 @@
using System.Globalization; using System.Globalization;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class DefaultAppLogStoreTests public class DefaultAppLogStoreTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IRequestLogStore requestLogStore = A.Fake<IRequestLogStore>(); private readonly IRequestLogStore requestLogStore = A.Fake<IRequestLogStore>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DefaultAppLogStore sut; private readonly DefaultAppLogStore sut;
public DefaultAppLogStoreTests() public DefaultAppLogStoreTests()
{ {
ct = cts.Token;
sut = new DefaultAppLogStore(requestLogStore); sut = new DefaultAppLogStore(requestLogStore);
} }
@ -38,11 +32,9 @@ public class DefaultAppLogStoreTests
[Fact] [Fact]
public async Task Should_remove_events_from_streams() public async Task Should_remove_events_from_streams()
{ {
var app = Mocks.App(NamedId.Of(appId, "my-app")); await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
await ((IDeleter)sut).DeleteAppAsync(app, ct);
A.CallTo(() => requestLogStore.DeleteAsync($"^[a-z]-{app.Id}", A<CancellationToken>._)) A.CallTo(() => requestLogStore.DeleteAsync($"^[a-z]-{AppId.Id}", A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -52,7 +44,7 @@ public class DefaultAppLogStoreTests
A.CallTo(() => requestLogStore.IsEnabled) A.CallTo(() => requestLogStore.IsEnabled)
.Returns(false); .Returns(false);
await sut.LogAsync(appId, default, ct); await sut.LogAsync(AppId.Id, default, CancellationToken);
A.CallTo(() => requestLogStore.LogAsync(A<Request>._, A<CancellationToken>._)) A.CallTo(() => requestLogStore.LogAsync(A<Request>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -66,7 +58,7 @@ public class DefaultAppLogStoreTests
A.CallTo(() => requestLogStore.IsEnabled) A.CallTo(() => requestLogStore.IsEnabled)
.Returns(true); .Returns(true);
A.CallTo(() => requestLogStore.LogAsync(A<Request>._, ct)) A.CallTo(() => requestLogStore.LogAsync(A<Request>._, CancellationToken))
.Invokes(x => recordedRequest = x.GetArgument<Request>(0)!); .Invokes(x => recordedRequest = x.GetArgument<Request>(0)!);
var request = default(RequestLog); var request = default(RequestLog);
@ -84,7 +76,7 @@ public class DefaultAppLogStoreTests
request.UserClientId = "frontend"; request.UserClientId = "frontend";
request.UserId = "user1"; request.UserId = "user1";
await sut.LogAsync(appId, request, ct); await sut.LogAsync(AppId.Id, request, CancellationToken);
Assert.NotNull(recordedRequest); Assert.NotNull(recordedRequest);
@ -100,7 +92,7 @@ public class DefaultAppLogStoreTests
Contains(request.UserClientId, recordedRequest); Contains(request.UserClientId, recordedRequest);
Contains(request.UserId, recordedRequest); Contains(request.UserId, recordedRequest);
Assert.Equal(appId.ToString(), recordedRequest?.Key); Assert.Equal(AppId.Id.ToString(), recordedRequest?.Key);
} }
[Fact] [Fact]
@ -109,7 +101,7 @@ public class DefaultAppLogStoreTests
var dateFrom = DateTime.UtcNow.Date.AddDays(-30); var dateFrom = DateTime.UtcNow.Date.AddDays(-30);
var dateTo = DateTime.UtcNow.Date; var dateTo = DateTime.UtcNow.Date;
A.CallTo(() => requestLogStore.QueryAllAsync(appId.ToString(), dateFrom, dateTo, ct)) A.CallTo(() => requestLogStore.QueryAllAsync(AppId.Id.ToString(), dateFrom, dateTo, CancellationToken))
.Returns(new[] .Returns(new[]
{ {
CreateRecord(), CreateRecord(),
@ -120,7 +112,7 @@ public class DefaultAppLogStoreTests
var stream = new MemoryStream(); var stream = new MemoryStream();
await sut.ReadLogAsync(appId, dateFrom, dateTo, stream, ct); await sut.ReadLogAsync(AppId.Id, dateFrom, dateTo, stream, CancellationToken);
stream.Position = 0; stream.Position = 0;

30
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs

@ -17,11 +17,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject;
public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State> public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
{ {
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly IAppImageStore appImageStore = A.Fake<IAppImageStore>(); private readonly IAppImageStore appImageStore = A.Fake<IAppImageStore>();
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>(); private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly Context requestContext;
private readonly AppCommandMiddleware sut; private readonly AppCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand public sealed class MyCommand : SquidexCommand
@ -30,27 +27,22 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
protected override DomainId Id protected override DomainId Id
{ {
get => appId.Id; get => AppId.Id;
} }
public AppCommandMiddlewareTests() public AppCommandMiddlewareTests()
{ {
requestContext = Context.Anonymous(Mocks.App(appId)); sut = new AppCommandMiddleware(domainObjectFactory, appImageStore, assetThumbnailGenerator, ApiContextProvider);
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new AppCommandMiddleware(domainObjectFactory, appImageStore, assetThumbnailGenerator, contextProvider);
} }
[Fact] [Fact]
public async Task Should_replace_context_app_with_domain_object_actual() public async Task Should_replace_context_app_with_domain_object_actual()
{ {
var actual = A.Fake<IAppEntity>(); var replaced = A.Fake<IAppEntity>();
await HandleAsync(new UpdateApp(), actual); await HandleAsync(new UpdateApp(), replaced);
Assert.Same(actual, requestContext.App); Assert.Same(replaced, ApiContext.App);
} }
[Fact] [Fact]
@ -58,12 +50,12 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
{ {
var file = new NoopAssetFile(); var file = new NoopAssetFile();
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, default)) A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, CancellationToken))
.Returns(new ImageInfo(100, 100, ImageOrientation.None, ImageFormat.PNG)); .Returns(new ImageInfo(100, 100, ImageOrientation.None, ImageFormat.PNG));
await HandleAsync(new UploadAppImage { File = file }, None.Value); await HandleAsync(new UploadAppImage { File = file }, None.Value);
A.CallTo(() => appImageStore.UploadAsync(appId.Id, A<Stream>._, A<CancellationToken>._)) A.CallTo(() => appImageStore.UploadAsync(AppId.Id, A<Stream>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -74,15 +66,15 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
var command = new UploadAppImage { File = file }; var command = new UploadAppImage { File = file };
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, default)) A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, CancellationToken))
.Returns(Task.FromResult<ImageInfo?>(null)); .Returns(Task.FromResult<ImageInfo?>(null));
await Assert.ThrowsAsync<ValidationException>(() => HandleAsync(sut, command)); await Assert.ThrowsAsync<ValidationException>(() => HandleAsync(sut, command, CancellationToken));
} }
private Task<CommandContext> HandleAsync(AppCommand command, object actual) private Task<CommandContext> HandleAsync(AppCommand command, object actual)
{ {
command.AppId = appId; command.AppId = AppId;
var domainObject = A.Fake<AppDomainObject>(); var domainObject = A.Fake<AppDomainObject>();
@ -92,6 +84,6 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
A.CallTo(() => domainObjectFactory.Create<AppDomainObject>(command.AggregateId)) A.CallTo(() => domainObjectFactory.Create<AppDomainObject>(command.AggregateId))
.Returns(domainObject); .Returns(domainObject);
return HandleAsync(sut, command); return HandleAsync(sut, command, CancellationToken);
} }
} }

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

@ -23,7 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject;
public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State> public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>(); private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>();
private readonly IBillingManager billingManager = A.Fake<IBillingManager>(); private readonly IBillingManager billingManager = A.Fake<IBillingManager>();
private readonly IUser user; private readonly IUser user;
@ -36,37 +35,33 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private readonly string planIdPaid = "premium"; private readonly string planIdPaid = "premium";
private readonly string planIdFree = "free"; private readonly string planIdFree = "free";
private readonly InitialSettings initialSettings; private readonly InitialSettings initialSettings;
private readonly DomainId teamId = DomainId.NewGuid();
private readonly DomainId workflowId = DomainId.NewGuid(); private readonly DomainId workflowId = DomainId.NewGuid();
private readonly AppDomainObject sut; private readonly AppDomainObject sut;
protected override DomainId Id protected override DomainId Id
{ {
get => AppId; get => AppId.Id;
} }
public AppDomainObjectTests() public AppDomainObjectTests()
{ {
user = UserMocks.User(contributorId); user = UserMocks.User(contributorId);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId, default)) A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId, CancellationToken))
.Returns(user); .Returns(user);
A.CallTo(() => usageGate.GetPlanForAppAsync(A<IAppEntity>.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdFree), false, default)) A.CallTo(() => usageGate.GetPlanForAppAsync(A<IAppEntity>.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdFree), false, CancellationToken))
.Returns((new Plan { Id = planIdFree, MaxContributors = 10 }, planIdFree, null)); .Returns((new Plan { Id = planIdFree, MaxContributors = 10 }, planIdFree, null));
A.CallTo(() => usageGate.GetPlanForAppAsync(A<IAppEntity>.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdPaid), false, default)) A.CallTo(() => usageGate.GetPlanForAppAsync(A<IAppEntity>.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdPaid), false, CancellationToken))
.Returns((new Plan { Id = planIdPaid, MaxContributors = 30 }, planIdPaid, null)); .Returns((new Plan { Id = planIdPaid, MaxContributors = 30 }, planIdPaid, null));
A.CallTo(() => billingPlans.GetFreePlan()) A.CallTo(() => billingPlans.GetFreePlan())
.Returns(new Plan { Id = planIdFree, MaxContributors = 10 }); .Returns(new Plan { Id = planIdFree, MaxContributors = 10 });
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, A<string>._, default)) A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, A<string>._, CancellationToken))
.Returns(Task.FromResult<Uri?>(null)); .Returns(Task.FromResult<Uri?>(null));
A.CallTo(() => appProvider.GetTeamAsync(teamId, default))
.Returns(Mocks.Team(teamId, contributor: Actor.Identifier));
// Create a non-empty setting, otherwise the event is not raised as it does not change the domain object. // Create a non-empty setting, otherwise the event is not raised as it does not change the domain object.
initialSettings = new InitialSettings initialSettings = new InitialSettings
{ {
@ -78,7 +73,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
var serviceProvider = var serviceProvider =
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(AppProvider)
.AddSingleton(billingManager) .AddSingleton(billingManager)
.AddSingleton(billingPlans) .AddSingleton(billingPlans)
.AddSingleton(initialSettings) .AddSingleton(initialSettings)
@ -105,18 +100,18 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
[Fact] [Fact]
public async Task Create_should_create_events_and_set_intitial_state() public async Task Create_should_create_events_and_set_intitial_state()
{ {
var command = new CreateApp { Name = AppName, AppId = AppId }; var command = new CreateApp { Name = AppId.Name, AppId = AppId.Id };
var actual = await PublishAsync(command); var actual = await PublishAsync(command);
actual.ShouldBeEquivalent(sut.Snapshot); actual.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name); Assert.Equal(AppId.Name, sut.Snapshot.Name);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppCreated { Name = AppName }), CreateEvent(new AppCreated { Name = AppId.Name }),
CreateEvent(new AppContributorAssigned { ContributorId = Actor.Identifier, Role = Role.Owner }), CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Role = Role.Owner }),
CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings }) CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings })
); );
} }
@ -124,17 +119,17 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
[Fact] [Fact]
public async Task Create_should_not_assign_client_as_contributor() public async Task Create_should_not_assign_client_as_contributor()
{ {
var command = new CreateApp { Name = AppName, Actor = ActorClient, AppId = AppId }; var command = new CreateApp { Name = AppId.Name, Actor = Client, AppId = AppId.Id };
var actual = await PublishAsync(command); var actual = await PublishAsync(command);
actual.ShouldBeEquivalent(sut.Snapshot); actual.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name); Assert.Equal(AppId.Name, sut.Snapshot.Name);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppCreated { Name = AppName }, true), // Must be with client actor. CreateEvent(new AppCreated { Name = AppId.Name }, true), // Must be with client actor.
CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings }, true) CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings }, true)
); );
} }
@ -227,7 +222,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
{ {
var command = new ChangePlan { PlanId = planIdPaid }; var command = new ChangePlan { PlanId = planIdPaid };
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default)) A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, planIdPaid, default))
.Returns(Task.FromResult<Uri?>(null)); .Returns(Task.FromResult<Uri?>(null));
await ExecuteCreateAsync(); await ExecuteCreateAsync();
@ -243,10 +238,10 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) CreateEvent(new AppPlanChanged { PlanId = planIdPaid })
); );
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default)) A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, planIdPaid, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => billingManager.SubscribeAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default)) A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A<IAppEntity>._, planIdPaid, default))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -320,7 +315,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
CreateEvent(new AppPlanReset()) CreateEvent(new AppPlanReset())
); );
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default)) A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, planIdPaid, CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
A.CallTo(() => billingManager.UnsubscribeAsync(A<string>._, A<IAppEntity>._, A<CancellationToken>._)) A.CallTo(() => billingManager.UnsubscribeAsync(A<string>._, A<IAppEntity>._, A<CancellationToken>._))
@ -332,7 +327,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
{ {
var command = new ChangePlan { PlanId = planIdPaid }; var command = new ChangePlan { PlanId = planIdPaid };
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default)) A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, planIdPaid, CancellationToken))
.Returns(new Uri("http://squidex.io")); .Returns(new Uri("http://squidex.io"));
await ExecuteCreateAsync(); await ExecuteCreateAsync();
@ -357,10 +352,10 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
Assert.Equal(planIdPaid, sut.Snapshot.Plan?.PlanId); Assert.Equal(planIdPaid, sut.Snapshot.Plan?.PlanId);
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, A<CancellationToken>._)) A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, planIdPaid, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
A.CallTo(() => billingManager.SubscribeAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, A<CancellationToken>._)) A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A<IAppEntity>._, planIdPaid, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -426,7 +421,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
[Fact] [Fact]
public async Task Transfer_should_create_events_and_set_team() public async Task Transfer_should_create_events_and_set_team()
{ {
var command = new TransferToTeam { TeamId = teamId }; var command = new TransferToTeam { TeamId = TeamId };
await ExecuteCreateAsync(); await ExecuteCreateAsync();
@ -434,11 +429,11 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
actual.ShouldBeEquivalent(sut.Snapshot); actual.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(teamId, sut.Snapshot.TeamId); Assert.Equal(TeamId, sut.Snapshot.TeamId);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppTransfered { TeamId = teamId }) CreateEvent(new AppTransfered { TeamId = TeamId })
); );
} }
@ -720,7 +715,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private Task ExecuteCreateAsync() private Task ExecuteCreateAsync()
{ {
return PublishAsync(new CreateApp { Name = AppName, AppId = AppId }); return PublishAsync(new CreateApp { Name = AppId.Name, AppId = AppId.Id });
} }
private Task ExecuteUploadImage() private Task ExecuteUploadImage()
@ -760,7 +755,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private Task ExecuteTransferAsync() private Task ExecuteTransferAsync()
{ {
return PublishAsync(new TransferToTeam { TeamId = teamId }); return PublishAsync(new TransferToTeam { TeamId = TeamId });
} }
private Task ExecuteArchiveAsync() private Task ExecuteArchiveAsync()
@ -775,7 +770,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private async Task<object> PublishAsync<T>(T command) where T : SquidexCommand, IAggregateCommand private async Task<object> PublishAsync<T>(T command) where T : SquidexCommand, IAggregateCommand
{ {
var actual = await sut.ExecuteAsync(CreateCommand(command), default); var actual = await sut.ExecuteAsync(CreateCommand(command), CancellationToken);
return actual.Payload; return actual.Payload;
} }

72
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppClientsTests.cs

@ -12,21 +12,27 @@ using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
#pragma warning disable SA1310 // Field names must not contain underscore
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
public class GuardAppClientsTests : IClassFixture<TranslationsFixture> public class GuardAppClientsTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly AppClients clients_0 = AppClients.Empty; private AppClients clients = AppClients.Empty;
private readonly Roles roles = Roles.Empty;
public GuardAppClientsTests()
{
A.CallTo(() => App.Roles)
.Returns(Roles.Empty);
A.CallTo(() => App.Clients)
.ReturnsLazily(() => clients);
}
[Fact] [Fact]
public void CanAttach_should_throw_execption_if_client_id_is_null() public void CanAttach_should_throw_execption_if_client_id_is_null()
{ {
var command = new AttachClient(); var command = new AttachClient();
ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App(clients_0)), ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App),
new ValidationError("Client ID is required.", "Id")); new ValidationError("Client ID is required.", "Id"));
} }
@ -35,9 +41,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AttachClient { Id = "android" }; var command = new AttachClient { Id = "android" };
var clients_1 = clients_0.Add("android", "secret"); clients = clients.Add("android", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App(clients_1)), ValidationAssert.Throws(() => GuardAppClients.CanAttach(command, App),
new ValidationError("A client with the same id already exists.")); new ValidationError("A client with the same id already exists."));
} }
@ -46,9 +52,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AttachClient { Id = "ios" }; var command = new AttachClient { Id = "ios" };
var clients_1 = clients_0.Add("android", "secret"); clients = clients.Add("android", "secret");
GuardAppClients.CanAttach(command, App(clients_1)); GuardAppClients.CanAttach(command, App);
} }
[Fact] [Fact]
@ -56,7 +62,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RevokeClient(); var command = new RevokeClient();
ValidationAssert.Throws(() => GuardAppClients.CanRevoke(command, App(clients_0)), ValidationAssert.Throws(() => GuardAppClients.CanRevoke(command, App),
new ValidationError("Client ID is required.", "Id")); new ValidationError("Client ID is required.", "Id"));
} }
@ -65,7 +71,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RevokeClient { Id = "ios" }; var command = new RevokeClient { Id = "ios" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanRevoke(command, App(clients_0))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanRevoke(command, App));
} }
[Fact] [Fact]
@ -73,9 +79,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RevokeClient { Id = "ios" }; var command = new RevokeClient { Id = "ios" };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
GuardAppClients.CanRevoke(command, App(clients_1)); GuardAppClients.CanRevoke(command, App);
} }
[Fact] [Fact]
@ -83,7 +89,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Name = "iOS" }; var command = new UpdateClient { Name = "iOS" };
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_0)), ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App),
new ValidationError("Client ID is required.", "Id")); new ValidationError("Client ID is required.", "Id"));
} }
@ -92,7 +98,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", Name = "iOS" }; var command = new UpdateClient { Id = "ios", Name = "iOS" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanUpdate(command, App(clients_0))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanUpdate(command, App));
} }
[Fact] [Fact]
@ -100,9 +106,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", Role = "Invalid" }; var command = new UpdateClient { Id = "ios", Role = "Invalid" };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)), ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App),
new ValidationError("Role is not a valid value.", "Role")); new ValidationError("Role is not a valid value.", "Role"));
} }
@ -111,9 +117,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", ApiCallsLimit = -10 }; var command = new UpdateClient { Id = "ios", ApiCallsLimit = -10 };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)), ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App),
new ValidationError("ApiCallsLimit must be greater or equal to 0.", "ApiCallsLimit")); new ValidationError("ApiCallsLimit must be greater or equal to 0.", "ApiCallsLimit"));
} }
@ -122,9 +128,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", ApiTrafficLimit = -10 }; var command = new UpdateClient { Id = "ios", ApiTrafficLimit = -10 };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App(clients_1)), ValidationAssert.Throws(() => GuardAppClients.CanUpdate(command, App),
new ValidationError("ApiTrafficLimit must be greater or equal to 0.", "ApiTrafficLimit")); new ValidationError("ApiTrafficLimit must be greater or equal to 0.", "ApiTrafficLimit"));
} }
@ -133,9 +139,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", Name = "ios" }; var command = new UpdateClient { Id = "ios", Name = "ios" };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
GuardAppClients.CanUpdate(command, App(clients_1)); GuardAppClients.CanUpdate(command, App);
} }
[Fact] [Fact]
@ -143,9 +149,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", Role = Role.Editor }; var command = new UpdateClient { Id = "ios", Role = Role.Editor };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
GuardAppClients.CanUpdate(command, App(clients_1)); GuardAppClients.CanUpdate(command, App);
} }
[Fact] [Fact]
@ -153,18 +159,8 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateClient { Id = "ios", Name = "iOS", Role = Role.Reader }; var command = new UpdateClient { Id = "ios", Name = "iOS", Role = Role.Reader };
var clients_1 = clients_0.Add("ios", "secret"); clients = clients.Add("ios", "secret");
GuardAppClients.CanUpdate(command, App(clients_1));
}
private IAppEntity App(AppClients clients)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Clients).Returns(clients);
A.CallTo(() => app.Roles).Returns(roles);
return app; GuardAppClients.CanUpdate(command, App);
} }
} }

79
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppContributorsTests.cs

@ -15,23 +15,26 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Shared.Users; using Squidex.Shared.Users;
#pragma warning disable SA1310 // Field names must not contain underscore
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
public class GuardAppContributorsTests : IClassFixture<TranslationsFixture> public class GuardAppContributorsTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IUser user1 = UserMocks.User("1"); private readonly IUser user1 = UserMocks.User("1");
private readonly IUser user2 = UserMocks.User("2"); private readonly IUser user2 = UserMocks.User("2");
private readonly IUser user3 = UserMocks.User("3"); private readonly IUser user3 = UserMocks.User("3");
private readonly IUserResolver users = A.Fake<IUserResolver>(); private readonly IUserResolver users = A.Fake<IUserResolver>();
private readonly Contributors contributors_0 = Contributors.Empty;
private readonly Plan planWithoutLimit = new Plan { MaxContributors = -1 }; private readonly Plan planWithoutLimit = new Plan { MaxContributors = -1 };
private readonly Plan planWithLimit = new Plan { MaxContributors = 2 }; private readonly Plan planWithLimit = new Plan { MaxContributors = 2 };
private readonly Roles roles = Roles.Empty; private Contributors contributors = Contributors.Empty;
public GuardAppContributorsTests() public GuardAppContributorsTests()
{ {
A.CallTo(() => App.Roles)
.Returns(Roles.Empty);
A.CallTo(() => App.Contributors)
.ReturnsLazily(() => contributors);
A.CallTo(() => user1.Id) A.CallTo(() => user1.Id)
.Returns("1"); .Returns("1");
@ -59,7 +62,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor(); var command = new AssignContributor();
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, planWithoutLimit), await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App, users, planWithoutLimit),
new ValidationError("Contributor ID or email is required.", "ContributorId")); new ValidationError("Contributor ID or email is required.", "ContributorId"));
} }
@ -68,7 +71,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "1", Role = "Invalid" }; var command = new AssignContributor { ContributorId = "1", Role = "Invalid" };
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, planWithoutLimit), await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App, users, planWithoutLimit),
new ValidationError("Role is not a valid value.", "Role")); new ValidationError("Role is not a valid value.", "Role"));
} }
@ -77,9 +80,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "1", Role = Role.Owner }; var command = new AssignContributor { ContributorId = "1", Role = Role.Owner };
var contributors_1 = contributors_0.Assign("1", Role.Owner); contributors = contributors.Assign("1", Role.Owner);
await GuardAppContributors.CanAssign(command, App(contributors_1), users, planWithoutLimit); await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
} }
[Fact] [Fact]
@ -87,9 +90,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "1", Role = Role.Owner, IgnoreActor = true }; var command = new AssignContributor { ContributorId = "1", Role = Role.Owner, IgnoreActor = true };
var contributors_1 = contributors_0.Assign("1", Role.Owner); contributors = contributors.Assign("1", Role.Owner);
await GuardAppContributors.CanAssign(command, App(contributors_1), users, planWithoutLimit); await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
} }
[Fact] [Fact]
@ -97,7 +100,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "notfound", Role = Role.Owner }; var command = new AssignContributor { ContributorId = "notfound", Role = Role.Owner };
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, planWithoutLimit)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => GuardAppContributors.CanAssign(command, App, users, planWithoutLimit));
} }
[Fact] [Fact]
@ -105,7 +108,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "3", Role = Role.Editor, Actor = RefToken.User("3") }; var command = new AssignContributor { ContributorId = "3", Role = Role.Editor, Actor = RefToken.User("3") };
await Assert.ThrowsAsync<DomainForbiddenException>(() => GuardAppContributors.CanAssign(command, App(contributors_0), users, planWithoutLimit)); await Assert.ThrowsAsync<DomainForbiddenException>(() => GuardAppContributors.CanAssign(command, App, users, planWithoutLimit));
} }
[Fact] [Fact]
@ -113,10 +116,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "3" }; var command = new AssignContributor { ContributorId = "3" };
var contributors_1 = contributors_0.Assign("1", Role.Owner); contributors = contributors.Assign("1", Role.Owner).Assign("2", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithLimit), await ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(command, App, users, planWithLimit),
new ValidationError("You have reached the maximum number of contributors for your plan.")); new ValidationError("You have reached the maximum number of contributors for your plan."));
} }
@ -125,10 +127,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "3", IgnorePlans = true }; var command = new AssignContributor { ContributorId = "3", IgnorePlans = true };
var contributors_1 = contributors_0.Assign("1", Role.Owner); contributors = contributors.Assign("1", Role.Owner).Assign("2", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithLimit); await GuardAppContributors.CanAssign(command, App, users, planWithLimit);
} }
[Fact] [Fact]
@ -136,7 +137,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "1" }; var command = new AssignContributor { ContributorId = "1" };
await GuardAppContributors.CanAssign(command, App(contributors_0), users, planWithoutLimit); await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
} }
[Fact] [Fact]
@ -144,9 +145,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "1" }; var command = new AssignContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", Role.Developer); contributors = contributors.Assign("1", Role.Developer);
await GuardAppContributors.CanAssign(command, App(contributors_1), users, planWithoutLimit); await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
} }
[Fact] [Fact]
@ -154,10 +155,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "1" }; var command = new AssignContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", Role.Developer); contributors = contributors.Assign("1", Role.Developer).Assign("2", Role.Developer);
var contributors_2 = contributors_1.Assign("2", Role.Developer);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithLimit); await GuardAppContributors.CanAssign(command, App, users, planWithLimit);
} }
[Fact] [Fact]
@ -165,10 +165,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new AssignContributor { ContributorId = "3", IgnorePlans = true }; var command = new AssignContributor { ContributorId = "3", IgnorePlans = true };
var contributors_1 = contributors_0.Assign("1", Role.Editor); contributors = contributors.Assign("1", Role.Editor).Assign("2", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithoutLimit); await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
} }
[Fact] [Fact]
@ -176,7 +175,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveContributor(); var command = new RemoveContributor();
ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_0)), ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App),
new ValidationError("Contributor ID or email is required.", "ContributorId")); new ValidationError("Contributor ID or email is required.", "ContributorId"));
} }
@ -185,7 +184,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveContributor { ContributorId = "1" }; var command = new RemoveContributor { ContributorId = "1" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(command, App(contributors_0))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(command, App));
} }
[Fact] [Fact]
@ -193,10 +192,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveContributor { ContributorId = "1" }; var command = new RemoveContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", Role.Owner); contributors = contributors.Assign("1", Role.Owner).Assign("2", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App(contributors_2)), ValidationAssert.Throws(() => GuardAppContributors.CanRemove(command, App),
new ValidationError("Cannot remove the only owner.")); new ValidationError("Cannot remove the only owner."));
} }
@ -205,19 +203,8 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveContributor { ContributorId = "1" }; var command = new RemoveContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", Role.Owner); contributors = contributors.Assign("1", Role.Owner).Assign("2", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Owner);
GuardAppContributors.CanRemove(command, App(contributors_2));
}
private IAppEntity App(Contributors contributors)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Contributors).Returns(contributors);
A.CallTo(() => app.Roles).Returns(roles);
return app; GuardAppContributors.CanRemove(command, App);
} }
} }

40
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppLanguagesTests.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
@ -14,16 +13,14 @@ using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture> public class GuardAppLanguagesTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE);
[Fact] [Fact]
public void CanAddLanguage_should_throw_exception_if_language_is_null() public void CanAddLanguage_should_throw_exception_if_language_is_null()
{ {
var command = new AddLanguage(); var command = new AddLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App),
new ValidationError("Language code is required.", "Language")); new ValidationError("Language code is required.", "Language"));
} }
@ -32,7 +29,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new AddLanguage { Language = Language.EN }; var command = new AddLanguage { Language = Language.EN };
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App),
new ValidationError("Language has already been added.")); new ValidationError("Language has already been added."));
} }
@ -41,7 +38,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new AddLanguage { Language = Language.IT }; var command = new AddLanguage { Language = Language.IT };
GuardAppLanguages.CanAdd(command, App(languages)); GuardAppLanguages.CanAdd(command, App);
} }
[Fact] [Fact]
@ -49,7 +46,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveLanguage(); var command = new RemoveLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App),
new ValidationError("Language code is required.", "Language")); new ValidationError("Language code is required.", "Language"));
} }
@ -58,7 +55,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveLanguage { Language = Language.IT }; var command = new RemoveLanguage { Language = Language.IT };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(command, App(languages))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(command, App));
} }
[Fact] [Fact]
@ -66,7 +63,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveLanguage { Language = Language.EN }; var command = new RemoveLanguage { Language = Language.EN };
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App),
new ValidationError("Master language cannot be removed.")); new ValidationError("Master language cannot be removed."));
} }
@ -75,7 +72,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new RemoveLanguage { Language = Language.DE }; var command = new RemoveLanguage { Language = Language.DE };
GuardAppLanguages.CanRemove(command, App(languages)); GuardAppLanguages.CanRemove(command, App);
} }
[Fact] [Fact]
@ -83,7 +80,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateLanguage(); var command = new UpdateLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
new ValidationError("Language code is required.", "Language")); new ValidationError("Language code is required.", "Language"));
} }
@ -92,7 +89,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateLanguage { Language = Language.EN, IsOptional = true }; var command = new UpdateLanguage { Language = Language.EN, IsOptional = true };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
new ValidationError("Master language cannot be made optional.", "IsMaster")); new ValidationError("Master language cannot be made optional.", "IsMaster"));
} }
@ -101,7 +98,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateLanguage { Language = Language.EN, Fallback = new[] { Language.DE } }; var command = new UpdateLanguage { Language = Language.EN, Fallback = new[] { Language.DE } };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
new ValidationError("Master language cannot have fallback languages.", "Fallback")); new ValidationError("Master language cannot have fallback languages.", "Fallback"));
} }
@ -110,7 +107,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.IT } }; var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.IT } };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)), ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
new ValidationError("App does not have fallback language 'Italian'.", "Fallback")); new ValidationError("App does not have fallback language 'Italian'.", "Fallback"));
} }
@ -119,7 +116,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateLanguage { Language = Language.IT }; var command = new UpdateLanguage { Language = Language.IT };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(command, App(languages))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(command, App));
} }
[Fact] [Fact]
@ -127,15 +124,6 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.EN } }; var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.EN } };
GuardAppLanguages.CanUpdate(command, App(languages)); GuardAppLanguages.CanUpdate(command, App);
}
private static IAppEntity App(LanguagesConfig languages)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Languages).Returns(languages);
return app;
} }
} }

81
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppRolesTests.cs

@ -13,34 +13,42 @@ using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
#pragma warning disable SA1310 // Field names must not contain underscore
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
public class GuardAppRolesTests : IClassFixture<TranslationsFixture> public class GuardAppRolesTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly string roleName = "Role1"; private readonly string roleName = "Role1";
private readonly Roles roles_0 = Roles.Empty; private Roles roles = Roles.Empty;
private readonly AppClients clients = AppClients.Empty.Add("client", "secret", "clientRole");
private readonly Contributors contributors = Contributors.Empty.Assign("contributor", "contributorRole"); public GuardAppRolesTests()
{
A.CallTo(() => App.Contributors)
.Returns(Contributors.Empty.Assign(User.Identifier, "contributorRole"));
A.CallTo(() => App.Clients)
.Returns(AppClients.Empty.Add(Client.Identifier, "secret", "clientRole"));
A.CallTo(() => App.Roles)
.ReturnsLazily(() => roles);
}
[Fact] [Fact]
public void CanAdd_should_throw_exception_if_name_empty() public void CanAdd_should_throw_exception_if_name_empty()
{ {
var command = new AddRole { Name = null! }; var command = new AddRole { Name = null! };
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_0)), ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App),
new ValidationError("Name is required.", "Name")); new ValidationError("Name is required.", "Name"));
} }
[Fact] [Fact]
public void CanAdd_should_throw_exception_if_name_exists() public void CanAdd_should_throw_exception_if_name_exists()
{ {
var roles_1 = roles_0.Add(roleName); roles = roles.Add(roleName);
var command = new AddRole { Name = roleName }; var command = new AddRole { Name = roleName };
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App),
new ValidationError("A role with the same name already exists.")); new ValidationError("A role with the same name already exists."));
} }
@ -49,7 +57,7 @@ public class GuardAppRolesTests : IClassFixture<TranslationsFixture>
{ {
var command = new AddRole { Name = roleName }; var command = new AddRole { Name = roleName };
GuardAppRoles.CanAdd(command, App(roles_0)); GuardAppRoles.CanAdd(command, App);
} }
[Fact] [Fact]
@ -57,7 +65,7 @@ public class GuardAppRolesTests : IClassFixture<TranslationsFixture>
{ {
var command = new DeleteRole { Name = null! }; var command = new DeleteRole { Name = null! };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_0)), ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App),
new ValidationError("Name is required.", "Name")); new ValidationError("Name is required.", "Name"));
} }
@ -66,82 +74,82 @@ public class GuardAppRolesTests : IClassFixture<TranslationsFixture>
{ {
var command = new DeleteRole { Name = roleName }; var command = new DeleteRole { Name = roleName };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanDelete(command, App(roles_0))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanDelete(command, App));
} }
[Fact] [Fact]
public void CanDelete_should_throw_exception_if_contributor_found() public void CanDelete_should_throw_exception_if_contributor_found()
{ {
var roles_1 = roles_0.Add("contributorRole"); roles = roles.Add("contributorRole");
var command = new DeleteRole { Name = "contributorRole" }; var command = new DeleteRole { Name = "contributorRole" };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App),
new ValidationError("Cannot remove a role when a contributor is assigned.")); new ValidationError("Cannot remove a role when a contributor is assigned."));
} }
[Fact] [Fact]
public void CanDelete_should_throw_exception_if_client_found() public void CanDelete_should_throw_exception_if_client_found()
{ {
var roles_1 = roles_0.Add("clientRole"); roles = roles.Add("clientRole");
var command = new DeleteRole { Name = "clientRole" }; var command = new DeleteRole { Name = "clientRole" };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App),
new ValidationError("Cannot remove a role when a client is assigned.")); new ValidationError("Cannot remove a role when a client is assigned."));
} }
[Fact] [Fact]
public void CanDelete_should_throw_exception_if_default_role() public void CanDelete_should_throw_exception_if_default_role()
{ {
var roles_1 = roles_0.Add(Role.Developer); roles = roles.Add(Role.Developer);
var command = new DeleteRole { Name = Role.Developer }; var command = new DeleteRole { Name = Role.Developer };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App),
new ValidationError("Cannot delete a default role.")); new ValidationError("Cannot delete a default role."));
} }
[Fact] [Fact]
public void CanDelete_should_not_throw_exception_if_command_is_valid() public void CanDelete_should_not_throw_exception_if_command_is_valid()
{ {
var roles_1 = roles_0.Add(roleName); roles = roles.Add(roleName);
var command = new DeleteRole { Name = roleName }; var command = new DeleteRole { Name = roleName };
GuardAppRoles.CanDelete(command, App(roles_1)); GuardAppRoles.CanDelete(command, App);
} }
[Fact] [Fact]
public void CanUpdate_should_throw_exception_if_name_empty() public void CanUpdate_should_throw_exception_if_name_empty()
{ {
var roles_1 = roles_0.Add(roleName); roles = roles.Add(roleName);
var command = new UpdateRole { Name = null!, Permissions = new[] { "P1" } }; var command = new UpdateRole { Name = null!, Permissions = new[] { "P1" } };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App),
new ValidationError("Name is required.", "Name")); new ValidationError("Name is required.", "Name"));
} }
[Fact] [Fact]
public void CanUpdate_should_throw_exception_if_permission_is_null() public void CanUpdate_should_throw_exception_if_permission_is_null()
{ {
var roles_1 = roles_0.Add(roleName); roles = roles.Add(roleName);
var command = new UpdateRole { Name = roleName }; var command = new UpdateRole { Name = roleName };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App),
new ValidationError("Permissions is required.", "Permissions")); new ValidationError("Permissions is required.", "Permissions"));
} }
[Fact] [Fact]
public void CanUpdate_should_throw_exception_if_default_role() public void CanUpdate_should_throw_exception_if_default_role()
{ {
var roles_1 = roles_0.Add(Role.Developer); roles = roles.Add(Role.Developer);
var command = new UpdateRole { Name = Role.Developer, Permissions = new[] { "P1" } }; var command = new UpdateRole { Name = Role.Developer, Permissions = new[] { "P1" } };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)), ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App),
new ValidationError("Cannot update a default role.")); new ValidationError("Cannot update a default role."));
} }
@ -150,37 +158,26 @@ public class GuardAppRolesTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } }; var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanUpdate(command, App(roles_0))); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanUpdate(command, App));
} }
[Fact] [Fact]
public void CanUpdate_should_not_throw_exception_if_properties_is_null() public void CanUpdate_should_not_throw_exception_if_properties_is_null()
{ {
var roles_1 = roles_0.Add(roleName); roles = roles.Add(roleName);
var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } }; var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } };
GuardAppRoles.CanUpdate(command, App(roles_1)); GuardAppRoles.CanUpdate(command, App);
} }
[Fact] [Fact]
public void CanUpdate_should_not_throw_exception_if_role_exist_with_valid_command() public void CanUpdate_should_not_throw_exception_if_role_exist_with_valid_command()
{ {
var roles_1 = roles_0.Add(roleName); roles = roles.Add(roleName);
var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } }; var command = new UpdateRole { Name = roleName, Permissions = new[] { "P1" } };
GuardAppRoles.CanUpdate(command, App(roles_1)); GuardAppRoles.CanUpdate(command, App);
}
private IAppEntity App(Roles roles)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Contributors).Returns(contributors);
A.CallTo(() => app.Clients).Returns(clients);
A.CallTo(() => app.Roles).Returns(roles);
return app;
} }
} }

96
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppTests.cs

@ -10,7 +10,6 @@ using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Billing; using Squidex.Domain.Apps.Entities.Billing;
using Squidex.Domain.Apps.Entities.Teams;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
@ -19,9 +18,8 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
public class GuardAppTests : IClassFixture<TranslationsFixture> public class GuardAppTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IUserResolver users = A.Fake<IUserResolver>(); private readonly IUserResolver users = A.Fake<IUserResolver>();
private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>(); private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>();
private readonly RefToken actor = RefToken.User("42"); private readonly RefToken actor = RefToken.User("42");
@ -75,121 +73,104 @@ public class GuardAppTests : IClassFixture<TranslationsFixture>
[Fact] [Fact]
public void CanChangePlan_should_throw_exception_if_plan_id_is_null() public void CanChangePlan_should_throw_exception_if_plan_id_is_null()
{ {
var command = new ChangePlan { Actor = RefToken.User("me") }; var command = new ChangePlan { Actor = User };
AssignedPlan? plan = null; ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans),
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), billingPlans),
new ValidationError("Plan ID is required.", "PlanId")); new ValidationError("Plan ID is required.", "PlanId"));
} }
[Fact] [Fact]
public void CanChangePlan_should_throw_exception_if_plan_not_found() public void CanChangePlan_should_throw_exception_if_plan_not_found()
{ {
var command = new ChangePlan { PlanId = "notfound", Actor = RefToken.User("me") }; var command = new ChangePlan { PlanId = "notfound", Actor = User };
AssignedPlan? plan = null;
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), billingPlans), ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans),
new ValidationError("A plan with this id does not exist.", "PlanId")); new ValidationError("A plan with this id does not exist.", "PlanId"));
} }
[Fact] [Fact]
public void CanChangePlan_should_throw_exception_if_plan_was_configured_from_another_user() public void CanChangePlan_should_throw_exception_if_plan_was_configured_from_another_user()
{ {
var command = new ChangePlan { PlanId = "basic", Actor = RefToken.User("me") }; var command = new ChangePlan { PlanId = "basic", Actor = User };
var plan = new AssignedPlan(RefToken.User("other"), "premium"); A.CallTo(() => App.Plan)
.Returns(new AssignedPlan(RefToken.User("other"), "premium"));
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(plan), billingPlans), ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans),
new ValidationError("Plan can only changed from the user who configured the plan initially.")); new ValidationError("Plan can only changed from the user who configured the plan initially."));
} }
[Fact] [Fact]
public void CanChangePlan_should_throw_exception_if_assigned_to_team() public void CanChangePlan_should_throw_exception_if_assigned_to_team()
{ {
var command = new ChangePlan { PlanId = "basic", Actor = RefToken.User("me") }; var command = new ChangePlan { PlanId = "basic", Actor = User };
var teamId = DomainId.NewGuid(); A.CallTo(() => App.TeamId)
.Returns(DomainId.NewGuid());
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App(null, teamId), billingPlans), ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans),
new ValidationError("Plan is managed by the team.")); new ValidationError("Plan is managed by the team."));
} }
[Fact] [Fact]
public void CanChangePlan_should_not_throw_exception_if_plan_is_the_same() public void CanChangePlan_should_not_throw_exception_if_plan_is_the_same()
{ {
var command = new ChangePlan { PlanId = "basic", Actor = RefToken.User("me") }; var command = new ChangePlan { PlanId = "basic", Actor = User };
var plan = new AssignedPlan(command.Actor, "basic"); A.CallTo(() => App.Plan)
.Returns(new AssignedPlan(command.Actor, "premium"));
GuardApp.CanChangePlan(command, App(plan), billingPlans); GuardApp.CanChangePlan(command, App, billingPlans);
} }
[Fact] [Fact]
public void CanChangePlan_should_not_throw_exception_if_same_user_but_other_plan() public void CanChangePlan_should_not_throw_exception_if_same_user_but_other_plan()
{ {
var command = new ChangePlan { PlanId = "basic", Actor = RefToken.User("me") }; var command = new ChangePlan { PlanId = "basic", Actor = User };
var plan = new AssignedPlan(command.Actor, "premium"); A.CallTo(() => App.Plan)
.Returns(new AssignedPlan(command.Actor, "premium"));
GuardApp.CanChangePlan(command, App(plan), billingPlans); GuardApp.CanChangePlan(command, App, billingPlans);
} }
[Fact] [Fact]
public async Task CanTransfer_should_not_throw_exception_if_team_exists() public async Task CanTransfer_should_not_throw_exception_if_team_exists()
{ {
var team = Mocks.Team(DomainId.NewGuid(), contributor: actor.Identifier); var command = new TransferToTeam { TeamId = TeamId, Actor = User };
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default))
.Returns(team);
var command = new TransferToTeam { TeamId = team.Id, Actor = actor };
await GuardApp.CanTransfer(command, App(null), appProvider, default); await GuardApp.CanTransfer(command, App, AppProvider, default);
} }
[Fact] [Fact]
public async Task CanTransfer_should_throw_exception_if_team_does_not_exist() public async Task CanTransfer_should_throw_exception_if_team_does_not_exist()
{ {
var team = Mocks.Team(DomainId.NewGuid(), contributor: actor.Identifier); Team = null!;
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default)) var command = new TransferToTeam { TeamId = TeamId, Actor = actor };
.Returns(Task.FromResult<ITeamEntity?>(null));
var command = new TransferToTeam { TeamId = team.Id, Actor = actor }; await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App(null), appProvider, default),
new ValidationError("The team does not exist.")); new ValidationError("The team does not exist."));
} }
[Fact] [Fact]
public async Task CanTransfer_should_throw_exception_if_actor_is_not_part_of_team() public async Task CanTransfer_should_throw_exception_if_actor_is_not_part_of_team()
{ {
var team = Mocks.Team(DomainId.NewGuid()); var command = new TransferToTeam { TeamId = TeamId, Actor = actor };
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default))
.Returns(team);
var command = new TransferToTeam { TeamId = team.Id, Actor = actor }; await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App(null), appProvider, default),
new ValidationError("The team does not exist.")); new ValidationError("The team does not exist."));
} }
[Fact] [Fact]
public async Task CanTransfer_should_throw_exception_if_app_has_plan() public async Task CanTransfer_should_throw_exception_if_app_has_plan()
{ {
var team = Mocks.Team(DomainId.NewGuid(), contributor: actor.Identifier); var command = new TransferToTeam { TeamId = TeamId, Actor = User };
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default))
.Returns(team);
var command = new TransferToTeam { TeamId = team.Id, Actor = actor }; A.CallTo(() => App.Plan)
.Returns(new AssignedPlan(User, "premium"));
var plan = new AssignedPlan(RefToken.User("me"), "premium"); await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App(plan), appProvider, default),
new ValidationError("Subscription must be cancelled first before the app can be transfered.")); new ValidationError("Subscription must be cancelled first before the app can be transfered."));
} }
@ -312,17 +293,4 @@ public class GuardAppTests : IClassFixture<TranslationsFixture>
GuardApp.CanUpdateSettings(command); GuardApp.CanUpdateSettings(command);
} }
private static IAppEntity App(AssignedPlan? plan, DomainId? teamId = null)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Plan)
.Returns(plan);
A.CallTo(() => app.TeamId)
.Returns(teamId);
return app;
}
} }

36
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppWorkflowTests.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards;
public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture> public class GuardAppWorkflowTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly DomainId workflowId = DomainId.NewGuid(); private readonly DomainId workflowId = DomainId.NewGuid();
private readonly Workflows workflows; private readonly Workflows workflows;
@ -23,6 +23,9 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
public GuardAppWorkflowTests() public GuardAppWorkflowTests()
{ {
workflows = Workflows.Empty.Add(workflowId, "name"); workflows = Workflows.Empty.Add(workflowId, "name");
A.CallTo(() => App.Workflows)
.Returns(workflows);
} }
[Fact] [Fact]
@ -51,7 +54,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = DomainId.NewGuid() WorkflowId = DomainId.NewGuid()
}; };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(command, App())); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(command, App));
} }
[Fact] [Fact]
@ -59,7 +62,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateWorkflow { WorkflowId = workflowId }; var command = new UpdateWorkflow { WorkflowId = workflowId };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Workflow is required.", "Workflow")); new ValidationError("Workflow is required.", "Workflow"));
} }
@ -77,7 +80,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Initial step is required.", "Workflow.Initial")); new ValidationError("Initial step is required.", "Workflow.Initial"));
} }
@ -95,7 +98,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Initial step cannot be published step.", "Workflow.Initial")); new ValidationError("Initial step cannot be published step.", "Workflow.Initial"));
} }
@ -113,7 +116,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Workflow must have a published step.", "Workflow.Steps")); new ValidationError("Workflow must have a published step.", "Workflow.Steps"));
} }
@ -132,7 +135,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Step is required.", "Workflow.Steps.Published")); new ValidationError("Step is required.", "Workflow.Steps.Published"));
} }
@ -156,7 +159,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Transition has an invalid target.", "Workflow.Steps.Published.Transitions.Archived")); new ValidationError("Transition has an invalid target.", "Workflow.Steps.Published.Transitions.Archived"));
} }
@ -181,7 +184,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Transition is required.", "Workflow.Steps.Published.Transitions.Draft")); new ValidationError("Transition is required.", "Workflow.Steps.Published.Transitions.Draft"));
} }
@ -190,7 +193,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{ {
var command = new UpdateWorkflow { Workflow = Workflow.Default, WorkflowId = workflowId }; var command = new UpdateWorkflow { Workflow = Workflow.Default, WorkflowId = workflowId };
GuardAppWorkflows.CanUpdate(command, App()); GuardAppWorkflows.CanUpdate(command, App);
} }
[Fact] [Fact]
@ -198,7 +201,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{ {
var command = new DeleteWorkflow { WorkflowId = DomainId.NewGuid() }; var command = new DeleteWorkflow { WorkflowId = DomainId.NewGuid() };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(command, App())); Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(command, App));
} }
[Fact] [Fact]
@ -206,15 +209,6 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{ {
var command = new DeleteWorkflow { WorkflowId = workflowId }; var command = new DeleteWorkflow { WorkflowId = workflowId };
GuardAppWorkflows.CanDelete(command, App()); GuardAppWorkflows.CanDelete(command, App);
}
private IAppEntity App()
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Workflows).Returns(workflows);
return app;
} }
} }

189
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs

@ -11,6 +11,7 @@ using Squidex.Caching;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
@ -21,23 +22,17 @@ using Squidex.Messaging;
namespace Squidex.Domain.Apps.Entities.Apps.Indexes; namespace Squidex.Domain.Apps.Entities.Apps.Indexes;
public class AppsIndexTests public class AppsIndexTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly TestState<NameReservationState.State> state; private readonly TestState<NameReservationState.State> state;
private readonly IAppRepository appRepository = A.Fake<IAppRepository>(); private readonly IAppRepository appRepository = A.Fake<IAppRepository>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly string userId = "user1";
private readonly AppsIndex sut; private readonly AppsIndex sut;
public AppsIndexTests() public AppsIndexTests()
{ {
state = new TestState<NameReservationState.State>("Apps"); state = new TestState<NameReservationState.State>("Apps");
ct = cts.Token;
var replicatedCache = var replicatedCache =
new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), A.Fake<IMessageBus>(), new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), A.Fake<IMessageBus>(),
Options.Create(new ReplicatedCacheOptions { Enable = true })); Options.Create(new ReplicatedCacheOptions { Enable = true }));
@ -48,129 +43,114 @@ public class AppsIndexTests
[Fact] [Fact]
public async Task Should_resolve_app_by_name() public async Task Should_resolve_app_by_name()
{ {
var expected = CreateApp(); A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(App);
A.CallTo(() => appRepository.FindAsync(appId.Name, ct))
.Returns(expected);
var actual1 = await sut.GetAppAsync(appId.Name, false, ct); var actual1 = await sut.GetAppAsync(AppId.Name, false, CancellationToken);
var actual2 = await sut.GetAppAsync(appId.Name, false, ct); var actual2 = await sut.GetAppAsync(AppId.Name, false, CancellationToken);
Assert.Same(expected, actual1); Assert.Same(App, actual1);
Assert.Same(expected, actual2); Assert.Same(App, actual2);
A.CallTo(() => appRepository.FindAsync(appId.Name, ct)) A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
[Fact] [Fact]
public async Task Should_resolve_app_by_name_and_id_if_cached_before() public async Task Should_resolve_app_by_name_and_id_if_cached_before()
{ {
var expected = CreateApp(); A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(App);
A.CallTo(() => appRepository.FindAsync(appId.Name, ct)) var actual1 = await sut.GetAppAsync(AppId.Name, true, CancellationToken);
.Returns(expected); var actual2 = await sut.GetAppAsync(AppId.Name, true, CancellationToken);
var actual3 = await sut.GetAppAsync(AppId.Id, true, CancellationToken);
var actual1 = await sut.GetAppAsync(appId.Name, true, ct); Assert.Same(App, actual1);
var actual2 = await sut.GetAppAsync(appId.Name, true, ct); Assert.Same(App, actual2);
var actual3 = await sut.GetAppAsync(appId.Id, true, ct); Assert.Same(App, actual3);
Assert.Same(expected, actual1); A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
Assert.Same(expected, actual2);
Assert.Same(expected, actual3);
A.CallTo(() => appRepository.FindAsync(appId.Name, ct))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
[Fact] [Fact]
public async Task Should_resolve_app_by_id() public async Task Should_resolve_app_by_id()
{ {
var expected = CreateApp(); A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
.Returns(App);
A.CallTo(() => appRepository.FindAsync(appId.Id, ct))
.Returns(expected);
var actual1 = await sut.GetAppAsync(appId.Id, false, ct); var actual1 = await sut.GetAppAsync(AppId.Id, false, CancellationToken);
var actual2 = await sut.GetAppAsync(appId.Id, false, ct); var actual2 = await sut.GetAppAsync(AppId.Id, false, CancellationToken);
Assert.Same(expected, actual1); Assert.Same(App, actual1);
Assert.Same(expected, actual2); Assert.Same(App, actual2);
A.CallTo(() => appRepository.FindAsync(appId.Id, ct)) A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
[Fact] [Fact]
public async Task Should_resolve_app_by_id_and_name_if_cached_before() public async Task Should_resolve_app_by_id_and_name_if_cached_before()
{ {
var expected = CreateApp(); A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
.Returns(App);
A.CallTo(() => appRepository.FindAsync(appId.Id, ct)) var actual1 = await sut.GetAppAsync(AppId.Id, true, CancellationToken);
.Returns(expected); var actual2 = await sut.GetAppAsync(AppId.Id, true, CancellationToken);
var actual3 = await sut.GetAppAsync(AppId.Name, true, CancellationToken);
var actual1 = await sut.GetAppAsync(appId.Id, true, ct); Assert.Same(App, actual1);
var actual2 = await sut.GetAppAsync(appId.Id, true, ct); Assert.Same(App, actual2);
var actual3 = await sut.GetAppAsync(appId.Name, true, ct); Assert.Same(App, actual3);
Assert.Same(expected, actual1); A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
Assert.Same(expected, actual2);
Assert.Same(expected, actual3);
A.CallTo(() => appRepository.FindAsync(appId.Id, ct))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
[Fact] [Fact]
public async Task Should_resolve_all_apps_from_user_permissions() public async Task Should_resolve_all_apps_from_user_permissions()
{ {
var expected = CreateApp(); A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.Is(AppId.Name), CancellationToken))
.Returns(new List<IAppEntity> { App });
A.CallTo(() => appRepository.QueryAllAsync(userId, A<IEnumerable<string>>.That.Is(appId.Name), ct))
.Returns(new List<IAppEntity> { expected });
var actual = await sut.GetAppsForUserAsync(userId, new PermissionSet($"squidex.apps.{appId.Name}"), ct); var actual = await sut.GetAppsForUserAsync(User.Identifier, new PermissionSet($"squidex.apps.{AppId.Name}"), CancellationToken);
Assert.Same(expected, actual[0]); Assert.Same(App, actual[0]);
} }
[Fact] [Fact]
public async Task Should_resolve_all_apps_from_user() public async Task Should_resolve_all_apps_from_user()
{ {
var expected = CreateApp(); A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.IsEmpty(), CancellationToken))
.Returns(new List<IAppEntity> { App });
A.CallTo(() => appRepository.QueryAllAsync(userId, A<IEnumerable<string>>.That.IsEmpty(), ct)) var actual = await sut.GetAppsForUserAsync(User.Identifier, PermissionSet.Empty, CancellationToken);
.Returns(new List<IAppEntity> { expected });
var actual = await sut.GetAppsForUserAsync(userId, PermissionSet.Empty, ct); Assert.Same(App, actual[0]);
Assert.Same(expected, actual[0]);
} }
[Fact] [Fact]
public async Task Should_resolve_all_apps_from_team() public async Task Should_resolve_all_apps_from_team()
{ {
var teamId = DomainId.NewGuid(); A.CallTo(() => appRepository.QueryAllAsync(TeamId, CancellationToken))
.Returns(new List<IAppEntity> { App });
var expected = CreateApp();
A.CallTo(() => appRepository.QueryAllAsync(teamId, ct)) var actual = await sut.GetAppsForTeamAsync(TeamId, CancellationToken);
.Returns(new List<IAppEntity> { expected });
var actual = await sut.GetAppsForTeamAsync(teamId, ct); Assert.Same(App, actual[0]);
Assert.Same(expected, actual[0]);
} }
[Fact] [Fact]
public async Task Should_return_empty_apps_if_app_not_created() public async Task Should_return_empty_apps_if_app_not_created()
{ {
var expected = CreateApp(EtagVersion.Empty); A.CallTo(() => App.Version)
.Returns(EtagVersion.Empty);
A.CallTo(() => appRepository.QueryAllAsync(userId, A<IEnumerable<string>>.That.IsEmpty(), ct)) A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.IsEmpty(), CancellationToken))
.Returns(new List<IAppEntity> { expected }); .Returns(new List<IAppEntity> { App });
var actual = await sut.GetAppsForUserAsync(userId, PermissionSet.Empty, ct); var actual = await sut.GetAppsForUserAsync(User.Identifier, PermissionSet.Empty, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
} }
@ -178,12 +158,13 @@ public class AppsIndexTests
[Fact] [Fact]
public async Task Should_return_empty_apps_if_app_deleted() public async Task Should_return_empty_apps_if_app_deleted()
{ {
var expected = CreateApp(0, true); A.CallTo(() => App.IsDeleted)
.Returns(true);
A.CallTo(() => appRepository.QueryAllAsync(userId, A<IEnumerable<string>>.That.IsEmpty(), ct)) A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.IsEmpty(), CancellationToken))
.Returns(new List<IAppEntity> { expected }); .Returns(new List<IAppEntity> { App });
var actual = await sut.GetAppsForUserAsync(userId, PermissionSet.Empty, ct); var actual = await sut.GetAppsForUserAsync(User.Identifier, PermissionSet.Empty, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
} }
@ -191,10 +172,10 @@ public class AppsIndexTests
[Fact] [Fact]
public async Task Should_take_and_remove_reservation_if_created() public async Task Should_take_and_remove_reservation_if_created()
{ {
A.CallTo(() => appRepository.FindAsync(appId.Name, ct)) A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(Task.FromResult<IAppEntity?>(null)); .Returns(Task.FromResult<IAppEntity?>(null));
var command = Create(appId.Name); var command = Create(AppId.Name);
var context = var context =
new CommandContext(command, commandBus) new CommandContext(command, commandBus)
@ -207,21 +188,21 @@ public class AppsIndexTests
madeReservation = state.Snapshot.Reservations.FirstOrDefault(); madeReservation = state.Snapshot.Reservations.FirstOrDefault();
return Task.CompletedTask; return Task.CompletedTask;
}, ct); }, CancellationToken);
Assert.Empty(state.Snapshot.Reservations); Assert.Empty(state.Snapshot.Reservations);
Assert.Equal(appId.Id, madeReservation?.Id); Assert.Equal(AppId.Id, madeReservation?.Id);
Assert.Equal(appId.Name, madeReservation?.Name); Assert.Equal(AppId.Name, madeReservation?.Name);
} }
[Fact] [Fact]
public async Task Should_clear_reservation_if_app_creation_failed() public async Task Should_clear_reservation_if_app_creation_failed()
{ {
A.CallTo(() => appRepository.FindAsync(appId.Name, ct)) A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(Task.FromResult<IAppEntity?>(null)); .Returns(Task.FromResult<IAppEntity?>(null));
var command = Create(appId.Name); var command = Create(AppId.Name);
var context = var context =
new CommandContext(command, commandBus) new CommandContext(command, commandBus)
@ -234,44 +215,44 @@ public class AppsIndexTests
madeReservation = state.Snapshot.Reservations.FirstOrDefault(); madeReservation = state.Snapshot.Reservations.FirstOrDefault();
throw new InvalidOperationException(); throw new InvalidOperationException();
}, ct)); }, CancellationToken));
Assert.Empty(state.Snapshot.Reservations); Assert.Empty(state.Snapshot.Reservations);
Assert.Equal(appId.Id, madeReservation?.Id); Assert.Equal(AppId.Id, madeReservation?.Id);
Assert.Equal(appId.Name, madeReservation?.Name); Assert.Equal(AppId.Name, madeReservation?.Name);
} }
[Fact] [Fact]
public async Task Should_not_create_app_if_name_is_reserved() public async Task Should_not_create_app_if_name_is_reserved()
{ {
state.Snapshot.Reservations.Add(new NameReservation(RandomHash.Simple(), appId.Name, DomainId.NewGuid())); state.Snapshot.Reservations.Add(new NameReservation(RandomHash.Simple(), AppId.Name, DomainId.NewGuid()));
A.CallTo(() => appRepository.FindAsync(appId.Name, ct)) A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(Task.FromResult<IAppEntity?>(null)); .Returns(Task.FromResult<IAppEntity?>(null));
var command = Create(appId.Name); var command = Create(AppId.Name);
var context = var context =
new CommandContext(command, commandBus) new CommandContext(command, commandBus)
.Complete(); .Complete();
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context, ct)); await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context, CancellationToken));
} }
[Fact] [Fact]
public async Task Should_not_create_app_if_name_is_taken() public async Task Should_not_create_app_if_name_is_taken()
{ {
A.CallTo(() => appRepository.FindAsync(appId.Name, ct)) A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(CreateApp()); .Returns(App);
var command = Create(appId.Name); var command = Create(AppId.Name);
var context = var context =
new CommandContext(command, commandBus) new CommandContext(command, commandBus)
.Complete(); .Complete();
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context, ct)); await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context, CancellationToken));
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<NameReservationState.State>._, A<CancellationToken>._)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<NameReservationState.State>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -280,15 +261,13 @@ public class AppsIndexTests
[Fact] [Fact]
public async Task Should_not_make_an_update_for_other_command() public async Task Should_not_make_an_update_for_other_command()
{ {
var app = CreateApp(); var command = new UpdateApp { AppId = AppId };
var command = new UpdateApp { AppId = appId };
var context = var context =
new CommandContext(command, commandBus) new CommandContext(command, commandBus)
.Complete(app); .Complete(App);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<NameReservationState.State>._, A<CancellationToken>._)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<NameReservationState.State>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -296,18 +275,6 @@ public class AppsIndexTests
private CreateApp Create(string name) private CreateApp Create(string name)
{ {
return new CreateApp { AppId = appId.Id, Name = name }; return new CreateApp { AppId = AppId.Id, Name = name };
}
private IAppEntity CreateApp(long version = 0, bool isDeleted = false)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.Version).Returns(version);
A.CallTo(() => app.IsDeleted).Returns(isDeleted);
return app;
} }
} }

21
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Plans/RestrictAppsCommandMiddlewareTests.cs

@ -8,6 +8,7 @@
using System.Security.Claims; using System.Security.Claims;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
@ -16,10 +17,8 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.Plans; namespace Squidex.Domain.Apps.Entities.Apps.Plans;
public sealed class RestrictAppsCommandMiddlewareTests public sealed class RestrictAppsCommandMiddlewareTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IUserResolver userResolver = A.Fake<IUserResolver>(); private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly RestrictAppsOptions options = new RestrictAppsOptions(); private readonly RestrictAppsOptions options = new RestrictAppsOptions();
@ -27,8 +26,6 @@ public sealed class RestrictAppsCommandMiddlewareTests
public RestrictAppsCommandMiddlewareTests() public RestrictAppsCommandMiddlewareTests()
{ {
ct = cts.Token;
sut = new RestrictAppsCommandMiddleware(Options.Create(options), userResolver); sut = new RestrictAppsCommandMiddleware(Options.Create(options), userResolver);
} }
@ -54,7 +51,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
A.CallTo(() => user.Claims) A.CallTo(() => user.Claims)
.Returns(Enumerable.Repeat(new Claim(SquidexClaimTypes.TotalApps, "5"), 1).ToList()); .Returns(Enumerable.Repeat(new Claim(SquidexClaimTypes.TotalApps, "5"), 1).ToList());
A.CallTo(() => userResolver.FindByIdAsync(userId, ct)) A.CallTo(() => userResolver.FindByIdAsync(userId, CancellationToken))
.Returns(user); .Returns(user);
var isNextCalled = false; var isNextCalled = false;
@ -64,7 +61,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
isNextCalled = true; isNextCalled = true;
return Task.CompletedTask; return Task.CompletedTask;
}, ct)); }, CancellationToken));
Assert.False(isNextCalled); Assert.False(isNextCalled);
} }
@ -91,7 +88,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
A.CallTo(() => user.Claims) A.CallTo(() => user.Claims)
.Returns(Enumerable.Repeat(new Claim(SquidexClaimTypes.TotalApps, "5"), 1).ToList()); .Returns(Enumerable.Repeat(new Claim(SquidexClaimTypes.TotalApps, "5"), 1).ToList());
A.CallTo(() => userResolver.FindByIdAsync(userId, ct)) A.CallTo(() => userResolver.FindByIdAsync(userId, CancellationToken))
.Returns(user); .Returns(user);
await sut.HandleAsync(commandContext, (c, ct) => await sut.HandleAsync(commandContext, (c, ct) =>
@ -99,7 +96,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true); c.Complete(true);
return Task.CompletedTask; return Task.CompletedTask;
}, ct); }, CancellationToken);
A.CallTo(() => userResolver.SetClaimAsync(userId, SquidexClaimTypes.TotalApps, "6", true, default)) A.CallTo(() => userResolver.SetClaimAsync(userId, SquidexClaimTypes.TotalApps, "6", true, default))
.MustHaveHappened(); .MustHaveHappened();
@ -122,7 +119,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true); c.Complete(true);
return Task.CompletedTask; return Task.CompletedTask;
}, ct); }, CancellationToken);
A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._)) A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -145,7 +142,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true); c.Complete(true);
return Task.CompletedTask; return Task.CompletedTask;
}, ct); }, CancellationToken);
A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._)) A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -168,7 +165,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true); c.Complete(true);
return Task.CompletedTask; return Task.CompletedTask;
}, ct); }, CancellationToken);
A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._)) A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();

17
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs

@ -13,31 +13,26 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Apps;
public class RolePermissionsProviderTests public class RolePermissionsProviderTests : GivenContext
{ {
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RolePermissionsProvider sut; private readonly RolePermissionsProvider sut;
public RolePermissionsProviderTests() public RolePermissionsProviderTests()
{ {
app = Mocks.App(appId); sut = new RolePermissionsProvider(AppProvider);
sut = new RolePermissionsProvider(appProvider);
} }
[Fact] [Fact]
public async Task Should_provide_all_permissions() public async Task Should_provide_all_permissions()
{ {
A.CallTo(() => appProvider.GetSchemasAsync(A<DomainId>._, default)) A.CallTo(() => AppProvider.GetSchemasAsync(A<DomainId>._, default))
.Returns(new List<ISchemaEntity> .Returns(new List<ISchemaEntity>
{ {
Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "schema1")), Mocks.Schema(AppId, NamedId.Of(DomainId.NewGuid(), "schema1")),
Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "schema2")) Mocks.Schema(AppId, NamedId.Of(DomainId.NewGuid(), "schema2"))
}); });
var actual = await sut.GetPermissionsAsync(app); var actual = await sut.GetPermissionsAsync(App);
Assert.True(actual.Contains("*")); Assert.True(actual.Contains("*"));
Assert.True(actual.Contains("clients.read")); Assert.True(actual.Contains("clients.read"));

15
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs

@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
@ -20,10 +21,8 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetChangedTriggerHandlerTests public class AssetChangedTriggerHandlerTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>(); private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>(); private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
@ -31,8 +30,6 @@ public class AssetChangedTriggerHandlerTests
public AssetChangedTriggerHandlerTests() public AssetChangedTriggerHandlerTests()
{ {
ct = cts.Token;
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true); .Returns(true);
@ -79,14 +76,14 @@ public class AssetChangedTriggerHandlerTests
{ {
var ctx = Context(); var ctx = Context();
A.CallTo(() => assetRepository.StreamAll(ctx.AppId.Id, ct)) A.CallTo(() => assetRepository.StreamAll(ctx.AppId.Id, CancellationToken))
.Returns(new List<AssetEntity> .Returns(new List<AssetEntity>
{ {
new AssetEntity(), new AssetEntity(),
new AssetEntity() new AssetEntity()
}.ToAsyncEnumerable()); }.ToAsyncEnumerable());
var actual = await sut.CreateSnapshotEventsAsync(ctx, ct).ToListAsync(ct); var actual = await sut.CreateSnapshotEventsAsync(ctx, CancellationToken).ToListAsync(CancellationToken);
var typed = actual.OfType<EnrichedAssetEvent>().ToList(); var typed = actual.OfType<EnrichedAssetEvent>().ToList();
@ -102,10 +99,10 @@ public class AssetChangedTriggerHandlerTests
var envelope = Envelope.Create<AppEvent>(@event).SetEventStreamNumber(12); var envelope = Envelope.Create<AppEvent>(@event).SetEventStreamNumber(12);
A.CallTo(() => assetLoader.GetAsync(ctx.AppId.Id, @event.AssetId, 12, ct)) A.CallTo(() => assetLoader.GetAsync(ctx.AppId.Id, @event.AssetId, 12, CancellationToken))
.Returns(new AssetEntity()); .Returns(new AssetEntity());
var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, ct).ToListAsync(ct); var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken);
var enrichedEvent = (EnrichedAssetEvent)actual.Single(); var enrichedEvent = (EnrichedAssetEvent)actual.Single();

20
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetPermanentDeleterTests.cs

@ -7,16 +7,16 @@
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetPermanentDeleterTests public class AssetPermanentDeleterTests : GivenContext
{ {
private readonly IAssetFileStore assetFiletore = A.Fake<IAssetFileStore>(); private readonly IAssetFileStore assetFiletore = A.Fake<IAssetFileStore>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AssetPermanentDeleter sut; private readonly AssetPermanentDeleter sut;
public AssetPermanentDeleterTests() public AssetPermanentDeleterTests()
@ -75,31 +75,31 @@ public class AssetPermanentDeleterTests
[Fact] [Fact]
public async Task Should_not_delete_assets_if_event_restored() public async Task Should_not_delete_assets_if_event_restored()
{ {
var @event = new AssetDeleted { AppId = appId, AssetId = DomainId.NewGuid() }; var @event = new AssetDeleted { AppId = AppId, AssetId = DomainId.NewGuid() };
await sut.On(Envelope.Create(@event).SetRestored()); await sut.On(Envelope.Create(@event).SetRestored());
A.CallTo(() => assetFiletore.DeleteAsync(appId.Id, @event.AssetId, A<CancellationToken>._)) A.CallTo(() => assetFiletore.DeleteAsync(AppId.Id, @event.AssetId, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
[Fact] [Fact]
public async Task Should_delete_asset() public async Task Should_delete_asset()
{ {
var @event = new AssetDeleted { AppId = appId, AssetId = DomainId.NewGuid() }; var @event = new AssetDeleted { AppId = AppId, AssetId = DomainId.NewGuid() };
await sut.On(Envelope.Create(@event)); await sut.On(Envelope.Create(@event));
A.CallTo(() => assetFiletore.DeleteAsync(appId.Id, @event.AssetId, A<CancellationToken>._)) A.CallTo(() => assetFiletore.DeleteAsync(AppId.Id, @event.AssetId, A<CancellationToken>._))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_ignore_not_found_assets() public async Task Should_ignore_not_found_assets()
{ {
var @event = new AssetDeleted { AppId = appId, AssetId = DomainId.NewGuid() }; var @event = new AssetDeleted { AppId = AppId, AssetId = DomainId.NewGuid() };
A.CallTo(() => assetFiletore.DeleteAsync(appId.Id, @event.AssetId, default)) A.CallTo(() => assetFiletore.DeleteAsync(AppId.Id, @event.AssetId, default))
.Throws(new AssetNotFoundException("fileName")); .Throws(new AssetNotFoundException("fileName"));
await sut.On(Envelope.Create(@event)); await sut.On(Envelope.Create(@event));
@ -108,9 +108,9 @@ public class AssetPermanentDeleterTests
[Fact] [Fact]
public async Task Should_not_ignore_exceptions() public async Task Should_not_ignore_exceptions()
{ {
var @event = new AssetDeleted { AppId = appId, AssetId = DomainId.NewGuid() }; var @event = new AssetDeleted { AppId = AppId, AssetId = DomainId.NewGuid() };
A.CallTo(() => assetFiletore.DeleteAsync(appId.Id, @event.AssetId, default)) A.CallTo(() => assetFiletore.DeleteAsync(AppId.Id, @event.AssetId, default))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.On(Envelope.Create(@event))); await Assert.ThrowsAsync<InvalidOperationException>(() => sut.On(Envelope.Create(@event)));

13
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetTagsDeleterTests.cs

@ -7,21 +7,16 @@
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetTagsDeleterTests public class AssetTagsDeleterTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly AssetTagsDeleter sut; private readonly AssetTagsDeleter sut;
public AssetTagsDeleterTests() public AssetTagsDeleterTests()
{ {
ct = cts.Token;
sut = new AssetTagsDeleter(tagService); sut = new AssetTagsDeleter(tagService);
} }
@ -36,11 +31,9 @@ public class AssetTagsDeleterTests
[Fact] [Fact]
public async Task Should_remove_events_from_streams() public async Task Should_remove_events_from_streams()
{ {
var app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app")); await sut.DeleteAppAsync(App, CancellationToken);
await sut.DeleteAppAsync(app, ct);
A.CallTo(() => tagService.ClearAsync(app.Id, TagGroups.Assets, ct)) A.CallTo(() => tagService.ClearAsync(AppId.Id, TagGroups.Assets, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

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

@ -8,6 +8,7 @@
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Billing; using Squidex.Domain.Apps.Entities.Billing;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
@ -15,20 +16,19 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetUsageTrackerTests public class AssetUsageTrackerTests : GivenContext
{ {
private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>(); private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>();
private readonly ISnapshotStore<AssetUsageTracker.State> store = A.Fake<ISnapshotStore<AssetUsageTracker.State>>(); private readonly ISnapshotStore<AssetUsageTracker.State> store = A.Fake<ISnapshotStore<AssetUsageTracker.State>>();
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly IUsageGate usageGate = A.Fake<IUsageGate>(); private readonly IUsageGate usageGate = A.Fake<IUsageGate>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly DomainId assetId = DomainId.NewGuid(); private readonly DomainId assetId = DomainId.NewGuid();
private readonly DomainId assetKey; private readonly DomainId assetKey;
private readonly AssetUsageTracker sut; private readonly AssetUsageTracker sut;
public AssetUsageTrackerTests() public AssetUsageTrackerTests()
{ {
assetKey = DomainId.Combine(appId, assetId); assetKey = DomainId.Combine(AppId, assetId);
sut = new AssetUsageTracker(usageGate, assetLoader, tagService, store); sut = new AssetUsageTracker(usageGate, assetLoader, tagService, store);
} }
@ -81,7 +81,7 @@ public class AssetUsageTrackerTests
{ {
var date = DateTime.UtcNow.Date.AddDays(13); var date = DateTime.UtcNow.Date.AddDays(13);
@event.AppId = appId; @event.AppId = AppId;
var envelope = var envelope =
Envelope.Create<IEvent>(@event) Envelope.Create<IEvent>(@event)
@ -89,7 +89,7 @@ public class AssetUsageTrackerTests
await sut.On(new[] { envelope }); await sut.On(new[] { envelope });
A.CallTo(() => usageGate.TrackAssetAsync(appId.Id, date, sizeDiff, countDiff, default)) A.CallTo(() => usageGate.TrackAssetAsync(AppId.Id, date, sizeDiff, countDiff, default))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -98,7 +98,7 @@ public class AssetUsageTrackerTests
{ {
var @event = new AssetCreated var @event = new AssetCreated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag1", "tag1",
@ -113,7 +113,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { envelope });
@ -130,7 +130,7 @@ public class AssetUsageTrackerTests
{ {
var @event1 = new AssetCreated var @event1 = new AssetCreated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag1", "tag1",
@ -141,7 +141,7 @@ public class AssetUsageTrackerTests
var @event2 = new AssetCreated var @event2 = new AssetCreated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag2", "tag2",
@ -160,7 +160,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { envelope1, envelope2 });
@ -181,7 +181,7 @@ public class AssetUsageTrackerTests
{ {
var @event1 = new AssetCreated var @event1 = new AssetCreated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag1", "tag1",
@ -192,7 +192,7 @@ public class AssetUsageTrackerTests
var @event2 = new AssetAnnotated var @event2 = new AssetAnnotated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag2", "tag2",
@ -211,7 +211,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { envelope1, envelope2 });
@ -229,7 +229,7 @@ public class AssetUsageTrackerTests
{ {
var @event1 = new AssetCreated var @event1 = new AssetCreated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag1", "tag1",
@ -240,7 +240,7 @@ public class AssetUsageTrackerTests
var @event2 = new AssetAnnotated var @event2 = new AssetAnnotated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag2", "tag2",
@ -259,7 +259,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { envelope1 });
@ -278,7 +278,7 @@ public class AssetUsageTrackerTests
{ {
var @event1 = new AssetCreated var @event1 = new AssetCreated
{ {
AppId = appId, AppId = AppId,
Tags = new HashSet<string> Tags = new HashSet<string>
{ {
"tag1", "tag1",
@ -287,7 +287,7 @@ public class AssetUsageTrackerTests
AssetId = assetId AssetId = assetId
}; };
var @event2 = new AssetDeleted { AppId = appId, AssetId = assetId }; var @event2 = new AssetDeleted { AppId = AppId, AssetId = assetId };
var envelope1 = var envelope1 =
Envelope.Create<IEvent>(@event1) Envelope.Create<IEvent>(@event1)
@ -299,7 +299,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { Envelope.Create<IEvent>(@event1), Envelope.Create<IEvent>(@event2) });
@ -326,7 +326,7 @@ public class AssetUsageTrackerTests
A.CallTo(() => store.ReadAsync(assetKey, default)) A.CallTo(() => store.ReadAsync(assetKey, default))
.Returns(new SnapshotResult<AssetUsageTracker.State>(assetKey, state, 0)); .Returns(new SnapshotResult<AssetUsageTracker.State>(assetKey, state, 0));
var @event = new AssetDeleted { AppId = appId, AssetId = assetId }; var @event = new AssetDeleted { AppId = AppId, AssetId = assetId };
var envelope = var envelope =
Envelope.Create<IEvent>(@event) Envelope.Create<IEvent>(@event)
@ -334,7 +334,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { envelope });
@ -358,10 +358,10 @@ public class AssetUsageTrackerTests
} }
}; };
A.CallTo(() => assetLoader.GetAsync(appId.Id, assetId, 41, default)) A.CallTo(() => assetLoader.GetAsync(AppId.Id, assetId, 41, default))
.Returns(asset); .Returns(asset);
var @event = new AssetDeleted { AppId = appId, AssetId = assetId }; var @event = new AssetDeleted { AppId = AppId, AssetId = assetId };
var envelope = var envelope =
Envelope.Create<IEvent>(@event) Envelope.Create<IEvent>(@event)
@ -370,7 +370,7 @@ public class AssetUsageTrackerTests
Dictionary<string, int>? update = null; Dictionary<string, int>? update = null;
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(new[] { envelope });

21
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs

@ -19,20 +19,18 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetsFluidExtensionTests public class AssetsFluidExtensionTests : GivenContext
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>(); private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>(); private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly FluidTemplateEngine sut; private readonly FluidTemplateEngine sut;
public AssetsFluidExtensionTests() public AssetsFluidExtensionTests()
{ {
var serviceProvider = var serviceProvider =
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(AppProvider)
.AddSingleton(assetFileStore) .AddSingleton(assetFileStore)
.AddSingleton(assetQuery) .AddSingleton(assetQuery)
.AddSingleton(assetThumbnailGenerator) .AddSingleton(assetThumbnailGenerator)
@ -44,9 +42,6 @@ public class AssetsFluidExtensionTests
new AssetsFluidExtension(serviceProvider) new AssetsFluidExtension(serviceProvider)
}; };
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false, default))
.Returns(Mocks.App(appId));
sut = new FluidTemplateEngine(extensions); sut = new FluidTemplateEngine(extensions);
} }
@ -179,7 +174,7 @@ public class AssetsFluidExtensionTests
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
FileVersion = 0, FileVersion = 0,
FileSize = 100, FileSize = 100,
AppId = appId AppId = AppId
}; };
SetupText(@event.ToRef(), Encode(encoding, "hello+assets")); SetupText(@event.ToRef(), Encode(encoding, "hello+assets"));
@ -276,7 +271,7 @@ public class AssetsFluidExtensionTests
AssetType = AssetType.Image, AssetType = AssetType.Image,
FileVersion = 0, FileVersion = 0,
FileSize = 100, FileSize = 100,
AppId = appId AppId = AppId
}; };
SetupBlurHash(@event.ToRef(), "Hash"); SetupBlurHash(@event.ToRef(), "Hash");
@ -307,7 +302,7 @@ public class AssetsFluidExtensionTests
private void SetupText(AssetRef asset, byte[] bytes) private void SetupText(AssetRef asset, byte[] bytes)
{ {
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, asset.Id, asset.FileVersion, null, A<Stream>._, A<BytesRange>._, A<CancellationToken>._)) A.CallTo(() => assetFileStore.DownloadAsync(AppId.Id, asset.Id, asset.FileVersion, null, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.Invokes(x => x.GetArgument<Stream>(4)?.Write(bytes)); .Invokes(x => x.GetArgument<Stream>(4)?.Write(bytes));
} }
@ -323,7 +318,7 @@ public class AssetsFluidExtensionTests
.AddField("assets", .AddField("assets",
new ContentFieldData() new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId))), .AddInvariant(JsonValue.Array(assetId))),
AppId = appId AppId = AppId
}; };
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId, EtagVersion.Any, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId, EtagVersion.Any, A<CancellationToken>._))
@ -351,7 +346,7 @@ public class AssetsFluidExtensionTests
.AddField("assets", .AddField("assets",
new ContentFieldData() new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId1, assetId2))), .AddInvariant(JsonValue.Array(assetId1, assetId2))),
AppId = appId AppId = AppId
}; };
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId1, EtagVersion.Any, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId1, EtagVersion.Any, A<CancellationToken>._))
@ -372,7 +367,7 @@ public class AssetsFluidExtensionTests
{ {
return new AssetEntity return new AssetEntity
{ {
AppId = appId, AppId = AppId,
Id = assetId, Id = assetId,
FileSize = fileSize, FileSize = fileSize,
FileName = $"file{index}.jpg", FileName = $"file{index}.jpg",

41
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs

@ -22,20 +22,18 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture> public class AssetsJintExtensionTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>(); private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>(); private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly JintScriptEngine sut; private readonly JintScriptEngine sut;
public AssetsJintExtensionTests() public AssetsJintExtensionTests()
{ {
var serviceProvider = var serviceProvider =
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(AppProvider)
.AddSingleton(assetFileStore) .AddSingleton(assetFileStore)
.AddSingleton(assetQuery) .AddSingleton(assetQuery)
.AddSingleton(assetThumbnailGenerator) .AddSingleton(assetThumbnailGenerator)
@ -46,9 +44,6 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
new AssetsJintExtension(serviceProvider) new AssetsJintExtension(serviceProvider)
}; };
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false, A<CancellationToken>._))
.Returns(Mocks.App(appId));
sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())),
Options.Create(new JintScriptOptions Options.Create(new JintScriptOptions
{ {
@ -97,7 +92,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
complete(`${actual1}`); complete(`${actual1}`);
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -120,7 +115,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
complete(`${actual1}\n${actual2}`); complete(`${actual1}\n${actual2}`);
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -146,7 +141,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
}}, '{encoding}'); }}, '{encoding}');
}});"; }});";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -169,7 +164,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
}); });
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
@ -186,7 +181,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
FileVersion = 0, FileVersion = 0,
FileSize = 100, FileSize = 100,
AppId = appId AppId = AppId
}; };
SetupText(@event.ToRef(), Encode(encoding, "hello+assets")); SetupText(@event.ToRef(), Encode(encoding, "hello+assets"));
@ -207,7 +202,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
complete(actual); complete(actual);
}}, '{encoding}');"; }}, '{encoding}');";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -232,7 +227,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
}); });
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -257,7 +252,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
}); });
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -282,7 +277,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
}); });
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -296,7 +291,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
AssetType = AssetType.Image, AssetType = AssetType.Image,
FileVersion = 0, FileVersion = 0,
FileSize = 100, FileSize = 100,
AppId = appId AppId = AppId
}; };
SetupBlurHash(@event.ToRef(), "Hash"); SetupBlurHash(@event.ToRef(), "Hash");
@ -317,7 +312,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
complete(actual); complete(actual);
});"; });";
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script, ct: CancellationToken)).ToString();
Assert.Equal(Cleanup(expected), Cleanup(actual)); Assert.Equal(Cleanup(expected), Cleanup(actual));
} }
@ -330,7 +325,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
private void SetupText(AssetRef asset, byte[] bytes) private void SetupText(AssetRef asset, byte[] bytes)
{ {
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, asset.Id, asset.FileVersion, null, A<Stream>._, A<BytesRange>._, A<CancellationToken>._)) A.CallTo(() => assetFileStore.DownloadAsync(AppId.Id, asset.Id, asset.FileVersion, null, A<Stream>._, A<BytesRange>._, A<CancellationToken>._))
.Invokes(x => x.GetArgument<Stream>(4)?.Write(bytes)); .Invokes(x => x.GetArgument<Stream>(4)?.Write(bytes));
} }
@ -348,14 +343,14 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
.AddInvariant(JsonValue.Array(assetIds))); .AddInvariant(JsonValue.Array(assetIds)));
A.CallTo(() => assetQuery.QueryAsync( A.CallTo(() => assetQuery.QueryAsync(
A<Context>.That.Matches(x => x.App.Id == appId.Id && x.UserPrincipal == user), null, A<Q>.That.HasIds(assetIds), A<CancellationToken>._)) A<Context>.That.Matches(x => x.App == App && x.UserPrincipal == user), null, A<Q>.That.HasIds(assetIds), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(2, assets)); .Returns(ResultList.CreateFrom(2, assets));
var vars = new ScriptVars var vars = new ScriptVars
{ {
["data"] = data, ["data"] = data,
["appId"] = appId.Id, ["appId"] = AppId.Id,
["appName"] = appId.Name, ["appName"] = AppId.Name,
["user"] = user ["user"] = user
}; };
@ -366,7 +361,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
{ {
return new AssetEntity return new AssetEntity
{ {
AppId = appId, AppId = AppId,
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
FileSize = fileSize, FileSize = fileSize,
FileName = $"file{index}.jpg", FileName = $"file{index}.jpg",

34
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsSearchSourceTests.cs

@ -5,21 +5,18 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetsSearchSourceTests public class AssetsSearchSourceTests : GivenContext
{ {
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AssetsSearchSource sut; private readonly AssetsSearchSource sut;
public AssetsSearchSourceTests() public AssetsSearchSourceTests()
@ -30,9 +27,7 @@ public class AssetsSearchSourceTests
[Fact] [Fact]
public async Task Should_return_empty_actuals_if_user_has_no_permission() public async Task Should_return_empty_actuals_if_user_has_no_permission()
{ {
var ctx = ContextWithPermission(); var actual = await sut.SearchAsync("logo", ApiContext, default);
var actual = await sut.SearchAsync("logo", ctx, default);
Assert.Empty(actual); Assert.Empty(actual);
@ -43,23 +38,23 @@ public class AssetsSearchSourceTests
[Fact] [Fact]
public async Task Should_return_assets_actuals_if_found() public async Task Should_return_assets_actuals_if_found()
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppAssetsRead, appId.Name); var permission = PermissionIds.ForApp(PermissionIds.AppAssetsRead, AppId.Name);
var ctx = ContextWithPermission(permission.Id); var requestContext = CreateContext(false, permission.Id);
var asset1 = CreateAsset("logo1.png"); var asset1 = CreateAsset("logo1.png");
var asset2 = CreateAsset("logo2.png"); var asset2 = CreateAsset("logo2.png");
A.CallTo(() => urlGenerator.AssetsUI(appId, asset1.Id.ToString())) A.CallTo(() => urlGenerator.AssetsUI(AppId, asset1.Id.ToString()))
.Returns("assets-url1"); .Returns("assets-url1");
A.CallTo(() => urlGenerator.AssetsUI(appId, asset2.Id.ToString())) A.CallTo(() => urlGenerator.AssetsUI(AppId, asset2.Id.ToString()))
.Returns("assets-url2"); .Returns("assets-url2");
A.CallTo(() => assetQuery.QueryAsync(ctx, null, A<Q>.That.HasQuery("Filter: contains(fileName, 'logo'); Take: 5"), A<CancellationToken>._)) A.CallTo(() => assetQuery.QueryAsync(requestContext, null, A<Q>.That.HasQuery("Filter: contains(fileName, 'logo'); Take: 5"), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(2, asset1, asset2)); .Returns(ResultList.CreateFrom(2, asset1, asset2));
var actual = await sut.SearchAsync("logo", ctx, default); var actual = await sut.SearchAsync("logo", requestContext, default);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -71,17 +66,4 @@ public class AssetsSearchSourceTests
{ {
return new AssetEntity { FileName = fileName, Id = DomainId.NewGuid() }; return new AssetEntity { FileName = fileName, Id = DomainId.NewGuid() };
} }
private Context ContextWithPermission(string? permission = null)
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
if (permission != null)
{
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
}
return new Context(claimsPrincipal, Mocks.App(appId));
}
} }

120
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/BackupAssetsTests.cs

@ -9,6 +9,7 @@ using Squidex.Assets;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -17,21 +18,16 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class BackupAssetsTests public class BackupAssetsTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly Rebuilder rebuilder = A.Fake<Rebuilder>(); private readonly Rebuilder rebuilder = A.Fake<Rebuilder>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>(); private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RefToken actor = RefToken.User("123"); private readonly RefToken actor = RefToken.User("123");
private readonly BackupAssets sut; private readonly BackupAssets sut;
public BackupAssetsTests() public BackupAssetsTests()
{ {
ct = cts.Token;
sut = new BackupAssets(rebuilder, assetFileStore, tagService); sut = new BackupAssets(rebuilder, assetFileStore, tagService);
} }
@ -51,12 +47,12 @@ public class BackupAssetsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, ct)) A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, CancellationToken))
.Returns(tags); .Returns(tags);
await sut.BackupAsync(context, ct); await sut.BackupAsync(context, CancellationToken);
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Tags, ct)) A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Tags, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias!, A<CancellationToken>._)) A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias!, A<CancellationToken>._))
@ -77,15 +73,15 @@ public class BackupAssetsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, ct)) A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, CancellationToken))
.Returns(tags); .Returns(tags);
await sut.BackupAsync(context, ct); await sut.BackupAsync(context, CancellationToken);
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Tags, ct)) A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Tags, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias, ct)) A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -99,18 +95,18 @@ public class BackupAssetsTests
var envelope = var envelope =
new Envelope<IEvent>(new AppCreated new Envelope<IEvent>(new AppCreated
{ {
AppId = appId AppId = AppId
}); });
A.CallTo(() => context.Reader.HasFileAsync(A<string>._, ct)) A.CallTo(() => context.Reader.HasFileAsync(A<string>._, CancellationToken))
.Returns(true); .Returns(true);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, ct)) A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, CancellationToken))
.Returns(tags); .Returns(tags);
await sut.RestoreEventAsync(envelope, context, ct); await sut.RestoreEventAsync(envelope, context, CancellationToken);
A.CallTo(() => tagService.RebuildTagsAsync(appId.Id, TagGroups.Assets, A<TagsExport>.That.Matches(x => x.Tags == tags), ct)) A.CallTo(() => tagService.RebuildTagsAsync(AppId.Id, TagGroups.Assets, A<TagsExport>.That.Matches(x => x.Tags == tags), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -124,18 +120,18 @@ public class BackupAssetsTests
var envelope = var envelope =
new Envelope<IEvent>(new AppCreated new Envelope<IEvent>(new AppCreated
{ {
AppId = appId AppId = AppId
}); });
A.CallTo(() => context.Reader.HasFileAsync(A<string>._, ct)) A.CallTo(() => context.Reader.HasFileAsync(A<string>._, CancellationToken))
.Returns(false).Once().Then.Returns(true); .Returns(false).Once().Then.Returns(true);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, ct)) A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, CancellationToken))
.Returns(alias); .Returns(alias);
await sut.RestoreEventAsync(envelope, context, ct); await sut.RestoreEventAsync(envelope, context, CancellationToken);
A.CallTo(() => tagService.RebuildTagsAsync(appId.Id, TagGroups.Assets, A<TagsExport>.That.Matches(x => x.Alias == alias), ct)) A.CallTo(() => tagService.RebuildTagsAsync(AppId.Id, TagGroups.Assets, A<TagsExport>.That.Matches(x => x.Alias == alias), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -149,16 +145,16 @@ public class BackupAssetsTests
var envelope = var envelope =
new Envelope<IEvent>(new AppCreated new Envelope<IEvent>(new AppCreated
{ {
AppId = appId AppId = AppId
}); });
A.CallTo(() => context.Reader.HasFileAsync(A<string>._, ct)) A.CallTo(() => context.Reader.HasFileAsync(A<string>._, CancellationToken))
.Returns(false); .Returns(false);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, ct)) A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, CancellationToken))
.Returns(alias); .Returns(alias);
await sut.RestoreEventAsync(envelope, context, ct); await sut.RestoreEventAsync(envelope, context, CancellationToken);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, A<CancellationToken>._)) A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -166,7 +162,7 @@ public class BackupAssetsTests
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, A<CancellationToken>._)) A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
A.CallTo(() => tagService.RebuildTagsAsync(appId.Id, TagGroups.Assets, A<TagsExport>.That.Matches(x => x.Alias == alias), A<CancellationToken>._)) A.CallTo(() => tagService.RebuildTagsAsync(AppId.Id, TagGroups.Assets, A<TagsExport>.That.Matches(x => x.Alias == alias), A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -209,12 +205,12 @@ public class BackupAssetsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", ct)) A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.Returns(assetStream); .Returns(assetStream);
await sut.BackupEventAsync(AppEvent(@event), context, ct); await sut.BackupEventAsync(AppEvent(@event), context, CancellationToken);
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, assetId, version, null, assetStream, default, ct)) A.CallTo(() => assetFileStore.DownloadAsync(AppId.Id, assetId, version, null, assetStream, default, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -225,13 +221,13 @@ public class BackupAssetsTests
var context = CreateBackupContext(); var context = CreateBackupContext();
A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", ct)) A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.Returns(assetStream); .Returns(assetStream);
A.CallTo(() => assetFileStore.DownloadAsync(appId.Id, assetId, version, null, assetStream, default, ct)) A.CallTo(() => assetFileStore.DownloadAsync(AppId.Id, assetId, version, null, assetStream, default, CancellationToken))
.Throws(new AssetNotFoundException(assetId.ToString())); .Throws(new AssetNotFoundException(assetId.ToString()));
await sut.BackupEventAsync(AppEvent(@event), context, ct); await sut.BackupEventAsync(AppEvent(@event), context, CancellationToken);
} }
[Fact] [Fact]
@ -253,7 +249,7 @@ public class BackupAssetsTests
[Fact] [Fact]
public async Task Should_restore_updated_asset() public async Task Should_restore_updated_asset()
{ {
var @event = new AssetUpdated { AppId = appId, AssetId = DomainId.NewGuid(), FileVersion = 3 }; var @event = new AssetUpdated { AppId = AppId, AssetId = DomainId.NewGuid(), FileVersion = 3 };
await TestRestoreAsync(@event, @event.FileVersion); await TestRestoreAsync(@event, @event.FileVersion);
} }
@ -261,7 +257,7 @@ public class BackupAssetsTests
[Fact] [Fact]
public async Task Should_restore_updated_asset_with_missing_file() public async Task Should_restore_updated_asset_with_missing_file()
{ {
var @event = new AssetUpdated { AppId = appId, AssetId = DomainId.NewGuid(), FileVersion = 3 }; var @event = new AssetUpdated { AppId = AppId, AssetId = DomainId.NewGuid(), FileVersion = 3 };
await TestRestoreFailedAsync(@event, @event.FileVersion); await TestRestoreFailedAsync(@event, @event.FileVersion);
} }
@ -273,12 +269,12 @@ public class BackupAssetsTests
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", ct)) A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.Returns(assetStream); .Returns(assetStream);
await sut.RestoreEventAsync(AppEvent(@event), context, ct); await sut.RestoreEventAsync(AppEvent(@event), context, CancellationToken);
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, assetId, version, null, assetStream, true, ct)) A.CallTo(() => assetFileStore.UploadAsync(AppId.Id, assetId, version, null, assetStream, true, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -289,12 +285,12 @@ public class BackupAssetsTests
var context = CreateRestoreContext(); var context = CreateRestoreContext();
A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", ct)) A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.Throws(new FileNotFoundException()); .Throws(new FileNotFoundException());
await sut.RestoreEventAsync(AppEvent(@event), context, ct); await sut.RestoreEventAsync(AppEvent(@event), context, CancellationToken);
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, assetId, version, null, assetStream, true, A<CancellationToken>._)) A.CallTo(() => assetFileStore.UploadAsync(AppId.Id, assetId, version, null, assetStream, true, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -309,29 +305,29 @@ public class BackupAssetsTests
await sut.RestoreEventAsync(AppEvent(new AssetCreated await sut.RestoreEventAsync(AppEvent(new AssetCreated
{ {
AssetId = assetId1 AssetId = assetId1
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetCreated await sut.RestoreEventAsync(AppEvent(new AssetCreated
{ {
AssetId = assetId2 AssetId = assetId2
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetDeleted await sut.RestoreEventAsync(AppEvent(new AssetDeleted
{ {
AssetId = assetId2 AssetId = assetId2
}), context, ct); }), context, CancellationToken);
var rebuildAssets = new HashSet<DomainId>(); var rebuildAssets = new HashSet<DomainId>();
A.CallTo(() => rebuilder.InsertManyAsync<AssetDomainObject, AssetDomainObject.State>(A<IEnumerable<DomainId>>._, A<int>._, ct)) A.CallTo(() => rebuilder.InsertManyAsync<AssetDomainObject, AssetDomainObject.State>(A<IEnumerable<DomainId>>._, A<int>._, CancellationToken))
.Invokes(x => rebuildAssets.AddRange(x.GetArgument<IEnumerable<DomainId>>(0)!)); .Invokes(x => rebuildAssets.AddRange(x.GetArgument<IEnumerable<DomainId>>(0)!));
await sut.RestoreAsync(context, ct); await sut.RestoreAsync(context, CancellationToken);
Assert.Equal(new HashSet<DomainId> Assert.Equal(new HashSet<DomainId>
{ {
DomainId.Combine(appId, assetId1), DomainId.Combine(AppId, assetId1),
DomainId.Combine(appId, assetId2) DomainId.Combine(AppId, assetId2)
}, rebuildAssets); }, rebuildAssets);
} }
@ -346,54 +342,54 @@ public class BackupAssetsTests
await sut.RestoreEventAsync(AppEvent(new AssetFolderCreated await sut.RestoreEventAsync(AppEvent(new AssetFolderCreated
{ {
AssetFolderId = assetFolderId1 AssetFolderId = assetFolderId1
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetFolderCreated await sut.RestoreEventAsync(AppEvent(new AssetFolderCreated
{ {
AssetFolderId = assetFolderId2 AssetFolderId = assetFolderId2
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetFolderDeleted await sut.RestoreEventAsync(AppEvent(new AssetFolderDeleted
{ {
AssetFolderId = assetFolderId2 AssetFolderId = assetFolderId2
}), context, ct); }), context, CancellationToken);
var rebuildAssetFolders = new HashSet<DomainId>(); var rebuildAssetFolders = new HashSet<DomainId>();
A.CallTo(() => rebuilder.InsertManyAsync<AssetFolderDomainObject, AssetFolderDomainObject.State>(A<IEnumerable<DomainId>>._, A<int>._, ct)) A.CallTo(() => rebuilder.InsertManyAsync<AssetFolderDomainObject, AssetFolderDomainObject.State>(A<IEnumerable<DomainId>>._, A<int>._, CancellationToken))
.Invokes(x => rebuildAssetFolders.AddRange(x.GetArgument<IEnumerable<DomainId>>(0)!)); .Invokes(x => rebuildAssetFolders.AddRange(x.GetArgument<IEnumerable<DomainId>>(0)!));
await sut.RestoreAsync(context, ct); await sut.RestoreAsync(context, CancellationToken);
Assert.Equal(new HashSet<DomainId> Assert.Equal(new HashSet<DomainId>
{ {
DomainId.Combine(appId, assetFolderId1), DomainId.Combine(AppId, assetFolderId1),
DomainId.Combine(appId, assetFolderId2) DomainId.Combine(AppId, assetFolderId2)
}, rebuildAssetFolders); }, rebuildAssetFolders);
} }
private BackupContext CreateBackupContext() private BackupContext CreateBackupContext()
{ {
return new BackupContext(appId.Id, CreateUserMapping(), A.Fake<IBackupWriter>()); return new BackupContext(AppId.Id, CreateUserMapping(), A.Fake<IBackupWriter>());
} }
private RestoreContext CreateRestoreContext() private RestoreContext CreateRestoreContext()
{ {
return new RestoreContext(appId.Id, CreateUserMapping(), A.Fake<IBackupReader>(), DomainId.NewGuid()); return new RestoreContext(AppId.Id, CreateUserMapping(), A.Fake<IBackupReader>(), DomainId.NewGuid());
} }
private Envelope<AssetEvent> AppEvent(AssetEvent @event) private Envelope<AssetEvent> AppEvent(AssetEvent @event)
{ {
@event.AppId = appId; @event.AppId = AppId;
return Envelope.Create(@event).SetAggregateId(DomainId.Combine(appId, @event.AssetId)); return Envelope.Create(@event).SetAggregateId(DomainId.Combine(AppId, @event.AssetId));
} }
private Envelope<AssetFolderEvent> AppEvent(AssetFolderEvent @event) private Envelope<AssetFolderEvent> AppEvent(AssetFolderEvent @event)
{ {
@event.AppId = appId; @event.AppId = AppId;
return Envelope.Create(@event).SetAggregateId(DomainId.Combine(appId, @event.AssetFolderId)); return Envelope.Create(@event).SetAggregateId(DomainId.Combine(AppId, @event.AssetFolderId));
} }
private IUserMapping CreateUserMapping() private IUserMapping CreateUserMapping()

83
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DefaultAssetFileStoreTests.cs

@ -14,13 +14,10 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class DefaultAssetFileStoreTests public class DefaultAssetFileStoreTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAssetStore assetStore = A.Fake<IAssetStore>(); private readonly IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DomainId assetId = DomainId.NewGuid(); private readonly DomainId assetId = DomainId.NewGuid();
private readonly long assetFileVersion = 21; private readonly long assetFileVersion = 21;
private readonly AssetOptions options = new AssetOptions(); private readonly AssetOptions options = new AssetOptions();
@ -28,8 +25,6 @@ public class DefaultAssetFileStoreTests
public DefaultAssetFileStoreTests() public DefaultAssetFileStoreTests()
{ {
ct = cts.Token;
sut = new DefaultAssetFileStore(assetStore, assetRepository, Options.Create(options)); sut = new DefaultAssetFileStore(assetStore, assetRepository, Options.Create(options));
} }
@ -60,7 +55,7 @@ public class DefaultAssetFileStoreTests
A.CallTo(() => assetStore.GeneratePublicUrl(fullName)) A.CallTo(() => assetStore.GeneratePublicUrl(fullName))
.Returns(url); .Returns(url);
var actual = sut.GeneratePublicUrl(appId, assetId, assetFileVersion, suffix); var actual = sut.GeneratePublicUrl(AppId.Id, assetId, assetFileVersion, suffix);
Assert.Equal(url, actual); Assert.Equal(url, actual);
} }
@ -75,10 +70,10 @@ public class DefaultAssetFileStoreTests
var size = 1024L; var size = 1024L;
A.CallTo(() => assetStore.GetSizeAsync(fullName, ct)) A.CallTo(() => assetStore.GetSizeAsync(fullName, CancellationToken))
.Returns(size); .Returns(size);
var actual = await sut.GetFileSizeAsync(appId, assetId, assetFileVersion, suffix, ct); var actual = await sut.GetFileSizeAsync(AppId.Id, assetId, assetFileVersion, suffix, CancellationToken);
Assert.Equal(size, actual); Assert.Equal(size, actual);
} }
@ -91,13 +86,13 @@ public class DefaultAssetFileStoreTests
var size = 1024L; var size = 1024L;
A.CallTo(() => assetStore.GetSizeAsync(A<string>._, ct)) A.CallTo(() => assetStore.GetSizeAsync(A<string>._, CancellationToken))
.Throws(new AssetNotFoundException(assetId.ToString())); .Throws(new AssetNotFoundException(assetId.ToString()));
A.CallTo(() => assetStore.GetSizeAsync(fullName, ct)) A.CallTo(() => assetStore.GetSizeAsync(fullName, CancellationToken))
.Returns(size); .Returns(size);
var actual = await sut.GetFileSizeAsync(appId, assetId, assetFileVersion, suffix, ct); var actual = await sut.GetFileSizeAsync(AppId.Id, assetId, assetFileVersion, suffix, CancellationToken);
Assert.Equal(size, actual); Assert.Equal(size, actual);
} }
@ -107,9 +102,9 @@ public class DefaultAssetFileStoreTests
{ {
var stream = new MemoryStream(); var stream = new MemoryStream();
await sut.UploadAsync("Temp", stream, ct); await sut.UploadAsync("Temp", stream, CancellationToken);
A.CallTo(() => assetStore.UploadAsync("Temp", stream, false, ct)) A.CallTo(() => assetStore.UploadAsync("Temp", stream, false, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -123,9 +118,9 @@ public class DefaultAssetFileStoreTests
var stream = new MemoryStream(); var stream = new MemoryStream();
await sut.UploadAsync(appId, assetId, assetFileVersion, suffix, stream, true, ct); await sut.UploadAsync(AppId.Id, assetId, assetFileVersion, suffix, stream, true, CancellationToken);
A.CallTo(() => assetStore.UploadAsync(fullName, stream, true, ct)) A.CallTo(() => assetStore.UploadAsync(fullName, stream, true, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -139,9 +134,9 @@ public class DefaultAssetFileStoreTests
var stream = new MemoryStream(); var stream = new MemoryStream();
await sut.DownloadAsync(appId, assetId, assetFileVersion, suffix, stream, default, ct); await sut.DownloadAsync(AppId.Id, assetId, assetFileVersion, suffix, stream, default, CancellationToken);
A.CallTo(() => assetStore.DownloadAsync(fullName, stream, default, ct)) A.CallTo(() => assetStore.DownloadAsync(fullName, stream, default, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -152,12 +147,12 @@ public class DefaultAssetFileStoreTests
var stream = new MemoryStream(); var stream = new MemoryStream();
A.CallTo(() => assetStore.DownloadAsync(A<string>._, stream, default, ct)) A.CallTo(() => assetStore.DownloadAsync(A<string>._, stream, default, CancellationToken))
.Throws(new AssetNotFoundException(assetId.ToString())).Once(); .Throws(new AssetNotFoundException(assetId.ToString())).Once();
await Assert.ThrowsAsync<AssetNotFoundException>(() => sut.DownloadAsync(appId, assetId, assetFileVersion, null, stream, default, ct)); await Assert.ThrowsAsync<AssetNotFoundException>(() => sut.DownloadAsync(AppId.Id, assetId, assetFileVersion, null, stream, default, CancellationToken));
A.CallTo(() => assetStore.DownloadAsync(A<string>._, stream, default, ct)) A.CallTo(() => assetStore.DownloadAsync(A<string>._, stream, default, CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -169,12 +164,12 @@ public class DefaultAssetFileStoreTests
var stream = new MemoryStream(); var stream = new MemoryStream();
A.CallTo(() => assetStore.DownloadAsync(A<string>.That.Matches(x => x != fileName), stream, default, ct)) A.CallTo(() => assetStore.DownloadAsync(A<string>.That.Matches(x => x != fileName), stream, default, CancellationToken))
.Throws(new AssetNotFoundException(assetId.ToString())).Once(); .Throws(new AssetNotFoundException(assetId.ToString())).Once();
await sut.DownloadAsync(appId, assetId, assetFileVersion, suffix, stream, default, ct); await sut.DownloadAsync(AppId.Id, assetId, assetFileVersion, suffix, stream, default, CancellationToken);
A.CallTo(() => assetStore.DownloadAsync(fullName, stream, default, ct)) A.CallTo(() => assetStore.DownloadAsync(fullName, stream, default, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -186,30 +181,30 @@ public class DefaultAssetFileStoreTests
options.FolderPerApp = folderPerApp; options.FolderPerApp = folderPerApp;
await sut.CopyAsync("Temp", appId, assetId, assetFileVersion, suffix, ct); await sut.CopyAsync("Temp", AppId.Id, assetId, assetFileVersion, suffix, CancellationToken);
A.CallTo(() => assetStore.CopyAsync("Temp", fullName, ct)) A.CallTo(() => assetStore.CopyAsync("Temp", fullName, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_delete_temporary_file_from_store() public async Task Should_delete_temporary_file_from_store()
{ {
await sut.DeleteAsync("Temp", ct); await sut.DeleteAsync("Temp", CancellationToken);
A.CallTo(() => assetStore.DeleteAsync("Temp", ct)) A.CallTo(() => assetStore.DeleteAsync("Temp", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_delete_file_from_store() public async Task Should_delete_file_from_store()
{ {
await sut.DeleteAsync(appId, assetId, ct); await sut.DeleteAsync(AppId.Id, assetId, CancellationToken);
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}_{assetId}", ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}_{assetId}", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync(assetId.ToString(), ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync(assetId.ToString(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -218,9 +213,9 @@ public class DefaultAssetFileStoreTests
{ {
options.FolderPerApp = true; options.FolderPerApp = true;
await sut.DeleteAsync(appId, assetId, ct); await sut.DeleteAsync(AppId.Id, assetId, CancellationToken);
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}/{assetId}", ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}/{assetId}", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -230,23 +225,21 @@ public class DefaultAssetFileStoreTests
var asset1 = new AssetEntity { Id = DomainId.NewGuid() }; var asset1 = new AssetEntity { Id = DomainId.NewGuid() };
var asset2 = new AssetEntity { Id = DomainId.NewGuid() }; var asset2 = new AssetEntity { Id = DomainId.NewGuid() };
A.CallTo(() => assetRepository.StreamAll(appId, ct)) A.CallTo(() => assetRepository.StreamAll(AppId.Id, CancellationToken))
.Returns(new[] { asset1, asset2 }.ToAsyncEnumerable()); .Returns(new[] { asset1, asset2 }.ToAsyncEnumerable());
var app = Mocks.App(NamedId.Of(appId, "my-app")); await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
await ((IDeleter)sut).DeleteAppAsync(app, ct);
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}_{asset1.Id}", ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}_{asset1.Id}", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}_{asset2.Id}", ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}_{asset2.Id}", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync(asset1.Id.ToString(), ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync(asset1.Id.ToString(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync(asset2.Id.ToString(), ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync(asset2.Id.ToString(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -255,18 +248,16 @@ public class DefaultAssetFileStoreTests
{ {
options.FolderPerApp = true; options.FolderPerApp = true;
var app = Mocks.App(NamedId.Of(appId, "my-app")); await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
await ((IDeleter)sut).DeleteAppAsync(app, ct);
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}/", ct)) A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}/", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
private string GetFullName(string fileName) private string GetFullName(string fileName)
{ {
return fileName return fileName
.Replace("{appId}", appId.ToString(), StringComparison.Ordinal) .Replace("{appId}", AppId.Id.ToString(), StringComparison.Ordinal)
.Replace("{assetId}", assetId.ToString(), StringComparison.Ordinal) .Replace("{assetId}", assetId.ToString(), StringComparison.Ordinal)
.Replace("{assetFileVersion}", assetFileVersion.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); .Replace("{assetFileVersion}", assetFileVersion.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
} }

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

@ -7,6 +7,7 @@
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.Queries;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -15,18 +16,14 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject;
public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.State> public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.State>
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>(); private readonly IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>();
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>(); private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>(); private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAssetMetadataSource assetMetadataSource = A.Fake<IAssetMetadataSource>(); private readonly IAssetMetadataSource assetMetadataSource = A.Fake<IAssetMetadataSource>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly DomainId assetId = DomainId.NewGuid(); private readonly DomainId assetId = DomainId.NewGuid();
private readonly AssetFile file = new NoopAssetFile(); private readonly AssetFile file = new NoopAssetFile();
private readonly Context requestContext;
private readonly AssetCommandMiddleware sut; private readonly AssetCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand public sealed class MyCommand : SquidexCommand
@ -35,21 +32,14 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
protected override DomainId Id protected override DomainId Id
{ {
get => DomainId.Combine(AppId, assetId); get => DomainId.Combine(AppId.Id, assetId);
} }
public AssetCommandMiddlewareTests() public AssetCommandMiddlewareTests()
{ {
ct = cts.Token;
file = new NoopAssetFile(); file = new NoopAssetFile();
requestContext = Context.Anonymous(Mocks.App(AppNamedId)); A.CallTo(() => assetQuery.FindByHashAsync(A<Context>._, A<string>._, A<string>._, A<long>._, CancellationToken))
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
A.CallTo(() => assetQuery.FindByHashAsync(A<Context>._, A<string>._, A<string>._, A<long>._, ct))
.Returns(Task.FromResult<IEnrichedAssetEntity?>(null)); .Returns(Task.FromResult<IEnrichedAssetEntity?>(null));
sut = new AssetCommandMiddleware( sut = new AssetCommandMiddleware(
@ -58,7 +48,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
assetEnricher, assetEnricher,
assetFileStore, assetFileStore,
assetQuery, assetQuery,
contextProvider, new[] { assetMetadataSource }); ApiContextProvider, new[] { assetMetadataSource });
} }
[Fact] [Fact]
@ -66,7 +56,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
{ {
await HandleAsync(new AnnotateAsset(), 12); await HandleAsync(new AnnotateAsset(), 12);
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, requestContext, A<CancellationToken>._)) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, ApiContext, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -79,7 +69,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
await HandleAsync(new AnnotateAsset(), await HandleAsync(new AnnotateAsset(),
actual); actual);
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, requestContext, A<CancellationToken>._)) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, ApiContext, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -90,7 +80,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
var enriched = new AssetEntity(); var enriched = new AssetEntity();
A.CallTo(() => assetEnricher.EnrichAsync(actual, requestContext, ct)) A.CallTo(() => assetEnricher.EnrichAsync(actual, ApiContext, CancellationToken))
.Returns(enriched); .Returns(enriched);
var context = var context =
@ -130,7 +120,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
{ {
var actual = CreateAsset(); var actual = CreateAsset();
SetupSameHashAsset(file.FileName, file.FileSize, out _); SetupSameHashAsset(file.FileName, file.FileSize, out var unused);
var context = var context =
await HandleAsync(new CreateAsset { File = file, Duplicate = true }, await HandleAsync(new CreateAsset { File = file, Duplicate = true },
@ -184,7 +174,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
{ {
var actual = CreateAsset(); var actual = CreateAsset();
SetupSameHashAsset(file.FileName, file.FileSize, out _); SetupSameHashAsset(file.FileName, file.FileSize, out var unused);
var context = var context =
await HandleAsync(new UpsertAsset { File = file }, await HandleAsync(new UpsertAsset { File = file },
@ -217,13 +207,13 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
private void AssertAssetHasBeenUploaded(long fileVersion) private void AssertAssetHasBeenUploaded(long fileVersion)
{ {
A.CallTo(() => assetFileStore.UploadAsync(A<string>._, A<HasherStream>._, ct)) A.CallTo(() => assetFileStore.UploadAsync(A<string>._, A<HasherStream>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetFileStore.CopyAsync(A<string>._, AppId, assetId, fileVersion, null, ct)) A.CallTo(() => assetFileStore.CopyAsync(A<string>._, AppId.Id, assetId, fileVersion, null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => assetFileStore.DeleteAsync(A<string>._, ct)) A.CallTo(() => assetFileStore.DeleteAsync(A<string>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -235,13 +225,13 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
FileSize = fileSize FileSize = fileSize
}; };
A.CallTo(() => assetQuery.FindByHashAsync(requestContext, A<string>._, fileName, fileSize, ct)) A.CallTo(() => assetQuery.FindByHashAsync(ApiContext, A<string>._, fileName, fileSize, CancellationToken))
.Returns(duplicate); .Returns(duplicate);
} }
private void AssertMetadataEnriched() private void AssertMetadataEnriched()
{ {
A.CallTo(() => assetMetadataSource.EnhanceAsync(A<UploadAssetCommand>._, ct)) A.CallTo(() => assetMetadataSource.EnhanceAsync(A<UploadAssetCommand>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -253,17 +243,17 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
var domainObject = A.Fake<AssetDomainObject>(); var domainObject = A.Fake<AssetDomainObject>();
A.CallTo(() => domainObject.ExecuteAsync(A<IAggregateCommand>._, ct)) A.CallTo(() => domainObject.ExecuteAsync(A<IAggregateCommand>._, CancellationToken))
.Returns(new CommandResult(command.AggregateId, 1, 0, actual)); .Returns(new CommandResult(command.AggregateId, 1, 0, actual));
A.CallTo(() => domainObjectFactory.Create<AssetDomainObject>(command.AggregateId)) A.CallTo(() => domainObjectFactory.Create<AssetDomainObject>(command.AggregateId))
.Returns(domainObject); .Returns(domainObject);
return HandleAsync(sut, command, ct); return HandleAsync(sut, command, CancellationToken);
} }
private IAssetEntity CreateAsset(long fileVersion = 0) private IAssetEntity CreateAsset(long fileVersion = 0)
{ {
return new AssetEntity { AppId = AppNamedId, Id = assetId, FileVersion = fileVersion }; return new AssetEntity { AppId = AppId, Id = assetId, FileVersion = fileVersion };
} }
} }

52
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetDomainObjectTests.cs

@ -11,7 +11,6 @@ using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
@ -23,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject;
public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State> public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
{ {
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>(); private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
@ -41,8 +38,6 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
public AssetDomainObjectTests() public AssetDomainObjectTests()
{ {
app = Mocks.App(AppNamedId, Language.DE);
var scripts = new AssetScripts var scripts = new AssetScripts
{ {
Annotate = "<annotate-script>", Annotate = "<annotate-script>",
@ -52,23 +47,20 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
Update = "<update-script>" Update = "<update-script>"
}; };
A.CallTo(() => app.AssetScripts) A.CallTo(() => App.AssetScripts)
.Returns(scripts); .Returns(scripts);
A.CallTo(() => appProvider.GetAppAsync(AppId, false, default)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A<CancellationToken>._))
.Returns(app);
A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId, parentId, A<CancellationToken>._))
.Returns(new List<IAssetFolderEntity> { A.Fake<IAssetFolderEntity>() }); .Returns(new List<IAssetFolderEntity> { A.Fake<IAssetFolderEntity>() });
A.CallTo(() => tagService.GetTagIdsAsync(AppId, TagGroups.Assets, A<HashSet<string>>._, default)) A.CallTo(() => tagService.GetTagIdsAsync(AppId.Id, TagGroups.Assets, A<HashSet<string>>._, default))
.ReturnsLazily(x => Task.FromResult(x.GetArgument<HashSet<string>>(2)?.ToDictionary(x => x) ?? new Dictionary<string, string>())); .ReturnsLazily(x => Task.FromResult(x.GetArgument<HashSet<string>>(2)?.ToDictionary(x => x) ?? new Dictionary<string, string>()));
var log = A.Fake<ILogger<AssetDomainObject>>(); var log = A.Fake<ILogger<AssetDomainObject>>();
var serviceProvider = var serviceProvider =
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(AppProvider)
.AddSingleton(assetQuery) .AddSingleton(assetQuery)
.AddSingleton(contentRepository) .AddSingleton(contentRepository)
.AddSingleton(log) .AddSingleton(log)
@ -117,7 +109,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
}) })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -170,7 +162,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
}) })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -200,7 +192,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
}) })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -230,7 +222,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
}) })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -252,7 +244,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetAnnotated { FileName = command.FileName }) CreateAssetEvent(new AssetAnnotated { FileName = command.FileName })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -274,7 +266,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetAnnotated { Slug = command.Slug }) CreateAssetEvent(new AssetAnnotated { Slug = command.Slug })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -296,7 +288,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetAnnotated { IsProtected = command.IsProtected }) CreateAssetEvent(new AssetAnnotated { IsProtected = command.IsProtected })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -318,7 +310,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetAnnotated { Metadata = command.Metadata }) CreateAssetEvent(new AssetAnnotated { Metadata = command.Metadata })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<anootate-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<anootate-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -338,7 +330,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetAnnotated { Tags = new HashSet<string> { "tag1" } }) CreateAssetEvent(new AssetAnnotated { Tags = new HashSet<string> { "tag1" } })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<annotate-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -360,7 +352,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetMoved { ParentId = parentId }) CreateAssetEvent(new AssetMoved { ParentId = parentId })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<move-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<move-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -383,7 +375,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
CreateAssetEvent(new AssetDeleted { DeletedSize = 2048, OldTags = new HashSet<string>() }) CreateAssetEvent(new AssetDeleted { DeletedSize = 2048, OldTags = new HashSet<string>() })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -394,7 +386,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
await ExecuteCreateAsync(); await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, Id, SearchScope.All, A<CancellationToken>._))
.Returns(false); .Returns(false);
var actual = await PublishAsync(command); var actual = await PublishAsync(command);
@ -404,7 +396,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version); Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version);
Assert.Empty(LastEvents); Assert.Empty(LastEvents);
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -415,12 +407,12 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
await ExecuteCreateAsync(); await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, Id, SearchScope.All, A<CancellationToken>._))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command)); await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -431,12 +423,12 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
await ExecuteCreateAsync(); await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, Id, SearchScope.All, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, Id, SearchScope.All, A<CancellationToken>._))
.Returns(true); .Returns(true);
await PublishAsync(command); await PublishAsync(command);
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -481,7 +473,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
private async Task<object> PublishAsync(AssetCommand command) private async Task<object> PublishAsync(AssetCommand command)
{ {
var actual = await sut.ExecuteAsync(CreateAssetCommand(command), default); var actual = await sut.ExecuteAsync(CreateAssetCommand(command), CancellationToken);
return actual.Payload; return actual.Payload;
} }

14
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetFolderDomainObjectTests.cs

@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
@ -18,8 +17,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject;
public class AssetFolderDomainObjectTests : HandlerTestBase<AssetFolderDomainObject.State> public class AssetFolderDomainObjectTests : HandlerTestBase<AssetFolderDomainObject.State>
{ {
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly DomainId parentId = DomainId.NewGuid(); private readonly DomainId parentId = DomainId.NewGuid();
@ -33,19 +30,14 @@ public class AssetFolderDomainObjectTests : HandlerTestBase<AssetFolderDomainObj
public AssetFolderDomainObjectTests() public AssetFolderDomainObjectTests()
{ {
app = Mocks.App(AppNamedId, Language.DE); A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A<CancellationToken>._))
A.CallTo(() => appProvider.GetAppAsync(AppId, false, default))
.Returns(app);
A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId, parentId, A<CancellationToken>._))
.Returns(new List<IAssetFolderEntity> { A.Fake<IAssetFolderEntity>() }); .Returns(new List<IAssetFolderEntity> { A.Fake<IAssetFolderEntity>() });
var log = A.Fake<ILogger<AssetFolderDomainObject>>(); var log = A.Fake<ILogger<AssetFolderDomainObject>>();
var serviceProvider = var serviceProvider =
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(AppProvider)
.AddSingleton(assetQuery) .AddSingleton(assetQuery)
.AddSingleton(contentRepository) .AddSingleton(contentRepository)
.AddSingleton(log) .AddSingleton(log)
@ -175,7 +167,7 @@ public class AssetFolderDomainObjectTests : HandlerTestBase<AssetFolderDomainObj
private async Task<object> PublishAsync(AssetFolderCommand command) private async Task<object> PublishAsync(AssetFolderCommand command)
{ {
var actual = await sut.ExecuteAsync(CreateAssetFolderCommand(command), default); var actual = await sut.ExecuteAsync(CreateAssetFolderCommand(command), CancellationToken);
return actual.Payload; return actual.Payload;
} }

28
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetsBulkUpdateCommandMiddlewareTests.cs

@ -5,30 +5,23 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject;
public class AssetsBulkUpdateCommandMiddlewareTests public class AssetsBulkUpdateCommandMiddlewareTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>(); private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly ICommandBus commandBus = A.Dummy<ICommandBus>(); private readonly ICommandBus commandBus = A.Dummy<ICommandBus>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AssetsBulkUpdateCommandMiddleware sut; private readonly AssetsBulkUpdateCommandMiddleware sut;
public AssetsBulkUpdateCommandMiddlewareTests() public AssetsBulkUpdateCommandMiddlewareTests()
{ {
ct = cts.Token;
var log = A.Fake<ILogger<AssetsBulkUpdateCommandMiddleware>>(); var log = A.Fake<ILogger<AssetsBulkUpdateCommandMiddleware>>();
sut = new AssetsBulkUpdateCommandMiddleware(contextProvider, log); sut = new AssetsBulkUpdateCommandMiddleware(contextProvider, log);
@ -68,7 +61,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
Assert.Single(actual); Assert.Single(actual);
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync(A<AnnotateAsset>.That.Matches(x => x.AssetId == id && x.FileName == "file"), ct)) A.CallTo(() => commandBus.PublishAsync(A<AnnotateAsset>.That.Matches(x => x.AssetId == id && x.FileName == "file"), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -104,7 +97,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
Assert.Single(actual); Assert.Single(actual);
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync(A<MoveAsset>.That.Matches(x => x.AssetId == id), ct)) A.CallTo(() => commandBus.PublishAsync(A<MoveAsset>.That.Matches(x => x.AssetId == id), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -141,7 +134,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<DeleteAsset>.That.Matches(x => x.AssetId == id), ct)) A<DeleteAsset>.That.Matches(x => x.AssetId == id), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -167,7 +160,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
{ {
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
return (context.PlainResult as BulkUpdateResult)!; return (context.PlainResult as BulkUpdateResult)!;
} }
@ -176,7 +169,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
{ {
return new BulkUpdateAssets return new BulkUpdateAssets
{ {
AppId = appId, AppId = AppId,
Jobs = new[] Jobs = new[]
{ {
new BulkUpdateJob new BulkUpdateJob
@ -191,14 +184,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
private Context SetupContext(string id) private Context SetupContext(string id)
{ {
var permission = PermissionIds.ForApp(id, appId.Name).Id; var requestContext = CreateContext(false, PermissionIds.ForApp(id, AppId.Name).Id);
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
var requestContext = new Context(claimsPrincipal, Mocks.App(appId));
A.CallTo(() => contextProvider.Context) A.CallTo(() => contextProvider.Context)
.Returns(requestContext); .Returns(requestContext);

27
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetFolderTests.cs

@ -15,10 +15,9 @@ using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards;
public class GuardAssetFolderTests : IClassFixture<TranslationsFixture> public class GuardAssetFolderTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RefToken actor = RefToken.User("123"); private readonly RefToken actor = RefToken.User("123");
[Fact] [Fact]
@ -45,10 +44,10 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAssetFolder()); var operation = Operation(CreateAssetFolder());
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.Returns(new List<IAssetFolderEntity>()); .Returns(new List<IAssetFolderEntity>());
await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId), await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken),
new ValidationError("Asset folder does not exist.", "ParentId")); new ValidationError("Asset folder does not exist.", "ParentId"));
} }
@ -59,10 +58,10 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAssetFolder()); var operation = Operation(CreateAssetFolder());
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.Returns(new List<IAssetFolderEntity> { CreateAssetFolder() }); .Returns(new List<IAssetFolderEntity> { CreateAssetFolder() });
await operation.MustMoveToValidFolder(parentId); await operation.MustMoveToValidFolder(parentId, CancellationToken);
} }
[Fact] [Fact]
@ -72,9 +71,9 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAssetFolder(default, parentId)); var operation = Operation(CreateAssetFolder(default, parentId));
await operation.MustMoveToValidFolder(parentId); await operation.MustMoveToValidFolder(parentId, CancellationToken);
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -85,9 +84,9 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAssetFolder()); var operation = Operation(CreateAssetFolder());
await operation.MustMoveToValidFolder(parentId); await operation.MustMoveToValidFolder(parentId, CancellationToken);
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, A<DomainId>._, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, A<DomainId>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -98,14 +97,14 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAssetFolder()); var operation = Operation(CreateAssetFolder());
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.Returns(new List<IAssetFolderEntity> .Returns(new List<IAssetFolderEntity>
{ {
CreateAssetFolder(operation.CommandId), CreateAssetFolder(operation.CommandId),
CreateAssetFolder(parentId, operation.CommandId) CreateAssetFolder(parentId, operation.CommandId)
}); });
await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId), await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken),
new ValidationError("Cannot add folder to its own child.", "ParentId")); new ValidationError("Cannot add folder to its own child.", "ParentId"));
} }
@ -123,7 +122,7 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
return new AssetFolderOperation(serviceProvider, () => assetFolder) return new AssetFolderOperation(serviceProvider, () => assetFolder)
{ {
App = Mocks.App(appId), App = App,
CommandId = assetFolder.Id, CommandId = assetFolder.Id,
Command = new CreateAssetFolder { User = currentUser, Actor = actor } Command = new CreateAssetFolder { User = currentUser, Actor = actor }
}; };
@ -136,7 +135,7 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
A.CallTo(() => assetFolder.Id) A.CallTo(() => assetFolder.Id)
.Returns(OrNew(id)); .Returns(OrNew(id));
A.CallTo(() => assetFolder.AppId) A.CallTo(() => assetFolder.AppId)
.Returns(appId); .Returns(AppId);
A.CallTo(() => assetFolder.ParentId) A.CallTo(() => assetFolder.ParentId)
.Returns(OrNew(parentId)); .Returns(OrNew(parentId));

38
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetTests.cs

@ -17,12 +17,10 @@ using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards;
public class GuardAssetTests : IClassFixture<TranslationsFixture> public class GuardAssetTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RefToken actor = RefToken.User("123");
[Fact] [Fact]
public async Task Should_throw_exception_if_moving_to_invalid_folder() public async Task Should_throw_exception_if_moving_to_invalid_folder()
@ -31,10 +29,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAsset()); var operation = Operation(CreateAsset());
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, default)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.Returns(new List<IAssetFolderEntity>()); .Returns(new List<IAssetFolderEntity>());
await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId), await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken),
new ValidationError("Asset folder does not exist.", "ParentId")); new ValidationError("Asset folder does not exist.", "ParentId"));
} }
@ -45,10 +43,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAsset()); var operation = Operation(CreateAsset());
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, default)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.Returns(new List<IAssetFolderEntity> { CreateAssetFolder() }); .Returns(new List<IAssetFolderEntity> { CreateAssetFolder() });
await operation.MustMoveToValidFolder(parentId); await operation.MustMoveToValidFolder(parentId, CancellationToken);
} }
[Fact] [Fact]
@ -58,9 +56,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAsset(default, parentId)); var operation = Operation(CreateAsset(default, parentId));
await operation.MustMoveToValidFolder(parentId); await operation.MustMoveToValidFolder(parentId, CancellationToken);
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -71,9 +69,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAsset(parentId)); var operation = Operation(CreateAsset(parentId));
await operation.MustMoveToValidFolder(parentId); await operation.MustMoveToValidFolder(parentId, CancellationToken);
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -82,10 +80,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
{ {
var operation = Operation(CreateAsset()); var operation = Operation(CreateAsset());
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, operation.CommandId, SearchScope.All, default)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync()); await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
} }
[Fact] [Fact]
@ -93,10 +91,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
{ {
var operation = Operation(CreateAsset()); var operation = Operation(CreateAsset());
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, operation.CommandId, SearchScope.All, default)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync()); await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
} }
private AssetOperation Operation(AssetEntity asset) private AssetOperation Operation(AssetEntity asset)
@ -114,9 +112,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
return new AssetOperation(serviceProvider, () => asset) return new AssetOperation(serviceProvider, () => asset)
{ {
App = Mocks.App(appId), App = App,
CommandId = asset.Id, CommandId = asset.Id,
Command = new CreateAsset { User = currentUser, Actor = actor } Command = new CreateAsset { User = currentUser, Actor = User }
}; };
} }
@ -125,9 +123,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
return new AssetEntity return new AssetEntity
{ {
Id = OrNew(id), Id = OrNew(id),
AppId = appId, AppId = AppId,
Created = default, Created = default,
CreatedBy = actor, CreatedBy = User,
ParentId = OrNew(parentId) ParentId = OrNew(parentId)
}; };
} }
@ -139,7 +137,7 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
A.CallTo(() => assetFolder.Id) A.CallTo(() => assetFolder.Id)
.Returns(OrNew(id)); .Returns(OrNew(id));
A.CallTo(() => assetFolder.AppId) A.CallTo(() => assetFolder.AppId)
.Returns(appId); .Returns(AppId);
A.CallTo(() => assetFolder.ParentId) A.CallTo(() => assetFolder.ParentId)
.Returns(OrNew(parentId)); .Returns(OrNew(parentId));

25
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/ScriptingExtensionsTests.cs

@ -17,11 +17,8 @@ using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards;
public sealed class ScriptingExtensionsTests public sealed class ScriptingExtensionsTests : GivenContext
{ {
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RefToken actor = RefToken.User("123");
[Fact] [Fact]
public async Task Should_add_tag_in_script() public async Task Should_add_tag_in_script()
{ {
@ -31,7 +28,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command); var operation = Operation(script, CreateAsset(), command);
await operation.ExecuteAnnotateScriptAsync(command); await operation.ExecuteAnnotateScriptAsync(command, CancellationToken);
Assert.Contains("tag", command.Tags); Assert.Contains("tag", command.Tags);
} }
@ -45,7 +42,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command); var operation = Operation(script, CreateAsset(), command);
await operation.ExecuteAnnotateScriptAsync(command); await operation.ExecuteAnnotateScriptAsync(command, CancellationToken);
Assert.Equal(JsonValue.Create(42), command.Metadata["foo"]); Assert.Equal(JsonValue.Create(42), command.Metadata["foo"]);
} }
@ -59,7 +56,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command); var operation = Operation(script, CreateAsset(), command);
await Assert.ThrowsAsync<ValidationException>(() => operation.ExecuteAnnotateScriptAsync(command)); await Assert.ThrowsAsync<ValidationException>(() => operation.ExecuteAnnotateScriptAsync(command, CancellationToken));
} }
[Fact] [Fact]
@ -71,7 +68,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command); var operation = Operation(script, CreateAsset(), command);
await Assert.ThrowsAsync<ValidationException>(() => operation.ExecuteAnnotateScriptAsync(command)); await Assert.ThrowsAsync<ValidationException>(() => operation.ExecuteAnnotateScriptAsync(command, CancellationToken));
} }
private AssetOperation Operation(string script, AssetEntity asset, AnnotateAsset command) private AssetOperation Operation(string script, AssetEntity asset, AnnotateAsset command)
@ -81,9 +78,7 @@ public sealed class ScriptingExtensionsTests
Annotate = script Annotate = script
}; };
var app = Mocks.App(appId); A.CallTo(() => App.AssetScripts)
A.CallTo(() => app.AssetScripts)
.Returns(scripts); .Returns(scripts);
var serviceProvider = var serviceProvider =
@ -94,12 +89,12 @@ public sealed class ScriptingExtensionsTests
.AddSingleton<IScriptEngine, JintScriptEngine>() .AddSingleton<IScriptEngine, JintScriptEngine>()
.BuildServiceProvider(); .BuildServiceProvider();
command.Actor = actor; command.Actor = User;
command.User = Mocks.FrontendUser(); command.User = Mocks.FrontendUser();
return new AssetOperation(serviceProvider, () => asset) return new AssetOperation(serviceProvider, () => asset)
{ {
App = app, App = App,
CommandId = asset.Id, CommandId = asset.Id,
Command = command Command = command
}; };
@ -110,9 +105,9 @@ public sealed class ScriptingExtensionsTests
return new AssetEntity return new AssetEntity
{ {
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
AppId = appId, AppId = AppId,
Created = default, Created = default,
CreatedBy = actor, CreatedBy = User,
Metadata = new AssetMetadata(), Metadata = new AssetMetadata(),
Tags = new HashSet<string>() Tags = new HashSet<string>()
}; };

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

@ -8,14 +8,13 @@
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class ImageAssetMetadataSourceTests public class ImageAssetMetadataSourceTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>(); private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly MemoryStream stream = new MemoryStream(); private readonly MemoryStream stream = new MemoryStream();
private readonly AssetFile file; private readonly AssetFile file;
@ -23,8 +22,6 @@ public class ImageAssetMetadataSourceTests
public ImageAssetMetadataSourceTests() public ImageAssetMetadataSourceTests()
{ {
ct = cts.Token;
file = new DelegateAssetFile("MyImage.png", "image/png", 1024, () => stream); file = new DelegateAssetFile("MyImage.png", "image/png", 1024, () => stream);
sut = new ImageAssetMetadataSource(assetThumbnailGenerator); sut = new ImageAssetMetadataSource(assetThumbnailGenerator);
@ -35,9 +32,9 @@ public class ImageAssetMetadataSourceTests
{ {
var command = new CreateAsset { File = file, Type = AssetType.Image }; var command = new CreateAsset { File = file, Type = AssetType.Image };
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, ct)) A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -46,7 +43,7 @@ public class ImageAssetMetadataSourceTests
{ {
var command = new CreateAsset { File = file }; var command = new CreateAsset { File = file };
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
Assert.Empty(command.Tags); Assert.Empty(command.Tags);
} }
@ -56,10 +53,10 @@ public class ImageAssetMetadataSourceTests
{ {
var command = new CreateAsset { File = file }; var command = new CreateAsset { File = file };
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, ct)) A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, CancellationToken))
.Returns(new ImageInfo(800, 600, ImageOrientation.None, ImageFormat.PNG)); .Returns(new ImageInfo(800, 600, ImageOrientation.None, ImageFormat.PNG));
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
Assert.Equal(800, command.Metadata.GetPixelWidth()); Assert.Equal(800, command.Metadata.GetPixelWidth());
Assert.Equal(600, command.Metadata.GetPixelHeight()); Assert.Equal(600, command.Metadata.GetPixelHeight());
@ -74,19 +71,19 @@ public class ImageAssetMetadataSourceTests
{ {
var command = new CreateAsset { File = file }; var command = new CreateAsset { File = file };
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, ct)) A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>._, file.MimeType, CancellationToken))
.Returns(new ImageInfo(800, 600, ImageOrientation.None, ImageFormat.PNG)); .Returns(new ImageInfo(800, 600, ImageOrientation.None, ImageFormat.PNG));
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, ct)) A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, CancellationToken))
.Returns(new ImageInfo(600, 800, ImageOrientation.BottomRight, ImageFormat.PNG)).Once(); .Returns(new ImageInfo(600, 800, ImageOrientation.BottomRight, ImageFormat.PNG)).Once();
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
Assert.Equal(800, command.Metadata.GetPixelWidth()); Assert.Equal(800, command.Metadata.GetPixelWidth());
Assert.Equal(600, command.Metadata.GetPixelHeight()); Assert.Equal(600, command.Metadata.GetPixelHeight());
Assert.Equal(AssetType.Image, command.Type); Assert.Equal(AssetType.Image, command.Type);
A.CallTo(() => assetThumbnailGenerator.FixOrientationAsync(stream, file.MimeType, A<Stream>._, ct)) A.CallTo(() => assetThumbnailGenerator.FixOrientationAsync(stream, file.MimeType, A<Stream>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -98,7 +95,7 @@ public class ImageAssetMetadataSourceTests
command.Metadata.SetPixelWidth(100); command.Metadata.SetPixelWidth(100);
command.Metadata.SetPixelWidth(100); command.Metadata.SetPixelWidth(100);
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
Assert.Contains("image", command.Tags); Assert.Contains("image", command.Tags);
Assert.Contains("image/small", command.Tags); Assert.Contains("image/small", command.Tags);
@ -112,7 +109,7 @@ public class ImageAssetMetadataSourceTests
command.Metadata.SetPixelWidth(800); command.Metadata.SetPixelWidth(800);
command.Metadata.SetPixelWidth(600); command.Metadata.SetPixelWidth(600);
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
Assert.Contains("image", command.Tags); Assert.Contains("image", command.Tags);
Assert.Contains("image/medium", command.Tags); Assert.Contains("image/medium", command.Tags);
@ -126,7 +123,7 @@ public class ImageAssetMetadataSourceTests
command.Metadata.SetPixelWidth(1200); command.Metadata.SetPixelWidth(1200);
command.Metadata.SetPixelWidth(1400); command.Metadata.SetPixelWidth(1400);
await sut.EnhanceAsync(command, ct); await sut.EnhanceAsync(command, CancellationToken);
Assert.Contains("image", command.Tags); Assert.Contains("image", command.Tags);
Assert.Contains("image/large", command.Tags); Assert.Contains("image/large", command.Tags);

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

@ -5,200 +5,65 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.Assets.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class AssetEnricherTests public class AssetEnricherTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly ITagService tagService = A.Fake<ITagService>();
private readonly IRequestCache requestCache = A.Fake<IRequestCache>();
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly IAssetMetadataSource assetMetadataSource1 = A.Fake<IAssetMetadataSource>();
private readonly IAssetMetadataSource assetMetadataSource2 = A.Fake<IAssetMetadataSource>();
private readonly IJsonSerializer serializer = A.Fake<IJsonSerializer>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly Context requestContext;
private readonly AssetEnricher sut;
public AssetEnricherTests()
{
ct = cts.Token;
var assetMetadataSources = new[]
{
assetMetadataSource1,
assetMetadataSource2
};
requestContext = Context.Anonymous(Mocks.App(appId));
sut = new AssetEnricher(tagService, assetMetadataSources, requestCache, urlGenerator, serializer);
}
[Fact] [Fact]
public async Task Should_not_enrich_if_asset_contains_null_tags() public async Task Should_only_invoke_pre_enrich_for_empty_assets()
{ {
var source = new AssetEntity { AppId = appId }; var assets = Array.Empty<IAssetEntity>();
var actual = await sut.EnrichAsync(source, requestContext, ct); var step1 = A.Fake<IAssetEnricherStep>();
var step2 = A.Fake<IAssetEnricherStep>();
Assert.Empty(actual.TagNames);
}
[Fact] var sut = new AssetEnricher(new[] { step1, step2 });
public async Task Should_enrich_with_cache_dependencies()
{
var source = new AssetEntity { AppId = appId, Id = DomainId.NewGuid(), Version = 13 };
var actual = await sut.EnrichAsync(source, requestContext, ct); await sut.EnrichAsync(assets, ApiContext, CancellationToken);
A.CallTo(() => requestCache.AddDependency(actual.UniqueId, actual.Version)) A.CallTo(() => step1.EnrichAsync(ApiContext, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
}
[Fact] A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken))
public async Task Should_enrich_asset_with_tag_names() .MustHaveHappened();
{
var source = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
A.CallTo(() => tagService.GetTagNamesAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Is("id1", "id2"), ct))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2"
});
var actual = await sut.EnrichAsync(source, requestContext, ct);
Assert.Equal(new HashSet<string> { "name1", "name2" }, actual.TagNames);
}
[Fact] A.CallTo(() => step1.EnrichAsync(ApiContext, A<IEnumerable<AssetEntity>>._, A<CancellationToken>._))
public async Task Should_not_enrich_asset_with_tag_names_if_disabled() .MustNotHaveHappened();
{
var source = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
var actual = await sut.EnrichAsync(source, requestContext.Clone(b => b.WithoutAssetEnrichment()), ct);
Assert.Null(actual.TagNames);
}
[Fact] A.CallTo(() => step2.EnrichAsync(ApiContext, A<IEnumerable<AssetEntity>>._, A<CancellationToken>._))
public async Task Should_enrich_asset_with_metadata() .MustNotHaveHappened();
{
var source = new AssetEntity
{
FileSize = 2 * 1024,
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
A.CallTo(() => assetMetadataSource1.Format(A<IAssetEntity>._))
.Returns(new[] { "metadata1" });
A.CallTo(() => assetMetadataSource2.Format(A<IAssetEntity>._))
.Returns(new[] { "metadata2", "metadata3" });
var actual = await sut.EnrichAsync(source, requestContext, ct);
Assert.Equal("metadata1, metadata2, metadata3, 2 kB", actual.MetadataText);
} }
[Fact] [Fact]
public async Task Should_enrich_multiple_assets_with_tag_names() public async Task Should_invoke_steps()
{ {
var source1 = new AssetEntity var source = CreateAsset();
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
var source2 = new AssetEntity
{
Tags = new HashSet<string>
{
"id2",
"id3"
},
AppId = appId
};
A.CallTo(() => tagService.GetTagNamesAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Is("id1", "id2", "id3"), ct))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2",
["id3"] = "name3"
});
var actual = await sut.EnrichAsync(new[] { source1, source2 }, requestContext, ct);
Assert.Equal(new HashSet<string> { "name1", "name2" }, actual[0].TagNames);
Assert.Equal(new HashSet<string> { "name2", "name3" }, actual[1].TagNames);
}
[Fact] var step1 = A.Fake<IAssetEnricherStep>();
public async Task Should_also_compute_ui_tokens_for_frontend() var step2 = A.Fake<IAssetEnricherStep>();
{
var source = new AssetEntity
{
AppId = appId
};
var actual = await sut.EnrichAsync(new[] { source }, new Context(Mocks.FrontendUser(), Mocks.App(appId)), ct); var sut = new AssetEnricher(new[] { step1, step2 });
Assert.NotNull(actual[0].EditToken); await sut.EnrichAsync(source, ApiContext, CancellationToken);
A.CallTo(() => urlGenerator.Root()) A.CallTo(() => step1.EnrichAsync(ApiContext, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
}
[Fact] A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken))
public async Task Should_compute_ui_tokens() .MustHaveHappened();
{
var source = new AssetEntity
{
AppId = appId
};
var actual = await sut.EnrichAsync(new[] { source }, requestContext, ct);
Assert.NotNull(actual[0].EditToken); A.CallTo(() => step1.EnrichAsync(ApiContext, A<IEnumerable<AssetEntity>>._, CancellationToken))
.MustHaveHappened();
A.CallTo(() => urlGenerator.Root()) A.CallTo(() => step2.EnrichAsync(ApiContext, A<IEnumerable<AssetEntity>>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
private AssetEntity CreateAsset()
{
return new AssetEntity { Id = DomainId.NewGuid(), AppId = AppId };
}
} }

36
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetLoaderTests.cs

@ -6,30 +6,26 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Assets.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class AssetLoaderTests public class AssetLoaderTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>(); private readonly IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>();
private readonly AssetDomainObject domainObject = A.Fake<AssetDomainObject>(); private readonly AssetDomainObject domainObject = A.Fake<AssetDomainObject>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DomainId id = DomainId.NewGuid(); private readonly DomainId id = DomainId.NewGuid();
private readonly DomainId uniqueId; private readonly DomainId uniqueId;
private readonly AssetLoader sut; private readonly AssetLoader sut;
public AssetLoaderTests() public AssetLoaderTests()
{ {
ct = cts.Token; uniqueId = DomainId.Combine(AppId.Id, id);
uniqueId = DomainId.Combine(appId, id); A.CallTo(() => domainObjectCache.GetAsync<AssetDomainObject.State>(A<DomainId>._, A<long>._, CancellationToken))
A.CallTo(() => domainObjectCache.GetAsync<AssetDomainObject.State>(A<DomainId>._, A<long>._, ct))
.Returns(Task.FromResult<AssetDomainObject.State>(null!)); .Returns(Task.FromResult<AssetDomainObject.State>(null!));
A.CallTo(() => domainObjectFactory.Create<AssetDomainObject>(uniqueId)) A.CallTo(() => domainObjectFactory.Create<AssetDomainObject>(uniqueId))
@ -43,10 +39,10 @@ public class AssetLoaderTests
{ {
var asset = (AssetDomainObject.State)null!; var asset = (AssetDomainObject.State)null!;
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct)) A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset); .Returns(asset);
Assert.Null(await sut.GetAsync(appId, id, 10, ct)); Assert.Null(await sut.GetAsync(AppId.Id, id, 10, CancellationToken));
} }
[Fact] [Fact]
@ -54,10 +50,10 @@ public class AssetLoaderTests
{ {
var asset = new AssetDomainObject.State { Version = EtagVersion.Empty }; var asset = new AssetDomainObject.State { Version = EtagVersion.Empty };
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct)) A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset); .Returns(asset);
Assert.Null(await sut.GetAsync(appId, id, 10, ct)); Assert.Null(await sut.GetAsync(AppId.Id, id, 10, CancellationToken));
} }
[Fact] [Fact]
@ -65,10 +61,10 @@ public class AssetLoaderTests
{ {
var asset = new AssetDomainObject.State { Version = 5 }; var asset = new AssetDomainObject.State { Version = 5 };
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct)) A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset); .Returns(asset);
Assert.Null(await sut.GetAsync(appId, id, 10, ct)); Assert.Null(await sut.GetAsync(AppId.Id, id, 10, CancellationToken));
} }
[Fact] [Fact]
@ -76,10 +72,10 @@ public class AssetLoaderTests
{ {
var asset = new AssetDomainObject.State { Version = 5 }; var asset = new AssetDomainObject.State { Version = 5 };
A.CallTo(() => domainObject.GetSnapshotAsync(EtagVersion.Any, ct)) A.CallTo(() => domainObject.GetSnapshotAsync(EtagVersion.Any, CancellationToken))
.Returns(asset); .Returns(asset);
var actual = await sut.GetAsync(appId, id, EtagVersion.Any, ct); var actual = await sut.GetAsync(AppId.Id, id, EtagVersion.Any, CancellationToken);
Assert.Same(asset, actual); Assert.Same(asset, actual);
} }
@ -89,10 +85,10 @@ public class AssetLoaderTests
{ {
var asset = new AssetDomainObject.State { Version = 10 }; var asset = new AssetDomainObject.State { Version = 10 };
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct)) A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset); .Returns(asset);
var actual = await sut.GetAsync(appId, id, 10, ct); var actual = await sut.GetAsync(AppId.Id, id, 10, CancellationToken);
Assert.Same(asset, actual); Assert.Same(asset, actual);
} }
@ -102,10 +98,10 @@ public class AssetLoaderTests
{ {
var content = new AssetDomainObject.State { Version = 10 }; var content = new AssetDomainObject.State { Version = 10 };
A.CallTo(() => domainObjectCache.GetAsync<AssetDomainObject.State>(DomainId.Combine(appId, id), 10, ct)) A.CallTo(() => domainObjectCache.GetAsync<AssetDomainObject.State>(DomainId.Combine(AppId.Id, id), 10, CancellationToken))
.Returns(content); .Returns(content);
var actual = await sut.GetAsync(appId, id, 10, ct); var actual = await sut.GetAsync(AppId.Id, id, 10, CancellationToken);
Assert.Same(content, actual); Assert.Same(content, actual);

43
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryParserTests.cs

@ -9,23 +9,18 @@ using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class AssetQueryParserTests public class AssetQueryParserTests : GivenContext
{ {
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly Context requestContext;
private readonly AssetQueryParser sut; private readonly AssetQueryParser sut;
public AssetQueryParserTests() public AssetQueryParserTests()
{ {
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId));
var options = Options.Create(new AssetOptions { DefaultPageSize = 30 }); var options = Options.Create(new AssetOptions { DefaultPageSize = 30 });
sut = new AssetQueryParser(TestUtils.DefaultSerializer, tagService, options); sut = new AssetQueryParser(TestUtils.DefaultSerializer, tagService, options);
@ -34,7 +29,7 @@ public class AssetQueryParserTests
[Fact] [Fact]
public async Task Should_skip_total_if_set_in_context() public async Task Should_skip_total_if_set_in_context()
{ {
var q = await sut.ParseAsync(requestContext.Clone(b => b.WithoutTotal()), Q.Empty); var q = await sut.ParseAsync(ApiContext.Clone(b => b.WithoutTotal()), Q.Empty, CancellationToken);
Assert.True(q.NoTotal); Assert.True(q.NoTotal);
} }
@ -44,7 +39,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithODataQuery("$filter=invalid"); var query = Q.Empty.WithODataQuery("$filter=invalid");
await Assert.ThrowsAsync<ValidationException>(() => sut.ParseAsync(requestContext, query)); await Assert.ThrowsAsync<ValidationException>(() => sut.ParseAsync(ApiContext, query, CancellationToken));
} }
[Fact] [Fact]
@ -52,7 +47,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithJsonQuery("invalid"); var query = Q.Empty.WithJsonQuery("invalid");
await Assert.ThrowsAsync<ValidationException>(() => sut.ParseAsync(requestContext, query)); await Assert.ThrowsAsync<ValidationException>(() => sut.ParseAsync(ApiContext, query, CancellationToken));
} }
[Fact] [Fact]
@ -60,7 +55,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World"); var query = Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending, id Ascending", q.Query.ToString()); Assert.Equal("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending, id Ascending", q.Query.ToString());
} }
@ -70,7 +65,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithODataQuery("$top=200&$filter=fileName eq 'ABC'"); var query = Q.Empty.WithODataQuery("$top=200&$filter=fileName eq 'ABC'");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -80,7 +75,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithJsonQuery("{ \"filter\": { \"path\": \"fileName\", \"op\": \"eq\", \"value\": \"ABC\" } }"); var query = Q.Empty.WithJsonQuery("{ \"filter\": { \"path\": \"fileName\", \"op\": \"eq\", \"value\": \"ABC\" } }");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Filter: fileName == 'ABC'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Filter: fileName == 'ABC'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -90,7 +85,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithJsonQuery("{ \"fullText\": \"Hello\" }"); var query = Q.Empty.WithJsonQuery("{ \"fullText\": \"Hello\" }");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("FullText: 'Hello'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("FullText: 'Hello'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -104,7 +99,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithQuery(new ClrQuery { Take = take }); var query = Q.Empty.WithQuery(new ClrQuery { Take = take });
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -114,7 +109,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithIds("1, 2, 3"); var query = Q.Empty.WithIds("1, 2, 3");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Take: 3; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Take: 3; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -124,7 +119,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithIds("1, 2, 3").WithQuery(new ClrQuery { Take = 20 }); var query = Q.Empty.WithIds("1, 2, 3").WithQuery(new ClrQuery { Take = 20 });
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Take: 20; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Take: 20; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -134,7 +129,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithODataQuery("$top=300&$skip=20"); var query = Q.Empty.WithODataQuery("$top=300&$skip=20");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Skip: 20; Take: 200; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Skip: 20; Take: 200; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -144,7 +139,7 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithODataQuery("$top=300&$skip=20&$orderby=id desc"); var query = Q.Empty.WithODataQuery("$top=300&$skip=20&$orderby=id desc");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Skip: 20; Take: 200; Sort: id Descending", q.Query.ToString()); Assert.Equal("Skip: 20; Take: 200; Sort: id Descending", q.Query.ToString());
} }
@ -152,12 +147,12 @@ public class AssetQueryParserTests
[Fact] [Fact]
public async Task Should_denormalize_tags() public async Task Should_denormalize_tags()
{ {
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"), default)) A.CallTo(() => tagService.GetTagIdsAsync(AppId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"), CancellationToken))
.Returns(new Dictionary<string, string> { ["name1"] = "id1" }); .Returns(new Dictionary<string, string> { ["name1"] = "id1" });
var query = Q.Empty.WithODataQuery("$filter=tags eq 'name1'"); var query = Q.Empty.WithODataQuery("$filter=tags eq 'name1'");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Filter: tags == 'id1'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Filter: tags == 'id1'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -165,12 +160,12 @@ public class AssetQueryParserTests
[Fact] [Fact]
public async Task Should_not_fail_if_tags_not_found() public async Task Should_not_fail_if_tags_not_found()
{ {
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"), default)) A.CallTo(() => tagService.GetTagIdsAsync(AppId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"), CancellationToken))
.Returns(new Dictionary<string, string>()); .Returns(new Dictionary<string, string>());
var query = Q.Empty.WithODataQuery("$filter=tags eq 'name1'"); var query = Q.Empty.WithODataQuery("$filter=tags eq 'name1'");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Filter: tags == 'name1'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Filter: tags == 'name1'; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString());
} }
@ -180,11 +175,11 @@ public class AssetQueryParserTests
{ {
var query = Q.Empty.WithODataQuery("$filter=fileSize eq 123"); var query = Q.Empty.WithODataQuery("$filter=fileSize eq 123");
var q = await sut.ParseAsync(requestContext, query); var q = await sut.ParseAsync(ApiContext, query, CancellationToken);
Assert.Equal("Filter: fileSize == 123; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString()); Assert.Equal("Filter: fileSize == 123; Take: 30; Sort: lastModified Descending, id Ascending", q.Query.ToString());
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, A<string>._, A<HashSet<string>>._, A<CancellationToken>._)) A.CallTo(() => tagService.GetTagIdsAsync(AppId.Id, A<string>._, A<HashSet<string>>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
} }

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

@ -13,28 +13,20 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets.Queries; namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class AssetQueryServiceTests public class AssetQueryServiceTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>(); private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>(); private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>();
private readonly IAssetFolderRepository assetFolderRepository = A.Fake<IAssetFolderRepository>(); private readonly IAssetFolderRepository assetFolderRepository = A.Fake<IAssetFolderRepository>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly Context requestContext;
private readonly AssetQueryParser queryParser = A.Fake<AssetQueryParser>(); private readonly AssetQueryParser queryParser = A.Fake<AssetQueryParser>();
private readonly AssetQueryService sut; private readonly AssetQueryService sut;
public AssetQueryServiceTests() public AssetQueryServiceTests()
{ {
ct = cts.Token;
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId));
SetupEnricher(); SetupEnricher();
A.CallTo(() => queryParser.ParseAsync(requestContext, A<Q>._, ct)) A.CallTo(() => queryParser.ParseAsync(ApiContext, A<Q>._, CancellationToken))
.ReturnsLazily(c => Task.FromResult(c.GetArgument<Q>(1)!)); .ReturnsLazily(c => Task.FromResult(c.GetArgument<Q>(1)!));
var options = Options.Create(new AssetOptions()); var options = Options.Create(new AssetOptions());
@ -53,10 +45,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetBySlugAsync(appId.Id, "slug", A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetBySlugAsync(AppId.Id, "slug", A<CancellationToken>._))
.Returns(asset); .Returns(asset);
var actual = await sut.FindBySlugAsync(requestContext, "slug", ct); var actual = await sut.FindBySlugAsync(ApiContext, "slug", CancellationToken);
AssertAsset(asset, actual); AssertAsset(asset, actual);
} }
@ -66,10 +58,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetBySlugAsync(appId.Id, "slug", A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetBySlugAsync(AppId.Id, "slug", A<CancellationToken>._))
.Returns(Task.FromResult<IAssetEntity?>(null)); .Returns(Task.FromResult<IAssetEntity?>(null));
var actual = await sut.FindBySlugAsync(requestContext, "slug", ct); var actual = await sut.FindBySlugAsync(ApiContext, "slug", CancellationToken);
Assert.Null(actual); Assert.Null(actual);
} }
@ -79,10 +71,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, asset.Id, A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetAsync(AppId.Id, asset.Id, A<CancellationToken>._))
.Returns(asset); .Returns(asset);
var actual = await sut.FindAsync(requestContext, asset.Id, ct: ct); var actual = await sut.FindAsync(ApiContext, asset.Id, ct: CancellationToken);
AssertAsset(asset, actual); AssertAsset(asset, actual);
} }
@ -92,10 +84,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, asset.Id, A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetAsync(AppId.Id, asset.Id, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetEntity?>(null)); .Returns(Task.FromResult<IAssetEntity?>(null));
var actual = await sut.FindAsync(requestContext, asset.Id, ct: ct); var actual = await sut.FindAsync(ApiContext, asset.Id, ct: CancellationToken);
Assert.Null(actual); Assert.Null(actual);
} }
@ -105,10 +97,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetLoader.GetAsync(appId.Id, asset.Id, 2, A<CancellationToken>._)) A.CallTo(() => assetLoader.GetAsync(AppId.Id, asset.Id, 2, A<CancellationToken>._))
.Returns(asset); .Returns(asset);
var actual = await sut.FindAsync(requestContext, asset.Id, 2, ct); var actual = await sut.FindAsync(ApiContext, asset.Id, 2, CancellationToken);
AssertAsset(asset, actual); AssertAsset(asset, actual);
} }
@ -118,10 +110,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetLoader.GetAsync(appId.Id, asset.Id, 2, A<CancellationToken>._)) A.CallTo(() => assetLoader.GetAsync(AppId.Id, asset.Id, 2, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetEntity?>(null)); .Returns(Task.FromResult<IAssetEntity?>(null));
var actual = await sut.FindAsync(requestContext, asset.Id, 2, ct); var actual = await sut.FindAsync(ApiContext, asset.Id, 2, CancellationToken);
Assert.Null(actual); Assert.Null(actual);
} }
@ -134,7 +126,7 @@ public class AssetQueryServiceTests
A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A<CancellationToken>._))
.Returns(asset); .Returns(asset);
var actual = await sut.FindGlobalAsync(requestContext, asset.Id, ct); var actual = await sut.FindGlobalAsync(ApiContext, asset.Id, CancellationToken);
AssertAsset(asset, actual); AssertAsset(asset, actual);
} }
@ -147,7 +139,7 @@ public class AssetQueryServiceTests
A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetEntity?>(null)); .Returns(Task.FromResult<IAssetEntity?>(null));
var actual = await sut.FindGlobalAsync(requestContext, asset.Id, ct); var actual = await sut.FindGlobalAsync(ApiContext, asset.Id, CancellationToken);
Assert.Null(actual); Assert.Null(actual);
} }
@ -157,10 +149,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetByHashAsync(appId.Id, "hash", "name", 123, A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetByHashAsync(AppId.Id, "hash", "name", 123, A<CancellationToken>._))
.Returns(asset); .Returns(asset);
var actual = await sut.FindByHashAsync(requestContext, "hash", "name", 123, ct); var actual = await sut.FindByHashAsync(ApiContext, "hash", "name", 123, CancellationToken);
AssertAsset(asset, actual); AssertAsset(asset, actual);
} }
@ -170,10 +162,10 @@ public class AssetQueryServiceTests
{ {
var asset = CreateAsset(DomainId.NewGuid()); var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetByHashAsync(appId.Id, "hash", "name", 123, A<CancellationToken>._)) A.CallTo(() => assetRepository.FindAssetByHashAsync(AppId.Id, "hash", "name", 123, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetEntity?>(null)); .Returns(Task.FromResult<IAssetEntity?>(null));
var actual = await sut.FindByHashAsync(requestContext, "hash", "name", 123, ct); var actual = await sut.FindByHashAsync(ApiContext, "hash", "name", 123, CancellationToken);
Assert.Null(actual); Assert.Null(actual);
} }
@ -188,10 +180,10 @@ public class AssetQueryServiceTests
var q = Q.Empty.WithODataQuery("fileName eq 'Name'"); var q = Q.Empty.WithODataQuery("fileName eq 'Name'");
A.CallTo(() => assetRepository.QueryAsync(appId.Id, parentId, q, A<CancellationToken>._)) A.CallTo(() => assetRepository.QueryAsync(AppId.Id, parentId, q, A<CancellationToken>._))
.Returns(ResultList.CreateFrom(8, asset1, asset2)); .Returns(ResultList.CreateFrom(8, asset1, asset2));
var actual = await sut.QueryAsync(requestContext, parentId, q, ct); var actual = await sut.QueryAsync(ApiContext, parentId, q, CancellationToken);
Assert.Equal(8, actual.Total); Assert.Equal(8, actual.Total);
@ -206,10 +198,10 @@ public class AssetQueryServiceTests
var assetFolders = ResultList.CreateFrom<IAssetFolderEntity>(10); var assetFolders = ResultList.CreateFrom<IAssetFolderEntity>(10);
A.CallTo(() => assetFolderRepository.QueryAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.QueryAsync(AppId.Id, parentId, A<CancellationToken>._))
.Returns(assetFolders); .Returns(assetFolders);
var actual = await sut.QueryAssetFoldersAsync(requestContext, parentId, ct); var actual = await sut.QueryAssetFoldersAsync(ApiContext, parentId, CancellationToken);
Assert.Same(assetFolders, actual); Assert.Same(assetFolders, actual);
} }
@ -221,10 +213,10 @@ public class AssetQueryServiceTests
var assetFolders = ResultList.CreateFrom<IAssetFolderEntity>(10); var assetFolders = ResultList.CreateFrom<IAssetFolderEntity>(10);
A.CallTo(() => assetFolderRepository.QueryAsync(appId.Id, parentId, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.QueryAsync(AppId.Id, parentId, A<CancellationToken>._))
.Returns(assetFolders); .Returns(assetFolders);
var actual = await sut.QueryAssetFoldersAsync(appId.Id, parentId, ct); var actual = await sut.QueryAssetFoldersAsync(AppId.Id, parentId, CancellationToken);
Assert.Same(assetFolders, actual); Assert.Same(assetFolders, actual);
} }
@ -235,10 +227,10 @@ public class AssetQueryServiceTests
var folderId1 = DomainId.NewGuid(); var folderId1 = DomainId.NewGuid();
var folder1 = CreateFolder(folderId1); var folder1 = CreateFolder(folderId1);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId1, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A<CancellationToken>._))
.Returns(folder1); .Returns(folder1);
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId1, ct); var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId1, CancellationToken);
Assert.Equal(actual, new[] { folder1 }); Assert.Equal(actual, new[] { folder1 });
} }
@ -254,16 +246,16 @@ public class AssetQueryServiceTests
var folder2 = CreateFolder(folderId2, folderId1); var folder2 = CreateFolder(folderId2, folderId1);
var folder3 = CreateFolder(folderId3, folderId2); var folder3 = CreateFolder(folderId3, folderId2);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId1, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A<CancellationToken>._))
.Returns(folder1); .Returns(folder1);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId2, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A<CancellationToken>._))
.Returns(folder2); .Returns(folder2);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId3, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId3, A<CancellationToken>._))
.Returns(folder3); .Returns(folder3);
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId3, ct); var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId3, CancellationToken);
Assert.Equal(actual, new[] { folder1, folder2, folder3 }); Assert.Equal(actual, new[] { folder1, folder2, folder3 });
} }
@ -273,10 +265,10 @@ public class AssetQueryServiceTests
{ {
var folderId1 = DomainId.NewGuid(); var folderId1 = DomainId.NewGuid();
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId1, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetFolderEntity?>(null)); .Returns(Task.FromResult<IAssetFolderEntity?>(null));
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId1, ct); var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId1, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
} }
@ -290,13 +282,13 @@ public class AssetQueryServiceTests
var folder1 = CreateFolder(folderId1); var folder1 = CreateFolder(folderId1);
var folder2 = CreateFolder(folderId2, folderId1); var folder2 = CreateFolder(folderId2, folderId1);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId1, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetFolderEntity?>(null)); .Returns(Task.FromResult<IAssetFolderEntity?>(null));
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId2, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A<CancellationToken>._))
.Returns(folder2); .Returns(folder2);
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId2, ct); var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId2, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
} }
@ -310,13 +302,13 @@ public class AssetQueryServiceTests
var folder1 = CreateFolder(folderId1, folderId2); var folder1 = CreateFolder(folderId1, folderId2);
var folder2 = CreateFolder(folderId2, folderId1); var folder2 = CreateFolder(folderId2, folderId1);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId1, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A<CancellationToken>._))
.Returns(Task.FromResult<IAssetFolderEntity?>(null)); .Returns(Task.FromResult<IAssetFolderEntity?>(null));
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId2, A<CancellationToken>._)) A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A<CancellationToken>._))
.Returns(folder2); .Returns(folder2);
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId2, ct); var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId2, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
} }
@ -345,7 +337,7 @@ public class AssetQueryServiceTests
private void SetupEnricher() private void SetupEnricher()
{ {
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>._, A<Context>._, ct)) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>._, A<Context>._, CancellationToken))
.ReturnsLazily(x => .ReturnsLazily(x =>
{ {
var input = x.GetArgument<IEnumerable<IAssetEntity>>(0)!; var input = x.GetArgument<IEnumerable<IAssetEntity>>(0)!;

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

@ -0,0 +1,66 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class CalculateTokensTests : GivenContext
{
private readonly IJsonSerializer serializer = A.Fake<IJsonSerializer>();
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly CalculateTokens sut;
public CalculateTokensTests()
{
sut = new CalculateTokens(urlGenerator, serializer);
}
[Fact]
public async Task Should_not_enrich_asset_edit_tokens_if_disabled()
{
var asset = CreateAsset();
await sut.EnrichAsync(ApiContext.Clone(b => b.WithoutAssetEnrichment()), Enumerable.Repeat(asset, 1), default);
Assert.Null(asset.EditToken);
}
[Fact]
public async Task Should_compute_ui_tokens()
{
var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
Assert.NotNull(asset.EditToken);
A.CallTo(() => urlGenerator.Root())
.MustHaveHappened();
}
[Fact]
public async Task Should_also_compute_ui_tokens_for_frontend()
{
var asset = CreateAsset();
await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken);
Assert.NotNull(asset.EditToken);
A.CallTo(() => urlGenerator.Root())
.MustHaveHappened();
}
private AssetEntity CreateAsset()
{
return new AssetEntity { AppId = AppId };
}
}

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

@ -0,0 +1,107 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class ConvertTagsTests : GivenContext
{
private readonly ITagService tagService = A.Fake<ITagService>();
private readonly ConvertTags sut;
public ConvertTagsTests()
{
sut = new ConvertTags(tagService);
}
[Fact]
public async Task Should_not_enrich_if_asset_has_null_tags()
{
var asset = new AssetEntity();
await sut.EnrichAsync(ApiContext, Enumerable.Repeat(asset, 1), CancellationToken);
Assert.Empty(asset.TagNames);
}
[Fact]
public async Task Should_not_enrich_asset_with_tag_names_if_disabled()
{
var asset = new AssetEntity();
await sut.EnrichAsync(ApiContext.Clone(b => b.WithoutAssetEnrichment()), Enumerable.Repeat(asset, 1), CancellationToken);
Assert.Null(asset.TagNames);
}
[Fact]
public async Task Should_enrich_asset_with_tag_names()
{
var asset = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = AppId
};
A.CallTo(() => tagService.GetTagNamesAsync(AppId.Id, TagGroups.Assets, A<HashSet<string>>.That.Is("id1", "id2"), CancellationToken))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2"
});
await sut.EnrichAsync(ApiContext, Enumerable.Repeat(asset, 1), CancellationToken);
Assert.Equal(new HashSet<string> { "name1", "name2" }, asset.TagNames);
}
[Fact]
public async Task Should_enrich_multiple_assets_with_tag_names()
{
var asset1 = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = AppId
};
var asset2 = new AssetEntity
{
Tags = new HashSet<string>
{
"id2",
"id3"
},
AppId = AppId
};
A.CallTo(() => tagService.GetTagNamesAsync(AppId.Id, TagGroups.Assets, A<HashSet<string>>.That.Is("id1", "id2", "id3"), CancellationToken))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2",
["id3"] = "name3"
});
await sut.EnrichAsync(ApiContext, new[] { asset1, asset2 }, CancellationToken);
Assert.Equal(new HashSet<string> { "name1", "name2" }, asset1.TagNames);
Assert.Equal(new HashSet<string> { "name2", "name3" }, asset2.TagNames);
}
}

66
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichForCachingTests.cs

@ -0,0 +1,66 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class EnrichForCachingTests : GivenContext
{
private readonly IRequestCache requestCache = A.Fake<IRequestCache>();
private readonly EnrichForCaching sut;
public EnrichForCachingTests()
{
sut = new EnrichForCaching(requestCache);
}
[Fact]
public async Task Should_add_cache_headers()
{
var headers = new List<string>();
A.CallTo(() => requestCache.AddHeader(A<string>._))
.Invokes(new Action<string>(header => headers.Add(header)));
await sut.EnrichAsync(ApiContext, CancellationToken);
Assert.Equal(new List<string>
{
"X-Flatten",
"X-Languages",
"X-NoCleanup",
"X-NoEnrichment",
"X-NoResolveLanguages",
"X-ResolveFlow",
"X-Resolve-Urls",
"X-Unpublished"
}, headers);
}
[Fact]
public async Task Should_add_app_version_as_dependency()
{
var asset = CreateAsset();
await sut.EnrichAsync(ApiContext, Enumerable.Repeat(asset, 1), CancellationToken);
A.CallTo(() => requestCache.AddDependency(asset.UniqueId, asset.Version))
.MustHaveHappened();
A.CallTo(() => requestCache.AddDependency(App.UniqueId, App.Version))
.MustHaveHappened();
}
private AssetEntity CreateAsset()
{
return new AssetEntity { AppId = AppId, Id = DomainId.NewGuid(), Version = 13 };
}
}

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

@ -0,0 +1,68 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
using Squidex.Domain.Apps.Entities.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class EnrichWithMetadataTextTests : GivenContext
{
private readonly IAssetMetadataSource assetMetadataSource1 = A.Fake<IAssetMetadataSource>();
private readonly IAssetMetadataSource assetMetadataSource2 = A.Fake<IAssetMetadataSource>();
private readonly EnrichWithMetadataText sut;
public EnrichWithMetadataTextTests()
{
var assetMetadataSources = new[]
{
assetMetadataSource1,
assetMetadataSource2
};
sut = new EnrichWithMetadataText(assetMetadataSources);
}
[Fact]
public async Task Should_not_enrich_if_disabled()
{
var asset = new AssetEntity();
await sut.EnrichAsync(ApiContext.Clone(b => b.WithoutAssetEnrichment()), Enumerable.Repeat(asset, 1), CancellationToken);
A.CallTo(() => assetMetadataSource1.Format(A<IAssetEntity>._))
.MustNotHaveHappened();
A.CallTo(() => assetMetadataSource2.Format(A<IAssetEntity>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_asset_with_metadata()
{
var asset = new AssetEntity
{
FileSize = 2 * 1024,
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = AppId
};
A.CallTo(() => assetMetadataSource1.Format(A<IAssetEntity>._))
.Returns(new[] { "metadata1" });
A.CallTo(() => assetMetadataSource2.Format(A<IAssetEntity>._))
.Returns(new[] { "metadata2", "metadata3" });
await sut.EnrichAsync(FrontendContext, Enumerable.Repeat(asset, 1), CancellationToken);
Assert.Equal("metadata1, metadata2, metadata3, 2 kB", asset.MetadataText);
}
}

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

@ -0,0 +1,107 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Assets.Queries.Steps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class ScriptAssetTests : GivenContext
{
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly ScriptAsset sut;
public ScriptAssetTests()
{
sut = new ScriptAsset(scriptEngine);
}
[Fact]
public async Task Should_not_call_script_engine_if_no_script_configured()
{
var asset = new AssetEntity();
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_call_script_engine_for_frontend_user()
{
A.CallTo(() => App.AssetScripts)
.Returns(new AssetScripts { Query = "my-query" });
var asset = new AssetEntity();
await sut.EnrichAsync(FrontendContext, new[] { asset }, CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(A<AssetScriptVars>._, A<string>._, ScriptOptions(), A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_call_script_engine()
{
A.CallTo(() => App.AssetScripts)
.Returns(new AssetScripts { Query = "my-query" });
var asset = new AssetEntity { Id = DomainId.NewGuid() };
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x =>
Equals(x["assetId"], asset.Id) &&
Equals(x["appId"], AppId.Id) &&
Equals(x["appName"], AppId.Name) &&
Equals(x["user"], ApiContext.UserPrincipal)),
"my-query",
ScriptOptions(), CancellationToken))
.MustHaveHappened();
}
[Fact]
public async Task Should_make_test_with_pre_query_script()
{
A.CallTo(() => App.AssetScripts)
.Returns(new AssetScripts { Query = "my-query", QueryPre = "my-pre-query" });
var asset = new AssetEntity { Id = DomainId.NewGuid() };
await sut.EnrichAsync(ApiContext, new[] { asset }, CancellationToken);
A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x =>
Equals(x.GetValue<object>("assetId"), null) &&
Equals(x["appId"], AppId.Id) &&
Equals(x["appName"], AppId.Name) &&
Equals(x["user"], ApiContext.UserPrincipal)),
"my-pre-query",
ScriptOptions(), CancellationToken))
.MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(
A<AssetScriptVars>.That.Matches(x =>
Equals(x.GetValue<object>("assetId"), asset.Id) &&
Equals(x["appId"], AppId.Id) &&
Equals(x["appName"], AppId.Name) &&
Equals(x["user"], ApiContext.UserPrincipal)),
"my-query",
ScriptOptions(), CancellationToken))
.MustHaveHappened();
}
private static ScriptOptions ScriptOptions()
{
return A<ScriptOptions>.That.Matches(x => x.AsContext);
}
}

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

@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -16,13 +17,12 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class RecursiveDeleterTests public class RecursiveDeleterTests : GivenContext
{ {
private readonly ILogger<RecursiveDeleter> log = A.Fake<ILogger<RecursiveDeleter>>(); private readonly ILogger<RecursiveDeleter> log = A.Fake<ILogger<RecursiveDeleter>>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAssetFolderRepository assetFolderRepository = A.Fake<IAssetFolderRepository>(); private readonly IAssetFolderRepository assetFolderRepository = A.Fake<IAssetFolderRepository>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RecursiveDeleter sut; private readonly RecursiveDeleter sut;
public RecursiveDeleterTests() public RecursiveDeleterTests()
@ -57,7 +57,7 @@ public class RecursiveDeleterTests
[Fact] [Fact]
public async Task Should_Not_invoke_delete_commands_if_event_restored() public async Task Should_Not_invoke_delete_commands_if_event_restored()
{ {
var @event = new AssetFolderDeleted { AppId = appId, AssetFolderId = DomainId.NewGuid() }; var @event = new AssetFolderDeleted { AppId = AppId, AssetFolderId = DomainId.NewGuid() };
await sut.On(Envelope.Create(@event).SetRestored()); await sut.On(Envelope.Create(@event).SetRestored());
@ -68,12 +68,12 @@ public class RecursiveDeleterTests
[Fact] [Fact]
public async Task Should_invoke_delete_commands_for_all_subfolders() public async Task Should_invoke_delete_commands_for_all_subfolders()
{ {
var @event = new AssetFolderDeleted { AppId = appId, AssetFolderId = DomainId.NewGuid() }; var @event = new AssetFolderDeleted { AppId = AppId, AssetFolderId = DomainId.NewGuid() };
var childFolderId1 = DomainId.NewGuid(); var childFolderId1 = DomainId.NewGuid();
var childFolderId2 = DomainId.NewGuid(); var childFolderId2 = DomainId.NewGuid();
A.CallTo(() => assetFolderRepository.QueryChildIdsAsync(appId.Id, @event.AssetFolderId, default)) A.CallTo(() => assetFolderRepository.QueryChildIdsAsync(AppId.Id, @event.AssetFolderId, default))
.Returns(new List<DomainId> { childFolderId1, childFolderId2 }); .Returns(new List<DomainId> { childFolderId1, childFolderId2 });
await sut.On(Envelope.Create(@event)); await sut.On(Envelope.Create(@event));
@ -88,12 +88,12 @@ public class RecursiveDeleterTests
[Fact] [Fact]
public async Task Should_invoke_delete_commands_for_all_assets() public async Task Should_invoke_delete_commands_for_all_assets()
{ {
var @event = new AssetFolderDeleted { AppId = appId, AssetFolderId = DomainId.NewGuid() }; var @event = new AssetFolderDeleted { AppId = AppId, AssetFolderId = DomainId.NewGuid() };
var childId1 = DomainId.NewGuid(); var childId1 = DomainId.NewGuid();
var childId2 = DomainId.NewGuid(); var childId2 = DomainId.NewGuid();
A.CallTo(() => assetRepository.QueryChildIdsAsync(appId.Id, @event.AssetFolderId, default)) A.CallTo(() => assetRepository.QueryChildIdsAsync(AppId.Id, @event.AssetFolderId, default))
.Returns(new List<DomainId> { childId1, childId2 }); .Returns(new List<DomainId> { childId1, childId2 });
await sut.On(Envelope.Create(@event)); await sut.On(Envelope.Create(@event));
@ -108,7 +108,7 @@ public class RecursiveDeleterTests
[Fact] [Fact]
public async Task Should_ignore_exceptions() public async Task Should_ignore_exceptions()
{ {
var @event = new AssetFolderDeleted { AppId = appId, AssetFolderId = DomainId.NewGuid() }; var @event = new AssetFolderDeleted { AppId = AppId, AssetFolderId = DomainId.NewGuid() };
var childId1 = DomainId.NewGuid(); var childId1 = DomainId.NewGuid();
var childId2 = DomainId.NewGuid(); var childId2 = DomainId.NewGuid();
@ -116,7 +116,7 @@ public class RecursiveDeleterTests
A.CallTo(() => commandBus.PublishAsync(A<DeleteAsset>.That.Matches(x => x.AssetId == childId1), default)) A.CallTo(() => commandBus.PublishAsync(A<DeleteAsset>.That.Matches(x => x.AssetId == childId1), default))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
A.CallTo(() => assetRepository.QueryChildIdsAsync(appId.Id, @event.AssetFolderId, default)) A.CallTo(() => assetRepository.QueryChildIdsAsync(AppId.Id, @event.AssetFolderId, default))
.Returns(new List<DomainId> { childId1, childId2 }); .Returns(new List<DomainId> { childId1, childId2 });
await sut.On(Envelope.Create(@event)); await sut.On(Envelope.Create(@event));

40
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs

@ -6,18 +6,18 @@
// ========================================================================== // ==========================================================================
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets; namespace Squidex.Domain.Apps.Entities.Assets;
public class RepairFilesTests public class RepairFilesTests : GivenContext
{ {
private readonly IEventStore eventStore = A.Fake<IEventStore>(); private readonly IEventStore eventStore = A.Fake<IEventStore>();
private readonly IEventFormatter eventFormatter = A.Fake<IEventFormatter>(); private readonly IEventFormatter eventFormatter = A.Fake<IEventFormatter>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>(); private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RebuildFiles sut; private readonly RebuildFiles sut;
public RepairFilesTests() public RepairFilesTests()
@ -28,64 +28,64 @@ public class RepairFilesTests
[Fact] [Fact]
public async Task Should_repair_created_asset_if_not_found() public async Task Should_repair_created_asset_if_not_found()
{ {
var @event = new AssetCreated { AppId = appId, AssetId = DomainId.NewGuid() }; var @event = new AssetCreated { AppId = AppId, AssetId = DomainId.NewGuid() };
SetupEvent(@event); SetupEvent(@event);
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 0, null, default)) A.CallTo(() => assetFileStore.GetFileSizeAsync(AppId.Id, @event.AssetId, 0, null, CancellationToken))
.Throws(new AssetNotFoundException("file")); .Throws(new AssetNotFoundException("file"));
await sut.RepairAsync(); await sut.RepairAsync(CancellationToken);
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 0, null, A<Stream>._, true, default)) A.CallTo(() => assetFileStore.UploadAsync(AppId.Id, @event.AssetId, 0, null, A<Stream>._, true, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_not_repair_created_asset_if_found() public async Task Should_not_repair_created_asset_if_found()
{ {
var @event = new AssetCreated { AppId = appId, AssetId = DomainId.NewGuid() }; var @event = new AssetCreated { AppId = AppId, AssetId = DomainId.NewGuid() };
SetupEvent(@event); SetupEvent(@event);
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 0, null, default)) A.CallTo(() => assetFileStore.GetFileSizeAsync(AppId.Id, @event.AssetId, 0, null, CancellationToken))
.Returns(100); .Returns(100);
await sut.RepairAsync(); await sut.RepairAsync(CancellationToken);
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 0, null, A<Stream>._, true, A<CancellationToken>._)) A.CallTo(() => assetFileStore.UploadAsync(AppId.Id, @event.AssetId, 0, null, A<Stream>._, true, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
[Fact] [Fact]
public async Task Should_repair_updated_asset_if_not_found() public async Task Should_repair_updated_asset_if_not_found()
{ {
var @event = new AssetUpdated { AppId = appId, AssetId = DomainId.NewGuid(), FileVersion = 3 }; var @event = new AssetUpdated { AppId = AppId, AssetId = DomainId.NewGuid(), FileVersion = 3 };
SetupEvent(@event); SetupEvent(@event);
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 3, null, A<CancellationToken>._)) A.CallTo(() => assetFileStore.GetFileSizeAsync(AppId.Id, @event.AssetId, 3, null, CancellationToken))
.Throws(new AssetNotFoundException("file")); .Throws(new AssetNotFoundException("file"));
await sut.RepairAsync(); await sut.RepairAsync(CancellationToken);
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 3, null, A<Stream>._, true, default)) A.CallTo(() => assetFileStore.UploadAsync(AppId.Id, @event.AssetId, 3, null, A<Stream>._, true, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_not_repair_updated_asset_if_found() public async Task Should_not_repair_updated_asset_if_found()
{ {
var @event = new AssetUpdated { AppId = appId, AssetId = DomainId.NewGuid(), FileVersion = 3 }; var @event = new AssetUpdated { AppId = AppId, AssetId = DomainId.NewGuid(), FileVersion = 3 };
SetupEvent(@event); SetupEvent(@event);
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 3, null, default)) A.CallTo(() => assetFileStore.GetFileSizeAsync(AppId.Id, @event.AssetId, 3, null, CancellationToken))
.Returns(100); .Returns(100);
await sut.RepairAsync(); await sut.RepairAsync(CancellationToken);
A.CallTo(() => assetFileStore.UploadAsync(appId.Id, @event.AssetId, 3, null, A<Stream>._, true, A<CancellationToken>._)) A.CallTo(() => assetFileStore.UploadAsync(AppId.Id, @event.AssetId, 3, null, A<Stream>._, true, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -94,7 +94,7 @@ public class RepairFilesTests
{ {
SetupEvent(null); SetupEvent(null);
await sut.RepairAsync(); await sut.RepairAsync(CancellationToken);
A.CallTo(() => assetFileStore.GetFileSizeAsync(A<DomainId>._, A<DomainId>._, A<long>._, null, A<CancellationToken>._)) A.CallTo(() => assetFileStore.GetFileSizeAsync(A<DomainId>._, A<DomainId>._, A<long>._, null, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -122,7 +122,7 @@ public class RepairFilesTests
.Returns(null); .Returns(null);
} }
A.CallTo(() => eventStore.QueryAllAsync("^asset\\-", null, int.MaxValue, default)) A.CallTo(() => eventStore.QueryAllAsync("^asset\\-", null, int.MaxValue, CancellationToken))
.Returns(storedEvents.ToAsyncEnumerable()); .Returns(storedEvents.ToAsyncEnumerable());
} }
} }

34
backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupServiceTests.cs

@ -14,20 +14,18 @@ using Squidex.Messaging;
namespace Squidex.Domain.Apps.Entities.Backup; namespace Squidex.Domain.Apps.Entities.Backup;
public class BackupServiceTests public class BackupServiceTests : GivenContext
{ {
private readonly TestState<BackupState> stateBackup; private readonly TestState<BackupState> stateBackup;
private readonly TestState<BackupRestoreState> stateRestore; private readonly TestState<BackupRestoreState> stateRestore;
private readonly IMessageBus messaging = A.Fake<IMessageBus>(); private readonly IMessageBus messaging = A.Fake<IMessageBus>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DomainId backupId = DomainId.NewGuid(); private readonly DomainId backupId = DomainId.NewGuid();
private readonly RefToken actor = RefToken.User("me");
private readonly BackupService sut; private readonly BackupService sut;
public BackupServiceTests() public BackupServiceTests()
{ {
stateRestore = new TestState<BackupRestoreState>("Default"); stateRestore = new TestState<BackupRestoreState>("Default");
stateBackup = new TestState<BackupState>(appId); stateBackup = new TestState<BackupState>(AppId.Id);
sut = new BackupService( sut = new BackupService(
stateRestore.PersistenceFactory, stateRestore.PersistenceFactory,
@ -40,36 +38,36 @@ public class BackupServiceTests
var restoreUrl = new Uri("http://squidex.io"); var restoreUrl = new Uri("http://squidex.io");
var restoreAppName = "New App"; var restoreAppName = "New App";
await sut.StartRestoreAsync(actor, restoreUrl, restoreAppName); await sut.StartRestoreAsync(User, restoreUrl, restoreAppName, CancellationToken);
A.CallTo(() => messaging.PublishAsync(new BackupRestore(actor, restoreUrl, restoreAppName), null, default)) A.CallTo(() => messaging.PublishAsync(new BackupRestore(User, restoreUrl, restoreAppName), null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_send_message_to_start_backup() public async Task Should_send_message_to_start_backup()
{ {
await sut.StartBackupAsync(appId, actor); await sut.StartBackupAsync(AppId.Id, User, CancellationToken);
A.CallTo(() => messaging.PublishAsync(new BackupStart(appId, actor), null, default)) A.CallTo(() => messaging.PublishAsync(new BackupStart(AppId.Id, User), null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_send_message_to_delete_backup() public async Task Should_send_message_to_delete_backup()
{ {
await sut.DeleteBackupAsync(appId, backupId); await sut.DeleteBackupAsync(AppId.Id, backupId, CancellationToken);
A.CallTo(() => messaging.PublishAsync(new BackupDelete(appId, backupId), null, default)) A.CallTo(() => messaging.PublishAsync(new BackupDelete(AppId.Id, backupId), null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_send_message_to_clear_backups() public async Task Should_send_message_to_clear_backups()
{ {
await ((IDeleter)sut).DeleteAppAsync(Mocks.App(NamedId.Of(appId, "my-app")), default); await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
A.CallTo(() => messaging.PublishAsync(new BackupClear(appId), null, default)) A.CallTo(() => messaging.PublishAsync(new BackupClear(AppId.Id), null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -86,7 +84,7 @@ public class BackupServiceTests
var restoreUrl = new Uri("http://squidex.io"); var restoreUrl = new Uri("http://squidex.io");
await Assert.ThrowsAnyAsync<DomainException>(() => sut.StartRestoreAsync(actor, restoreUrl, null)); await Assert.ThrowsAnyAsync<DomainException>(() => sut.StartRestoreAsync(User, restoreUrl, null, CancellationToken));
} }
[Fact] [Fact]
@ -97,7 +95,7 @@ public class BackupServiceTests
stateBackup.Snapshot.Jobs.Add(new BackupJob()); stateBackup.Snapshot.Jobs.Add(new BackupJob());
} }
await Assert.ThrowsAnyAsync<DomainException>(() => sut.StartBackupAsync(appId, actor)); await Assert.ThrowsAnyAsync<DomainException>(() => sut.StartBackupAsync(AppId.Id, User, CancellationToken));
} }
[Fact] [Fact]
@ -108,7 +106,7 @@ public class BackupServiceTests
stateBackup.Snapshot.Jobs.Add(new BackupJob { Status = JobStatus.Started }); stateBackup.Snapshot.Jobs.Add(new BackupJob { Status = JobStatus.Started });
} }
await Assert.ThrowsAnyAsync<DomainException>(() => sut.StartBackupAsync(appId, actor)); await Assert.ThrowsAnyAsync<DomainException>(() => sut.StartBackupAsync(AppId.Id, User, CancellationToken));
} }
[Fact] [Fact]
@ -122,7 +120,7 @@ public class BackupServiceTests
} }
}; };
var actual = await sut.GetRestoreAsync(); var actual = await sut.GetRestoreAsync(CancellationToken);
actual.Should().BeEquivalentTo(stateRestore.Snapshot.Job); actual.Should().BeEquivalentTo(stateRestore.Snapshot.Job);
} }
@ -139,7 +137,7 @@ public class BackupServiceTests
stateBackup.Snapshot.Jobs.Add(job); stateBackup.Snapshot.Jobs.Add(job);
var actual = await sut.GetBackupsAsync(appId); var actual = await sut.GetBackupsAsync(AppId.Id, CancellationToken);
actual.Should().BeEquivalentTo(stateBackup.Snapshot.Jobs); actual.Should().BeEquivalentTo(stateBackup.Snapshot.Jobs);
} }
@ -156,7 +154,7 @@ public class BackupServiceTests
stateBackup.Snapshot.Jobs.Add(job); stateBackup.Snapshot.Jobs.Add(job);
var actual = await sut.GetBackupAsync(appId, backupId); var actual = await sut.GetBackupAsync(AppId.Id, backupId, CancellationToken);
actual.Should().BeEquivalentTo(job); actual.Should().BeEquivalentTo(job);
} }

19
backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/DefaultBackupArchiveStoreTests.cs

@ -6,14 +6,13 @@
// ========================================================================== // ==========================================================================
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Backup; namespace Squidex.Domain.Apps.Entities.Backup;
public class DefaultBackupArchiveStoreTests public class DefaultBackupArchiveStoreTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IAssetStore assetStore = A.Fake<IAssetStore>(); private readonly IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly DomainId backupId = DomainId.NewGuid(); private readonly DomainId backupId = DomainId.NewGuid();
private readonly string fileName; private readonly string fileName;
@ -21,8 +20,6 @@ public class DefaultBackupArchiveStoreTests
public DefaultBackupArchiveStoreTests() public DefaultBackupArchiveStoreTests()
{ {
ct = cts.Token;
fileName = $"{backupId}_0"; fileName = $"{backupId}_0";
sut = new DefaultBackupArchiveStore(assetStore); sut = new DefaultBackupArchiveStore(assetStore);
@ -33,9 +30,9 @@ public class DefaultBackupArchiveStoreTests
{ {
var stream = new MemoryStream(); var stream = new MemoryStream();
await sut.UploadAsync(backupId, stream, ct); await sut.UploadAsync(backupId, stream, CancellationToken);
A.CallTo(() => assetStore.UploadAsync(fileName, stream, true, ct)) A.CallTo(() => assetStore.UploadAsync(fileName, stream, true, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -44,18 +41,18 @@ public class DefaultBackupArchiveStoreTests
{ {
var stream = new MemoryStream(); var stream = new MemoryStream();
await sut.DownloadAsync(backupId, stream, ct); await sut.DownloadAsync(backupId, stream, CancellationToken);
A.CallTo(() => assetStore.DownloadAsync(fileName, stream, default, ct)) A.CallTo(() => assetStore.DownloadAsync(fileName, stream, default, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_invoke_asset_store_to_delete_archive_using_suffix_for_compatibility() public async Task Should_invoke_asset_store_to_delete_archive_using_suffix_for_compatibility()
{ {
await sut.DeleteAsync(backupId, ct); await sut.DeleteAsync(backupId, CancellationToken);
A.CallTo(() => assetStore.DeleteAsync(fileName, ct)) A.CallTo(() => assetStore.DeleteAsync(fileName, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

47
backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/UserMappingTests.cs

@ -6,22 +6,19 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup; namespace Squidex.Domain.Apps.Entities.Backup;
public class UserMappingTests public class UserMappingTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource(); private readonly RefToken initiator = RefToken.User("me");
private readonly CancellationToken ct;
private readonly RefToken initiator = Subject("me");
private readonly UserMapping sut; private readonly UserMapping sut;
public UserMappingTests() public UserMappingTests()
{ {
ct = cts.Token;
sut = new UserMapping(initiator); sut = new UserMapping(initiator);
} }
@ -29,9 +26,9 @@ public class UserMappingTests
public async Task Should_backup_users_but_no_clients() public async Task Should_backup_users_but_no_clients()
{ {
sut.Backup("1"); sut.Backup("1");
sut.Backup(Subject("2")); sut.Backup(RefToken.User("2"));
sut.Backup(Client("client")); sut.Backup(RefToken.Client("client"));
var user1 = UserMocks.User("1", "1@email.com"); var user1 = UserMocks.User("1", "1@email.com");
var user2 = UserMocks.User("2", "1@email.com"); var user2 = UserMocks.User("2", "1@email.com");
@ -44,17 +41,17 @@ public class UserMappingTests
var userResolver = A.Fake<IUserResolver>(); var userResolver = A.Fake<IUserResolver>();
A.CallTo(() => userResolver.QueryManyAsync(A<string[]>.That.Is(user1.Id, user2.Id), ct)) A.CallTo(() => userResolver.QueryManyAsync(A<string[]>.That.Is(user1.Id, user2.Id), CancellationToken))
.Returns(users); .Returns(users);
var writer = A.Fake<IBackupWriter>(); var writer = A.Fake<IBackupWriter>();
Dictionary<string, string>? storedUsers = null; Dictionary<string, string>? storedUsers = null;
A.CallTo(() => writer.WriteJsonAsync(A<string>._, A<object>._, ct)) A.CallTo(() => writer.WriteJsonAsync(A<string>._, A<object>._, CancellationToken))
.Invokes(x => storedUsers = x.GetArgument<Dictionary<string, string>>(1)); .Invokes(x => storedUsers = x.GetArgument<Dictionary<string, string>>(1));
await sut.StoreAsync(writer, userResolver, ct); await sut.StoreAsync(writer, userResolver, CancellationToken);
Assert.Equal(new Dictionary<string, string> Assert.Equal(new Dictionary<string, string>
{ {
@ -73,25 +70,25 @@ public class UserMappingTests
var userResolver = A.Fake<IUserResolver>(); var userResolver = A.Fake<IUserResolver>();
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user1.Email, false, ct)) A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user1.Email, false, CancellationToken))
.Returns((user1, false)); .Returns((user1, false));
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user2.Email, false, ct)) A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user2.Email, false, CancellationToken))
.Returns((user2, true)); .Returns((user2, true));
await sut.RestoreAsync(reader, userResolver, ct); await sut.RestoreAsync(reader, userResolver, CancellationToken);
Assert.True(sut.TryMap("1_old", out var mapped1)); Assert.True(sut.TryMap("1_old", out var mapped1));
Assert.True(sut.TryMap(Subject("2_old"), out var mapped2)); Assert.True(sut.TryMap(RefToken.User("2_old"), out var mapped2));
Assert.Equal(Subject("1"), mapped1); Assert.Equal(RefToken.User("1"), mapped1);
Assert.Equal(Subject("2"), mapped2); Assert.Equal(RefToken.User("2"), mapped2);
} }
[Fact] [Fact]
public void Should_return_initiator_if_user_not_found() public void Should_return_initiator_if_user_not_found()
{ {
var user = Subject("user1"); var user = RefToken.User("user1");
Assert.False(sut.TryMap(user, out var mapped)); Assert.False(sut.TryMap(user, out var mapped));
Assert.Same(initiator, mapped); Assert.Same(initiator, mapped);
@ -100,7 +97,7 @@ public class UserMappingTests
[Fact] [Fact]
public void Should_create_same_token_if_mapping_client() public void Should_create_same_token_if_mapping_client()
{ {
var client = Client("client1"); var client = RefToken.Client("client1");
Assert.True(sut.TryMap(client, out var mapped)); Assert.True(sut.TryMap(client, out var mapped));
Assert.Same(client, mapped); Assert.Same(client, mapped);
@ -112,19 +109,9 @@ public class UserMappingTests
var reader = A.Fake<IBackupReader>(); var reader = A.Fake<IBackupReader>();
A.CallTo(() => reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, ct)) A.CallTo(() => reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, CancellationToken))
.Returns(storedUsers); .Returns(storedUsers);
return reader; return reader;
} }
private static RefToken Client(string identifier)
{
return RefToken.Client(identifier);
}
private static RefToken Subject(string identifier)
{
return RefToken.User(identifier);
}
} }

145
backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageGateTests.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.Teams;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
@ -17,20 +16,14 @@ using Squidex.Messaging;
namespace Squidex.Domain.Apps.Entities.Billing; namespace Squidex.Domain.Apps.Entities.Billing;
public class UsageGateTests public class UsageGateTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IMessageBus messaging = A.Fake<IMessageBus>(); private readonly IMessageBus messaging = A.Fake<IMessageBus>();
private readonly IApiUsageTracker apiUsageTracker = A.Fake<IApiUsageTracker>(); private readonly IApiUsageTracker apiUsageTracker = A.Fake<IApiUsageTracker>();
private readonly IAppEntity appWithoutTeam;
private readonly IAppEntity appWithTeam;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>(); private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>();
private readonly IUsageTracker usageTracker = A.Fake<IUsageTracker>(); private readonly IUsageTracker usageTracker = A.Fake<IUsageTracker>();
private readonly string clientId = Guid.NewGuid().ToString(); private readonly string clientId = Guid.NewGuid().ToString();
private readonly DomainId teamId = DomainId.NewGuid(); private readonly DomainId teamId = DomainId.NewGuid();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly DateTime today = new DateTime(2020, 10, 3); private readonly DateTime today = new DateTime(2020, 10, 3);
private readonly Plan planFree = new Plan { Id = "free" }; private readonly Plan planFree = new Plan { Id = "free" };
private readonly Plan planPaid = new Plan { Id = "paid" }; private readonly Plan planPaid = new Plan { Id = "paid" };
@ -38,14 +31,6 @@ public class UsageGateTests
public UsageGateTests() public UsageGateTests()
{ {
appWithoutTeam = Mocks.App(appId);
appWithTeam = Mocks.App(appId);
ct = cts.Token;
A.CallTo(() => appWithTeam.TeamId)
.Returns(teamId);
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (planFree, planFree.Id)); .ReturnsLazily(x => (planFree, planFree.Id));
@ -55,31 +40,31 @@ public class UsageGateTests
A.CallTo(() => usageTracker.FallbackCategory) A.CallTo(() => usageTracker.FallbackCategory)
.Returns("*"); .Returns("*");
sut = new UsageGate(appProvider, apiUsageTracker, billingPlans, messaging, usageTracker); sut = new UsageGate(AppProvider, apiUsageTracker, billingPlans, messaging, usageTracker);
} }
[Fact] [Fact]
public async Task Should_delete_app_asset_usage() public async Task Should_delete_app_asset_usage()
{ {
await sut.DeleteAssetUsageAsync(appId.Id, ct); await sut.DeleteAssetUsageAsync(AppId.Id, CancellationToken);
A.CallTo(() => usageTracker.DeleteAsync($"{appId.Id}_Assets", ct)) A.CallTo(() => usageTracker.DeleteAsync($"{AppId.Id}_Assets", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_delete_assets_usage() public async Task Should_delete_assets_usage()
{ {
await sut.DeleteAssetsUsageAsync(ct); await sut.DeleteAssetsUsageAsync(CancellationToken);
A.CallTo(() => usageTracker.DeleteByKeyPatternAsync("^([a-zA-Z0-9]+)_Assets", ct)) A.CallTo(() => usageTracker.DeleteByKeyPatternAsync("^([a-zA-Z0-9]+)_Assets", CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_get_free_plan_for_app() public async Task Should_get_free_plan_for_app()
{ {
var plan = await sut.GetPlanForAppAsync(appWithoutTeam, false, ct); var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken);
Assert.Equal((planFree, planFree.Id, null), plan); Assert.Equal((planFree, planFree.Id, null), plan);
} }
@ -89,13 +74,16 @@ public class UsageGateTests
{ {
var team = A.Fake<ITeamEntity>(); var team = A.Fake<ITeamEntity>();
A.CallTo(() => appProvider.GetTeamAsync(teamId, ct)) A.CallTo(() => AppProvider.GetTeamAsync(teamId, CancellationToken))
.Returns(team); .Returns(team);
A.CallTo(() => team.Id) A.CallTo(() => team.Id)
.Returns(teamId); .Returns(teamId);
var plan = await sut.GetPlanForAppAsync(appWithTeam, false, ct); A.CallTo(() => App.TeamId)
.Returns(teamId);
var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken);
Assert.Equal((planFree, planFree.Id, teamId), plan); Assert.Equal((planFree, planFree.Id, teamId), plan);
} }
@ -103,10 +91,10 @@ public class UsageGateTests
[Fact] [Fact]
public async Task Should_get_paid_plan_for_app() public async Task Should_get_paid_plan_for_app()
{ {
A.CallTo(() => appWithoutTeam.Plan) A.CallTo(() => App.Plan)
.Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id)); .Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id));
var plan = await sut.GetPlanForAppAsync(appWithoutTeam, false, ct); var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken);
Assert.Equal((planPaid, planPaid.Id, null), plan); Assert.Equal((planPaid, planPaid.Id, null), plan);
} }
@ -114,13 +102,10 @@ public class UsageGateTests
[Fact] [Fact]
public async Task Should_get_paid_plan_for_app_id() public async Task Should_get_paid_plan_for_app_id()
{ {
A.CallTo(() => appProvider.GetAppAsync(appWithoutTeam.Id, true, ct)) A.CallTo(() => App.Plan)
.Returns(appWithoutTeam);
A.CallTo(() => appWithoutTeam.Plan)
.Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id)); .Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id));
var plan = await sut.GetPlanForAppAsync(appWithoutTeam.Id, false, ct); var plan = await sut.GetPlanForAppAsync(AppId.Id, false, CancellationToken);
Assert.Equal((planPaid, planPaid.Id, null), plan); Assert.Equal((planPaid, planPaid.Id, null), plan);
} }
@ -130,7 +115,7 @@ public class UsageGateTests
{ {
var team = A.Fake<ITeamEntity>(); var team = A.Fake<ITeamEntity>();
A.CallTo(() => appProvider.GetTeamAsync(teamId, ct)) A.CallTo(() => AppProvider.GetTeamAsync(teamId, CancellationToken))
.Returns(team); .Returns(team);
A.CallTo(() => team.Id) A.CallTo(() => team.Id)
@ -139,7 +124,10 @@ public class UsageGateTests
A.CallTo(() => team.Plan) A.CallTo(() => team.Plan)
.Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id)); .Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id));
var plan = await sut.GetPlanForAppAsync(appWithTeam, false, ct); A.CallTo(() => App.TeamId)
.Returns(teamId);
var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken);
Assert.Equal((planPaid, planPaid.Id, teamId), plan); Assert.Equal((planPaid, planPaid.Id, teamId), plan);
} }
@ -152,17 +140,17 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => appWithoutTeam.Clients) A.CallTo(() => App.Clients)
.Returns(AppClients.Empty.Add(clientId, clientId).Update(clientId, apiCallsLimit: 1000)); .Returns(AppClients.Empty.Add(clientId, clientId).Update(clientId, apiCallsLimit: 1000));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), today, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A<string>._, CancellationToken))
.Returns(1000); .Returns(1000);
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.True(isBlocked); Assert.True(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct)) A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -174,14 +162,14 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), today, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A<string>._, CancellationToken))
.Returns(1000); .Returns(1000);
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.True(isBlocked); Assert.True(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct)) A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -193,10 +181,10 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), today, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A<string>._, CancellationToken))
.Returns(100); .Returns(100);
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.False(isBlocked); Assert.False(isBlocked);
@ -212,14 +200,14 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), today, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A<string>._, CancellationToken))
.Returns(1200); // in 10 days = 4000 / month .Returns(1200); // in 10 days = 4000 / month
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.False(isBlocked); Assert.False(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct)) A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -231,14 +219,14 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), today, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A<string>._, CancellationToken))
.Returns(1200); // in 10 days = 4000 / month .Returns(1200); // in 10 days = 4000 / month
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.False(isBlocked); Assert.False(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct)) A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -250,13 +238,13 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), today, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A<string>._, CancellationToken))
.Returns(1200); // in 10 days = 4000 / month .Returns(1200); // in 10 days = 4000 / month
await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct); await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct)) A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -270,11 +258,11 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._)) A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id)); .ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(appId.Id.ToString(), now, A<string>._, ct)) A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), now, A<string>._, CancellationToken))
.Returns(220); // in 3 days = 3300 / month .Returns(220); // in 3 days = 3300 / month
await sut.IsBlockedAsync(appWithoutTeam, clientId, now, ct); await sut.IsBlockedAsync(App, clientId, now, CancellationToken);
await sut.IsBlockedAsync(appWithoutTeam, clientId, now, ct); await sut.IsBlockedAsync(App, clientId, now, CancellationToken);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, A<CancellationToken>._)) A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -283,10 +271,10 @@ public class UsageGateTests
[Fact] [Fact]
public async Task Should_get_app_asset_total_size_from_summary_date() public async Task Should_get_app_asset_total_size_from_summary_date()
{ {
A.CallTo(() => usageTracker.GetAsync($"{appId.Id}_Assets", default, default, null, ct)) A.CallTo(() => usageTracker.GetAsync($"{AppId.Id}_Assets", default, default, null, CancellationToken))
.Returns(new Counters { ["TotalSize"] = 2048 }); .Returns(new Counters { ["TotalSize"] = 2048 });
var size = await sut.GetTotalSizeByAppAsync(appId.Id, ct); var size = await sut.GetTotalSizeByAppAsync(AppId.Id, CancellationToken);
Assert.Equal(2048, size); Assert.Equal(2048, size);
} }
@ -294,10 +282,10 @@ public class UsageGateTests
[Fact] [Fact]
public async Task Should_get_team_asset_total_size_from_summary_date() public async Task Should_get_team_asset_total_size_from_summary_date()
{ {
A.CallTo(() => usageTracker.GetAsync($"{appId.Id}_TeamAssets", default, default, null, ct)) A.CallTo(() => usageTracker.GetAsync($"{AppId.Id}_TeamAssets", default, default, null, CancellationToken))
.Returns(new Counters { ["TotalSize"] = 2048 }); .Returns(new Counters { ["TotalSize"] = 2048 });
var size = await sut.GetTotalSizeByTeamAsync(appId.Id, ct); var size = await sut.GetTotalSizeByTeamAsync(AppId.Id, CancellationToken);
Assert.Equal(2048, size); Assert.Equal(2048, size);
} }
@ -305,27 +293,30 @@ public class UsageGateTests
[Fact] [Fact]
public async Task Should_track_request_async() public async Task Should_track_request_async()
{ {
await sut.TrackRequestAsync(appWithoutTeam, "client", today, 42, 50, 512, ct); await sut.TrackRequestAsync(App, "client", today, 42, 50, 512, CancellationToken);
A.CallTo(() => apiUsageTracker.TrackAsync(today, appWithoutTeam.Id.ToString(), "client", 42, 50, 512, ct)) A.CallTo(() => apiUsageTracker.TrackAsync(today, AppId.Id.ToString(), "client", 42, 50, 512, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_track_request_for_team_async() public async Task Should_track_request_for_team_async()
{ {
await sut.TrackRequestAsync(appWithTeam, "client", today, 42, 50, 512, ct); A.CallTo(() => App.TeamId)
.Returns(teamId);
await sut.TrackRequestAsync(App, "client", today, 42, 50, 512, CancellationToken);
A.CallTo(() => apiUsageTracker.TrackAsync(today, appWithTeam.TeamId!.ToString()!, appWithTeam.Name, 42, 50, 512, ct)) A.CallTo(() => apiUsageTracker.TrackAsync(today, App.TeamId!.ToString()!, AppId.Name, 42, 50, 512, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_get_app_asset_counters_from_categories() public async Task Should_get_app_asset_counters_from_categories()
{ {
SetupAssetQuery($"{appId.Id}_Assets"); SetupAssetQuery($"{AppId.Id}_Assets");
var actual = await sut.QueryByAppAsync(appId.Id, today, today.AddDays(3), ct); var actual = await sut.QueryByAppAsync(AppId.Id, today, today.AddDays(3), CancellationToken);
actual.Should().BeEquivalentTo(new List<AssetStats> actual.Should().BeEquivalentTo(new List<AssetStats>
{ {
@ -338,9 +329,9 @@ public class UsageGateTests
[Fact] [Fact]
public async Task Should_get_team_asset_counters_from_categories() public async Task Should_get_team_asset_counters_from_categories()
{ {
SetupAssetQuery($"{appId.Id}_TeamAssets"); SetupAssetQuery($"{AppId.Id}_TeamAssets");
var actual = await sut.QueryByTeamAsync(appId.Id, today, today.AddDays(3), ct); var actual = await sut.QueryByTeamAsync(AppId.Id, today, today.AddDays(3), CancellationToken);
actual.Should().BeEquivalentTo(new List<AssetStats> actual.Should().BeEquivalentTo(new List<AssetStats>
{ {
@ -352,7 +343,7 @@ public class UsageGateTests
private void SetupAssetQuery(string key) private void SetupAssetQuery(string key)
{ {
A.CallTo(() => usageTracker.QueryAsync(key, today, today.AddDays(3), ct)) A.CallTo(() => usageTracker.QueryAsync(key, today, today.AddDays(3), CancellationToken))
.Returns(new Dictionary<string, List<(DateTime, Counters)>> .Returns(new Dictionary<string, List<(DateTime, Counters)>>
{ {
[usageTracker.FallbackCategory] = new List<(DateTime, Counters)> [usageTracker.FallbackCategory] = new List<(DateTime, Counters)>
@ -382,13 +373,13 @@ public class UsageGateTests
Counters? countersSummary = null; Counters? countersSummary = null;
Counters? countersDate = null; Counters? countersDate = null;
A.CallTo(() => usageTracker.TrackAsync(default, $"{appId.Id}_Assets", null, A<Counters>._, ct)) A.CallTo(() => usageTracker.TrackAsync(default, $"{AppId.Id}_Assets", null, A<Counters>._, CancellationToken))
.Invokes(x => countersSummary = x.GetArgument<Counters>(3)); .Invokes(x => countersSummary = x.GetArgument<Counters>(3));
A.CallTo(() => usageTracker.TrackAsync(today, $"{appId.Id}_Assets", null, A<Counters>._, ct)) A.CallTo(() => usageTracker.TrackAsync(today, $"{AppId.Id}_Assets", null, A<Counters>._, CancellationToken))
.Invokes(x => countersDate = x.GetArgument<Counters>(3)); .Invokes(x => countersDate = x.GetArgument<Counters>(3));
await sut.TrackAssetAsync(appWithoutTeam.Id, today, 512, 3, ct); await sut.TrackAssetAsync(AppId.Id, today, 512, 3, CancellationToken);
var expected = new Counters var expected = new Counters
{ {
@ -411,19 +402,19 @@ public class UsageGateTests
A.CallTo(() => team.Id) A.CallTo(() => team.Id)
.Returns(teamId); .Returns(teamId);
A.CallTo(() => appProvider.GetAppAsync(appWithTeam.Id, true, ct)) A.CallTo(() => App.TeamId)
.Returns(appWithTeam); .Returns(teamId);
A.CallTo(() => appProvider.GetTeamAsync(teamId, ct)) A.CallTo(() => AppProvider.GetTeamAsync(teamId, CancellationToken))
.Returns(team); .Returns(team);
A.CallTo(() => usageTracker.TrackAsync(default, $"{teamId}_TeamAssets", null, A<Counters>._, ct)) A.CallTo(() => usageTracker.TrackAsync(default, $"{teamId}_TeamAssets", null, A<Counters>._, CancellationToken))
.Invokes(x => countersSummary = x.GetArgument<Counters>(3)); .Invokes(x => countersSummary = x.GetArgument<Counters>(3));
A.CallTo(() => usageTracker.TrackAsync(today, $"{teamId}_TeamAssets", null, A<Counters>._, ct)) A.CallTo(() => usageTracker.TrackAsync(today, $"{teamId}_TeamAssets", null, A<Counters>._, CancellationToken))
.Invokes(x => countersDate = x.GetArgument<Counters>(3)); .Invokes(x => countersDate = x.GetArgument<Counters>(3));
await sut.TrackAssetAsync(appWithTeam.Id, today, 512, 3, ct); await sut.TrackAssetAsync(AppId.Id, today, 512, 3, CancellationToken);
var expected = new Counters var expected = new Counters
{ {

31
backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageNotifierWorkerTest.cs

@ -16,29 +16,24 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Billing; namespace Squidex.Domain.Apps.Entities.Billing;
public class UsageNotifierWorkerTest public class UsageNotifierWorkerTest : GivenContext
{ {
private readonly TestState<UsageNotifierWorker.State> state = new TestState<UsageNotifierWorker.State>("Default"); private readonly TestState<UsageNotifierWorker.State> state = new TestState<UsageNotifierWorker.State>("Default");
private readonly IClock clock = A.Fake<IClock>(); private readonly IClock clock = A.Fake<IClock>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IUserNotifications notificationSender = A.Fake<IUserNotifications>(); private readonly IUserNotifications notificationSender = A.Fake<IUserNotifications>();
private readonly IUserResolver userResolver = A.Fake<IUserResolver>(); private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly IAppEntity app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app"));
private readonly UsageNotifierWorker sut; private readonly UsageNotifierWorker sut;
private Instant time = SystemClock.Instance.GetCurrentInstant(); private Instant time = SystemClock.Instance.GetCurrentInstant();
public UsageNotifierWorkerTest() public UsageNotifierWorkerTest()
{ {
A.CallTo(() => appProvider.GetAppAsync(app.Id, true, default))
.Returns(app);
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.ReturnsLazily(() => time); .ReturnsLazily(() => time);
A.CallTo(() => notificationSender.IsActive) A.CallTo(() => notificationSender.IsActive)
.Returns(true); .Returns(true);
sut = new UsageNotifierWorker(state.PersistenceFactory, appProvider, notificationSender, userResolver) sut = new UsageNotifierWorker(state.PersistenceFactory, AppProvider, notificationSender, userResolver)
{ {
Clock = clock Clock = clock
}; };
@ -61,7 +56,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck var message = new UsageTrackingCheck
{ {
AppId = app.Id, AppId = AppId.Id,
Usage = 1000, Usage = 1000,
UsageLimit = 3000, UsageLimit = 3000,
Users = new[] { "1", "2" } Users = new[] { "1", "2" }
@ -82,7 +77,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck var message = new UsageTrackingCheck
{ {
AppId = app.Id, AppId = AppId.Id,
Usage = 1000, Usage = 1000,
UsageLimit = 3000, UsageLimit = 3000,
Users = new[] { "1", "2", "3" } Users = new[] { "1", "2", "3" }
@ -90,13 +85,13 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default); await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user1!, app, 1000, 3000, default)) A.CallTo(() => notificationSender.SendUsageAsync(user1!, App, 1000, 3000, default))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => notificationSender.SendUsageAsync(user2!, app, 1000, 3000, default)) A.CallTo(() => notificationSender.SendUsageAsync(user2!, App, 1000, 3000, default))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => notificationSender.SendUsageAsync(user3!, app, 1000, 3000, default)) A.CallTo(() => notificationSender.SendUsageAsync(user3!, App, 1000, 3000, default))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -107,7 +102,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck var message = new UsageTrackingCheck
{ {
AppId = app.Id, AppId = AppId.Id,
Usage = 1000, Usage = 1000,
UsageLimit = 3000, UsageLimit = 3000,
Users = new[] { "1" } Users = new[] { "1" }
@ -116,7 +111,7 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default); await sut.HandleAsync(message, default);
await sut.HandleAsync(message, default); await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user!, app, 1000, 3000, default)) A.CallTo(() => notificationSender.SendUsageAsync(user!, App, 1000, 3000, default))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -127,7 +122,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck var message = new UsageTrackingCheck
{ {
AppId = app.Id, AppId = AppId.Id,
Usage = 1000, Usage = 1000,
UsageLimit = 3000, UsageLimit = 3000,
Users = new[] { "1" } Users = new[] { "1" }
@ -139,7 +134,7 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default); await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user!, app, 1000, 3000, default)) A.CallTo(() => notificationSender.SendUsageAsync(user!, App, 1000, 3000, default))
.MustHaveHappenedTwiceExactly(); .MustHaveHappenedTwiceExactly();
} }
@ -155,7 +150,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck var message = new UsageTrackingCheck
{ {
AppId = app.Id, AppId = AppId.Id,
Usage = 1000, Usage = 1000,
UsageLimit = 3000, UsageLimit = 3000,
Users = new[] { "1" } Users = new[] { "1" }
@ -167,7 +162,7 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default); await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user!, app, 1000, 3000, default)) A.CallTo(() => notificationSender.SendUsageAsync(user!, App, 1000, 3000, default))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }

11
backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentsLoaderTests.cs

@ -6,22 +6,19 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.Comments.DomainObject; using Squidex.Domain.Apps.Entities.Comments.DomainObject;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Comments; namespace Squidex.Domain.Apps.Entities.Comments;
public sealed class CommentsLoaderTests public sealed class CommentsLoaderTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly CommentsLoader sut; private readonly CommentsLoader sut;
public CommentsLoaderTests() public CommentsLoaderTests()
{ {
ct = cts.Token;
sut = new CommentsLoader(domainObjectFactory); sut = new CommentsLoader(domainObjectFactory);
} }
@ -39,11 +36,11 @@ public sealed class CommentsLoaderTests
A.CallTo(() => domainObject.GetComments(11)) A.CallTo(() => domainObject.GetComments(11))
.Returns(comments); .Returns(comments);
var actual = await sut.GetCommentsAsync(commentsId, 11, ct); var actual = await sut.GetCommentsAsync(commentsId, 11, CancellationToken);
Assert.Same(comments, actual); Assert.Same(comments, actual);
A.CallTo(() => domainObject.LoadAsync(ct)) A.CallTo(() => domainObject.LoadAsync(CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

25
backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/DomainObject/CommentsCommandMiddlewareTests.cs

@ -7,29 +7,24 @@
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject; namespace Squidex.Domain.Apps.Entities.Comments.DomainObject;
public class CommentsCommandMiddlewareTests public class CommentsCommandMiddlewareTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IUserResolver userResolver = A.Fake<IUserResolver>(); private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly RefToken actor = RefToken.User("me");
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly DomainId commentsId = DomainId.NewGuid(); private readonly DomainId commentsId = DomainId.NewGuid();
private readonly DomainId commentId = DomainId.NewGuid(); private readonly DomainId commentId = DomainId.NewGuid();
private readonly CommentsCommandMiddleware sut; private readonly CommentsCommandMiddleware sut;
public CommentsCommandMiddlewareTests() public CommentsCommandMiddlewareTests()
{ {
ct = cts.Token;
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, default)) A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, default))
.Returns(Task.FromResult<IUser?>(null)); .Returns(Task.FromResult<IUser?>(null));
@ -44,7 +39,7 @@ public class CommentsCommandMiddlewareTests
var domainObject = A.Fake<CommentsStream>(); var domainObject = A.Fake<CommentsStream>();
A.CallTo(() => domainObject.ExecuteAsync(command, ct)) A.CallTo(() => domainObject.ExecuteAsync(command, CancellationToken))
.Returns(CommandResult.Empty(commentsId, 0, 0)); .Returns(CommandResult.Empty(commentsId, 0, 0));
A.CallTo(() => domainObjectFactory.Create<CommentsStream>(commentsId)) A.CallTo(() => domainObjectFactory.Create<CommentsStream>(commentsId))
@ -57,7 +52,7 @@ public class CommentsCommandMiddlewareTests
isNextCalled = true; isNextCalled = true;
return Task.CompletedTask; return Task.CompletedTask;
}, ct); }, CancellationToken);
Assert.True(isNextCalled); Assert.True(isNextCalled);
} }
@ -76,7 +71,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command); var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
Assert.Equal(command.Mentions, new[] { "id1", "id2" }); Assert.Equal(command.Mentions, new[] { "id1", "id2" });
} }
@ -95,7 +90,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command); var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, A<CancellationToken>._)) A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -112,7 +107,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command); var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, A<CancellationToken>._)) A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -129,7 +124,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command); var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, A<CancellationToken>._)) A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -150,10 +145,10 @@ public class CommentsCommandMiddlewareTests
private T CreateCommentsCommand<T>(T command) where T : CommentCommand private T CreateCommentsCommand<T>(T command) where T : CommentCommand
{ {
command.Actor = actor; command.AppId = AppId;
command.AppId = appId;
command.CommentsId = commentsId; command.CommentsId = commentsId;
command.CommentId = commentId; command.CommentId = commentId;
command.Actor = User;
return command; return command;
} }

39
backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/WatchingServiceTests.cs

@ -6,19 +6,16 @@
// ========================================================================== // ==========================================================================
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.TestHelpers; using Squidex.Infrastructure.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Comments; namespace Squidex.Domain.Apps.Entities.Comments;
public class WatchingServiceTests public class WatchingServiceTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly TestState<WatchingService.State> state1; private readonly TestState<WatchingService.State> state1;
private readonly TestState<WatchingService.State> state2; private readonly TestState<WatchingService.State> state2;
private readonly IClock clock = A.Fake<IClock>(); private readonly IClock clock = A.Fake<IClock>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly string resource1 = "resource1"; private readonly string resource1 = "resource1";
private readonly string resource2 = "resource2"; private readonly string resource2 = "resource2";
private readonly WatchingService sut; private readonly WatchingService sut;
@ -26,13 +23,11 @@ public class WatchingServiceTests
public WatchingServiceTests() public WatchingServiceTests()
{ {
ct = cts.Token;
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.ReturnsLazily(() => now); .ReturnsLazily(() => now);
state1 = new TestState<WatchingService.State>($"{appId}_{resource1}"); state1 = new TestState<WatchingService.State>($"{AppId.Id}_{resource1}");
state2 = new TestState<WatchingService.State>($"{appId}_{resource2}", state1.PersistenceFactory); state2 = new TestState<WatchingService.State>($"{AppId.Id}_{resource2}", state1.PersistenceFactory);
sut = new WatchingService(state1.PersistenceFactory) sut = new WatchingService(state1.PersistenceFactory)
{ {
@ -43,7 +38,7 @@ public class WatchingServiceTests
[Fact] [Fact]
public async Task Should_only_return_self_if_no_one_watching() public async Task Should_only_return_self_if_no_one_watching()
{ {
var watching = await sut.GetWatchingUsersAsync(appId, resource1, "user1", ct); var watching = await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user1", CancellationToken);
Assert.Equal(new[] { "user1" }, watching); Assert.Equal(new[] { "user1" }, watching);
} }
@ -51,11 +46,11 @@ public class WatchingServiceTests
[Fact] [Fact]
public async Task Should_return_users_watching_on_same_resource() public async Task Should_return_users_watching_on_same_resource()
{ {
await sut.GetWatchingUsersAsync(appId, resource1, "user1", ct); await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user1", CancellationToken);
await sut.GetWatchingUsersAsync(appId, resource2, "user2", ct); await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user2", CancellationToken);
var watching1 = await sut.GetWatchingUsersAsync(appId, resource1, "user3", ct); var watching1 = await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user3", CancellationToken);
var watching2 = await sut.GetWatchingUsersAsync(appId, resource2, "user4", ct); var watching2 = await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user4", CancellationToken);
Assert.Equal(new[] { "user1", "user3" }, watching1); Assert.Equal(new[] { "user1", "user3" }, watching1);
Assert.Equal(new[] { "user2", "user4" }, watching2); Assert.Equal(new[] { "user2", "user4" }, watching2);
@ -64,24 +59,24 @@ public class WatchingServiceTests
[Fact] [Fact]
public async Task Should_cleanup_old_users() public async Task Should_cleanup_old_users()
{ {
await sut.GetWatchingUsersAsync(appId, resource1, "user1", ct); await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user1", CancellationToken);
await sut.GetWatchingUsersAsync(appId, resource2, "user2", ct); await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user2", CancellationToken);
now = now.Plus(Duration.FromMinutes(2)); now = now.Plus(Duration.FromMinutes(2));
await sut.GetWatchingUsersAsync(appId, resource1, "user3", ct); await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user3", CancellationToken);
await sut.GetWatchingUsersAsync(appId, resource2, "user4", ct); await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user4", CancellationToken);
var watching1 = await sut.GetWatchingUsersAsync(appId, resource1, "user5", ct); var watching1 = await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user5", CancellationToken);
var watching2 = await sut.GetWatchingUsersAsync(appId, resource2, "user6", ct); var watching2 = await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user6", CancellationToken);
Assert.Equal(new[] { "user3", "user5" }, watching1); Assert.Equal(new[] { "user3", "user5" }, watching1);
Assert.Equal(new[] { "user4", "user6" }, watching2); Assert.Equal(new[] { "user4", "user6" }, watching2);
A.CallTo(() => state1.Persistence.WriteSnapshotAsync(A<WatchingService.State>._, ct)) A.CallTo(() => state1.Persistence.WriteSnapshotAsync(A<WatchingService.State>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => state2.Persistence.WriteSnapshotAsync(A<WatchingService.State>._, ct)) A.CallTo(() => state2.Persistence.WriteSnapshotAsync(A<WatchingService.State>._, CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

54
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BackupContentsTests.cs

@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas;
@ -19,19 +20,14 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents; namespace Squidex.Domain.Apps.Entities.Contents;
public class BackupContentsTests public class BackupContentsTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly Rebuilder rebuilder = A.Fake<Rebuilder>(); private readonly Rebuilder rebuilder = A.Fake<Rebuilder>();
private readonly BackupContents sut; private readonly BackupContents sut;
public BackupContentsTests() public BackupContentsTests()
{ {
ct = cts.Token;
sut = new BackupContents(rebuilder, urlGenerator); sut = new BackupContents(rebuilder, urlGenerator);
} }
@ -52,22 +48,22 @@ public class BackupContentsTests
A.CallTo(() => urlGenerator.AssetContentBase()) A.CallTo(() => urlGenerator.AssetContentBase())
.Returns(assetsUrl); .Returns(assetsUrl);
A.CallTo(() => urlGenerator.AssetContentBase(appId.Name)) A.CallTo(() => urlGenerator.AssetContentBase(AppId.Name))
.Returns(assetsUrlApp); .Returns(assetsUrlApp);
var writer = A.Fake<IBackupWriter>(); var writer = A.Fake<IBackupWriter>();
var context = new BackupContext(appId.Id, new UserMapping(me), writer); var context = new BackupContext(AppId.Id, new UserMapping(me), writer);
await sut.BackupEventAsync(Envelope.Create(new AppCreated await sut.BackupEventAsync(Envelope.Create(new AppCreated
{ {
Name = appId.Name Name = AppId.Name
}), context, ct); }), context, CancellationToken);
A.CallTo(() => writer.WriteJsonAsync(A<string>._, A.CallTo(() => writer.WriteJsonAsync(A<string>._,
A<BackupContents.Urls>.That.Matches(x => A<BackupContents.Urls>.That.Matches(x =>
x.Assets == assetsUrl && x.Assets == assetsUrl &&
x.AssetsApp == assetsUrlApp), ct)) x.AssetsApp == assetsUrlApp), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -87,10 +83,10 @@ public class BackupContentsTests
A.CallTo(() => urlGenerator.AssetContentBase()) A.CallTo(() => urlGenerator.AssetContentBase())
.Returns(newAssetsUrl); .Returns(newAssetsUrl);
A.CallTo(() => urlGenerator.AssetContentBase(appId.Name)) A.CallTo(() => urlGenerator.AssetContentBase(AppId.Name))
.Returns(newAssetsUrlApp); .Returns(newAssetsUrlApp);
A.CallTo(() => reader.ReadJsonAsync<BackupContents.Urls>(A<string>._, ct)) A.CallTo(() => reader.ReadJsonAsync<BackupContents.Urls>(A<string>._, CancellationToken))
.Returns(new BackupContents.Urls .Returns(new BackupContents.Urls
{ {
Assets = oldAssetsUrl, Assets = oldAssetsUrl,
@ -131,17 +127,17 @@ public class BackupContentsTests
new JsonObject() new JsonObject()
.Add("asset", $"Asset: {newAssetsUrlApp}/my-asset.jpg."))); .Add("asset", $"Asset: {newAssetsUrlApp}/my-asset.jpg.")));
var context = new RestoreContext(appId.Id, new UserMapping(me), reader, DomainId.NewGuid()); var context = new RestoreContext(AppId.Id, new UserMapping(me), reader, DomainId.NewGuid());
await sut.RestoreEventAsync(Envelope.Create(new AppCreated await sut.RestoreEventAsync(Envelope.Create(new AppCreated
{ {
Name = appId.Name Name = AppId.Name
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(Envelope.Create(new ContentUpdated await sut.RestoreEventAsync(Envelope.Create(new ContentUpdated
{ {
Data = data Data = data
}), context, ct); }), context, CancellationToken);
Assert.Equal(updateData, data); Assert.Equal(updateData, data);
} }
@ -158,55 +154,55 @@ public class BackupContentsTests
var contentId2 = DomainId.NewGuid(); var contentId2 = DomainId.NewGuid();
var contentId3 = DomainId.NewGuid(); var contentId3 = DomainId.NewGuid();
var context = new RestoreContext(appId.Id, new UserMapping(me), A.Fake<IBackupReader>(), DomainId.NewGuid()); var context = new RestoreContext(AppId.Id, new UserMapping(me), A.Fake<IBackupReader>(), DomainId.NewGuid());
await sut.RestoreEventAsync(ContentEvent(new ContentCreated await sut.RestoreEventAsync(ContentEvent(new ContentCreated
{ {
ContentId = contentId1, ContentId = contentId1,
SchemaId = schemaId1 SchemaId = schemaId1
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(ContentEvent(new ContentCreated await sut.RestoreEventAsync(ContentEvent(new ContentCreated
{ {
ContentId = contentId2, ContentId = contentId2,
SchemaId = schemaId1 SchemaId = schemaId1
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(ContentEvent(new ContentCreated await sut.RestoreEventAsync(ContentEvent(new ContentCreated
{ {
ContentId = contentId3, ContentId = contentId3,
SchemaId = schemaId2 SchemaId = schemaId2
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(ContentEvent(new ContentDeleted await sut.RestoreEventAsync(ContentEvent(new ContentDeleted
{ {
ContentId = contentId2, ContentId = contentId2,
SchemaId = schemaId1 SchemaId = schemaId1
}), context, ct); }), context, CancellationToken);
await sut.RestoreEventAsync(Envelope.Create(new SchemaDeleted await sut.RestoreEventAsync(Envelope.Create(new SchemaDeleted
{ {
SchemaId = schemaId2 SchemaId = schemaId2
}), context, ct); }), context, CancellationToken);
var rebuildContents = new HashSet<DomainId>(); var rebuildContents = new HashSet<DomainId>();
A.CallTo(() => rebuilder.InsertManyAsync<ContentDomainObject, ContentDomainObject.State>(A<IEnumerable<DomainId>>._, A<int>._, ct)) A.CallTo(() => rebuilder.InsertManyAsync<ContentDomainObject, ContentDomainObject.State>(A<IEnumerable<DomainId>>._, A<int>._, CancellationToken))
.Invokes(x => rebuildContents.AddRange(x.GetArgument<IEnumerable<DomainId>>(0)!)); .Invokes(x => rebuildContents.AddRange(x.GetArgument<IEnumerable<DomainId>>(0)!));
await sut.RestoreAsync(context, ct); await sut.RestoreAsync(context, CancellationToken);
Assert.Equal(new HashSet<DomainId> Assert.Equal(new HashSet<DomainId>
{ {
DomainId.Combine(appId, contentId1), DomainId.Combine(AppId, contentId1),
DomainId.Combine(appId, contentId2) DomainId.Combine(AppId, contentId2)
}, rebuildContents); }, rebuildContents);
} }
private Envelope<ContentEvent> ContentEvent(ContentEvent @event) private Envelope<ContentEvent> ContentEvent(ContentEvent @event)
{ {
@event.AppId = appId; @event.AppId = AppId;
return Envelope.Create(@event).SetAggregateId(DomainId.Combine(appId, @event.ContentId)); return Envelope.Create(@event).SetAggregateId(DomainId.Combine(AppId, @event.ContentId));
} }
} }

29
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs

@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
@ -23,10 +24,8 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Contents; namespace Squidex.Domain.Apps.Entities.Contents;
public class ContentChangedTriggerHandlerTests public class ContentChangedTriggerHandlerTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>(); private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IContentLoader contentLoader = A.Fake<IContentLoader>(); private readonly IContentLoader contentLoader = A.Fake<IContentLoader>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
@ -36,8 +35,6 @@ public class ContentChangedTriggerHandlerTests
public ContentChangedTriggerHandlerTests() public ContentChangedTriggerHandlerTests()
{ {
ct = cts.Token;
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default)) A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true); .Returns(true);
@ -128,14 +125,14 @@ public class ContentChangedTriggerHandlerTests
{ {
var ctx = Context(); var ctx = Context();
A.CallTo(() => contentRepository.StreamAll(ctx.AppId.Id, null, ct)) A.CallTo(() => contentRepository.StreamAll(ctx.AppId.Id, null, CancellationToken))
.Returns(new List<ContentEntity> .Returns(new List<ContentEntity>
{ {
new ContentEntity { SchemaId = schemaMatch }, new ContentEntity { SchemaId = schemaMatch },
new ContentEntity { SchemaId = schemaNonMatch } new ContentEntity { SchemaId = schemaNonMatch }
}.ToAsyncEnumerable()); }.ToAsyncEnumerable());
var actual = await sut.CreateSnapshotEventsAsync(ctx, ct).ToListAsync(ct); var actual = await sut.CreateSnapshotEventsAsync(ctx, CancellationToken).ToListAsync(CancellationToken);
var typed = actual.OfType<EnrichedContentEvent>().ToList(); var typed = actual.OfType<EnrichedContentEvent>().ToList();
@ -160,14 +157,14 @@ public class ContentChangedTriggerHandlerTests
var ctx = Context(trigger); var ctx = Context(trigger);
A.CallTo(() => contentRepository.StreamAll(ctx.AppId.Id, A<HashSet<DomainId>>.That.Is(schemaMatch.Id), ct)) A.CallTo(() => contentRepository.StreamAll(ctx.AppId.Id, A<HashSet<DomainId>>.That.Is(schemaMatch.Id), CancellationToken))
.Returns(new List<ContentEntity> .Returns(new List<ContentEntity>
{ {
new ContentEntity { SchemaId = schemaMatch }, new ContentEntity { SchemaId = schemaMatch },
new ContentEntity { SchemaId = schemaMatch } new ContentEntity { SchemaId = schemaMatch }
}.ToAsyncEnumerable()); }.ToAsyncEnumerable());
var actual = await sut.CreateSnapshotEventsAsync(ctx, ct).ToListAsync(ct); var actual = await sut.CreateSnapshotEventsAsync(ctx, CancellationToken).ToListAsync(CancellationToken);
var typed = actual.OfType<EnrichedContentEvent>().ToList(); var typed = actual.OfType<EnrichedContentEvent>().ToList();
@ -186,10 +183,10 @@ public class ContentChangedTriggerHandlerTests
var envelope = Envelope.Create<AppEvent>(@event).SetEventStreamNumber(12); var envelope = Envelope.Create<AppEvent>(@event).SetEventStreamNumber(12);
A.CallTo(() => contentLoader.GetAsync(ctx.AppId.Id, @event.ContentId, 12, ct)) A.CallTo(() => contentLoader.GetAsync(ctx.AppId.Id, @event.ContentId, 12, CancellationToken))
.Returns(SimpleMapper.Map(@event, new ContentEntity())); .Returns(SimpleMapper.Map(@event, new ContentEntity()));
var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, ct).ToListAsync(ct); var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken);
var enrichedEvent = (EnrichedContentEvent)actual.Single(); var enrichedEvent = (EnrichedContentEvent)actual.Single();
@ -213,13 +210,13 @@ public class ContentChangedTriggerHandlerTests
var dataNow = new ContentData(); var dataNow = new ContentData();
var dataOld = new ContentData(); var dataOld = new ContentData();
A.CallTo(() => contentLoader.GetAsync(ctx.AppId.Id, @event.ContentId, 12, ct)) A.CallTo(() => contentLoader.GetAsync(ctx.AppId.Id, @event.ContentId, 12, CancellationToken))
.Returns(new ContentEntity { AppId = ctx.AppId, SchemaId = schemaMatch, Version = 12, Data = dataNow, Id = @event.ContentId }); .Returns(new ContentEntity { AppId = ctx.AppId, SchemaId = schemaMatch, Version = 12, Data = dataNow, Id = @event.ContentId });
A.CallTo(() => contentLoader.GetAsync(ctx.AppId.Id, @event.ContentId, 11, ct)) A.CallTo(() => contentLoader.GetAsync(ctx.AppId.Id, @event.ContentId, 11, CancellationToken))
.Returns(new ContentEntity { AppId = ctx.AppId, SchemaId = schemaMatch, Version = 11, Data = dataOld }); .Returns(new ContentEntity { AppId = ctx.AppId, SchemaId = schemaMatch, Version = 11, Data = dataOld });
var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, ct).ToListAsync(ct); var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken);
var enrichedEvent = actual.Single() as EnrichedContentEvent; var enrichedEvent = actual.Single() as EnrichedContentEvent;
@ -388,13 +385,13 @@ public class ContentChangedTriggerHandlerTests
} }
} }
private static RuleContext Context(RuleTrigger? trigger = null) private RuleContext Context(RuleTrigger? trigger = null)
{ {
trigger ??= new ContentChangedTriggerV2(); trigger ??= new ContentChangedTriggerV2();
return new RuleContext return new RuleContext
{ {
AppId = NamedId.Of(DomainId.NewGuid(), "my-app"), AppId = AppId,
Rule = new Rule(trigger, A.Fake<RuleAction>()), Rule = new Rule(trigger, A.Fake<RuleAction>()),
RuleId = DomainId.NewGuid() RuleId = DomainId.NewGuid()
}; };

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

@ -10,17 +10,17 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents; namespace Squidex.Domain.Apps.Entities.Contents;
public class ContentSchedulerProcessTests public class ContentSchedulerProcessTests : GivenContext
{ {
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly IClock clock = A.Fake<IClock>(); private readonly IClock clock = A.Fake<IClock>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly ContentSchedulerProcess sut; private readonly ContentSchedulerProcess sut;
public ContentSchedulerProcessTests() public ContentSchedulerProcessTests()
@ -38,14 +38,14 @@ public class ContentSchedulerProcessTests
var content1 = new ContentEntity var content1 = new ContentEntity
{ {
AppId = appId, AppId = AppId,
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now) ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now)
}; };
var content2 = new ContentEntity var content2 = new ContentEntity
{ {
AppId = appId, AppId = AppId,
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Draft, null!, now) ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Draft, null!, now)
}; };
@ -53,10 +53,10 @@ public class ContentSchedulerProcessTests
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.Returns(now); .Returns(now);
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, default)) A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, CancellationToken))
.Returns(new[] { content1, content2 }.ToAsyncEnumerable()); .Returns(new[] { content1, content2 }.ToAsyncEnumerable());
await sut.PublishAsync(); await sut.PublishAsync(CancellationToken);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x => A<ChangeContentStatus>.That.Matches(x =>
@ -82,7 +82,7 @@ public class ContentSchedulerProcessTests
var content1 = new ContentEntity var content1 = new ContentEntity
{ {
AppId = appId, AppId = AppId,
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
ScheduleJob = null ScheduleJob = null
}; };
@ -90,10 +90,10 @@ public class ContentSchedulerProcessTests
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.Returns(now); .Returns(now);
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, default)) A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, CancellationToken))
.Returns(new[] { content1 }.ToAsyncEnumerable()); .Returns(new[] { content1 }.ToAsyncEnumerable());
await sut.PublishAsync(); await sut.PublishAsync(CancellationToken);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, A<CancellationToken>._)) A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -106,7 +106,7 @@ public class ContentSchedulerProcessTests
var content1 = new ContentEntity var content1 = new ContentEntity
{ {
AppId = appId, AppId = AppId,
Id = DomainId.NewGuid(), Id = DomainId.NewGuid(),
ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now) ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now)
}; };
@ -114,13 +114,13 @@ public class ContentSchedulerProcessTests
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.Returns(now); .Returns(now);
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, default)) A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, CancellationToken))
.Returns(new[] { content1 }.ToAsyncEnumerable()); .Returns(new[] { content1 }.ToAsyncEnumerable());
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, default)) A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, default))
.Throws(new DomainObjectNotFoundException(content1.Id.ToString())); .Throws(new DomainObjectNotFoundException(content1.Id.ToString()));
await sut.PublishAsync(); await sut.PublishAsync(CancellationToken);
A.CallTo(() => contentRepository.ResetScheduledAsync(content1.UniqueId, default)) A.CallTo(() => contentRepository.ResetScheduledAsync(content1.UniqueId, default))
.MustHaveHappened(); .MustHaveHappened();

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

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -15,19 +14,14 @@ using Squidex.Domain.Apps.Entities.Search;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Contents; namespace Squidex.Domain.Apps.Entities.Contents;
public class ContentsSearchSourceTests public class ContentsSearchSourceTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly ITextIndex contentIndex = A.Fake<ITextIndex>(); private readonly ITextIndex contentIndex = A.Fake<ITextIndex>();
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId1 = NamedId.Of(DomainId.NewGuid(), "my-schema1"); private readonly NamedId<DomainId> schemaId1 = NamedId.Of(DomainId.NewGuid(), "my-schema1");
private readonly NamedId<DomainId> schemaId2 = NamedId.Of(DomainId.NewGuid(), "my-schema2"); private readonly NamedId<DomainId> schemaId2 = NamedId.Of(DomainId.NewGuid(), "my-schema2");
private readonly NamedId<DomainId> schemaId3 = NamedId.Of(DomainId.NewGuid(), "my-schema3"); private readonly NamedId<DomainId> schemaId3 = NamedId.Of(DomainId.NewGuid(), "my-schema3");
@ -35,17 +29,15 @@ public class ContentsSearchSourceTests
public ContentsSearchSourceTests() public ContentsSearchSourceTests()
{ {
ct = cts.Token; A.CallTo(() => AppProvider.GetSchemasAsync(AppId.Id, CancellationToken))
A.CallTo(() => appProvider.GetSchemasAsync(appId.Id, ct))
.Returns(new List<ISchemaEntity> .Returns(new List<ISchemaEntity>
{ {
Mocks.Schema(appId, schemaId1), Mocks.Schema(AppId, schemaId1),
Mocks.Schema(appId, schemaId2), Mocks.Schema(AppId, schemaId2),
Mocks.Schema(appId, schemaId3) Mocks.Schema(AppId, schemaId3)
}); });
sut = new ContentsSearchSource(appProvider, contentQuery, contentIndex, urlGenerator); sut = new ContentsSearchSource(AppProvider, contentQuery, contentIndex, urlGenerator);
} }
[Fact] [Fact]
@ -152,50 +144,50 @@ public class ContentsSearchSourceTests
[Fact] [Fact]
public async Task Should_not_invoke_content_index_if_user_has_no_permission() public async Task Should_not_invoke_content_index_if_user_has_no_permission()
{ {
var ctx = ContextWithPermissions(); var requestContext = ContextWithPermissions();
var actual = await sut.SearchAsync("query", ctx, ct); var actual = await sut.SearchAsync("query", requestContext, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
A.CallTo(() => contentIndex.SearchAsync(ctx.App, A<TextQuery>._, A<SearchScope>._, A<CancellationToken>._)) A.CallTo(() => contentIndex.SearchAsync(App, A<TextQuery>._, A<SearchScope>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
[Fact] [Fact]
public async Task Should_not_invoke_context_query_if_no_id_found() public async Task Should_not_invoke_context_query_if_no_id_found()
{ {
var ctx = ContextWithPermissions(schemaId1, schemaId2); var requestContext = ContextWithPermissions(schemaId1, schemaId2);
A.CallTo(() => contentIndex.SearchAsync(ctx.App, A<TextQuery>.That.Matches(x => x.Text == "query~"), ctx.Scope(), ct)) A.CallTo(() => contentIndex.SearchAsync(App, A<TextQuery>.That.Matches(x => x.Text == "query~"), ApiContext.Scope(), CancellationToken))
.Returns(new List<DomainId>()); .Returns(new List<DomainId>());
var actual = await sut.SearchAsync("query", ctx, ct); var actual = await sut.SearchAsync("query", requestContext, CancellationToken);
Assert.Empty(actual); Assert.Empty(actual);
A.CallTo(() => contentQuery.QueryAsync(ctx, A<Q>._, A<CancellationToken>._)) A.CallTo(() => contentQuery.QueryAsync(requestContext, A<Q>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
private async Task TestContentAsync(ContentEntity content, string expectedName) private async Task TestContentAsync(ContentEntity content, string expectedName)
{ {
content.AppId = appId; content.AppId = AppId;
var ctx = ContextWithPermissions(schemaId1, schemaId2); var requestContext = ContextWithPermissions(schemaId1, schemaId2);
var ids = new List<DomainId> { content.Id }; var ids = new List<DomainId> { content.Id };
A.CallTo(() => contentIndex.SearchAsync(ctx.App, A<TextQuery>.That.Matches(x => x.Text == "query~"), ctx.Scope(), ct)) A.CallTo(() => contentIndex.SearchAsync(App, A<TextQuery>.That.Matches(x => x.Text == "query~"), ApiContext.Scope(), CancellationToken))
.Returns(ids); .Returns(ids);
A.CallTo(() => contentQuery.QueryAsync(ctx, A<Q>.That.HasIds(ids), ct)) A.CallTo(() => contentQuery.QueryAsync(requestContext, A<Q>.That.HasIds(ids), CancellationToken))
.Returns(ResultList.CreateFrom<IEnrichedContentEntity>(1, content)); .Returns(ResultList.CreateFrom<IEnrichedContentEntity>(1, content));
A.CallTo(() => urlGenerator.ContentUI(appId, schemaId1, content.Id)) A.CallTo(() => urlGenerator.ContentUI(AppId, schemaId1, content.Id))
.Returns("content-url"); .Returns("content-url");
var actual = await sut.SearchAsync("query", ctx, ct); var actual = await sut.SearchAsync("query", requestContext, CancellationToken);
actual.Should().BeEquivalentTo( actual.Should().BeEquivalentTo(
new SearchResults() new SearchResults()
@ -204,16 +196,13 @@ public class ContentsSearchSourceTests
private Context ContextWithPermissions(params NamedId<DomainId>[] allowedSchemas) private Context ContextWithPermissions(params NamedId<DomainId>[] allowedSchemas)
{ {
var claimsIdentity = new ClaimsIdentity(); var permissions = new List<string>();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
foreach (var schemaId in allowedSchemas) foreach (var schemaId in allowedSchemas)
{ {
var permission = PermissionIds.ForApp(PermissionIds.AppContentsReadOwn, appId.Name, schemaId.Name).Id; permissions.Add(PermissionIds.ForApp(PermissionIds.AppContentsReadOwn, AppId.Name, schemaId.Name).Id);
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
} }
return new Context(claimsPrincipal, Mocks.App(appId)); return CreateContext(false, permissions.ToArray());
} }
} }

28
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Counter/CounterJintExtensionTests.cs

@ -8,11 +8,11 @@
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure; using Squidex.Domain.Apps.Entities.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Contents.Counter; namespace Squidex.Domain.Apps.Entities.Contents.Counter;
public class CounterJintExtensionTests public class CounterJintExtensionTests : GivenContext
{ {
private readonly ICounterService counterService = A.Fake<ICounterService>(); private readonly ICounterService counterService = A.Fake<ICounterService>();
private readonly JintScriptEngine sut; private readonly JintScriptEngine sut;
@ -35,9 +35,7 @@ public class CounterJintExtensionTests
[Fact] [Fact]
public void Should_reset_counter() public void Should_reset_counter()
{ {
var appId = DomainId.NewGuid(); A.CallTo(() => counterService.ResetAsync(AppId.Id, "my", 4, default))
A.CallTo(() => counterService.ResetAsync(appId, "my", 4, default))
.Returns(3); .Returns(3);
const string script = @" const string script = @"
@ -46,7 +44,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars var vars = new ScriptVars
{ {
["appId"] = appId ["appId"] = AppId.Id
}; };
var actual = sut.Execute(vars, script).ToString(); var actual = sut.Execute(vars, script).ToString();
@ -57,9 +55,7 @@ public class CounterJintExtensionTests
[Fact] [Fact]
public async Task Should_reset_counter_with_callback() public async Task Should_reset_counter_with_callback()
{ {
var appId = DomainId.NewGuid(); A.CallTo(() => counterService.ResetAsync(AppId.Id, "my", 4, A<CancellationToken>._))
A.CallTo(() => counterService.ResetAsync(appId, "my", 4, A<CancellationToken>._))
.Returns(3); .Returns(3);
const string script = @" const string script = @"
@ -70,7 +66,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars var vars = new ScriptVars
{ {
["appId"] = appId ["appId"] = AppId.Id
}; };
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script)).ToString();
@ -81,9 +77,7 @@ public class CounterJintExtensionTests
[Fact] [Fact]
public void Should_increment_counter() public void Should_increment_counter()
{ {
var appId = DomainId.NewGuid(); A.CallTo(() => counterService.IncrementAsync(AppId.Id, "my", A<CancellationToken>._))
A.CallTo(() => counterService.IncrementAsync(appId, "my", A<CancellationToken>._))
.Returns(3); .Returns(3);
const string script = @" const string script = @"
@ -92,7 +86,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars var vars = new ScriptVars
{ {
["appId"] = appId ["appId"] = AppId.Id
}; };
var actual = sut.Execute(vars, script).ToString(); var actual = sut.Execute(vars, script).ToString();
@ -103,9 +97,7 @@ public class CounterJintExtensionTests
[Fact] [Fact]
public async Task Should_increment_counter_with_callback() public async Task Should_increment_counter_with_callback()
{ {
var appId = DomainId.NewGuid(); A.CallTo(() => counterService.IncrementAsync(AppId.Id, "my", A<CancellationToken>._))
A.CallTo(() => counterService.IncrementAsync(appId, "my", A<CancellationToken>._))
.Returns(3); .Returns(3);
const string script = @" const string script = @"
@ -116,7 +108,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars var vars = new ScriptVars
{ {
["appId"] = appId ["appId"] = AppId.Id
}; };
var actual = (await sut.ExecuteAsync(vars, script)).ToString(); var actual = (await sut.ExecuteAsync(vars, script)).ToString();

34
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Counter/CounterServiceTests.cs

@ -6,24 +6,18 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.TestHelpers; using Squidex.Infrastructure.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Contents.Counter; namespace Squidex.Domain.Apps.Entities.Contents.Counter;
public class CounterServiceTests public class CounterServiceTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly TestState<CounterService.State> state; private readonly TestState<CounterService.State> state;
private readonly DomainId appId = DomainId.NewGuid();
private readonly CounterService sut; private readonly CounterService sut;
public CounterServiceTests() public CounterServiceTests()
{ {
ct = cts.Token; state = new TestState<CounterService.State>(AppId.Id);
state = new TestState<CounterService.State>(appId);
sut = new CounterService(state.PersistenceFactory); sut = new CounterService(state.PersistenceFactory);
} }
@ -39,36 +33,36 @@ public class CounterServiceTests
[Fact] [Fact]
public async Task Should_delete_state_when_app_deleted() public async Task Should_delete_state_when_app_deleted()
{ {
await ((IDeleter)sut).DeleteAppAsync(Mocks.App(NamedId.Of(appId, "my-app")), ct); await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
A.CallTo(() => state.Persistence.DeleteAsync(ct)) A.CallTo(() => state.Persistence.DeleteAsync(CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
[Fact] [Fact]
public async Task Should_increment_counters() public async Task Should_increment_counters()
{ {
Assert.Equal(1, await sut.IncrementAsync(appId, "Counter1", ct)); Assert.Equal(1, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter1", ct)); Assert.Equal(2, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
Assert.Equal(1, await sut.IncrementAsync(appId, "Counter2", ct)); Assert.Equal(1, await sut.IncrementAsync(AppId.Id, "Counter2", CancellationToken));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter2", ct)); Assert.Equal(2, await sut.IncrementAsync(AppId.Id, "Counter2", CancellationToken));
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<CounterService.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<CounterService.State>._, CancellationToken))
.MustHaveHappened(4, Times.Exactly); .MustHaveHappened(4, Times.Exactly);
} }
[Fact] [Fact]
public async Task Should_reset_counter() public async Task Should_reset_counter()
{ {
Assert.Equal(1, await sut.IncrementAsync(appId, "Counter1", ct)); Assert.Equal(1, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter1", ct)); Assert.Equal(2, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
Assert.Equal(1, await sut.ResetAsync(appId, "Counter1", 1, ct)); Assert.Equal(1, await sut.ResetAsync(AppId.Id, "Counter1", 1, CancellationToken));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter1", ct)); Assert.Equal(2, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<CounterService.State>._, ct)) A.CallTo(() => state.Persistence.WriteSnapshotAsync(A<CounterService.State>._, CancellationToken))
.MustHaveHappened(4, Times.Exactly); .MustHaveHappened(4, Times.Exactly);
} }
} }

41
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultWorkflowsValidatorTests.cs

@ -6,33 +6,20 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Contents; namespace Squidex.Domain.Apps.Entities.Contents;
public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture> public class DefaultWorkflowsValidatorTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly DefaultWorkflowsValidator sut; private readonly DefaultWorkflowsValidator sut;
public DefaultWorkflowsValidatorTests() public DefaultWorkflowsValidatorTests()
{ {
var schema = Mocks.Schema(appId, schemaId, new Schema(schemaId.Name)); sut = new DefaultWorkflowsValidator(AppProvider);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A<DomainId>._, false, default))
.Returns(Task.FromResult<ISchemaEntity?>(null));
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false, default))
.Returns(schema);
sut = new DefaultWorkflowsValidator(appProvider);
} }
[Fact] [Fact]
@ -42,7 +29,7 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
.Add(DomainId.NewGuid(), "workflow1") .Add(DomainId.NewGuid(), "workflow1")
.Add(DomainId.NewGuid(), "workflow2"); .Add(DomainId.NewGuid(), "workflow2");
var errors = await sut.ValidateAsync(appId.Id, workflows); var errors = await sut.ValidateAsync(AppId.Id, workflows);
Assert.Equal(new[] { "Multiple workflows cover all schemas." }, errors.ToArray()); Assert.Equal(new[] { "Multiple workflows cover all schemas." }, errors.ToArray());
} }
@ -56,10 +43,10 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
var workflows = Workflows.Empty var workflows = Workflows.Empty
.Add(id1, "workflow1") .Add(id1, "workflow1")
.Add(id2, "workflow2") .Add(id2, "workflow2")
.Update(id1, new Workflow(default, null, ReadonlyList.Create(schemaId.Id))) .Update(id1, new Workflow(default, null, ReadonlyList.Create(SchemaId.Id)))
.Update(id2, new Workflow(default, null, ReadonlyList.Create(schemaId.Id))); .Update(id2, new Workflow(default, null, ReadonlyList.Create(SchemaId.Id)));
var errors = await sut.ValidateAsync(appId.Id, workflows); var errors = await sut.ValidateAsync(AppId.Id, workflows);
Assert.Equal(new[] { "The schema 'my-schema' is covered by multiple workflows." }, errors.ToArray()); Assert.Equal(new[] { "The schema 'my-schema' is covered by multiple workflows." }, errors.ToArray());
} }
@ -67,18 +54,18 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
[Fact] [Fact]
public async Task Should_not_generate_error_if_schema_deleted() public async Task Should_not_generate_error_if_schema_deleted()
{ {
Schema = null!;
var id1 = DomainId.NewGuid(); var id1 = DomainId.NewGuid();
var id2 = DomainId.NewGuid(); var id2 = DomainId.NewGuid();
var oldSchemaId = DomainId.NewGuid();
var workflows = Workflows.Empty var workflows = Workflows.Empty
.Add(id1, "workflow1") .Add(id1, "workflow1")
.Add(id2, "workflow2") .Add(id2, "workflow2")
.Update(id1, new Workflow(default, null, ReadonlyList.Create(oldSchemaId))) .Update(id1, new Workflow(default, null, ReadonlyList.Create(SchemaId.Id)))
.Update(id2, new Workflow(default, null, ReadonlyList.Create(oldSchemaId))); .Update(id2, new Workflow(default, null, ReadonlyList.Create(SchemaId.Id)));
var errors = await sut.ValidateAsync(appId.Id, workflows); var errors = await sut.ValidateAsync(AppId.Id, workflows);
Assert.Empty(errors); Assert.Empty(errors);
} }
@ -92,9 +79,9 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
var workflows = Workflows.Empty var workflows = Workflows.Empty
.Add(id1, "workflow1") .Add(id1, "workflow1")
.Add(id2, "workflow2") .Add(id2, "workflow2")
.Update(id1, new Workflow(default, null, ReadonlyList.Create(schemaId.Id))); .Update(id1, new Workflow(default, null, ReadonlyList.Create(SchemaId.Id)));
var errors = await sut.ValidateAsync(appId.Id, workflows); var errors = await sut.ValidateAsync(AppId.Id, workflows);
Assert.Empty(errors); Assert.Empty(errors);
} }
@ -102,7 +89,7 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
[Fact] [Fact]
public async Task Should_not_generate_errors_for_empty_workflows() public async Task Should_not_generate_errors_for_empty_workflows()
{ {
var errors = await sut.ValidateAsync(appId.Id, Workflows.Empty); var errors = await sut.ValidateAsync(AppId.Id, Workflows.Empty);
Assert.Empty(errors); Assert.Empty(errors);
} }

19
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentCommandMiddlewareTests.cs

@ -18,9 +18,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
private readonly IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>(); private readonly IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>();
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>(); private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly DomainId contentId = DomainId.NewGuid(); private readonly DomainId contentId = DomainId.NewGuid();
private readonly Context requestContext;
private readonly ContentCommandMiddleware sut; private readonly ContentCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand public sealed class MyCommand : SquidexCommand
@ -34,16 +32,11 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
public ContentCommandMiddlewareTests() public ContentCommandMiddlewareTests()
{ {
requestContext = Context.Anonymous(Mocks.App(AppNamedId));
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new ContentCommandMiddleware( sut = new ContentCommandMiddleware(
domainObjectFactory, domainObjectFactory,
domainObjectCache, domainObjectCache,
contentEnricher, contentEnricher,
contextProvider); ApiContextProvider);
} }
[Fact] [Fact]
@ -51,7 +44,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
{ {
await HandleAsync(new CreateContent(), 12); await HandleAsync(new CreateContent(), 12);
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, A<bool>._, requestContext, A<CancellationToken>._)) A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, A<bool>._, ApiContext, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -66,7 +59,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
Assert.Same(actual, context.Result<IEnrichedContentEntity>()); Assert.Same(actual, context.Result<IEnrichedContentEntity>());
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, A<bool>._, requestContext, A<CancellationToken>._)) A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>._, A<bool>._, ApiContext, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -77,7 +70,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
var enriched = new ContentEntity(); var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(actual, true, requestContext, A<CancellationToken>._)) A.CallTo(() => contentEnricher.EnrichAsync(actual, true, ApiContext, CancellationToken))
.Returns(enriched); .Returns(enriched);
var context = var context =
@ -95,12 +88,12 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
var domainObject = A.Fake<ContentDomainObject>(); var domainObject = A.Fake<ContentDomainObject>();
A.CallTo(() => domainObject.ExecuteAsync(A<IAggregateCommand>._, A<CancellationToken>._)) A.CallTo(() => domainObject.ExecuteAsync(A<IAggregateCommand>._, CancellationToken))
.Returns(new CommandResult(command.AggregateId, 1, 0, actual)); .Returns(new CommandResult(command.AggregateId, 1, 0, actual));
A.CallTo(() => domainObjectFactory.Create<ContentDomainObject>(command.AggregateId)) A.CallTo(() => domainObjectFactory.Create<ContentDomainObject>(command.AggregateId))
.Returns(domainObject); .Returns(domainObject);
return HandleAsync(sut, command); return HandleAsync(sut, command, CancellationToken);
} }
} }

106
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs

@ -14,10 +14,8 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -29,11 +27,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject;
public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.State> public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.State>
{ {
private readonly DomainId contentId = DomainId.NewGuid(); private readonly DomainId contentId = DomainId.NewGuid();
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(x => x.Wrapping(new DefaultContentWorkflow())); private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(x => x.Wrapping(new DefaultContentWorkflow()));
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly ISchemaEntity schema;
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>(); private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly ContentData invalidData = private readonly ContentData invalidData =
@ -72,8 +67,6 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
public ContentDomainObjectTests() public ContentDomainObjectTests()
{ {
app = Mocks.App(AppNamedId, Language.DE);
var scripts = new SchemaScripts var scripts = new SchemaScripts
{ {
Change = "<change-script>", Change = "<change-script>",
@ -90,15 +83,10 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
new NumberFieldProperties { IsRequired = false }) new NumberFieldProperties { IsRequired = false })
.SetScripts(scripts); .SetScripts(scripts);
schema = Mocks.Schema(AppNamedId, SchemaNamedId, schemaDef); A.CallTo(() => Schema.SchemaDef)
.Returns(schemaDef);
A.CallTo(() => appProvider.GetAppAsync(AppName, false, default))
.Returns(app);
A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId, false, default))
.Returns((app, schema));
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, A<string>._, ScriptOptions(), CancellationToken))
.ReturnsLazily(x => Task.FromResult(x.GetArgument<DataScriptVars>(0)!.Data!)); .ReturnsLazily(x => Task.FromResult(x.GetArgument<DataScriptVars>(0)!.Data!));
patched = patch.MergeInto(data); patched = patch.MergeInto(data);
@ -107,7 +95,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
var serviceProvider = var serviceProvider =
new ServiceCollection() new ServiceCollection()
.AddSingleton(appProvider) .AddSingleton(AppProvider)
.AddSingleton(A.Fake<ILogger<ContentValidator>>()) .AddSingleton(A.Fake<ILogger<ContentValidator>>())
.AddSingleton(log) .AddSingleton(log)
.AddSingleton(contentWorkflow) .AddSingleton(contentWorkflow)
@ -148,9 +136,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -171,9 +159,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -195,9 +183,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -248,9 +236,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -271,9 +259,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -295,9 +283,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "<create-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -319,7 +307,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = otherData }) CreateContentEvent(new ContentUpdated { Data = otherData })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -341,7 +329,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = patched }) CreateContentEvent(new ContentUpdated { Data = patched })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -363,7 +351,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = otherData }) CreateContentEvent(new ContentUpdated { Data = otherData })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -387,9 +375,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -443,7 +431,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = otherData }) CreateContentEvent(new ContentUpdated { Data = otherData })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -467,7 +455,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = otherData }) CreateContentEvent(new ContentUpdated { Data = otherData })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -484,7 +472,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Assert.Single(LastEvents); Assert.Single(LastEvents);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -516,7 +504,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = patched }) CreateContentEvent(new ContentUpdated { Data = patched })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -540,7 +528,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentUpdated { Data = patched }) CreateContentEvent(new ContentUpdated { Data = patched })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "<update-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -557,7 +545,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Assert.Single(LastEvents); Assert.Single(LastEvents);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -579,7 +567,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -601,7 +589,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -624,7 +612,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -633,7 +621,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
{ {
var command = new ChangeContentStatus { Status = Status.Draft }; var command = new ChangeContentStatus { Status = Status.Draft };
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions(), CancellationToken))
.Returns(otherData); .Returns(otherData);
await ExecuteCreateAsync(); await ExecuteCreateAsync();
@ -652,7 +640,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -676,7 +664,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Change, Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Change, Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -700,7 +688,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Published, Status = Status.Published }) CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Published, Status = Status.Published })
); );
A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Published, Status.Draft), "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Published, Status.Draft), "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -727,7 +715,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime })
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -741,7 +729,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
var command = new ChangeContentStatus { Status = Status.Archived, StatusJobId = sut.Snapshot.ScheduleJob!.Id }; var command = new ChangeContentStatus { Status = Status.Archived, StatusJobId = sut.Snapshot.ScheduleJob!.Id };
A.CallTo(() => contentWorkflow.CanMoveToAsync(A<IContentEntity>._, Status.Draft, Status.Archived, User)) A.CallTo(() => contentWorkflow.CanMoveToAsync(A<IContentEntity>._, Status.Draft, Status.Archived, ApiContext.UserPrincipal))
.Returns(true); .Returns(true);
var actual = await PublishAsync(command); var actual = await PublishAsync(command);
@ -755,7 +743,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged { Status = Status.Archived })
); );
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -769,7 +757,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
var command = new ChangeContentStatus { Status = Status.Published, StatusJobId = sut.Snapshot.ScheduleJob!.Id }; var command = new ChangeContentStatus { Status = Status.Published, StatusJobId = sut.Snapshot.ScheduleJob!.Id };
A.CallTo(() => contentWorkflow.CanMoveToAsync(A<IContentEntity>._, Status.Draft, Status.Published, User)) A.CallTo(() => contentWorkflow.CanMoveToAsync(A<IContentEntity>._, Status.Draft, Status.Published, ApiContext.UserPrincipal))
.Returns(false); .Returns(false);
var actual = await PublishAsync(command); var actual = await PublishAsync(command);
@ -783,7 +771,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentSchedulingCancelled()) CreateContentEvent(new ContentSchedulingCancelled())
); );
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -795,7 +783,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
await ExecuteCreateAsync(); await ExecuteCreateAsync();
await ExecuteChangeStatusAsync(Status.Published); await ExecuteChangeStatusAsync(Status.Published);
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.All, A<CancellationToken>._))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command)); await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
@ -809,7 +797,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
await ExecuteCreateAsync(); await ExecuteCreateAsync();
await ExecuteChangeStatusAsync(Status.Published); await ExecuteChangeStatusAsync(Status.Published);
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.Published, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.Published, A<CancellationToken>._))
.Returns(true); .Returns(true);
await PublishAsync(command); await PublishAsync(command);
@ -865,7 +853,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentDeleted()) CreateContentEvent(new ContentDeleted())
); );
A.CallTo(() => scriptEngine.ExecuteAsync(DataScriptVars(data, null, Status.Draft), "<delete-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(DataScriptVars(data, null, Status.Draft), "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -883,7 +871,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version); Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version);
Assert.Empty(LastEvents); Assert.Empty(LastEvents);
A.CallTo(() => scriptEngine.ExecuteAsync(DataScriptVars(data, null, Status.Draft), "<delete-script>", ScriptOptions(), default)) A.CallTo(() => scriptEngine.ExecuteAsync(DataScriptVars(data, null, Status.Draft), "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -894,7 +882,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
var command = new DeleteContent { CheckReferrers = true }; var command = new DeleteContent { CheckReferrers = true };
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.All, A<CancellationToken>._))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command)); await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
@ -907,7 +895,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
await ExecuteCreateAsync(); await ExecuteCreateAsync();
A.CallTo(() => contentRepository.HasReferrersAsync(AppId, contentId, SearchScope.All, A<CancellationToken>._)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.All, A<CancellationToken>._))
.Returns(true); .Returns(true);
await PublishAsync(command); await PublishAsync(command);
@ -1007,7 +995,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Equals(x["dataOld"], oldData) && Equals(x["dataOld"], oldData) &&
Equals(x["status"], newStatus) && Equals(x["status"], newStatus) &&
Equals(x["statusOld"], oldStatus) && Equals(x["statusOld"], oldStatus) &&
Equals(x["user"], User); Equals(x["user"], ApiContext.UserPrincipal);
} }
private T CreateContentEvent<T>(T @event) where T : ContentEvent private T CreateContentEvent<T>(T @event) where T : ContentEvent
@ -1026,7 +1014,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
private async Task<object> PublishAsync(ContentCommand command) private async Task<object> PublishAsync(ContentCommand command)
{ {
var actual = await sut.ExecuteAsync(CreateContentCommand(command), default); var actual = await sut.ExecuteAsync(CreateContentCommand(command), CancellationToken);
return actual.Payload; return actual.Payload;
} }

70
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -16,18 +15,14 @@ using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject;
public class ContentsBulkUpdateCommandMiddlewareTests public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>(); private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly ICommandBus commandBus = A.Dummy<ICommandBus>(); private readonly ICommandBus commandBus = A.Dummy<ICommandBus>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly NamedId<DomainId> schemaCustomId = NamedId.Of(DomainId.NewGuid(), "my-schema2"); private readonly NamedId<DomainId> schemaCustomId = NamedId.Of(DomainId.NewGuid(), "my-schema2");
private readonly Instant time = Instant.FromDateTimeUtc(DateTime.UtcNow); private readonly Instant time = Instant.FromDateTimeUtc(DateTime.UtcNow);
@ -35,8 +30,6 @@ public class ContentsBulkUpdateCommandMiddlewareTests
public ContentsBulkUpdateCommandMiddlewareTests() public ContentsBulkUpdateCommandMiddlewareTests()
{ {
ct = cts.Token;
var log = A.Fake<ILogger<ContentsBulkUpdateCommandMiddleware>>(); var log = A.Fake<ILogger<ContentsBulkUpdateCommandMiddleware>>();
sut = new ContentsBulkUpdateCommandMiddleware(contentQuery, contextProvider, log); sut = new ContentsBulkUpdateCommandMiddleware(contentQuery, contextProvider, log);
@ -92,7 +85,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
x.ShouldSkipCleanup() && x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() && x.ShouldSkipContentEnrichment() &&
x.ShouldSkipTotal()), x.ShouldSkipTotal()),
schemaId.Name, A<Q>.That.Matches(x => x.JsonQuery == query), ct)) schemaId.Name, A<Q>.That.Matches(x => x.JsonQuery == query), CancellationToken))
.Returns(ResultList.CreateFrom(2, CreateContent(id), CreateContent(id))); .Returns(ResultList.CreateFrom(2, CreateContent(id), CreateContent(id)));
var command = BulkCommand(BulkUpdateContentType.ChangeStatus, new BulkUpdateJob { Query = query }); var command = BulkCommand(BulkUpdateContentType.ChangeStatus, new BulkUpdateJob { Query = query });
@ -118,7 +111,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
x.ShouldSkipCleanup() && x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() && x.ShouldSkipContentEnrichment() &&
x.ShouldSkipTotal()), x.ShouldSkipTotal()),
schemaId.Name, A<Q>.That.Matches(x => x.JsonQuery == query), ct)) schemaId.Name, A<Q>.That.Matches(x => x.JsonQuery == query), CancellationToken))
.Returns(ResultList.CreateFrom(1, CreateContent(id))); .Returns(ResultList.CreateFrom(1, CreateContent(id)));
var command = BulkCommand(BulkUpdateContentType.Upsert, new BulkUpdateJob { Query = query, Data = data }); var command = BulkCommand(BulkUpdateContentType.Upsert, new BulkUpdateJob { Query = query, Data = data });
@ -129,7 +122,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -148,7 +141,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
x.ShouldSkipCleanup() && x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() && x.ShouldSkipContentEnrichment() &&
x.ShouldSkipTotal()), x.ShouldSkipTotal()),
schemaId.Name, A<Q>.That.Matches(x => x.JsonQuery == query), ct)) schemaId.Name, A<Q>.That.Matches(x => x.JsonQuery == query), CancellationToken))
.Returns(ResultList.CreateFrom(2, .Returns(ResultList.CreateFrom(2,
CreateContent(id1), CreateContent(id1),
CreateContent(id2))); CreateContent(id2)));
@ -164,11 +157,11 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id2 && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id2 && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id1), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id1), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id2), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id2), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -187,7 +180,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId != default), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId != default), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -206,7 +199,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId != default), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId != default), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -225,7 +218,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -244,7 +237,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id), ct)) A<UpsertContent>.That.Matches(x => x.Data == data && x.ContentId == id), CancellationToken))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -263,7 +256,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<CreateContent>.That.Matches(x => x.ContentId == id && x.Data == data), ct)) A<CreateContent>.That.Matches(x => x.ContentId == id && x.Data == data), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -300,7 +293,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<UpdateContent>.That.Matches(x => x.ContentId == id && x.Data == data), ct)) A<UpdateContent>.That.Matches(x => x.ContentId == id && x.Data == data), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -337,7 +330,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<PatchContent>.That.Matches(x => x.ContentId == id && x.Data == data), ct)) A<PatchContent>.That.Matches(x => x.ContentId == id && x.Data == data), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -374,7 +367,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x => x.ContentId == id && x.DueTime == null), ct)) A<ChangeContentStatus>.That.Matches(x => x.ContentId == id && x.DueTime == null), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -393,7 +386,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x => x.ContentId == id && x.DueTime == time), ct)) A<ChangeContentStatus>.That.Matches(x => x.ContentId == id && x.DueTime == time), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -430,7 +423,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<ValidateContent>.That.Matches(x => x.ContentId == id), ct)) A<ValidateContent>.That.Matches(x => x.ContentId == id), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -467,7 +460,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x => x.ContentId == id), ct)) A<DeleteContent>.That.Matches(x => x.ContentId == id), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -494,8 +487,8 @@ public class ContentsBulkUpdateCommandMiddlewareTests
{ {
SetupContext(PermissionIds.AppContentsDeleteOwn); SetupContext(PermissionIds.AppContentsDeleteOwn);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>._, schemaCustomId.Name, ct)) A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>._, schemaCustomId.Name, CancellationToken))
.Returns(Mocks.Schema(appId, schemaCustomId)); .Returns(Mocks.Schema(AppId, schemaCustomId));
var (id, _, _) = CreateTestData(false); var (id, _, _) = CreateTestData(false);
@ -507,7 +500,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), ct)) A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), CancellationToken))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -529,7 +522,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception is DomainObjectNotFoundException); Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception is DomainObjectNotFoundException);
A.CallTo(() => commandBus.PublishAsync( A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), ct)) A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), CancellationToken))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -537,7 +530,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
{ {
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context, ct); await sut.HandleAsync(context, CancellationToken);
return (context.PlainResult as BulkUpdateResult)!; return (context.PlainResult as BulkUpdateResult)!;
} }
@ -550,7 +543,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
return new BulkUpdateContents return new BulkUpdateContents
{ {
AppId = appId, AppId = AppId,
Jobs = new[] Jobs = new[]
{ {
job job
@ -561,18 +554,9 @@ public class ContentsBulkUpdateCommandMiddlewareTests
private Context SetupContext(string id) private Context SetupContext(string id)
{ {
var claimsIdentity = new ClaimsIdentity(); var requestContext = CreateContext(false,
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); PermissionIds.ForApp(id, AppId.Name, schemaId.Name).Id,
PermissionIds.ForApp(id, AppId.Name, schemaCustomId.Name).Id);
claimsIdentity.AddClaim(
new Claim(SquidexClaimTypes.Permissions,
PermissionIds.ForApp(id, appId.Name, schemaId.Name).Id));
claimsIdentity.AddClaim(
new Claim(SquidexClaimTypes.Permissions,
PermissionIds.ForApp(id, appId.Name, schemaCustomId.Name).Id));
var requestContext = new Context(claimsPrincipal, Mocks.App(appId));
A.CallTo(() => contextProvider.Context) A.CallTo(() => contextProvider.Context)
.Returns(requestContext); .Returns(requestContext);

37
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs

@ -22,35 +22,32 @@ using Squidex.Shared;
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards;
public class GuardContentTests : IClassFixture<TranslationsFixture> public class GuardContentTests : GivenContext, IClassFixture<TranslationsFixture>
{ {
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly ISchemaEntity normalSchema; private readonly ISchemaEntity normalSchema;
private readonly ISchemaEntity normalUnpublishedSchema; private readonly ISchemaEntity normalUnpublishedSchema;
private readonly ISchemaEntity singletonSchema; private readonly ISchemaEntity singletonSchema;
private readonly ISchemaEntity singletonUnpublishedSchema; private readonly ISchemaEntity singletonUnpublishedSchema;
private readonly ISchemaEntity componentSchema; private readonly ISchemaEntity componentSchema;
private readonly RefToken actor = RefToken.User("123");
public GuardContentTests() public GuardContentTests()
{ {
normalUnpublishedSchema = normalUnpublishedSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name)); Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name));
normalSchema = normalSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name).Publish()); Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name).Publish());
singletonUnpublishedSchema = singletonUnpublishedSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Singleton)); Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name, type: SchemaType.Singleton));
singletonSchema = singletonSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Singleton).Publish()); Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name, type: SchemaType.Singleton).Publish());
componentSchema = componentSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Component).Publish()); Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name, type: SchemaType.Component).Publish());
} }
[Fact] [Fact]
@ -315,7 +312,7 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
[Fact] [Fact]
public void Should_not_throw_exception_if_content_is_from_another_user_but_user_has_permission() public void Should_not_throw_exception_if_content_is_from_another_user_but_user_has_permission()
{ {
var userPermission = PermissionIds.ForApp(PermissionIds.AppContentsDelete, appId.Name, schemaId.Name).Id; var userPermission = PermissionIds.ForApp(PermissionIds.AppContentsDelete, AppId.Name, SchemaId.Name).Id;
var userObject = Mocks.FrontendUser(permission: userPermission); var userObject = Mocks.FrontendUser(permission: userPermission);
var operation = Operation(CreateContent(Status.Draft), normalSchema, userObject); var operation = Operation(CreateContent(Status.Draft), normalSchema, userObject);
@ -330,7 +327,7 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
{ {
var operation = Operation(CreateContent(Status.Draft), normalSchema); var operation = Operation(CreateContent(Status.Draft), normalSchema);
((ContentEntity)operation.Snapshot).CreatedBy = actor; ((ContentEntity)operation.Snapshot).CreatedBy = User;
operation.MustHavePermission(PermissionIds.AppContentsDelete); operation.MustHavePermission(PermissionIds.AppContentsDelete);
} }
@ -360,10 +357,10 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
{ {
var operation = Operation(CreateContent(Status.Draft), normalSchema); var operation = Operation(CreateContent(Status.Draft), normalSchema);
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, operation.CommandId, SearchScope.All, default)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync()); await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
} }
[Fact] [Fact]
@ -371,10 +368,10 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
{ {
var operation = Operation(CreateContent(Status.Draft), normalSchema); var operation = Operation(CreateContent(Status.Draft), normalSchema);
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, operation.CommandId, SearchScope.All, default)) A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken))
.Returns(true); .Returns(true);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync()); await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
} }
private ContentOperation Operation(ContentEntity content, ISchemaEntity operationSchema) private ContentOperation Operation(ContentEntity content, ISchemaEntity operationSchema)
@ -392,8 +389,8 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
return new ContentOperation(serviceProvider, () => content) return new ContentOperation(serviceProvider, () => content)
{ {
App = Mocks.App(appId), App = App,
Command = new CreateContent { User = currentUser, Actor = actor }, Command = new CreateContent { User = currentUser, Actor = User },
CommandId = content.Id, CommandId = content.Id,
Schema = operationSchema Schema = operationSchema
}; };
@ -412,10 +409,10 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
private ContentEntity CreateContentCore(ContentEntity content, DomainId? id = null) private ContentEntity CreateContentCore(ContentEntity content, DomainId? id = null)
{ {
content.Id = id ?? DomainId.NewGuid(); content.Id = id ?? DomainId.NewGuid();
content.AppId = appId; content.AppId = AppId;
content.Created = default; content.Created = default;
content.CreatedBy = actor; content.CreatedBy = User;
content.SchemaId = schemaId; content.SchemaId = SchemaId;
return content; return content;
} }

43
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs

@ -10,7 +10,6 @@ using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -18,12 +17,8 @@ using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Contents; namespace Squidex.Domain.Apps.Entities.Contents;
public class DynamicContentWorkflowTests public class DynamicContentWorkflowTests : GivenContext
{ {
private readonly IAppEntity app;
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly NamedId<DomainId> simpleSchemaId = NamedId.Of(DomainId.NewGuid(), "my-simple-schema"); private readonly NamedId<DomainId> simpleSchemaId = NamedId.Of(DomainId.NewGuid(), "my-simple-schema");
private readonly DynamicContentWorkflow sut; private readonly DynamicContentWorkflow sut;
@ -58,8 +53,6 @@ public class DynamicContentWorkflowTests
public DynamicContentWorkflowTests() public DynamicContentWorkflowTests()
{ {
app = Mocks.App(appId);
var simpleWorkflow = new Workflow( var simpleWorkflow = new Workflow(
Status.Draft, Status.Draft,
new Dictionary<Status, WorkflowStep> new Dictionary<Status, WorkflowStep>
@ -83,10 +76,7 @@ public class DynamicContentWorkflowTests
var workflows = Workflows.Empty.Set(workflow).Set(DomainId.NewGuid(), simpleWorkflow); var workflows = Workflows.Empty.Set(workflow).Set(DomainId.NewGuid(), simpleWorkflow);
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false, default)) A.CallTo(() => App.Workflows)
.Returns(app);
A.CallTo(() => app.Workflows)
.Returns(workflows); .Returns(workflows);
var scriptEngine = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), var scriptEngine = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())),
@ -96,7 +86,7 @@ public class DynamicContentWorkflowTests
TimeoutExecution = TimeSpan.FromSeconds(10) TimeoutExecution = TimeSpan.FromSeconds(10)
})); }));
sut = new DynamicContentWorkflow(scriptEngine, appProvider); sut = new DynamicContentWorkflow(scriptEngine, AppProvider);
} }
[Fact] [Fact]
@ -122,7 +112,7 @@ public class DynamicContentWorkflowTests
[Fact] [Fact]
public async Task Should_return_draft_as_initial_status() public async Task Should_return_draft_as_initial_status()
{ {
var actual = await sut.GetInitialStatusAsync(Mocks.Schema(appId, schemaId)); var actual = await sut.GetInitialStatusAsync(Schema);
Assert.Equal(Status.Draft, actual); Assert.Equal(Status.Draft, actual);
} }
@ -130,7 +120,7 @@ public class DynamicContentWorkflowTests
[Fact] [Fact]
public async Task Should_allow_publish_on_create() public async Task Should_allow_publish_on_create()
{ {
var actual = await sut.CanPublishInitialAsync(Mocks.Schema(appId, schemaId), Mocks.FrontendUser("Editor")); var actual = await sut.CanPublishInitialAsync(Schema, Mocks.FrontendUser("Editor"));
Assert.True(actual); Assert.True(actual);
} }
@ -138,7 +128,7 @@ public class DynamicContentWorkflowTests
[Fact] [Fact]
public async Task Should_not_allow_publish_on_create_if_role_not_allowed() public async Task Should_not_allow_publish_on_create_if_role_not_allowed()
{ {
var actual = await sut.CanPublishInitialAsync(Mocks.Schema(appId, schemaId), Mocks.FrontendUser("Developer")); var actual = await sut.CanPublishInitialAsync(Schema, Mocks.FrontendUser("Developer"));
Assert.False(actual); Assert.False(actual);
} }
@ -148,7 +138,7 @@ public class DynamicContentWorkflowTests
{ {
var content = CreateContent(Status.Draft, 2); var content = CreateContent(Status.Draft, 2);
var actual = await sut.CanMoveToAsync(Mocks.Schema(appId, schemaId), content.Status, Status.Published, content.Data, Mocks.FrontendUser("Editor")); var actual = await sut.CanMoveToAsync(Schema, content.Status, Status.Published, content.Data, Mocks.FrontendUser("Editor"));
Assert.True(actual); Assert.True(actual);
} }
@ -350,7 +340,7 @@ public class DynamicContentWorkflowTests
new StatusInfo(Status.Published, StatusColors.Published) new StatusInfo(Status.Published, StatusColors.Published)
}; };
var actual = await sut.GetAllAsync(Mocks.Schema(appId, schemaId)); var actual = await sut.GetAllAsync(Schema);
actual.Should().BeEquivalentTo(expected); actual.Should().BeEquivalentTo(expected);
} }
@ -364,7 +354,7 @@ public class DynamicContentWorkflowTests
new StatusInfo(Status.Published, StatusColors.Published) new StatusInfo(Status.Published, StatusColors.Published)
}; };
var actual = await sut.GetAllAsync(Mocks.Schema(appId, simpleSchemaId)); var actual = await sut.GetAllAsync(Mocks.Schema(AppId, simpleSchemaId));
actual.Should().BeEquivalentTo(expected); actual.Should().BeEquivalentTo(expected);
} }
@ -372,7 +362,8 @@ public class DynamicContentWorkflowTests
[Fact] [Fact]
public async Task Should_return_all_statuses_for_default_workflow_if_no_workflow_configured() public async Task Should_return_all_statuses_for_default_workflow_if_no_workflow_configured()
{ {
A.CallTo(() => app.Workflows).Returns(Workflows.Empty); A.CallTo(() => App.Workflows)
.Returns(Workflows.Empty);
var expected = new[] var expected = new[]
{ {
@ -381,7 +372,7 @@ public class DynamicContentWorkflowTests
new StatusInfo(Status.Published, StatusColors.Published) new StatusInfo(Status.Published, StatusColors.Published)
}; };
var actual = await sut.GetAllAsync(Mocks.Schema(appId, simpleSchemaId)); var actual = await sut.GetAllAsync(Mocks.Schema(AppId, simpleSchemaId));
actual.Should().BeEquivalentTo(expected); actual.Should().BeEquivalentTo(expected);
} }
@ -389,7 +380,7 @@ public class DynamicContentWorkflowTests
[Fact] [Fact]
public async Task Should_not_validate_when_not_publishing() public async Task Should_not_validate_when_not_publishing()
{ {
var actual = await sut.ShouldValidateAsync(Mocks.Schema(appId, schemaId), Status.Draft); var actual = await sut.ShouldValidateAsync(Schema, Status.Draft);
Assert.False(actual); Assert.False(actual);
} }
@ -413,7 +404,7 @@ public class DynamicContentWorkflowTests
[Fact] [Fact]
public async Task Should_validate_when_enabled_in_step() public async Task Should_validate_when_enabled_in_step()
{ {
var actual = await sut.ShouldValidateAsync(Mocks.Schema(appId, schemaId), Status.Archived); var actual = await sut.ShouldValidateAsync(Schema, Status.Archived);
Assert.True(actual); Assert.True(actual);
} }
@ -425,12 +416,12 @@ public class DynamicContentWorkflowTests
ValidateOnPublish = validateOnPublish ValidateOnPublish = validateOnPublish
}); });
return Mocks.Schema(appId, simpleSchemaId, schema); return Mocks.Schema(AppId, simpleSchemaId, schema);
} }
private ContentEntity CreateContent(Status status, int value, bool simple = false) private ContentEntity CreateContent(Status status, int value, bool simple = false)
{ {
var content = new ContentEntity { AppId = appId, Status = status }; var content = new ContentEntity { AppId = AppId, Status = status };
if (simple) if (simple)
{ {
@ -438,7 +429,7 @@ public class DynamicContentWorkflowTests
} }
else else
{ {
content.SchemaId = schemaId; content.SchemaId = SchemaId;
} }
content.Data = content.Data =

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs

@ -8,7 +8,6 @@
using System.Globalization; using System.Globalization;
using LoremNET; using LoremNET;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
@ -18,9 +17,7 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Domain.Apps.Entities.MongoDb.Assets;
using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;

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

@ -8,42 +8,30 @@
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.Contents.Queries; namespace Squidex.Domain.Apps.Entities.Contents.Queries;
public class CalculateTokensTests public class CalculateTokensTests : GivenContext
{ {
private readonly ISchemaEntity schema;
private readonly IJsonSerializer serializer = A.Fake<IJsonSerializer>(); private readonly IJsonSerializer serializer = A.Fake<IJsonSerializer>();
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly Context requestContext;
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly ProvideSchema schemaProvider;
private readonly CalculateTokens sut; private readonly CalculateTokens sut;
public CalculateTokensTests() public CalculateTokensTests()
{ {
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId));
schema = Mocks.Schema(appId, schemaId);
schemaProvider = x => Task.FromResult((schema, ResolvedComponents.Empty));
sut = new CalculateTokens(urlGenerator, serializer); sut = new CalculateTokens(urlGenerator, serializer);
} }
[Fact] [Fact]
public async Task Should_compute_ui_tokens() public async Task Should_compute_ui_tokens()
{ {
var source = CreateContent(); var content = CreateContent();
await sut.EnrichAsync(requestContext, new[] { source }, schemaProvider, default); await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken);
Assert.NotNull(source.EditToken); Assert.NotNull(content.EditToken);
A.CallTo(() => urlGenerator.Root()) A.CallTo(() => urlGenerator.Root())
.MustHaveHappened(); .MustHaveHappened();
@ -52,11 +40,11 @@ public class CalculateTokensTests
[Fact] [Fact]
public async Task Should_also_compute_ui_tokens_for_frontend() public async Task Should_also_compute_ui_tokens_for_frontend()
{ {
var source = CreateContent(); var content = CreateContent();
await sut.EnrichAsync(new Context(Mocks.FrontendUser(), Mocks.App(appId)), new[] { source }, schemaProvider, default); await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken);
Assert.NotNull(source.EditToken); Assert.NotNull(content.EditToken);
A.CallTo(() => urlGenerator.Root()) A.CallTo(() => urlGenerator.Root())
.MustHaveHappened(); .MustHaveHappened();
@ -64,6 +52,11 @@ public class CalculateTokensTests
private ContentEntity CreateContent() private ContentEntity CreateContent()
{ {
return new ContentEntity { AppId = appId, SchemaId = schemaId }; return new ContentEntity { AppId = AppId, SchemaId = SchemaId };
}
private ProvideSchema SchemaProvider()
{
return x => Task.FromResult((Schema, ResolvedComponents.Empty));
} }
} }

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

Loading…
Cancel
Save