Browse Source

Asset query scripts (#962)

* First implementation.

* Tests simplified.

* API test and UI fixes
pull/965/head
Sebastian Stehle 3 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? 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;
public struct ScriptOptions
public record struct ScriptOptions
{
public bool CanReject { get; set; }
public bool CanDisallow { 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;
}
public ValueTask ValidateInputPartialAsync(ContentData data)
public ValueTask ValidateInputPartialAsync(ContentData data,
CancellationToken ct = default)
{
Guard.NotNull(data);
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);
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);
ValidateInputCore(data, false);
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);
ValidateContentCore(data);
return context.Root.CompleteAsync();
return context.Root.CompleteAsync(ct);
}
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.
// ==========================================================================
using System.Net;
using System.Net.Http.Json;
using Squidex.Hosting.Configuration;

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

@ -8,6 +8,7 @@
using System.Security.Cryptography;
using Squidex.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.Queries;
using Squidex.Infrastructure;
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))
{
await UpdateCore(c.AsUpdate(), operation);
await UpdateCore(c.AsUpdate(), operation, ct);
}
else
{
await CreateCore(c.AsCreate(), operation);
await CreateCore(c.AsCreate(), operation, ct);
}
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;
@ -90,11 +90,11 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await CreateCore(c, operation);
await CreateCore(c, operation, ct);
if (Is.Change(Snapshot.ParentId, c.ParentId))
{
await MoveCore(c.AsMove(), operation);
await MoveCore(c.AsMove(), operation, ct);
}
return Snapshot;
@ -105,7 +105,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await AnnotateCore(c, operation);
await AnnotateCore(c, operation, ct);
return Snapshot;
}, ct);
@ -115,7 +115,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await UpdateCore(c, operation);
await UpdateCore(c, operation, ct);
return Snapshot;
}, ct);
@ -125,7 +125,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await MoveCore(c, operation);
await MoveCore(c, operation, ct);
return Snapshot;
}, ct);
@ -135,7 +135,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation);
await DeleteCore(c, operation, ct);
}, ct);
case DeleteAsset delete:
@ -143,7 +143,7 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{
var operation = await AssetOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation);
await DeleteCore(c, operation, ct);
}, ct);
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)
{
await operation.MustMoveToValidFolder(create.ParentId);
await operation.MustMoveToValidFolder(create.ParentId, ct);
}
if (!create.DoNotScript)
{
await operation.ExecuteCreateScriptAsync(create);
await operation.ExecuteCreateScriptAsync(create, ct);
}
if (create.Tags != null)
@ -172,11 +173,12 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
Create(create);
}
private async Task AnnotateCore(AnnotateAsset annotate, AssetOperation operation)
private async Task AnnotateCore(AnnotateAsset annotate, AssetOperation operation,
CancellationToken ct)
{
if (!annotate.DoNotScript)
{
await operation.ExecuteAnnotateScriptAsync(annotate);
await operation.ExecuteAnnotateScriptAsync(annotate, ct);
}
if (annotate.Tags != null)
@ -187,41 +189,44 @@ public partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
Annotate(annotate);
}
private async Task UpdateCore(UpdateAsset update, AssetOperation operation)
private async Task UpdateCore(UpdateAsset update, AssetOperation operation,
CancellationToken ct)
{
if (!update.DoNotScript)
{
await operation.ExecuteUpdateScriptAsync(update);
await operation.ExecuteUpdateScriptAsync(update, ct);
}
Update(update);
}
private async Task MoveCore(MoveAsset move, AssetOperation operation)
private async Task MoveCore(MoveAsset move, AssetOperation operation,
CancellationToken ct)
{
if (!move.OptimizeValidation)
{
await operation.MustMoveToValidFolder(move.ParentId);
await operation.MustMoveToValidFolder(move.ParentId, ct);
}
if (!move.DoNotScript)
{
await operation.ExecuteMoveScriptAsync(move);
await operation.ExecuteMoveScriptAsync(move, ct);
}
Move(move);
}
private async Task DeleteCore(DeleteAsset delete, AssetOperation operation)
private async Task DeleteCore(DeleteAsset delete, AssetOperation operation,
CancellationToken ct)
{
if (delete.CheckReferrers)
{
await operation.CheckReferrersAsync();
await operation.CheckReferrersAsync(ct);
}
if (!delete.DoNotScript)
{
await operation.ExecuteDeleteScriptAsync(delete);
await operation.ExecuteDeleteScriptAsync(delete, ct);
}
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:
return CreateReturnAsync(create, async (c, ct) =>
{
await CreateCore(c, c);
await CreateCore(c, ct);
return Snapshot;
}, ct);
@ -64,7 +64,7 @@ public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDo
case MoveAssetFolder move:
return UpdateReturnAsync(move, async (c, ct) =>
{
await MoveCore(c);
await MoveCore(c, ct);
return Snapshot;
}, 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);
@ -97,19 +98,20 @@ public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDo
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);
if (!c.OptimizeValidation)
{
await operation.MustMoveToValidFolder(c.ParentId);
await operation.MustMoveToValidFolder(c.ParentId, ct);
}
Move(c);

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

@ -21,7 +21,8 @@ public static class ScriptingExtensions
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;
@ -30,7 +31,7 @@ public static class ScriptingExtensions
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.
var vars = new AssetScriptVars
@ -51,10 +52,11 @@ public static class ScriptingExtensions
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;
@ -79,10 +81,11 @@ public static class ScriptingExtensions
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;
@ -106,10 +109,11 @@ public static class ScriptingExtensions
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;
@ -118,7 +122,7 @@ public static class ScriptingExtensions
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.
var vars = new AssetScriptVars
@ -131,10 +135,11 @@ public static class ScriptingExtensions
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;
@ -153,14 +158,15 @@ public static class ScriptingExtensions
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 parentPath = await GetPathAsync(operation, snapshot.ParentId);
var parentPath = await GetPathAsync(operation, snapshot.ParentId, ct);
// Script vars are just wrappers over dictionaries for better performance.
var asset = new AssetEntityScriptVars
@ -186,10 +192,11 @@ public static class ScriptingExtensions
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)
{
@ -197,7 +204,7 @@ public static class ScriptingExtensions
}
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();
}

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

@ -26,7 +26,8 @@ public static class ValidationExtensions
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 (parentId == DomainId.Empty || parentId == operation.Snapshot.ParentId)
@ -36,7 +37,7 @@ public static class ValidationExtensions
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)
{
@ -46,7 +47,8 @@ public static class ValidationExtensions
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 (parentId == DomainId.Empty || parentId == operation.Snapshot.ParentId)
@ -56,7 +58,7 @@ public static class ValidationExtensions
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)
{
@ -77,11 +79,12 @@ public static class ValidationExtensions
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 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)
{

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

@ -5,32 +5,18 @@
// 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.Caching;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public sealed class AssetEnricher : IAssetEnricher
{
private readonly ITagService tagService;
private readonly IEnumerable<IAssetMetadataSource> assetMetadataSources;
private readonly IRequestCache requestCache;
private readonly IUrlGenerator urlGenerator;
private readonly IJsonSerializer serializer;
public AssetEnricher(ITagService tagService, IEnumerable<IAssetMetadataSource> assetMetadataSources, IRequestCache requestCache,
IUrlGenerator urlGenerator, IJsonSerializer serializer)
private readonly IEnumerable<IAssetEnricherStep> steps;
public AssetEnricher(IEnumerable<IAssetEnricherStep> steps)
{
this.tagService = tagService;
this.assetMetadataSources = assetMetadataSources;
this.requestCache = requestCache;
this.urlGenerator = urlGenerator;
this.serializer = serializer;
this.steps = steps;
}
public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset, Context context,
@ -52,108 +38,42 @@ public sealed class AssetEnricher : IAssetEnricher
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);
EnrichWithMetadataText(results);
EnrichWithEditTokens(results);
return results;
}
return results;
}
}
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
foreach (var asset in assets)
{
a = asset.AppId.Name,
i = asset.Id.ToString(),
u = url
};
var result = SimpleMapper.Map(asset, new AssetEntity());
var json = serializer.Serialize(token);
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);
results.Add(result);
}
}
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);
}
}
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);
ct.ThrowIfCancellationRequested();
foreach (var asset in group)
{
asset.TagNames = new HashSet<string>();
if (asset.Tags != null)
{
foreach (var id in asset.Tags)
using (Telemetry.Activities.StartActivity(step.ToString()!))
{
if (tagsById.TryGetValue(id, out var name))
{
asset.TagNames.Add(name);
}
await step.EnrichAsync(context, results, ct);
}
}
}
}
}
private async Task<Dictionary<string, string>> CalculateTagsAsync(IGrouping<DomainId, IAssetEntity> group,
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);
return results;
}
}
}

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.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Assets;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
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))
{
await CreateCore(c.AsCreate(), operation);
await CreateCore(c.AsCreate(), operation, ct);
}
else if (c.Patch)
{
await PatchCore(c.AsUpdate(), operation);
await PatchCore(c.AsUpdate(), operation, ct);
}
else
{
await UpdateCore(c.AsUpdate(), operation);
await UpdateCore(c.AsUpdate(), operation, ct);
}
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;
@ -99,7 +99,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await CreateCore(c, operation);
await CreateCore(c, operation, ct);
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))
{
await ChangeCore(c.AsChange(c.Status.Value), operation);
await ChangeCore(c.AsChange(c.Status.Value), operation, ct);
}
return Snapshot;
@ -118,7 +118,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await ValidateCore(operation);
await ValidateCore(operation, ct);
return true;
}, ct);
@ -148,7 +148,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await PatchCore(c, operation);
await PatchCore(c, operation, ct);
return Snapshot;
}, ct);
@ -158,7 +158,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await UpdateCore(c, operation);
await UpdateCore(c, operation, ct);
return Snapshot;
}, ct);
@ -186,7 +186,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await ChangeCore(c, operation);
await ChangeCore(c, operation, ct);
}
}
catch (Exception)
@ -209,7 +209,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation);
await DeleteCore(c, operation, ct);
}, ct);
case DeleteContent deleteContent:
@ -217,7 +217,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
{
var operation = await ContentOperation.CreateAsync(serviceProvider, c, () => Snapshot);
await DeleteCore(c, operation);
await DeleteCore(c, operation, ct);
}, ct);
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.MustNotCreateSingleton();
@ -235,27 +236,28 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
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();
if (!c.DoNotScript)
{
c.Data = await operation.ExecuteCreateScriptAsync(c.Data, status);
c.Data = await operation.ExecuteCreateScriptAsync(c.Data, status, ct);
}
operation.GenerateDefaultValues(c.Data);
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);
}
private async Task ChangeCore(ChangeContentStatus c, ContentOperation operation)
private async Task ChangeCore(ChangeContentStatus c, ContentOperation operation,
CancellationToken ct)
{
operation.MustHavePermission(PermissionIds.AppContentsChangeStatus);
operation.MustNotChangeSingleton(c.Status);
@ -276,7 +278,7 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
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))
{
@ -297,25 +299,26 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (c.CheckReferrers && Snapshot.IsPublished())
{
await operation.CheckReferrersAsync();
await operation.CheckReferrersAsync(ct);
}
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);
}
private async Task UpdateCore(UpdateContent c, ContentOperation operation)
private async Task UpdateCore(UpdateContent c, ContentOperation operation,
CancellationToken ct)
{
operation.MustHavePermission(PermissionIds.AppContentsUpdate);
operation.MustHaveData(c.Data);
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)
@ -332,25 +335,26 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (!c.DoNotScript)
{
newData = await operation.ExecuteUpdateScriptAsync(newData);
newData = await operation.ExecuteUpdateScriptAsync(newData, ct);
}
if (!c.DoNotValidate)
{
await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished());
await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished(), ct);
}
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.MustHaveData(c.Data);
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)
@ -367,12 +371,12 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
if (!c.DoNotScript)
{
newData = await operation.ExecuteUpdateScriptAsync(newData);
newData = await operation.ExecuteUpdateScriptAsync(newData, ct);
}
if (!c.DoNotValidate)
{
await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished());
await operation.ValidateContentAsync(newData, c.OptimizeValidation, Snapshot.IsPublished(), ct);
}
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);
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)
@ -413,19 +418,20 @@ public partial class ContentDomainObject : DomainObject<ContentDomainObject.Stat
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.MustNotDeleteSingleton();
if (!c.DoNotScript)
{
await operation.ExecuteDeleteScriptAsync(c.Permanent);
await operation.ExecuteDeleteScriptAsync(c.Permanent, ct);
}
if (c.CheckReferrers)
{
await operation.CheckReferrersAsync();
await operation.CheckReferrersAsync(ct);
}
Delete(c);

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

@ -19,7 +19,8 @@ public static class ScriptingExtensions
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;
@ -40,10 +41,11 @@ public static class ScriptingExtensions
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;
@ -64,10 +66,11 @@ public static class ScriptingExtensions
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;
@ -89,10 +92,11 @@ public static class ScriptingExtensions
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;
@ -114,17 +118,19 @@ public static class ScriptingExtensions
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)
@ -135,7 +141,7 @@ public static class ScriptingExtensions
{
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)
{

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

@ -47,38 +47,42 @@ public static class ValidationExtensions
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);
await validator.ValidateInputAsync(data);
await validator.ValidateInputAsync(data, ct);
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);
await validator.ValidateInputPartialAsync(data);
await validator.ValidateInputPartialAsync(data, ct);
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);
await validator.ValidateContentAsync(data);
await validator.ValidateContentAsync(data, ct);
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);
await validator.ValidateInputAndContentAsync(data);
await validator.ValidateInputAndContentAsync(data, ct);
operation.AddErrors(validator.Errors).ThrowOnErrors();
}
@ -88,11 +92,12 @@ public static class ValidationExtensions
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 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)
{

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)
{
var result = SimpleMapper.Map(content, new ContentEntity());
return results;
}
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)
{
var schemaCache = new Dictionary<DomainId, Task<(ISchemaEntity, ResolvedComponents)>>();
results.Add(result);
}
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)
{
throw new DomainObjectNotFoundException(x.ToString());
}
var components = await appProvider.GetComponentsAsync(schema, ct);
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();
using (Telemetry.Activities.StartActivity(step.ToString()!))
{
await step.EnrichAsync(context, results, GetSchema, ct);
}
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,
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 (schema, components) = await schemas(group.Key);
var ids = new HashSet<DomainId>();
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 (schema, components) = await schemas(group.Key);
var assets = await GetAssetsAsync(context, ids, ct);
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;
}
// Script vars are just wrappers over dictionaries for better performance.
var vars = new ContentScriptVars
{
AppId = schema.AppId.Id,
@ -68,6 +69,7 @@ public sealed class ScriptContent : IContentEnricherStep
private async Task TransformAsync(ContentScriptVars sharedVars, string script, ContentEntity content,
CancellationToken ct)
{
// Script vars are just wrappers over dictionaries for better performance.
var vars = new ContentScriptVars
{
ContentId = content.Id,
@ -87,7 +89,9 @@ public sealed class ScriptContent : IContentEnricherStep
var options = new ScriptOptions
{
AsContext = true
AsContext = true,
CanDisallow = true,
CanReject = true
};
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 app = await appProvider.GetAppAsync(appName, !isFrontend, default);
var app = await appProvider.GetAppAsync(appName, !isFrontend, context.HttpContext.RequestAborted);
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.DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull;
settings.FlattenInheritanceHierarchy = flatten;
settings.SchemaNameGenerator = new SchemaNameGenerator();
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
{
/// <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>
/// The script that is executed when creating an asset.
/// </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
{
/// <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>
/// The script that is executed when creating an asset.
/// </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.Assets;
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.Search;
using Squidex.Hosting;
@ -72,6 +73,21 @@ public static class AssetServices
services.AddSingletonAs<AssetEnricher>()
.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>()
.As<IAssetQueryService>();

2
backend/src/Squidex/Squidex.csproj

@ -57,7 +57,7 @@
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="2.1.1" />
<PackageReference Include="NJsonSchema" Version="10.8.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="OpenTelemetry.Instrumentation.AspNetCore" 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;
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> componentId1 = 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]
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);
A.CallTo(() => appProvider.GetSchemaAsync(A<DomainId>._, A<DomainId>._, false, A<CancellationToken>._))
A.CallTo(() => AppProvider.GetSchemaAsync(A<DomainId>._, A<DomainId>._, false, A<CancellationToken>._))
.MustNotHaveHappened();
}
@ -37,39 +35,39 @@ public class AppProviderExtensionsTests
public async Task Should_resolve_self_as_component()
{
var schema =
Mocks.Schema(appId, schemaId,
Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
SchemaId = schemaId.Id
}));
var components = await appProvider.GetComponentsAsync(schema);
var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components);
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();
}
[Fact]
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);
var schema =
Mocks.Schema(appId, schemaId,
Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
SchemaId = componentId1.Id
}));
var components = await appProvider.GetComponentsAsync(schema);
var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -78,20 +76,20 @@ public class AppProviderExtensionsTests
[Fact]
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);
var schema =
Mocks.Schema(appId, schemaId,
Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name)
.AddComponents(1, "1", Partitioning.Invariant, new ComponentsFieldProperties
{
SchemaId = componentId1.Id
}));
var components = await appProvider.GetComponentsAsync(schema);
var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -100,13 +98,13 @@ public class AppProviderExtensionsTests
[Fact]
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);
var schema =
Mocks.Schema(appId, schemaId,
Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name)
.AddArray(1, "1", Partitioning.Invariant, a => a
.AddComponent(2, "2", new ComponentFieldProperties
@ -114,7 +112,7 @@ public class AppProviderExtensionsTests
SchemaId = componentId1.Id
})));
var components = await appProvider.GetComponentsAsync(schema);
var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -124,25 +122,25 @@ public class AppProviderExtensionsTests
public async Task Should_resolve_self_referencing_component()
{
var component =
Mocks.Schema(appId, componentId1,
Mocks.Schema(AppId, componentId1,
new Schema(componentId1.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
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);
var schema =
Mocks.Schema(appId, schemaId,
Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
SchemaId = componentId1.Id
}));
var components = await appProvider.GetComponentsAsync(schema);
var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Single(components);
Assert.Same(component.SchemaDef, components[componentId1.Id]);
@ -152,7 +150,7 @@ public class AppProviderExtensionsTests
public async Task Should_resolve_component_of_component()
{
var component1 =
Mocks.Schema(appId, componentId1,
Mocks.Schema(AppId, componentId1,
new Schema(componentId1.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
@ -160,28 +158,28 @@ public class AppProviderExtensionsTests
}));
var component2 =
Mocks.Schema(appId, componentId2,
Mocks.Schema(AppId, componentId2,
new Schema(componentId2.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
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);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, componentId2.Id, false, default))
A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId2.Id, false, CancellationToken))
.Returns(component2);
var schema =
Mocks.Schema(appId, schemaId,
Mocks.Schema(AppId, schemaId,
new Schema(schemaId.Name)
.AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties
{
SchemaId = componentId1.Id
}));
var components = await appProvider.GetComponentsAsync(schema);
var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken);
Assert.Equal(2, components.Count);
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;
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 IRulesIndex indexForRules = A.Fake<IRulesIndex>();
private readonly ISchemasIndex indexForSchemas = A.Fake<ISchemasIndex>();
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;
public AppProviderTests()
{
ct = cts.Token;
app = Mocks.App(appId);
sut = new AppProvider(indexForApps, indexForRules, indexForSchemas, indexForTeams, new AsyncLocalCache());
}
[Fact]
public async Task Should_get_app_with_schema_from_index()
{
var schema = Mocks.Schema(app.NamedId(), schemaId);
A.CallTo(() => indexForApps.GetAppAsync(app.Id, false, ct))
.Returns(app);
A.CallTo(() => indexForApps.GetAppAsync(AppId.Id, false, CancellationToken))
.Returns(App);
A.CallTo(() => indexForSchemas.GetSchemaAsync(app.Id, schema.Id, false, ct))
.Returns(schema);
A.CallTo(() => indexForSchemas.GetSchemaAsync(AppId.Id, SchemaId.Id, false, CancellationToken))
.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]
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))
.Returns(new List<IAppEntity> { app });
var actual = await sut.GetTeamAppsAsync(TeamId, CancellationToken);
var actual = await sut.GetTeamAppsAsync(team.Id, ct);
Assert.Equal(app, actual.Single());
Assert.Equal(App, actual.Single());
}
[Fact]
@ -76,99 +63,89 @@ public class AppProviderTests
{
var permissions = new PermissionSet("*");
A.CallTo(() => indexForApps.GetAppsForUserAsync("user1", permissions, ct))
.Returns(new List<IAppEntity> { app });
A.CallTo(() => indexForApps.GetAppsForUserAsync("user1", permissions, CancellationToken))
.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]
public async Task Should_get_app_from_index()
{
A.CallTo(() => indexForApps.GetAppAsync(app.Id, false, ct))
.Returns(app);
A.CallTo(() => indexForApps.GetAppAsync(AppId.Id, false, CancellationToken))
.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]
public async Task Should_get_app_by_name_from_index()
{
A.CallTo(() => indexForApps.GetAppAsync(app.Name, false, ct))
.Returns(app);
A.CallTo(() => indexForApps.GetAppAsync(AppId.Name, false, CancellationToken))
.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]
public async Task Should_get_team_from_index()
{
var team = Mocks.Team(DomainId.NewGuid());
A.CallTo(() => indexForTeams.GetTeamAsync(team.Id, ct))
.Returns(team);
A.CallTo(() => indexForTeams.GetTeamAsync(TeamId, CancellationToken))
.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]
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))
.Returns(new List<ITeamEntity> { team });
var actual = await sut.GetUserTeamsAsync("user1", CancellationToken);
var actual = await sut.GetUserTeamsAsync("user1", ct);
Assert.Equal(team, actual.Single());
Assert.Equal(Team, actual.Single());
}
[Fact]
public async Task Should_get_schema_from_index()
{
var schema = Mocks.Schema(app.NamedId(), schemaId);
A.CallTo(() => indexForSchemas.GetSchemaAsync(app.Id, schema.Id, false, ct))
.Returns(schema);
A.CallTo(() => indexForSchemas.GetSchemaAsync(AppId.Id, SchemaId.Id, false, CancellationToken))
.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]
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))
.Returns(schema);
var actual = await sut.GetSchemaAsync(AppId.Id, SchemaId.Name, false, CancellationToken);
var actual = await sut.GetSchemaAsync(app.Id, schemaId.Name, false, ct);
Assert.Equal(schema, actual);
Assert.Equal(Schema, actual);
}
[Fact]
public async Task Should_get_schemas_from_index()
{
var schema = Mocks.Schema(app.NamedId(), schemaId);
A.CallTo(() => indexForSchemas.GetSchemasAsync(app.Id, ct))
.Returns(new List<ISchemaEntity> { schema });
A.CallTo(() => indexForSchemas.GetSchemasAsync(AppId.Id, CancellationToken))
.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]
@ -176,10 +153,10 @@ public class AppProviderTests
{
var rule = new RuleEntity();
A.CallTo(() => indexForRules.GetRulesAsync(app.Id, ct))
A.CallTo(() => indexForRules.GetRulesAsync(AppId.Id, CancellationToken))
.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());
}
@ -189,10 +166,10 @@ public class AppProviderTests
{
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 });
var actual = await sut.GetRuleAsync(app.Id, rule.Id, ct);
var actual = await sut.GetRuleAsync(AppId.Id, rule.Id, CancellationToken);
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.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
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 AppEventDeleter sut;
public AppEventDeleterTests()
{
ct = cts.Token;
sut = new AppEventDeleter(eventStore);
}
@ -36,11 +31,9 @@ public class AppEventDeleterTests
[Fact]
public async Task Should_remove_events_from_streams()
{
var app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app"));
await sut.DeleteAppAsync(app, ct);
await sut.DeleteAppAsync(App, CancellationToken);
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();
}
}

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.TestHelpers;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Apps;
public class AppPermanentDeleterTests
public class AppPermanentDeleterTests : GivenContext
{
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IDeleter deleter1 = A.Fake<IDeleter>();
@ -90,24 +89,22 @@ public class AppPermanentDeleterTests
[Fact]
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
{
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();
A.CallTo(() => deleter2.DeleteContributorAsync(app.Id, "user1", default))
A.CallTo(() => deleter2.DeleteContributorAsync(AppId.Id, "user1", default))
.MustHaveHappened();
}
[Fact]
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>();
@ -119,7 +116,7 @@ public class AppPermanentDeleterTests
await sut.On(Envelope.Create(new AppDeleted
{
AppId = app.NamedId()
AppId = AppId
}));
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.
// ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Search;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Apps;
public sealed class AppSettingsSearchSourceTests
public sealed class AppSettingsSearchSourceTests : GivenContext
{
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AppSettingsSearchSource sut;
public AppSettingsSearchSourceTests()
@ -29,24 +25,20 @@ public sealed class AppSettingsSearchSourceTests
[Fact]
public async Task Should_return_empty_if_nothing_matching()
{
var ctx = ContextWithPermission();
var actual = await sut.SearchAsync("xyz", ctx, default);
var actual = await sut.SearchAsync("xyz", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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 ctx = ContextWithPermission(permission.Id);
var requestContext = SetupContext(PermissionIds.AppUsage);
A.CallTo(() => urlGenerator.DashboardUI(appId))
A.CallTo(() => urlGenerator.DashboardUI(AppId))
.Returns("dashboard-url");
var actual = await sut.SearchAsync("dashboard", ctx, default);
var actual = await sut.SearchAsync("dashboard", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -54,26 +46,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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 ctx = ContextWithPermission(permission.Id);
var requestContext = SetupContext(PermissionIds.AppLanguagesRead);
A.CallTo(() => urlGenerator.LanguagesUI(appId))
A.CallTo(() => urlGenerator.LanguagesUI(AppId))
.Returns("languages-url");
var actual = await sut.SearchAsync("languages", ctx, default);
var actual = await sut.SearchAsync("languages", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -81,36 +69,22 @@ public sealed class AppSettingsSearchSourceTests
}
[Fact]
public async Task Should_not_return_languages_actual_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()
public async Task Should_not_return_languages_result_if_user_has_no_permission()
{
var ctx = ContextWithPermission();
var actual = await sut.SearchAsync("patterns", ctx, default);
var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("schemas", ctx, default);
var actual = await sut.SearchAsync("schemas", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -118,26 +92,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("schemas", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("assets", ctx, default);
var actual = await sut.SearchAsync("assets", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -145,26 +115,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("backups", ctx, default);
var actual = await sut.SearchAsync("backups", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -172,26 +138,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("backups", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("clients", ctx, default);
var actual = await sut.SearchAsync("clients", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -199,26 +161,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("clients", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("contributors", ctx, default);
var actual = await sut.SearchAsync("contributors", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -226,26 +184,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("contributors", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("subscription", ctx, default);
var actual = await sut.SearchAsync("subscription", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -253,26 +207,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("subscription", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("roles", ctx, default);
var actual = await sut.SearchAsync("roles", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -280,26 +230,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("roles", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("rules", ctx, default);
var actual = await sut.SearchAsync("rules", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -307,26 +253,22 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("assets", ApiContext, CancellationToken);
Assert.Empty(actual);
}
[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");
var actual = await sut.SearchAsync("workflows", ctx, default);
var actual = await sut.SearchAsync("workflows", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -334,25 +276,15 @@ public sealed class AppSettingsSearchSourceTests
}
[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", ctx, default);
var actual = await sut.SearchAsync("workflows", ApiContext, CancellationToken);
Assert.Empty(actual);
}
private Context ContextWithPermission(string? permission = null)
private Context SetupContext(string permission)
{
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));
return CreateContext(false, PermissionIds.ForApp(permission, AppId.Name).Id);
}
}

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.Apps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.TestHelpers;
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 DomainId appId = DomainId.NewGuid();
private readonly string userId = Guid.NewGuid().ToString();
private readonly string stateId;
private readonly AppUISettings sut;
public AppUISettingsTests()
{
ct = cts.Token;
stateId = $"{appId}_{userId}";
stateId = $"{AppId.Id}_{userId}";
state = new TestState<AppUISettings.State>(stateId);
sut = new AppUISettings(state.PersistenceFactory);
@ -45,87 +39,85 @@ public sealed class AppUISettingsTests
[Fact]
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();
}
[Fact]
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));
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();
A.CallTo(() => rootState.Persistence.DeleteAsync(ct))
A.CallTo(() => rootState.Persistence.DeleteAsync(CancellationToken))
.MustHaveHappened();
}
[Fact]
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 =
new JsonObject().Add("key", 42);
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();
}
[Fact]
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 =
new JsonObject().Add("key", 42);
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();
}
[Fact]
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, userId, "key", ct);
await sut.RemoveAsync(AppId.Id, userId, "key", CancellationToken);
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();
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();
}
[Fact]
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 =
new JsonObject().Add("root",
@ -133,17 +125,17 @@ public sealed class AppUISettingsTests
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();
}
[Fact]
public async Task Should_not_write_state_if_value_not_changed()
{
await sut.SetAsync(appId, userId, "root.nested", 42, ct);
await sut.SetAsync(appId, userId, "root.nested", 42, ct);
await sut.SetAsync(AppId.Id, userId, "root.nested", 42, CancellationToken);
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 =
new JsonObject().Add("root",
@ -151,19 +143,19 @@ public sealed class AppUISettingsTests
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();
}
[Fact]
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, userId, "key", ct);
await sut.RemoveAsync(AppId.Id, userId, "root.nested", CancellationToken);
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().Add("root",
@ -171,22 +163,22 @@ public sealed class AppUISettingsTests
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();
}
[Fact]
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]
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>._))
.MustNotHaveHappened();
@ -195,7 +187,7 @@ public sealed class AppUISettingsTests
[Fact]
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>._))
.MustNotHaveHappened();

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

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

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

@ -8,14 +8,13 @@
using Microsoft.Extensions.Options;
using Squidex.Assets;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Domain.Apps.Entities.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Apps;
public class DefaultAppImageStoreTests
public class DefaultAppImageStoreTests : GivenContext
{
private readonly IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly string fileNameDefault;
private readonly string fileNameFolder;
private readonly AssetOptions options = new AssetOptions();
@ -23,8 +22,8 @@ public class DefaultAppImageStoreTests
public DefaultAppImageStoreTests()
{
fileNameDefault = appId.ToString();
fileNameFolder = $"{appId}/thumbnail";
fileNameDefault = AppId.Id.ToString();
fileNameFolder = $"{AppId.Id}/thumbnail";
sut = new DefaultAppImageStore(assetStore, Options.Create(options));
}
@ -40,9 +39,9 @@ public class DefaultAppImageStoreTests
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();
}
@ -57,9 +56,9 @@ public class DefaultAppImageStoreTests
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();
}

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

@ -7,23 +7,17 @@
using System.Globalization;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
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 DomainId appId = DomainId.NewGuid();
private readonly DefaultAppLogStore sut;
public DefaultAppLogStoreTests()
{
ct = cts.Token;
sut = new DefaultAppLogStore(requestLogStore);
}
@ -38,11 +32,9 @@ public class DefaultAppLogStoreTests
[Fact]
public async Task Should_remove_events_from_streams()
{
var app = Mocks.App(NamedId.Of(appId, "my-app"));
await ((IDeleter)sut).DeleteAppAsync(app, ct);
await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
A.CallTo(() => requestLogStore.DeleteAsync($"^[a-z]-{app.Id}", A<CancellationToken>._))
A.CallTo(() => requestLogStore.DeleteAsync($"^[a-z]-{AppId.Id}", A<CancellationToken>._))
.MustNotHaveHappened();
}
@ -52,7 +44,7 @@ public class DefaultAppLogStoreTests
A.CallTo(() => requestLogStore.IsEnabled)
.Returns(false);
await sut.LogAsync(appId, default, ct);
await sut.LogAsync(AppId.Id, default, CancellationToken);
A.CallTo(() => requestLogStore.LogAsync(A<Request>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -66,7 +58,7 @@ public class DefaultAppLogStoreTests
A.CallTo(() => requestLogStore.IsEnabled)
.Returns(true);
A.CallTo(() => requestLogStore.LogAsync(A<Request>._, ct))
A.CallTo(() => requestLogStore.LogAsync(A<Request>._, CancellationToken))
.Invokes(x => recordedRequest = x.GetArgument<Request>(0)!);
var request = default(RequestLog);
@ -84,7 +76,7 @@ public class DefaultAppLogStoreTests
request.UserClientId = "frontend";
request.UserId = "user1";
await sut.LogAsync(appId, request, ct);
await sut.LogAsync(AppId.Id, request, CancellationToken);
Assert.NotNull(recordedRequest);
@ -100,7 +92,7 @@ public class DefaultAppLogStoreTests
Contains(request.UserClientId, recordedRequest);
Contains(request.UserId, recordedRequest);
Assert.Equal(appId.ToString(), recordedRequest?.Key);
Assert.Equal(AppId.Id.ToString(), recordedRequest?.Key);
}
[Fact]
@ -109,7 +101,7 @@ public class DefaultAppLogStoreTests
var dateFrom = DateTime.UtcNow.Date.AddDays(-30);
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[]
{
CreateRecord(),
@ -120,7 +112,7 @@ public class DefaultAppLogStoreTests
var stream = new MemoryStream();
await sut.ReadLogAsync(appId, dateFrom, dateTo, stream, ct);
await sut.ReadLogAsync(AppId.Id, dateFrom, dateTo, stream, CancellationToken);
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>
{
private readonly IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly IAppImageStore appImageStore = A.Fake<IAppImageStore>();
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;
public sealed class MyCommand : SquidexCommand
@ -30,27 +27,22 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
protected override DomainId Id
{
get => appId.Id;
get => AppId.Id;
}
public AppCommandMiddlewareTests()
{
requestContext = Context.Anonymous(Mocks.App(appId));
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new AppCommandMiddleware(domainObjectFactory, appImageStore, assetThumbnailGenerator, contextProvider);
sut = new AppCommandMiddleware(domainObjectFactory, appImageStore, assetThumbnailGenerator, ApiContextProvider);
}
[Fact]
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]
@ -58,12 +50,12 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
{
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));
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();
}
@ -74,15 +66,15 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
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));
await Assert.ThrowsAsync<ValidationException>(() => HandleAsync(sut, command));
await Assert.ThrowsAsync<ValidationException>(() => HandleAsync(sut, command, CancellationToken));
}
private Task<CommandContext> HandleAsync(AppCommand command, object actual)
{
command.AppId = appId;
command.AppId = AppId;
var domainObject = A.Fake<AppDomainObject>();
@ -92,6 +84,6 @@ public class AppCommandMiddlewareTests : HandlerTestBase<AppDomainObject.State>
A.CallTo(() => domainObjectFactory.Create<AppDomainObject>(command.AggregateId))
.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>
{
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IBillingPlans billingPlans = A.Fake<IBillingPlans>();
private readonly IBillingManager billingManager = A.Fake<IBillingManager>();
private readonly IUser user;
@ -36,37 +35,33 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private readonly string planIdPaid = "premium";
private readonly string planIdFree = "free";
private readonly InitialSettings initialSettings;
private readonly DomainId teamId = DomainId.NewGuid();
private readonly DomainId workflowId = DomainId.NewGuid();
private readonly AppDomainObject sut;
protected override DomainId Id
{
get => AppId;
get => AppId.Id;
}
public AppDomainObjectTests()
{
user = UserMocks.User(contributorId);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId, default))
A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId, CancellationToken))
.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));
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));
A.CallTo(() => billingPlans.GetFreePlan())
.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));
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.
initialSettings = new InitialSettings
{
@ -78,7 +73,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
var serviceProvider =
new ServiceCollection()
.AddSingleton(appProvider)
.AddSingleton(AppProvider)
.AddSingleton(billingManager)
.AddSingleton(billingPlans)
.AddSingleton(initialSettings)
@ -105,18 +100,18 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
[Fact]
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);
actual.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name);
Assert.Equal(AppId.Name, sut.Snapshot.Name);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppCreated { Name = AppName }),
CreateEvent(new AppContributorAssigned { ContributorId = Actor.Identifier, Role = Role.Owner }),
CreateEvent(new AppCreated { Name = AppId.Name }),
CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Role = Role.Owner }),
CreateEvent(new AppSettingsUpdated { Settings = initialSettings.Settings })
);
}
@ -124,17 +119,17 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
[Fact]
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);
actual.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name);
Assert.Equal(AppId.Name, sut.Snapshot.Name);
LastEvents
.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)
);
}
@ -227,7 +222,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
{
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));
await ExecuteCreateAsync();
@ -243,10 +238,10 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
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();
A.CallTo(() => billingManager.SubscribeAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default))
A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A<IAppEntity>._, planIdPaid, default))
.MustHaveHappened();
}
@ -320,7 +315,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
CreateEvent(new AppPlanReset())
);
A.CallTo(() => billingManager.MustRedirectToPortalAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, default))
A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A<IAppEntity>._, planIdPaid, CancellationToken))
.MustHaveHappenedOnceExactly();
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 };
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"));
await ExecuteCreateAsync();
@ -357,10 +352,10 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
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();
A.CallTo(() => billingManager.SubscribeAsync(Actor.Identifier, A<IAppEntity>._, planIdPaid, A<CancellationToken>._))
A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A<IAppEntity>._, planIdPaid, A<CancellationToken>._))
.MustNotHaveHappened();
}
@ -426,7 +421,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
[Fact]
public async Task Transfer_should_create_events_and_set_team()
{
var command = new TransferToTeam { TeamId = teamId };
var command = new TransferToTeam { TeamId = TeamId };
await ExecuteCreateAsync();
@ -434,11 +429,11 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
actual.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(teamId, sut.Snapshot.TeamId);
Assert.Equal(TeamId, sut.Snapshot.TeamId);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppTransfered { TeamId = teamId })
CreateEvent(new AppTransfered { TeamId = TeamId })
);
}
@ -720,7 +715,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private Task ExecuteCreateAsync()
{
return PublishAsync(new CreateApp { Name = AppName, AppId = AppId });
return PublishAsync(new CreateApp { Name = AppId.Name, AppId = AppId.Id });
}
private Task ExecuteUploadImage()
@ -760,7 +755,7 @@ public class AppDomainObjectTests : HandlerTestBase<AppDomainObject.State>
private Task ExecuteTransferAsync()
{
return PublishAsync(new TransferToTeam { TeamId = teamId });
return PublishAsync(new TransferToTeam { TeamId = TeamId });
}
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
{
var actual = await sut.ExecuteAsync(CreateCommand(command), default);
var actual = await sut.ExecuteAsync(CreateCommand(command), CancellationToken);
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.Validation;
#pragma warning disable SA1310 // Field names must not contain underscore
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 readonly Roles roles = Roles.Empty;
private AppClients clients = AppClients.Empty;
public GuardAppClientsTests()
{
A.CallTo(() => App.Roles)
.Returns(Roles.Empty);
A.CallTo(() => App.Clients)
.ReturnsLazily(() => clients);
}
[Fact]
public void CanAttach_should_throw_execption_if_client_id_is_null()
{
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"));
}
@ -35,9 +41,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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."));
}
@ -46,9 +52,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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]
@ -56,7 +62,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -65,7 +71,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
var command = new RevokeClient { Id = "ios" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanRevoke(command, App(clients_0)));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppClients.CanRevoke(command, App));
}
[Fact]
@ -73,9 +79,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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]
@ -83,7 +89,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -92,7 +98,7 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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]
@ -100,9 +106,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -111,9 +117,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -122,9 +128,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -133,9 +139,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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]
@ -143,9 +149,9 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
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]
@ -153,18 +159,8 @@ public class GuardAppClientsTests : IClassFixture<TranslationsFixture>
{
var command = new UpdateClient { Id = "ios", Name = "iOS", Role = Role.Reader };
var clients_1 = clients_0.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);
clients = clients.Add("ios", "secret");
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.Shared.Users;
#pragma warning disable SA1310 // Field names must not contain underscore
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 user2 = UserMocks.User("2");
private readonly IUser user3 = UserMocks.User("3");
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 planWithLimit = new Plan { MaxContributors = 2 };
private readonly Roles roles = Roles.Empty;
private Contributors contributors = Contributors.Empty;
public GuardAppContributorsTests()
{
A.CallTo(() => App.Roles)
.Returns(Roles.Empty);
A.CallTo(() => App.Contributors)
.ReturnsLazily(() => contributors);
A.CallTo(() => user1.Id)
.Returns("1");
@ -59,7 +62,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -68,7 +71,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -77,9 +80,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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]
@ -87,9 +90,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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]
@ -97,7 +100,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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]
@ -105,7 +108,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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]
@ -113,10 +116,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new AssignContributor { ContributorId = "3" };
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
contributors = contributors.Assign("1", Role.Owner).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."));
}
@ -125,10 +127,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new AssignContributor { ContributorId = "3", IgnorePlans = true };
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
contributors = contributors.Assign("1", Role.Owner).Assign("2", Role.Editor);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithLimit);
await GuardAppContributors.CanAssign(command, App, users, planWithLimit);
}
[Fact]
@ -136,7 +137,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new AssignContributor { ContributorId = "1" };
await GuardAppContributors.CanAssign(command, App(contributors_0), users, planWithoutLimit);
await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
}
[Fact]
@ -144,9 +145,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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]
@ -154,10 +155,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new AssignContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", Role.Developer);
var contributors_2 = contributors_1.Assign("2", Role.Developer);
contributors = contributors.Assign("1", Role.Developer).Assign("2", Role.Developer);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithLimit);
await GuardAppContributors.CanAssign(command, App, users, planWithLimit);
}
[Fact]
@ -165,10 +165,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new AssignContributor { ContributorId = "3", IgnorePlans = true };
var contributors_1 = contributors_0.Assign("1", Role.Editor);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
contributors = contributors.Assign("1", Role.Editor).Assign("2", Role.Editor);
await GuardAppContributors.CanAssign(command, App(contributors_2), users, planWithoutLimit);
await GuardAppContributors.CanAssign(command, App, users, planWithoutLimit);
}
[Fact]
@ -176,7 +175,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -185,7 +184,7 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new RemoveContributor { ContributorId = "1" };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(command, App(contributors_0)));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppContributors.CanRemove(command, App));
}
[Fact]
@ -193,10 +192,9 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new RemoveContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", Role.Owner);
var contributors_2 = contributors_1.Assign("2", Role.Editor);
contributors = contributors.Assign("1", Role.Owner).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."));
}
@ -205,19 +203,8 @@ public class GuardAppContributorsTests : IClassFixture<TranslationsFixture>
{
var command = new RemoveContributor { ContributorId = "1" };
var contributors_1 = contributors_0.Assign("1", 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);
contributors = contributors.Assign("1", Role.Owner).Assign("2", Role.Owner);
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.
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
@ -14,16 +13,14 @@ using Squidex.Infrastructure.Validation;
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]
public void CanAddLanguage_should_throw_exception_if_language_is_null()
{
var command = new AddLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App(languages)),
ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(command, App),
new ValidationError("Language code is required.", "Language"));
}
@ -32,7 +29,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
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."));
}
@ -41,7 +38,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
var command = new AddLanguage { Language = Language.IT };
GuardAppLanguages.CanAdd(command, App(languages));
GuardAppLanguages.CanAdd(command, App);
}
[Fact]
@ -49,7 +46,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
var command = new RemoveLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App(languages)),
ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(command, App),
new ValidationError("Language code is required.", "Language"));
}
@ -58,7 +55,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
var command = new RemoveLanguage { Language = Language.IT };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(command, App(languages)));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(command, App));
}
[Fact]
@ -66,7 +63,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
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."));
}
@ -75,7 +72,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
var command = new RemoveLanguage { Language = Language.DE };
GuardAppLanguages.CanRemove(command, App(languages));
GuardAppLanguages.CanRemove(command, App);
}
[Fact]
@ -83,7 +80,7 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
var command = new UpdateLanguage();
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
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 };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
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 } };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
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 } };
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App(languages)),
ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(command, App),
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 };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(command, App(languages)));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppLanguages.CanUpdate(command, App));
}
[Fact]
@ -127,15 +124,6 @@ public class GuardAppLanguagesTests : IClassFixture<TranslationsFixture>
{
var command = new UpdateLanguage { Language = Language.DE, Fallback = new[] { Language.EN } };
GuardAppLanguages.CanUpdate(command, App(languages));
}
private static IAppEntity App(LanguagesConfig languages)
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Languages).Returns(languages);
return app;
GuardAppLanguages.CanUpdate(command, 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.Validation;
#pragma warning disable SA1310 // Field names must not contain underscore
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 Roles roles_0 = Roles.Empty;
private readonly AppClients clients = AppClients.Empty.Add("client", "secret", "clientRole");
private readonly Contributors contributors = Contributors.Empty.Assign("contributor", "contributorRole");
private Roles roles = Roles.Empty;
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]
public void CanAdd_should_throw_exception_if_name_empty()
{
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"));
}
[Fact]
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 };
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App(roles_1)),
ValidationAssert.Throws(() => GuardAppRoles.CanAdd(command, App),
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 };
GuardAppRoles.CanAdd(command, App(roles_0));
GuardAppRoles.CanAdd(command, App);
}
[Fact]
@ -57,7 +65,7 @@ public class GuardAppRolesTests : IClassFixture<TranslationsFixture>
{
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"));
}
@ -66,82 +74,82 @@ public class GuardAppRolesTests : IClassFixture<TranslationsFixture>
{
var command = new DeleteRole { Name = roleName };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanDelete(command, App(roles_0)));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanDelete(command, App));
}
[Fact]
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" };
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."));
}
[Fact]
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" };
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."));
}
[Fact]
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 };
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App(roles_1)),
ValidationAssert.Throws(() => GuardAppRoles.CanDelete(command, App),
new ValidationError("Cannot delete a default role."));
}
[Fact]
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 };
GuardAppRoles.CanDelete(command, App(roles_1));
GuardAppRoles.CanDelete(command, App);
}
[Fact]
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" } };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)),
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App),
new ValidationError("Name is required.", "Name"));
}
[Fact]
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 };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)),
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App),
new ValidationError("Permissions is required.", "Permissions"));
}
[Fact]
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" } };
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App(roles_1)),
ValidationAssert.Throws(() => GuardAppRoles.CanUpdate(command, App),
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" } };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanUpdate(command, App(roles_0)));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppRoles.CanUpdate(command, App));
}
[Fact]
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" } };
GuardAppRoles.CanUpdate(command, App(roles_1));
GuardAppRoles.CanUpdate(command, App);
}
[Fact]
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" } };
GuardAppRoles.CanUpdate(command, App(roles_1));
}
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;
GuardAppRoles.CanUpdate(command, 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.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Billing;
using Squidex.Domain.Apps.Entities.Teams;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
@ -19,9 +18,8 @@ using Squidex.Shared.Users;
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 IBillingPlans billingPlans = A.Fake<IBillingPlans>();
private readonly RefToken actor = RefToken.User("42");
@ -75,121 +73,104 @@ public class GuardAppTests : IClassFixture<TranslationsFixture>
[Fact]
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(plan), billingPlans),
ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans),
new ValidationError("Plan ID is required.", "PlanId"));
}
[Fact]
public void CanChangePlan_should_throw_exception_if_plan_not_found()
{
var command = new ChangePlan { PlanId = "notfound", Actor = RefToken.User("me") };
AssignedPlan? plan = null;
var command = new ChangePlan { PlanId = "notfound", Actor = User };
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"));
}
[Fact]
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."));
}
[Fact]
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."));
}
[Fact]
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]
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]
public async Task CanTransfer_should_not_throw_exception_if_team_exists()
{
var team = Mocks.Team(DomainId.NewGuid(), contributor: actor.Identifier);
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default))
.Returns(team);
var command = new TransferToTeam { TeamId = team.Id, Actor = actor };
var command = new TransferToTeam { TeamId = TeamId, Actor = User };
await GuardApp.CanTransfer(command, App(null), appProvider, default);
await GuardApp.CanTransfer(command, App, AppProvider, default);
}
[Fact]
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))
.Returns(Task.FromResult<ITeamEntity?>(null));
var command = new TransferToTeam { TeamId = TeamId, Actor = actor };
var command = new TransferToTeam { TeamId = team.Id, Actor = actor };
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App(null), appProvider, default),
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
new ValidationError("The team does not exist."));
}
[Fact]
public async Task CanTransfer_should_throw_exception_if_actor_is_not_part_of_team()
{
var team = Mocks.Team(DomainId.NewGuid());
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default))
.Returns(team);
var command = new TransferToTeam { TeamId = TeamId, Actor = actor };
var command = new TransferToTeam { TeamId = team.Id, Actor = actor };
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App(null), appProvider, default),
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
new ValidationError("The team does not exist."));
}
[Fact]
public async Task CanTransfer_should_throw_exception_if_app_has_plan()
{
var team = Mocks.Team(DomainId.NewGuid(), contributor: actor.Identifier);
A.CallTo(() => appProvider.GetTeamAsync(team.Id, default))
.Returns(team);
var command = new TransferToTeam { TeamId = TeamId, Actor = User };
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(plan), appProvider, default),
await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default),
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);
}
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;
public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
public class GuardAppWorkflowTests : GivenContext, IClassFixture<TranslationsFixture>
{
private readonly DomainId workflowId = DomainId.NewGuid();
private readonly Workflows workflows;
@ -23,6 +23,9 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
public GuardAppWorkflowTests()
{
workflows = Workflows.Empty.Add(workflowId, "name");
A.CallTo(() => App.Workflows)
.Returns(workflows);
}
[Fact]
@ -51,7 +54,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = DomainId.NewGuid()
};
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(command, App()));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(command, App));
}
[Fact]
@ -59,7 +62,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{
var command = new UpdateWorkflow { WorkflowId = workflowId };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Workflow is required.", "Workflow"));
}
@ -77,7 +80,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Initial step is required.", "Workflow.Initial"));
}
@ -95,7 +98,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Initial step cannot be published step.", "Workflow.Initial"));
}
@ -113,7 +116,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Workflow must have a published step.", "Workflow.Steps"));
}
@ -132,7 +135,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
new ValidationError("Step is required.", "Workflow.Steps.Published"));
}
@ -156,7 +159,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
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"));
}
@ -181,7 +184,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
WorkflowId = workflowId
};
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App()),
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command, App),
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 };
GuardAppWorkflows.CanUpdate(command, App());
GuardAppWorkflows.CanUpdate(command, App);
}
[Fact]
@ -198,7 +201,7 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{
var command = new DeleteWorkflow { WorkflowId = DomainId.NewGuid() };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(command, App()));
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(command, App));
}
[Fact]
@ -206,15 +209,6 @@ public class GuardAppWorkflowTests : IClassFixture<TranslationsFixture>
{
var command = new DeleteWorkflow { WorkflowId = workflowId };
GuardAppWorkflows.CanDelete(command, App());
}
private IAppEntity App()
{
var app = A.Fake<IAppEntity>();
A.CallTo(() => app.Workflows).Returns(workflows);
return app;
GuardAppWorkflows.CanDelete(command, 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.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security;
@ -21,23 +22,17 @@ using Squidex.Messaging;
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 IAppRepository appRepository = A.Fake<IAppRepository>();
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;
public AppsIndexTests()
{
state = new TestState<NameReservationState.State>("Apps");
ct = cts.Token;
var replicatedCache =
new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), A.Fake<IMessageBus>(),
Options.Create(new ReplicatedCacheOptions { Enable = true }));
@ -48,129 +43,114 @@ public class AppsIndexTests
[Fact]
public async Task Should_resolve_app_by_name()
{
var expected = CreateApp();
A.CallTo(() => appRepository.FindAsync(appId.Name, ct))
.Returns(expected);
A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(App);
var actual1 = await sut.GetAppAsync(appId.Name, false, ct);
var actual2 = await sut.GetAppAsync(appId.Name, false, ct);
var actual1 = await sut.GetAppAsync(AppId.Name, false, CancellationToken);
var actual2 = await sut.GetAppAsync(AppId.Name, false, CancellationToken);
Assert.Same(expected, actual1);
Assert.Same(expected, actual2);
Assert.Same(App, actual1);
Assert.Same(App, actual2);
A.CallTo(() => appRepository.FindAsync(appId.Name, ct))
A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.MustHaveHappenedTwiceExactly();
}
[Fact]
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))
.Returns(expected);
var actual1 = await sut.GetAppAsync(AppId.Name, true, CancellationToken);
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);
var actual2 = await sut.GetAppAsync(appId.Name, true, ct);
var actual3 = await sut.GetAppAsync(appId.Id, true, ct);
Assert.Same(App, actual1);
Assert.Same(App, actual2);
Assert.Same(App, actual3);
Assert.Same(expected, actual1);
Assert.Same(expected, actual2);
Assert.Same(expected, actual3);
A.CallTo(() => appRepository.FindAsync(appId.Name, ct))
A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.MustHaveHappenedOnceExactly();
}
[Fact]
public async Task Should_resolve_app_by_id()
{
var expected = CreateApp();
A.CallTo(() => appRepository.FindAsync(appId.Id, ct))
.Returns(expected);
A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
.Returns(App);
var actual1 = await sut.GetAppAsync(appId.Id, false, ct);
var actual2 = await sut.GetAppAsync(appId.Id, false, ct);
var actual1 = await sut.GetAppAsync(AppId.Id, false, CancellationToken);
var actual2 = await sut.GetAppAsync(AppId.Id, false, CancellationToken);
Assert.Same(expected, actual1);
Assert.Same(expected, actual2);
Assert.Same(App, actual1);
Assert.Same(App, actual2);
A.CallTo(() => appRepository.FindAsync(appId.Id, ct))
A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
.MustHaveHappenedTwiceExactly();
}
[Fact]
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))
.Returns(expected);
var actual1 = await sut.GetAppAsync(AppId.Id, true, CancellationToken);
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);
var actual2 = await sut.GetAppAsync(appId.Id, true, ct);
var actual3 = await sut.GetAppAsync(appId.Name, true, ct);
Assert.Same(App, actual1);
Assert.Same(App, actual2);
Assert.Same(App, actual3);
Assert.Same(expected, actual1);
Assert.Same(expected, actual2);
Assert.Same(expected, actual3);
A.CallTo(() => appRepository.FindAsync(appId.Id, ct))
A.CallTo(() => appRepository.FindAsync(AppId.Id, CancellationToken))
.MustHaveHappenedOnceExactly();
}
[Fact]
public async Task Should_resolve_all_apps_from_user_permissions()
{
var expected = CreateApp();
A.CallTo(() => appRepository.QueryAllAsync(userId, A<IEnumerable<string>>.That.Is(appId.Name), ct))
.Returns(new List<IAppEntity> { expected });
A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.Is(AppId.Name), CancellationToken))
.Returns(new List<IAppEntity> { App });
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]
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))
.Returns(new List<IAppEntity> { expected });
var actual = await sut.GetAppsForUserAsync(User.Identifier, PermissionSet.Empty, CancellationToken);
var actual = await sut.GetAppsForUserAsync(userId, PermissionSet.Empty, ct);
Assert.Same(expected, actual[0]);
Assert.Same(App, actual[0]);
}
[Fact]
public async Task Should_resolve_all_apps_from_team()
{
var teamId = DomainId.NewGuid();
var expected = CreateApp();
A.CallTo(() => appRepository.QueryAllAsync(TeamId, CancellationToken))
.Returns(new List<IAppEntity> { App });
A.CallTo(() => appRepository.QueryAllAsync(teamId, ct))
.Returns(new List<IAppEntity> { expected });
var actual = await sut.GetAppsForTeamAsync(TeamId, CancellationToken);
var actual = await sut.GetAppsForTeamAsync(teamId, ct);
Assert.Same(expected, actual[0]);
Assert.Same(App, actual[0]);
}
[Fact]
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))
.Returns(new List<IAppEntity> { expected });
A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.IsEmpty(), CancellationToken))
.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);
}
@ -178,12 +158,13 @@ public class AppsIndexTests
[Fact]
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))
.Returns(new List<IAppEntity> { expected });
A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A<IEnumerable<string>>.That.IsEmpty(), CancellationToken))
.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);
}
@ -191,10 +172,10 @@ public class AppsIndexTests
[Fact]
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));
var command = Create(appId.Name);
var command = Create(AppId.Name);
var context =
new CommandContext(command, commandBus)
@ -207,21 +188,21 @@ public class AppsIndexTests
madeReservation = state.Snapshot.Reservations.FirstOrDefault();
return Task.CompletedTask;
}, ct);
}, CancellationToken);
Assert.Empty(state.Snapshot.Reservations);
Assert.Equal(appId.Id, madeReservation?.Id);
Assert.Equal(appId.Name, madeReservation?.Name);
Assert.Equal(AppId.Id, madeReservation?.Id);
Assert.Equal(AppId.Name, madeReservation?.Name);
}
[Fact]
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));
var command = Create(appId.Name);
var command = Create(AppId.Name);
var context =
new CommandContext(command, commandBus)
@ -234,44 +215,44 @@ public class AppsIndexTests
madeReservation = state.Snapshot.Reservations.FirstOrDefault();
throw new InvalidOperationException();
}, ct));
}, CancellationToken));
Assert.Empty(state.Snapshot.Reservations);
Assert.Equal(appId.Id, madeReservation?.Id);
Assert.Equal(appId.Name, madeReservation?.Name);
Assert.Equal(AppId.Id, madeReservation?.Id);
Assert.Equal(AppId.Name, madeReservation?.Name);
}
[Fact]
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));
var command = Create(appId.Name);
var command = Create(AppId.Name);
var context =
new CommandContext(command, commandBus)
.Complete();
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context, ct));
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context, CancellationToken));
}
[Fact]
public async Task Should_not_create_app_if_name_is_taken()
{
A.CallTo(() => appRepository.FindAsync(appId.Name, ct))
.Returns(CreateApp());
A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken))
.Returns(App);
var command = Create(appId.Name);
var command = Create(AppId.Name);
var context =
new CommandContext(command, commandBus)
.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>._))
.MustNotHaveHappened();
@ -280,15 +261,13 @@ public class AppsIndexTests
[Fact]
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 =
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>._))
.MustNotHaveHappened();
@ -296,18 +275,6 @@ public class AppsIndexTests
private CreateApp Create(string 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;
return new CreateApp { AppId = AppId.Id, Name = name };
}
}

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

@ -8,6 +8,7 @@
using System.Security.Claims;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Validation;
@ -16,10 +17,8 @@ using Squidex.Shared.Users;
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 ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly RestrictAppsOptions options = new RestrictAppsOptions();
@ -27,8 +26,6 @@ public sealed class RestrictAppsCommandMiddlewareTests
public RestrictAppsCommandMiddlewareTests()
{
ct = cts.Token;
sut = new RestrictAppsCommandMiddleware(Options.Create(options), userResolver);
}
@ -54,7 +51,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
A.CallTo(() => user.Claims)
.Returns(Enumerable.Repeat(new Claim(SquidexClaimTypes.TotalApps, "5"), 1).ToList());
A.CallTo(() => userResolver.FindByIdAsync(userId, ct))
A.CallTo(() => userResolver.FindByIdAsync(userId, CancellationToken))
.Returns(user);
var isNextCalled = false;
@ -64,7 +61,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
isNextCalled = true;
return Task.CompletedTask;
}, ct));
}, CancellationToken));
Assert.False(isNextCalled);
}
@ -91,7 +88,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
A.CallTo(() => user.Claims)
.Returns(Enumerable.Repeat(new Claim(SquidexClaimTypes.TotalApps, "5"), 1).ToList());
A.CallTo(() => userResolver.FindByIdAsync(userId, ct))
A.CallTo(() => userResolver.FindByIdAsync(userId, CancellationToken))
.Returns(user);
await sut.HandleAsync(commandContext, (c, ct) =>
@ -99,7 +96,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true);
return Task.CompletedTask;
}, ct);
}, CancellationToken);
A.CallTo(() => userResolver.SetClaimAsync(userId, SquidexClaimTypes.TotalApps, "6", true, default))
.MustHaveHappened();
@ -122,7 +119,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true);
return Task.CompletedTask;
}, ct);
}, CancellationToken);
A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -145,7 +142,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true);
return Task.CompletedTask;
}, ct);
}, CancellationToken);
A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -168,7 +165,7 @@ public sealed class RestrictAppsCommandMiddlewareTests
c.Complete(true);
return Task.CompletedTask;
}, ct);
}, CancellationToken);
A.CallTo(() => userResolver.FindByIdAsync(A<string>._, A<CancellationToken>._))
.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;
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;
public RolePermissionsProviderTests()
{
app = Mocks.App(appId);
sut = new RolePermissionsProvider(appProvider);
sut = new RolePermissionsProvider(AppProvider);
}
[Fact]
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>
{
Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "schema1")),
Mocks.Schema(appId, NamedId.Of(DomainId.NewGuid(), "schema2"))
Mocks.Schema(AppId, NamedId.Of(DomainId.NewGuid(), "schema1")),
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("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.TestHelpers;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Events.Contents;
@ -20,10 +21,8 @@ using Squidex.Infrastructure.EventSourcing;
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 IAssetLoader assetLoader = A.Fake<IAssetLoader>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
@ -31,8 +30,6 @@ public class AssetChangedTriggerHandlerTests
public AssetChangedTriggerHandlerTests()
{
ct = cts.Token;
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true);
@ -79,14 +76,14 @@ public class AssetChangedTriggerHandlerTests
{
var ctx = Context();
A.CallTo(() => assetRepository.StreamAll(ctx.AppId.Id, ct))
A.CallTo(() => assetRepository.StreamAll(ctx.AppId.Id, CancellationToken))
.Returns(new List<AssetEntity>
{
new AssetEntity(),
new AssetEntity()
}.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();
@ -102,10 +99,10 @@ public class AssetChangedTriggerHandlerTests
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());
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();

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

@ -7,16 +7,16 @@
using Squidex.Assets;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetPermanentDeleterTests
public class AssetPermanentDeleterTests : GivenContext
{
private readonly IAssetFileStore assetFiletore = A.Fake<IAssetFileStore>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AssetPermanentDeleter sut;
public AssetPermanentDeleterTests()
@ -75,31 +75,31 @@ public class AssetPermanentDeleterTests
[Fact]
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());
A.CallTo(() => assetFiletore.DeleteAsync(appId.Id, @event.AssetId, A<CancellationToken>._))
A.CallTo(() => assetFiletore.DeleteAsync(AppId.Id, @event.AssetId, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
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));
A.CallTo(() => assetFiletore.DeleteAsync(appId.Id, @event.AssetId, A<CancellationToken>._))
A.CallTo(() => assetFiletore.DeleteAsync(AppId.Id, @event.AssetId, A<CancellationToken>._))
.MustHaveHappened();
}
[Fact]
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"));
await sut.On(Envelope.Create(@event));
@ -108,9 +108,9 @@ public class AssetPermanentDeleterTests
[Fact]
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());
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.Entities.TestHelpers;
using Squidex.Infrastructure;
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 AssetTagsDeleter sut;
public AssetTagsDeleterTests()
{
ct = cts.Token;
sut = new AssetTagsDeleter(tagService);
}
@ -36,11 +31,9 @@ public class AssetTagsDeleterTests
[Fact]
public async Task Should_remove_events_from_streams()
{
var app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app"));
await sut.DeleteAppAsync(app, ct);
await sut.DeleteAppAsync(App, CancellationToken);
A.CallTo(() => tagService.ClearAsync(app.Id, TagGroups.Assets, ct))
A.CallTo(() => tagService.ClearAsync(AppId.Id, TagGroups.Assets, CancellationToken))
.MustHaveHappened();
}
}

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

@ -8,6 +8,7 @@
using NodaTime;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Billing;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
@ -15,20 +16,19 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetUsageTrackerTests
public class AssetUsageTrackerTests : GivenContext
{
private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>();
private readonly ISnapshotStore<AssetUsageTracker.State> store = A.Fake<ISnapshotStore<AssetUsageTracker.State>>();
private readonly ITagService tagService = A.Fake<ITagService>();
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 assetKey;
private readonly AssetUsageTracker sut;
public AssetUsageTrackerTests()
{
assetKey = DomainId.Combine(appId, assetId);
assetKey = DomainId.Combine(AppId, assetId);
sut = new AssetUsageTracker(usageGate, assetLoader, tagService, store);
}
@ -81,7 +81,7 @@ public class AssetUsageTrackerTests
{
var date = DateTime.UtcNow.Date.AddDays(13);
@event.AppId = appId;
@event.AppId = AppId;
var envelope =
Envelope.Create<IEvent>(@event)
@ -89,7 +89,7 @@ public class AssetUsageTrackerTests
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();
}
@ -98,7 +98,7 @@ public class AssetUsageTrackerTests
{
var @event = new AssetCreated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag1",
@ -113,7 +113,7 @@ public class AssetUsageTrackerTests
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); });
await sut.On(new[] { envelope });
@ -130,7 +130,7 @@ public class AssetUsageTrackerTests
{
var @event1 = new AssetCreated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag1",
@ -141,7 +141,7 @@ public class AssetUsageTrackerTests
var @event2 = new AssetCreated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag2",
@ -160,7 +160,7 @@ public class AssetUsageTrackerTests
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); });
await sut.On(new[] { envelope1, envelope2 });
@ -181,7 +181,7 @@ public class AssetUsageTrackerTests
{
var @event1 = new AssetCreated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag1",
@ -192,7 +192,7 @@ public class AssetUsageTrackerTests
var @event2 = new AssetAnnotated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag2",
@ -211,7 +211,7 @@ public class AssetUsageTrackerTests
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); });
await sut.On(new[] { envelope1, envelope2 });
@ -229,7 +229,7 @@ public class AssetUsageTrackerTests
{
var @event1 = new AssetCreated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag1",
@ -240,7 +240,7 @@ public class AssetUsageTrackerTests
var @event2 = new AssetAnnotated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag2",
@ -259,7 +259,7 @@ public class AssetUsageTrackerTests
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); });
await sut.On(new[] { envelope1 });
@ -278,7 +278,7 @@ public class AssetUsageTrackerTests
{
var @event1 = new AssetCreated
{
AppId = appId,
AppId = AppId,
Tags = new HashSet<string>
{
"tag1",
@ -287,7 +287,7 @@ public class AssetUsageTrackerTests
AssetId = assetId
};
var @event2 = new AssetDeleted { AppId = appId, AssetId = assetId };
var @event2 = new AssetDeleted { AppId = AppId, AssetId = assetId };
var envelope1 =
Envelope.Create<IEvent>(@event1)
@ -299,7 +299,7 @@ public class AssetUsageTrackerTests
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); });
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))
.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 =
Envelope.Create<IEvent>(@event)
@ -334,7 +334,7 @@ public class AssetUsageTrackerTests
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); });
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);
var @event = new AssetDeleted { AppId = appId, AssetId = assetId };
var @event = new AssetDeleted { AppId = AppId, AssetId = assetId };
var envelope =
Envelope.Create<IEvent>(@event)
@ -370,7 +370,7 @@ public class AssetUsageTrackerTests
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); });
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;
public class AssetsFluidExtensionTests
public class AssetsFluidExtensionTests : GivenContext
{
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly FluidTemplateEngine sut;
public AssetsFluidExtensionTests()
{
var serviceProvider =
new ServiceCollection()
.AddSingleton(appProvider)
.AddSingleton(AppProvider)
.AddSingleton(assetFileStore)
.AddSingleton(assetQuery)
.AddSingleton(assetThumbnailGenerator)
@ -44,9 +42,6 @@ public class AssetsFluidExtensionTests
new AssetsFluidExtension(serviceProvider)
};
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false, default))
.Returns(Mocks.App(appId));
sut = new FluidTemplateEngine(extensions);
}
@ -179,7 +174,7 @@ public class AssetsFluidExtensionTests
Id = DomainId.NewGuid(),
FileVersion = 0,
FileSize = 100,
AppId = appId
AppId = AppId
};
SetupText(@event.ToRef(), Encode(encoding, "hello+assets"));
@ -276,7 +271,7 @@ public class AssetsFluidExtensionTests
AssetType = AssetType.Image,
FileVersion = 0,
FileSize = 100,
AppId = appId
AppId = AppId
};
SetupBlurHash(@event.ToRef(), "Hash");
@ -307,7 +302,7 @@ public class AssetsFluidExtensionTests
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));
}
@ -323,7 +318,7 @@ public class AssetsFluidExtensionTests
.AddField("assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId))),
AppId = appId
AppId = AppId
};
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId, EtagVersion.Any, A<CancellationToken>._))
@ -351,7 +346,7 @@ public class AssetsFluidExtensionTests
.AddField("assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId1, assetId2))),
AppId = appId
AppId = AppId
};
A.CallTo(() => assetQuery.FindAsync(A<Context>._, assetId1, EtagVersion.Any, A<CancellationToken>._))
@ -372,7 +367,7 @@ public class AssetsFluidExtensionTests
{
return new AssetEntity
{
AppId = appId,
AppId = AppId,
Id = assetId,
FileSize = fileSize,
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;
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 IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly JintScriptEngine sut;
public AssetsJintExtensionTests()
{
var serviceProvider =
new ServiceCollection()
.AddSingleton(appProvider)
.AddSingleton(AppProvider)
.AddSingleton(assetFileStore)
.AddSingleton(assetQuery)
.AddSingleton(assetThumbnailGenerator)
@ -46,9 +44,6 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
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())),
Options.Create(new JintScriptOptions
{
@ -97,7 +92,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
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));
}
@ -120,7 +115,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
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));
}
@ -146,7 +141,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
}}, '{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));
}
@ -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));
@ -186,7 +181,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
Id = DomainId.NewGuid(),
FileVersion = 0,
FileSize = 100,
AppId = appId
AppId = AppId
};
SetupText(@event.ToRef(), Encode(encoding, "hello+assets"));
@ -207,7 +202,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
complete(actual);
}}, '{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));
}
@ -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));
}
@ -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));
}
@ -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));
}
@ -296,7 +291,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
AssetType = AssetType.Image,
FileVersion = 0,
FileSize = 100,
AppId = appId
AppId = AppId
};
SetupBlurHash(@event.ToRef(), "Hash");
@ -317,7 +312,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
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));
}
@ -330,7 +325,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
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));
}
@ -348,14 +343,14 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
.AddInvariant(JsonValue.Array(assetIds)));
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));
var vars = new ScriptVars
{
["data"] = data,
["appId"] = appId.Id,
["appName"] = appId.Name,
["appId"] = AppId.Id,
["appName"] = AppId.Name,
["user"] = user
};
@ -366,7 +361,7 @@ public class AssetsJintExtensionTests : IClassFixture<TranslationsFixture>
{
return new AssetEntity
{
AppId = appId,
AppId = AppId,
Id = DomainId.NewGuid(),
FileSize = fileSize,
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.
// ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Search;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Shared.Identity;
namespace Squidex.Domain.Apps.Entities.Assets;
public class AssetsSearchSourceTests
public class AssetsSearchSourceTests : GivenContext
{
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AssetsSearchSource sut;
public AssetsSearchSourceTests()
@ -30,9 +27,7 @@ public class AssetsSearchSourceTests
[Fact]
public async Task Should_return_empty_actuals_if_user_has_no_permission()
{
var ctx = ContextWithPermission();
var actual = await sut.SearchAsync("logo", ctx, default);
var actual = await sut.SearchAsync("logo", ApiContext, default);
Assert.Empty(actual);
@ -43,23 +38,23 @@ public class AssetsSearchSourceTests
[Fact]
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 asset2 = CreateAsset("logo2.png");
A.CallTo(() => urlGenerator.AssetsUI(appId, asset1.Id.ToString()))
A.CallTo(() => urlGenerator.AssetsUI(AppId, asset1.Id.ToString()))
.Returns("assets-url1");
A.CallTo(() => urlGenerator.AssetsUI(appId, asset2.Id.ToString()))
A.CallTo(() => urlGenerator.AssetsUI(AppId, asset2.Id.ToString()))
.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));
var actual = await sut.SearchAsync("logo", ctx, default);
var actual = await sut.SearchAsync("logo", requestContext, default);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -71,17 +66,4 @@ public class AssetsSearchSourceTests
{
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.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
@ -17,21 +18,16 @@ using Squidex.Infrastructure.EventSourcing;
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 IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
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 BackupAssets sut;
public BackupAssetsTests()
{
ct = cts.Token;
sut = new BackupAssets(rebuilder, assetFileStore, tagService);
}
@ -51,12 +47,12 @@ public class BackupAssetsTests
var context = CreateBackupContext();
A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, ct))
A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, CancellationToken))
.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();
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias!, A<CancellationToken>._))
@ -77,15 +73,15 @@ public class BackupAssetsTests
var context = CreateBackupContext();
A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, ct))
A.CallTo(() => tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, CancellationToken))
.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();
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias, ct))
A.CallTo(() => context.Writer.WriteJsonAsync(A<string>._, tags.Alias, CancellationToken))
.MustHaveHappened();
}
@ -99,18 +95,18 @@ public class BackupAssetsTests
var envelope =
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);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, ct))
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, CancellationToken))
.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();
}
@ -124,18 +120,18 @@ public class BackupAssetsTests
var envelope =
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);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, ct))
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, CancellationToken))
.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();
}
@ -149,16 +145,16 @@ public class BackupAssetsTests
var envelope =
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);
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, ct))
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, string>>(A<string>._, CancellationToken))
.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>._))
.MustNotHaveHappened();
@ -166,7 +162,7 @@ public class BackupAssetsTests
A.CallTo(() => context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(A<string>._, A<CancellationToken>._))
.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();
}
@ -209,12 +205,12 @@ public class BackupAssetsTests
var context = CreateBackupContext();
A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", ct))
A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.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();
}
@ -225,13 +221,13 @@ public class BackupAssetsTests
var context = CreateBackupContext();
A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", ct))
A.CallTo(() => context.Writer.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.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()));
await sut.BackupEventAsync(AppEvent(@event), context, ct);
await sut.BackupEventAsync(AppEvent(@event), context, CancellationToken);
}
[Fact]
@ -253,7 +249,7 @@ public class BackupAssetsTests
[Fact]
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);
}
@ -261,7 +257,7 @@ public class BackupAssetsTests
[Fact]
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);
}
@ -273,12 +269,12 @@ public class BackupAssetsTests
var context = CreateRestoreContext();
A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", ct))
A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.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();
}
@ -289,12 +285,12 @@ public class BackupAssetsTests
var context = CreateRestoreContext();
A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", ct))
A.CallTo(() => context.Reader.OpenBlobAsync($"{assetId}_{version}.asset", CancellationToken))
.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();
}
@ -309,29 +305,29 @@ public class BackupAssetsTests
await sut.RestoreEventAsync(AppEvent(new AssetCreated
{
AssetId = assetId1
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetCreated
{
AssetId = assetId2
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetDeleted
{
AssetId = assetId2
}), context, ct);
}), context, CancellationToken);
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)!));
await sut.RestoreAsync(context, ct);
await sut.RestoreAsync(context, CancellationToken);
Assert.Equal(new HashSet<DomainId>
{
DomainId.Combine(appId, assetId1),
DomainId.Combine(appId, assetId2)
DomainId.Combine(AppId, assetId1),
DomainId.Combine(AppId, assetId2)
}, rebuildAssets);
}
@ -346,54 +342,54 @@ public class BackupAssetsTests
await sut.RestoreEventAsync(AppEvent(new AssetFolderCreated
{
AssetFolderId = assetFolderId1
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetFolderCreated
{
AssetFolderId = assetFolderId2
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(AppEvent(new AssetFolderDeleted
{
AssetFolderId = assetFolderId2
}), context, ct);
}), context, CancellationToken);
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)!));
await sut.RestoreAsync(context, ct);
await sut.RestoreAsync(context, CancellationToken);
Assert.Equal(new HashSet<DomainId>
{
DomainId.Combine(appId, assetFolderId1),
DomainId.Combine(appId, assetFolderId2)
DomainId.Combine(AppId, assetFolderId1),
DomainId.Combine(AppId, assetFolderId2)
}, rebuildAssetFolders);
}
private BackupContext CreateBackupContext()
{
return new BackupContext(appId.Id, CreateUserMapping(), A.Fake<IBackupWriter>());
return new BackupContext(AppId.Id, CreateUserMapping(), A.Fake<IBackupWriter>());
}
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)
{
@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)
{
@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()

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

@ -14,13 +14,10 @@ using Squidex.Infrastructure;
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 IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DomainId assetId = DomainId.NewGuid();
private readonly long assetFileVersion = 21;
private readonly AssetOptions options = new AssetOptions();
@ -28,8 +25,6 @@ public class DefaultAssetFileStoreTests
public DefaultAssetFileStoreTests()
{
ct = cts.Token;
sut = new DefaultAssetFileStore(assetStore, assetRepository, Options.Create(options));
}
@ -60,7 +55,7 @@ public class DefaultAssetFileStoreTests
A.CallTo(() => assetStore.GeneratePublicUrl(fullName))
.Returns(url);
var actual = sut.GeneratePublicUrl(appId, assetId, assetFileVersion, suffix);
var actual = sut.GeneratePublicUrl(AppId.Id, assetId, assetFileVersion, suffix);
Assert.Equal(url, actual);
}
@ -75,10 +70,10 @@ public class DefaultAssetFileStoreTests
var size = 1024L;
A.CallTo(() => assetStore.GetSizeAsync(fullName, ct))
A.CallTo(() => assetStore.GetSizeAsync(fullName, CancellationToken))
.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);
}
@ -91,13 +86,13 @@ public class DefaultAssetFileStoreTests
var size = 1024L;
A.CallTo(() => assetStore.GetSizeAsync(A<string>._, ct))
A.CallTo(() => assetStore.GetSizeAsync(A<string>._, CancellationToken))
.Throws(new AssetNotFoundException(assetId.ToString()));
A.CallTo(() => assetStore.GetSizeAsync(fullName, ct))
A.CallTo(() => assetStore.GetSizeAsync(fullName, CancellationToken))
.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);
}
@ -107,9 +102,9 @@ public class DefaultAssetFileStoreTests
{
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();
}
@ -123,9 +118,9 @@ public class DefaultAssetFileStoreTests
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();
}
@ -139,9 +134,9 @@ public class DefaultAssetFileStoreTests
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();
}
@ -152,12 +147,12 @@ public class DefaultAssetFileStoreTests
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();
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();
}
@ -169,12 +164,12 @@ public class DefaultAssetFileStoreTests
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();
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();
}
@ -186,30 +181,30 @@ public class DefaultAssetFileStoreTests
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();
}
[Fact]
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();
}
[Fact]
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();
A.CallTo(() => assetStore.DeleteByPrefixAsync(assetId.ToString(), ct))
A.CallTo(() => assetStore.DeleteByPrefixAsync(assetId.ToString(), CancellationToken))
.MustHaveHappened();
}
@ -218,9 +213,9 @@ public class DefaultAssetFileStoreTests
{
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();
}
@ -230,23 +225,21 @@ public class DefaultAssetFileStoreTests
var asset1 = 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());
var app = Mocks.App(NamedId.Of(appId, "my-app"));
await ((IDeleter)sut).DeleteAppAsync(app, ct);
await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}_{asset1.Id}", ct))
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}_{asset1.Id}", CancellationToken))
.MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}_{asset2.Id}", ct))
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}_{asset2.Id}", CancellationToken))
.MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync(asset1.Id.ToString(), ct))
A.CallTo(() => assetStore.DeleteByPrefixAsync(asset1.Id.ToString(), CancellationToken))
.MustHaveHappened();
A.CallTo(() => assetStore.DeleteByPrefixAsync(asset2.Id.ToString(), ct))
A.CallTo(() => assetStore.DeleteByPrefixAsync(asset2.Id.ToString(), CancellationToken))
.MustHaveHappened();
}
@ -255,18 +248,16 @@ public class DefaultAssetFileStoreTests
{
options.FolderPerApp = true;
var app = Mocks.App(NamedId.Of(appId, "my-app"));
await ((IDeleter)sut).DeleteAppAsync(app, ct);
await ((IDeleter)sut).DeleteAppAsync(App, CancellationToken);
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{appId}/", ct))
A.CallTo(() => assetStore.DeleteByPrefixAsync($"{AppId.Id}/", CancellationToken))
.MustHaveHappened();
}
private string GetFullName(string fileName)
{
return fileName
.Replace("{appId}", appId.ToString(), StringComparison.Ordinal)
.Replace("{appId}", AppId.Id.ToString(), StringComparison.Ordinal)
.Replace("{assetId}", assetId.ToString(), 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.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.Queries;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
@ -15,18 +16,14 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject;
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 IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly IAssetMetadataSource assetMetadataSource = A.Fake<IAssetMetadataSource>();
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly DomainId assetId = DomainId.NewGuid();
private readonly AssetFile file = new NoopAssetFile();
private readonly Context requestContext;
private readonly AssetCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand
@ -35,21 +32,14 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
protected override DomainId Id
{
get => DomainId.Combine(AppId, assetId);
get => DomainId.Combine(AppId.Id, assetId);
}
public AssetCommandMiddlewareTests()
{
ct = cts.Token;
file = new NoopAssetFile();
requestContext = Context.Anonymous(Mocks.App(AppNamedId));
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
A.CallTo(() => assetQuery.FindByHashAsync(A<Context>._, A<string>._, A<string>._, A<long>._, ct))
A.CallTo(() => assetQuery.FindByHashAsync(A<Context>._, A<string>._, A<string>._, A<long>._, CancellationToken))
.Returns(Task.FromResult<IEnrichedAssetEntity?>(null));
sut = new AssetCommandMiddleware(
@ -58,7 +48,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
assetEnricher,
assetFileStore,
assetQuery,
contextProvider, new[] { assetMetadataSource });
ApiContextProvider, new[] { assetMetadataSource });
}
[Fact]
@ -66,7 +56,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
{
await HandleAsync(new AnnotateAsset(), 12);
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, requestContext, A<CancellationToken>._))
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, ApiContext, A<CancellationToken>._))
.MustNotHaveHappened();
}
@ -79,7 +69,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
await HandleAsync(new AnnotateAsset(),
actual);
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, requestContext, A<CancellationToken>._))
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnrichedAssetEntity>._, ApiContext, A<CancellationToken>._))
.MustNotHaveHappened();
}
@ -90,7 +80,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
var enriched = new AssetEntity();
A.CallTo(() => assetEnricher.EnrichAsync(actual, requestContext, ct))
A.CallTo(() => assetEnricher.EnrichAsync(actual, ApiContext, CancellationToken))
.Returns(enriched);
var context =
@ -130,7 +120,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
{
var actual = CreateAsset();
SetupSameHashAsset(file.FileName, file.FileSize, out _);
SetupSameHashAsset(file.FileName, file.FileSize, out var unused);
var context =
await HandleAsync(new CreateAsset { File = file, Duplicate = true },
@ -184,7 +174,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
{
var actual = CreateAsset();
SetupSameHashAsset(file.FileName, file.FileSize, out _);
SetupSameHashAsset(file.FileName, file.FileSize, out var unused);
var context =
await HandleAsync(new UpsertAsset { File = file },
@ -217,13 +207,13 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
private void AssertAssetHasBeenUploaded(long fileVersion)
{
A.CallTo(() => assetFileStore.UploadAsync(A<string>._, A<HasherStream>._, ct))
A.CallTo(() => assetFileStore.UploadAsync(A<string>._, A<HasherStream>._, CancellationToken))
.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();
A.CallTo(() => assetFileStore.DeleteAsync(A<string>._, ct))
A.CallTo(() => assetFileStore.DeleteAsync(A<string>._, CancellationToken))
.MustHaveHappened();
}
@ -235,13 +225,13 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
FileSize = fileSize
};
A.CallTo(() => assetQuery.FindByHashAsync(requestContext, A<string>._, fileName, fileSize, ct))
A.CallTo(() => assetQuery.FindByHashAsync(ApiContext, A<string>._, fileName, fileSize, CancellationToken))
.Returns(duplicate);
}
private void AssertMetadataEnriched()
{
A.CallTo(() => assetMetadataSource.EnhanceAsync(A<UploadAssetCommand>._, ct))
A.CallTo(() => assetMetadataSource.EnhanceAsync(A<UploadAssetCommand>._, CancellationToken))
.MustHaveHappened();
}
@ -253,17 +243,17 @@ public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject.Sta
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));
A.CallTo(() => domainObjectFactory.Create<AssetDomainObject>(command.AggregateId))
.Returns(domainObject);
return HandleAsync(sut, command, ct);
return HandleAsync(sut, command, CancellationToken);
}
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.Scripting;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
@ -23,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject;
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 IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
@ -41,8 +38,6 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
public AssetDomainObjectTests()
{
app = Mocks.App(AppNamedId, Language.DE);
var scripts = new AssetScripts
{
Annotate = "<annotate-script>",
@ -52,23 +47,20 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
Update = "<update-script>"
};
A.CallTo(() => app.AssetScripts)
A.CallTo(() => App.AssetScripts)
.Returns(scripts);
A.CallTo(() => appProvider.GetAppAsync(AppId, false, default))
.Returns(app);
A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId, parentId, A<CancellationToken>._))
A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A<CancellationToken>._))
.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>()));
var log = A.Fake<ILogger<AssetDomainObject>>();
var serviceProvider =
new ServiceCollection()
.AddSingleton(appProvider)
.AddSingleton(AppProvider)
.AddSingleton(assetQuery)
.AddSingleton(contentRepository)
.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();
}
@ -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();
}
@ -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();
}
@ -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();
}
@ -252,7 +244,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -274,7 +266,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -296,7 +288,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -318,7 +310,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -338,7 +330,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -360,7 +352,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -383,7 +375,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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();
}
@ -394,7 +386,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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);
var actual = await PublishAsync(command);
@ -404,7 +396,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version);
Assert.Empty(LastEvents);
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened();
}
@ -415,12 +407,12 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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);
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();
}
@ -431,12 +423,12 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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);
await PublishAsync(command);
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<ScriptVars>._, "<delete-script>", ScriptOptions(), CancellationToken))
.MustHaveHappened();
}
@ -481,7 +473,7 @@ public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject.State>
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;
}

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

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

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

@ -5,30 +5,23 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Security.Claims;
using Microsoft.Extensions.Logging;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared;
using Squidex.Shared.Identity;
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 ICommandBus commandBus = A.Dummy<ICommandBus>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly AssetsBulkUpdateCommandMiddleware sut;
public AssetsBulkUpdateCommandMiddlewareTests()
{
ct = cts.Token;
var log = A.Fake<ILogger<AssetsBulkUpdateCommandMiddleware>>();
sut = new AssetsBulkUpdateCommandMiddleware(contextProvider, log);
@ -68,7 +61,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
Assert.Single(actual);
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();
}
@ -104,7 +97,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
Assert.Single(actual);
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();
}
@ -141,7 +134,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync(
A<DeleteAsset>.That.Matches(x => x.AssetId == id), ct))
A<DeleteAsset>.That.Matches(x => x.AssetId == id), CancellationToken))
.MustHaveHappened();
}
@ -167,7 +160,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
{
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context, ct);
await sut.HandleAsync(context, CancellationToken);
return (context.PlainResult as BulkUpdateResult)!;
}
@ -176,7 +169,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
{
return new BulkUpdateAssets
{
AppId = appId,
AppId = AppId,
Jobs = new[]
{
new BulkUpdateJob
@ -191,14 +184,7 @@ public class AssetsBulkUpdateCommandMiddlewareTests
private Context SetupContext(string id)
{
var permission = 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));
var requestContext = CreateContext(false, PermissionIds.ForApp(id, AppId.Name).Id);
A.CallTo(() => contextProvider.Context)
.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;
public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
public class GuardAssetFolderTests : GivenContext, IClassFixture<TranslationsFixture>
{
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");
[Fact]
@ -45,10 +44,10 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
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>());
await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId),
await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken),
new ValidationError("Asset folder does not exist.", "ParentId"));
}
@ -59,10 +58,10 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
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() });
await operation.MustMoveToValidFolder(parentId);
await operation.MustMoveToValidFolder(parentId, CancellationToken);
}
[Fact]
@ -72,9 +71,9 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
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();
}
@ -85,9 +84,9 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
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();
}
@ -98,14 +97,14 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
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(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"));
}
@ -123,7 +122,7 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
return new AssetFolderOperation(serviceProvider, () => assetFolder)
{
App = Mocks.App(appId),
App = App,
CommandId = assetFolder.Id,
Command = new CreateAssetFolder { User = currentUser, Actor = actor }
};
@ -136,7 +135,7 @@ public class GuardAssetFolderTests : IClassFixture<TranslationsFixture>
A.CallTo(() => assetFolder.Id)
.Returns(OrNew(id));
A.CallTo(() => assetFolder.AppId)
.Returns(appId);
.Returns(AppId);
A.CallTo(() => assetFolder.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;
public class GuardAssetTests : IClassFixture<TranslationsFixture>
public class GuardAssetTests : GivenContext, IClassFixture<TranslationsFixture>
{
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
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]
public async Task Should_throw_exception_if_moving_to_invalid_folder()
@ -31,10 +29,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
var operation = Operation(CreateAsset());
A.CallTo(() => assetQuery.FindAssetFolderAsync(appId.Id, parentId, default))
A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken))
.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"));
}
@ -45,10 +43,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
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() });
await operation.MustMoveToValidFolder(parentId);
await operation.MustMoveToValidFolder(parentId, CancellationToken);
}
[Fact]
@ -58,9 +56,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
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();
}
@ -71,9 +69,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
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();
}
@ -82,10 +80,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
{
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);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync());
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
}
[Fact]
@ -93,10 +91,10 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
{
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);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync());
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
}
private AssetOperation Operation(AssetEntity asset)
@ -114,9 +112,9 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
return new AssetOperation(serviceProvider, () => asset)
{
App = Mocks.App(appId),
App = App,
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
{
Id = OrNew(id),
AppId = appId,
AppId = AppId,
Created = default,
CreatedBy = actor,
CreatedBy = User,
ParentId = OrNew(parentId)
};
}
@ -139,7 +137,7 @@ public class GuardAssetTests : IClassFixture<TranslationsFixture>
A.CallTo(() => assetFolder.Id)
.Returns(OrNew(id));
A.CallTo(() => assetFolder.AppId)
.Returns(appId);
.Returns(AppId);
A.CallTo(() => assetFolder.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;
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]
public async Task Should_add_tag_in_script()
{
@ -31,7 +28,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command);
await operation.ExecuteAnnotateScriptAsync(command);
await operation.ExecuteAnnotateScriptAsync(command, CancellationToken);
Assert.Contains("tag", command.Tags);
}
@ -45,7 +42,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command);
await operation.ExecuteAnnotateScriptAsync(command);
await operation.ExecuteAnnotateScriptAsync(command, CancellationToken);
Assert.Equal(JsonValue.Create(42), command.Metadata["foo"]);
}
@ -59,7 +56,7 @@ public sealed class ScriptingExtensionsTests
var operation = Operation(script, CreateAsset(), command);
await Assert.ThrowsAsync<ValidationException>(() => operation.ExecuteAnnotateScriptAsync(command));
await Assert.ThrowsAsync<ValidationException>(() => operation.ExecuteAnnotateScriptAsync(command, CancellationToken));
}
[Fact]
@ -71,7 +68,7 @@ public sealed class ScriptingExtensionsTests
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)
@ -81,9 +78,7 @@ public sealed class ScriptingExtensionsTests
Annotate = script
};
var app = Mocks.App(appId);
A.CallTo(() => app.AssetScripts)
A.CallTo(() => App.AssetScripts)
.Returns(scripts);
var serviceProvider =
@ -94,12 +89,12 @@ public sealed class ScriptingExtensionsTests
.AddSingleton<IScriptEngine, JintScriptEngine>()
.BuildServiceProvider();
command.Actor = actor;
command.Actor = User;
command.User = Mocks.FrontendUser();
return new AssetOperation(serviceProvider, () => asset)
{
App = app,
App = App,
CommandId = asset.Id,
Command = command
};
@ -110,9 +105,9 @@ public sealed class ScriptingExtensionsTests
return new AssetEntity
{
Id = DomainId.NewGuid(),
AppId = appId,
AppId = AppId,
Created = default,
CreatedBy = actor,
CreatedBy = User,
Metadata = new AssetMetadata(),
Tags = new HashSet<string>()
};

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

@ -8,14 +8,13 @@
using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Json.Objects;
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 MemoryStream stream = new MemoryStream();
private readonly AssetFile file;
@ -23,8 +22,6 @@ public class ImageAssetMetadataSourceTests
public ImageAssetMetadataSourceTests()
{
ct = cts.Token;
file = new DelegateAssetFile("MyImage.png", "image/png", 1024, () => stream);
sut = new ImageAssetMetadataSource(assetThumbnailGenerator);
@ -35,9 +32,9 @@ public class ImageAssetMetadataSourceTests
{
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();
}
@ -46,7 +43,7 @@ public class ImageAssetMetadataSourceTests
{
var command = new CreateAsset { File = file };
await sut.EnhanceAsync(command, ct);
await sut.EnhanceAsync(command, CancellationToken);
Assert.Empty(command.Tags);
}
@ -56,10 +53,10 @@ public class ImageAssetMetadataSourceTests
{
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));
await sut.EnhanceAsync(command, ct);
await sut.EnhanceAsync(command, CancellationToken);
Assert.Equal(800, command.Metadata.GetPixelWidth());
Assert.Equal(600, command.Metadata.GetPixelHeight());
@ -74,19 +71,19 @@ public class ImageAssetMetadataSourceTests
{
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));
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();
await sut.EnhanceAsync(command, ct);
await sut.EnhanceAsync(command, CancellationToken);
Assert.Equal(800, command.Metadata.GetPixelWidth());
Assert.Equal(600, command.Metadata.GetPixelHeight());
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();
}
@ -98,7 +95,7 @@ public class ImageAssetMetadataSourceTests
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/small", command.Tags);
@ -112,7 +109,7 @@ public class ImageAssetMetadataSourceTests
command.Metadata.SetPixelWidth(800);
command.Metadata.SetPixelWidth(600);
await sut.EnhanceAsync(command, ct);
await sut.EnhanceAsync(command, CancellationToken);
Assert.Contains("image", command.Tags);
Assert.Contains("image/medium", command.Tags);
@ -126,7 +123,7 @@ public class ImageAssetMetadataSourceTests
command.Metadata.SetPixelWidth(1200);
command.Metadata.SetPixelWidth(1400);
await sut.EnhanceAsync(command, ct);
await sut.EnhanceAsync(command, CancellationToken);
Assert.Contains("image", 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.
// ==========================================================================
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.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Json;
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]
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);
Assert.Empty(actual.TagNames);
}
var step1 = A.Fake<IAssetEnricherStep>();
var step2 = A.Fake<IAssetEnricherStep>();
[Fact]
public async Task Should_enrich_with_cache_dependencies()
{
var source = new AssetEntity { AppId = appId, Id = DomainId.NewGuid(), Version = 13 };
var sut = new AssetEnricher(new[] { step1, step2 });
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();
}
[Fact]
public async Task Should_enrich_asset_with_tag_names()
{
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);
}
A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken))
.MustHaveHappened();
[Fact]
public async Task Should_not_enrich_asset_with_tag_names_if_disabled()
{
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);
}
A.CallTo(() => step1.EnrichAsync(ApiContext, A<IEnumerable<AssetEntity>>._, A<CancellationToken>._))
.MustNotHaveHappened();
[Fact]
public async Task Should_enrich_asset_with_metadata()
{
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);
A.CallTo(() => step2.EnrichAsync(ApiContext, A<IEnumerable<AssetEntity>>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_multiple_assets_with_tag_names()
public async Task Should_invoke_steps()
{
var source1 = new AssetEntity
{
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);
}
var source = CreateAsset();
[Fact]
public async Task Should_also_compute_ui_tokens_for_frontend()
{
var source = new AssetEntity
{
AppId = appId
};
var step1 = A.Fake<IAssetEnricherStep>();
var step2 = A.Fake<IAssetEnricherStep>();
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();
}
[Fact]
public async Task Should_compute_ui_tokens()
{
var source = new AssetEntity
{
AppId = appId
};
var actual = await sut.EnrichAsync(new[] { source }, requestContext, ct);
A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken))
.MustHaveHappened();
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();
}
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.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
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 IDomainObjectCache domainObjectCache = A.Fake<IDomainObjectCache>();
private readonly AssetDomainObject domainObject = A.Fake<AssetDomainObject>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DomainId id = DomainId.NewGuid();
private readonly DomainId uniqueId;
private readonly AssetLoader sut;
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>._, ct))
A.CallTo(() => domainObjectCache.GetAsync<AssetDomainObject.State>(A<DomainId>._, A<long>._, CancellationToken))
.Returns(Task.FromResult<AssetDomainObject.State>(null!));
A.CallTo(() => domainObjectFactory.Create<AssetDomainObject>(uniqueId))
@ -43,10 +39,10 @@ public class AssetLoaderTests
{
var asset = (AssetDomainObject.State)null!;
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct))
A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset);
Assert.Null(await sut.GetAsync(appId, id, 10, ct));
Assert.Null(await sut.GetAsync(AppId.Id, id, 10, CancellationToken));
}
[Fact]
@ -54,10 +50,10 @@ public class AssetLoaderTests
{
var asset = new AssetDomainObject.State { Version = EtagVersion.Empty };
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct))
A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset);
Assert.Null(await sut.GetAsync(appId, id, 10, ct));
Assert.Null(await sut.GetAsync(AppId.Id, id, 10, CancellationToken));
}
[Fact]
@ -65,10 +61,10 @@ public class AssetLoaderTests
{
var asset = new AssetDomainObject.State { Version = 5 };
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct))
A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.Returns(asset);
Assert.Null(await sut.GetAsync(appId, id, 10, ct));
Assert.Null(await sut.GetAsync(AppId.Id, id, 10, CancellationToken));
}
[Fact]
@ -76,10 +72,10 @@ public class AssetLoaderTests
{
var asset = new AssetDomainObject.State { Version = 5 };
A.CallTo(() => domainObject.GetSnapshotAsync(EtagVersion.Any, ct))
A.CallTo(() => domainObject.GetSnapshotAsync(EtagVersion.Any, CancellationToken))
.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);
}
@ -89,10 +85,10 @@ public class AssetLoaderTests
{
var asset = new AssetDomainObject.State { Version = 10 };
A.CallTo(() => domainObject.GetSnapshotAsync(10, ct))
A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken))
.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);
}
@ -102,10 +98,10 @@ public class AssetLoaderTests
{
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);
var actual = await sut.GetAsync(appId, id, 10, ct);
var actual = await sut.GetAsync(AppId.Id, id, 10, CancellationToken);
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.TestHelpers;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.Queries;
public class AssetQueryParserTests
public class AssetQueryParserTests : GivenContext
{
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;
public AssetQueryParserTests()
{
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId));
var options = Options.Create(new AssetOptions { DefaultPageSize = 30 });
sut = new AssetQueryParser(TestUtils.DefaultSerializer, tagService, options);
@ -34,7 +29,7 @@ public class AssetQueryParserTests
[Fact]
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);
}
@ -44,7 +39,7 @@ public class AssetQueryParserTests
{
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]
@ -52,7 +47,7 @@ public class AssetQueryParserTests
{
var query = Q.Empty.WithJsonQuery("invalid");
await Assert.ThrowsAsync<ValidationException>(() => sut.ParseAsync(requestContext, query));
await Assert.ThrowsAsync<ValidationException>(() => sut.ParseAsync(ApiContext, query, CancellationToken));
}
[Fact]
@ -60,7 +55,7 @@ public class AssetQueryParserTests
{
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());
}
@ -70,7 +65,7 @@ public class AssetQueryParserTests
{
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());
}
@ -80,7 +75,7 @@ public class AssetQueryParserTests
{
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());
}
@ -90,7 +85,7 @@ public class AssetQueryParserTests
{
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());
}
@ -104,7 +99,7 @@ public class AssetQueryParserTests
{
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());
}
@ -114,7 +109,7 @@ public class AssetQueryParserTests
{
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());
}
@ -124,7 +119,7 @@ public class AssetQueryParserTests
{
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());
}
@ -134,7 +129,7 @@ public class AssetQueryParserTests
{
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());
}
@ -144,7 +139,7 @@ public class AssetQueryParserTests
{
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());
}
@ -152,12 +147,12 @@ public class AssetQueryParserTests
[Fact]
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" });
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());
}
@ -165,12 +160,12 @@ public class AssetQueryParserTests
[Fact]
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>());
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());
}
@ -180,11 +175,11 @@ public class AssetQueryParserTests
{
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());
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();
}
}

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;
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 IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>();
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 AssetQueryService sut;
public AssetQueryServiceTests()
{
ct = cts.Token;
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId));
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)!));
var options = Options.Create(new AssetOptions());
@ -53,10 +45,10 @@ public class AssetQueryServiceTests
{
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);
var actual = await sut.FindBySlugAsync(requestContext, "slug", ct);
var actual = await sut.FindBySlugAsync(ApiContext, "slug", CancellationToken);
AssertAsset(asset, actual);
}
@ -66,10 +58,10 @@ public class AssetQueryServiceTests
{
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));
var actual = await sut.FindBySlugAsync(requestContext, "slug", ct);
var actual = await sut.FindBySlugAsync(ApiContext, "slug", CancellationToken);
Assert.Null(actual);
}
@ -79,10 +71,10 @@ public class AssetQueryServiceTests
{
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);
var actual = await sut.FindAsync(requestContext, asset.Id, ct: ct);
var actual = await sut.FindAsync(ApiContext, asset.Id, ct: CancellationToken);
AssertAsset(asset, actual);
}
@ -92,10 +84,10 @@ public class AssetQueryServiceTests
{
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));
var actual = await sut.FindAsync(requestContext, asset.Id, ct: ct);
var actual = await sut.FindAsync(ApiContext, asset.Id, ct: CancellationToken);
Assert.Null(actual);
}
@ -105,10 +97,10 @@ public class AssetQueryServiceTests
{
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);
var actual = await sut.FindAsync(requestContext, asset.Id, 2, ct);
var actual = await sut.FindAsync(ApiContext, asset.Id, 2, CancellationToken);
AssertAsset(asset, actual);
}
@ -118,10 +110,10 @@ public class AssetQueryServiceTests
{
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));
var actual = await sut.FindAsync(requestContext, asset.Id, 2, ct);
var actual = await sut.FindAsync(ApiContext, asset.Id, 2, CancellationToken);
Assert.Null(actual);
}
@ -134,7 +126,7 @@ public class AssetQueryServiceTests
A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A<CancellationToken>._))
.Returns(asset);
var actual = await sut.FindGlobalAsync(requestContext, asset.Id, ct);
var actual = await sut.FindGlobalAsync(ApiContext, asset.Id, CancellationToken);
AssertAsset(asset, actual);
}
@ -147,7 +139,7 @@ public class AssetQueryServiceTests
A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A<CancellationToken>._))
.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);
}
@ -157,10 +149,10 @@ public class AssetQueryServiceTests
{
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);
var actual = await sut.FindByHashAsync(requestContext, "hash", "name", 123, ct);
var actual = await sut.FindByHashAsync(ApiContext, "hash", "name", 123, CancellationToken);
AssertAsset(asset, actual);
}
@ -170,10 +162,10 @@ public class AssetQueryServiceTests
{
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));
var actual = await sut.FindByHashAsync(requestContext, "hash", "name", 123, ct);
var actual = await sut.FindByHashAsync(ApiContext, "hash", "name", 123, CancellationToken);
Assert.Null(actual);
}
@ -188,10 +180,10 @@ public class AssetQueryServiceTests
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));
var actual = await sut.QueryAsync(requestContext, parentId, q, ct);
var actual = await sut.QueryAsync(ApiContext, parentId, q, CancellationToken);
Assert.Equal(8, actual.Total);
@ -206,10 +198,10 @@ public class AssetQueryServiceTests
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);
var actual = await sut.QueryAssetFoldersAsync(requestContext, parentId, ct);
var actual = await sut.QueryAssetFoldersAsync(ApiContext, parentId, CancellationToken);
Assert.Same(assetFolders, actual);
}
@ -221,10 +213,10 @@ public class AssetQueryServiceTests
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);
var actual = await sut.QueryAssetFoldersAsync(appId.Id, parentId, ct);
var actual = await sut.QueryAssetFoldersAsync(AppId.Id, parentId, CancellationToken);
Assert.Same(assetFolders, actual);
}
@ -235,10 +227,10 @@ public class AssetQueryServiceTests
var folderId1 = DomainId.NewGuid();
var folder1 = CreateFolder(folderId1);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId1, A<CancellationToken>._))
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A<CancellationToken>._))
.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 });
}
@ -254,16 +246,16 @@ public class AssetQueryServiceTests
var folder2 = CreateFolder(folderId2, folderId1);
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);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId2, A<CancellationToken>._))
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A<CancellationToken>._))
.Returns(folder2);
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId3, A<CancellationToken>._))
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId3, A<CancellationToken>._))
.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 });
}
@ -273,10 +265,10 @@ public class AssetQueryServiceTests
{
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));
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId1, ct);
var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId1, CancellationToken);
Assert.Empty(actual);
}
@ -290,13 +282,13 @@ public class AssetQueryServiceTests
var folder1 = CreateFolder(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));
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId2, A<CancellationToken>._))
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A<CancellationToken>._))
.Returns(folder2);
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId2, ct);
var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId2, CancellationToken);
Assert.Empty(actual);
}
@ -310,13 +302,13 @@ public class AssetQueryServiceTests
var folder1 = CreateFolder(folderId1, folderId2);
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));
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(appId.Id, folderId2, A<CancellationToken>._))
A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A<CancellationToken>._))
.Returns(folder2);
var actual = await sut.FindAssetFolderAsync(appId.Id, folderId2, ct);
var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId2, CancellationToken);
Assert.Empty(actual);
}
@ -345,7 +337,7 @@ public class AssetQueryServiceTests
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 =>
{
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.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
@ -16,13 +17,12 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets;
public class RecursiveDeleterTests
public class RecursiveDeleterTests : GivenContext
{
private readonly ILogger<RecursiveDeleter> log = A.Fake<ILogger<RecursiveDeleter>>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAssetFolderRepository assetFolderRepository = A.Fake<IAssetFolderRepository>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RecursiveDeleter sut;
public RecursiveDeleterTests()
@ -57,7 +57,7 @@ public class RecursiveDeleterTests
[Fact]
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());
@ -68,12 +68,12 @@ public class RecursiveDeleterTests
[Fact]
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 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 });
await sut.On(Envelope.Create(@event));
@ -88,12 +88,12 @@ public class RecursiveDeleterTests
[Fact]
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 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 });
await sut.On(Envelope.Create(@event));
@ -108,7 +108,7 @@ public class RecursiveDeleterTests
[Fact]
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 childId2 = DomainId.NewGuid();
@ -116,7 +116,7 @@ public class RecursiveDeleterTests
A.CallTo(() => commandBus.PublishAsync(A<DeleteAsset>.That.Matches(x => x.AssetId == childId1), default))
.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 });
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.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Assets;
public class RepairFilesTests
public class RepairFilesTests : GivenContext
{
private readonly IEventStore eventStore = A.Fake<IEventStore>();
private readonly IEventFormatter eventFormatter = A.Fake<IEventFormatter>();
private readonly IAssetFileStore assetFileStore = A.Fake<IAssetFileStore>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly RebuildFiles sut;
public RepairFilesTests()
@ -28,64 +28,64 @@ public class RepairFilesTests
[Fact]
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);
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"));
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();
}
[Fact]
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);
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 0, null, default))
A.CallTo(() => assetFileStore.GetFileSizeAsync(AppId.Id, @event.AssetId, 0, null, CancellationToken))
.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();
}
[Fact]
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);
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"));
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();
}
[Fact]
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);
A.CallTo(() => assetFileStore.GetFileSizeAsync(appId.Id, @event.AssetId, 3, null, default))
A.CallTo(() => assetFileStore.GetFileSizeAsync(AppId.Id, @event.AssetId, 3, null, CancellationToken))
.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();
}
@ -94,7 +94,7 @@ public class RepairFilesTests
{
SetupEvent(null);
await sut.RepairAsync();
await sut.RepairAsync(CancellationToken);
A.CallTo(() => assetFileStore.GetFileSizeAsync(A<DomainId>._, A<DomainId>._, A<long>._, null, A<CancellationToken>._))
.MustNotHaveHappened();
@ -122,7 +122,7 @@ public class RepairFilesTests
.Returns(null);
}
A.CallTo(() => eventStore.QueryAllAsync("^asset\\-", null, int.MaxValue, default))
A.CallTo(() => eventStore.QueryAllAsync("^asset\\-", null, int.MaxValue, CancellationToken))
.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;
public class BackupServiceTests
public class BackupServiceTests : GivenContext
{
private readonly TestState<BackupState> stateBackup;
private readonly TestState<BackupRestoreState> stateRestore;
private readonly IMessageBus messaging = A.Fake<IMessageBus>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly DomainId backupId = DomainId.NewGuid();
private readonly RefToken actor = RefToken.User("me");
private readonly BackupService sut;
public BackupServiceTests()
{
stateRestore = new TestState<BackupRestoreState>("Default");
stateBackup = new TestState<BackupState>(appId);
stateBackup = new TestState<BackupState>(AppId.Id);
sut = new BackupService(
stateRestore.PersistenceFactory,
@ -40,36 +38,36 @@ public class BackupServiceTests
var restoreUrl = new Uri("http://squidex.io");
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();
}
[Fact]
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();
}
[Fact]
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();
}
[Fact]
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();
}
@ -86,7 +84,7 @@ public class BackupServiceTests
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]
@ -97,7 +95,7 @@ public class BackupServiceTests
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]
@ -108,7 +106,7 @@ public class BackupServiceTests
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]
@ -122,7 +120,7 @@ public class BackupServiceTests
}
};
var actual = await sut.GetRestoreAsync();
var actual = await sut.GetRestoreAsync(CancellationToken);
actual.Should().BeEquivalentTo(stateRestore.Snapshot.Job);
}
@ -139,7 +137,7 @@ public class BackupServiceTests
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);
}
@ -156,7 +154,7 @@ public class BackupServiceTests
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);
}

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

@ -6,14 +6,13 @@
// ==========================================================================
using Squidex.Assets;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
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 DomainId backupId = DomainId.NewGuid();
private readonly string fileName;
@ -21,8 +20,6 @@ public class DefaultBackupArchiveStoreTests
public DefaultBackupArchiveStoreTests()
{
ct = cts.Token;
fileName = $"{backupId}_0";
sut = new DefaultBackupArchiveStore(assetStore);
@ -33,9 +30,9 @@ public class DefaultBackupArchiveStoreTests
{
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();
}
@ -44,18 +41,18 @@ public class DefaultBackupArchiveStoreTests
{
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();
}
[Fact]
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();
}
}

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.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup;
public class UserMappingTests
public class UserMappingTests : GivenContext
{
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly CancellationToken ct;
private readonly RefToken initiator = Subject("me");
private readonly RefToken initiator = RefToken.User("me");
private readonly UserMapping sut;
public UserMappingTests()
{
ct = cts.Token;
sut = new UserMapping(initiator);
}
@ -29,9 +26,9 @@ public class UserMappingTests
public async Task Should_backup_users_but_no_clients()
{
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 user2 = UserMocks.User("2", "1@email.com");
@ -44,17 +41,17 @@ public class UserMappingTests
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);
var writer = A.Fake<IBackupWriter>();
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));
await sut.StoreAsync(writer, userResolver, ct);
await sut.StoreAsync(writer, userResolver, CancellationToken);
Assert.Equal(new Dictionary<string, string>
{
@ -73,25 +70,25 @@ public class UserMappingTests
var userResolver = A.Fake<IUserResolver>();
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user1.Email, false, ct))
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user1.Email, false, CancellationToken))
.Returns((user1, false));
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user2.Email, false, ct))
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user2.Email, false, CancellationToken))
.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(Subject("2_old"), out var mapped2));
Assert.True(sut.TryMap(RefToken.User("2_old"), out var mapped2));
Assert.Equal(Subject("1"), mapped1);
Assert.Equal(Subject("2"), mapped2);
Assert.Equal(RefToken.User("1"), mapped1);
Assert.Equal(RefToken.User("2"), mapped2);
}
[Fact]
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.Same(initiator, mapped);
@ -100,7 +97,7 @@ public class UserMappingTests
[Fact]
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.Same(client, mapped);
@ -112,19 +109,9 @@ public class UserMappingTests
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);
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.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Teams;
using Squidex.Domain.Apps.Entities.TestHelpers;
@ -17,20 +16,14 @@ using Squidex.Messaging;
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 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 IUsageTracker usageTracker = A.Fake<IUsageTracker>();
private readonly string clientId = Guid.NewGuid().ToString();
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 Plan planFree = new Plan { Id = "free" };
private readonly Plan planPaid = new Plan { Id = "paid" };
@ -38,14 +31,6 @@ public class 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>._))
.ReturnsLazily(x => (planFree, planFree.Id));
@ -55,31 +40,31 @@ public class UsageGateTests
A.CallTo(() => usageTracker.FallbackCategory)
.Returns("*");
sut = new UsageGate(appProvider, apiUsageTracker, billingPlans, messaging, usageTracker);
sut = new UsageGate(AppProvider, apiUsageTracker, billingPlans, messaging, usageTracker);
}
[Fact]
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();
}
[Fact]
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();
}
[Fact]
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);
}
@ -89,13 +74,16 @@ public class UsageGateTests
{
var team = A.Fake<ITeamEntity>();
A.CallTo(() => appProvider.GetTeamAsync(teamId, ct))
A.CallTo(() => AppProvider.GetTeamAsync(teamId, CancellationToken))
.Returns(team);
A.CallTo(() => team.Id)
.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);
}
@ -103,10 +91,10 @@ public class UsageGateTests
[Fact]
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));
var plan = await sut.GetPlanForAppAsync(appWithoutTeam, false, ct);
var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken);
Assert.Equal((planPaid, planPaid.Id, null), plan);
}
@ -114,13 +102,10 @@ public class UsageGateTests
[Fact]
public async Task Should_get_paid_plan_for_app_id()
{
A.CallTo(() => appProvider.GetAppAsync(appWithoutTeam.Id, true, ct))
.Returns(appWithoutTeam);
A.CallTo(() => appWithoutTeam.Plan)
A.CallTo(() => App.Plan)
.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);
}
@ -130,7 +115,7 @@ public class UsageGateTests
{
var team = A.Fake<ITeamEntity>();
A.CallTo(() => appProvider.GetTeamAsync(teamId, ct))
A.CallTo(() => AppProvider.GetTeamAsync(teamId, CancellationToken))
.Returns(team);
A.CallTo(() => team.Id)
@ -139,7 +124,10 @@ public class UsageGateTests
A.CallTo(() => team.Plan)
.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);
}
@ -152,17 +140,17 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.ReturnsLazily(x => (plan, plan.Id));
A.CallTo(() => appWithoutTeam.Clients)
A.CallTo(() => App.Clients)
.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);
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.True(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct))
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened();
}
@ -174,14 +162,14 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.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);
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.True(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct))
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened();
}
@ -193,10 +181,10 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.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);
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.False(isBlocked);
@ -212,14 +200,14 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.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
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.False(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct))
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened();
}
@ -231,14 +219,14 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.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
var isBlocked = await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
var isBlocked = await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
Assert.False(isBlocked);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct))
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappened();
}
@ -250,13 +238,13 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.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
await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
await sut.IsBlockedAsync(appWithoutTeam, clientId, today, ct);
await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
await sut.IsBlockedAsync(App, clientId, today, CancellationToken);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, ct))
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, CancellationToken))
.MustHaveHappenedOnceExactly();
}
@ -270,11 +258,11 @@ public class UsageGateTests
A.CallTo(() => billingPlans.GetActualPlan(A<string>._))
.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
await sut.IsBlockedAsync(appWithoutTeam, clientId, now, ct);
await sut.IsBlockedAsync(appWithoutTeam, clientId, now, ct);
await sut.IsBlockedAsync(App, clientId, now, CancellationToken);
await sut.IsBlockedAsync(App, clientId, now, CancellationToken);
A.CallTo(() => messaging.PublishAsync(A<UsageTrackingCheck>._, null, A<CancellationToken>._))
.MustNotHaveHappened();
@ -283,10 +271,10 @@ public class UsageGateTests
[Fact]
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 });
var size = await sut.GetTotalSizeByAppAsync(appId.Id, ct);
var size = await sut.GetTotalSizeByAppAsync(AppId.Id, CancellationToken);
Assert.Equal(2048, size);
}
@ -294,10 +282,10 @@ public class UsageGateTests
[Fact]
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 });
var size = await sut.GetTotalSizeByTeamAsync(appId.Id, ct);
var size = await sut.GetTotalSizeByTeamAsync(AppId.Id, CancellationToken);
Assert.Equal(2048, size);
}
@ -305,27 +293,30 @@ public class UsageGateTests
[Fact]
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();
}
[Fact]
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();
}
[Fact]
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>
{
@ -338,9 +329,9 @@ public class UsageGateTests
[Fact]
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>
{
@ -352,7 +343,7 @@ public class UsageGateTests
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)>>
{
[usageTracker.FallbackCategory] = new List<(DateTime, Counters)>
@ -382,13 +373,13 @@ public class UsageGateTests
Counters? countersSummary = 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));
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));
await sut.TrackAssetAsync(appWithoutTeam.Id, today, 512, 3, ct);
await sut.TrackAssetAsync(AppId.Id, today, 512, 3, CancellationToken);
var expected = new Counters
{
@ -411,19 +402,19 @@ public class UsageGateTests
A.CallTo(() => team.Id)
.Returns(teamId);
A.CallTo(() => appProvider.GetAppAsync(appWithTeam.Id, true, ct))
.Returns(appWithTeam);
A.CallTo(() => App.TeamId)
.Returns(teamId);
A.CallTo(() => appProvider.GetTeamAsync(teamId, ct))
A.CallTo(() => AppProvider.GetTeamAsync(teamId, CancellationToken))
.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));
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));
await sut.TrackAssetAsync(appWithTeam.Id, today, 512, 3, ct);
await sut.TrackAssetAsync(AppId.Id, today, 512, 3, CancellationToken);
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;
public class UsageNotifierWorkerTest
public class UsageNotifierWorkerTest : GivenContext
{
private readonly TestState<UsageNotifierWorker.State> state = new TestState<UsageNotifierWorker.State>("Default");
private readonly IClock clock = A.Fake<IClock>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IUserNotifications notificationSender = A.Fake<IUserNotifications>();
private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly IAppEntity app = Mocks.App(NamedId.Of(DomainId.NewGuid(), "my-app"));
private readonly UsageNotifierWorker sut;
private Instant time = SystemClock.Instance.GetCurrentInstant();
public UsageNotifierWorkerTest()
{
A.CallTo(() => appProvider.GetAppAsync(app.Id, true, default))
.Returns(app);
A.CallTo(() => clock.GetCurrentInstant())
.ReturnsLazily(() => time);
A.CallTo(() => notificationSender.IsActive)
.Returns(true);
sut = new UsageNotifierWorker(state.PersistenceFactory, appProvider, notificationSender, userResolver)
sut = new UsageNotifierWorker(state.PersistenceFactory, AppProvider, notificationSender, userResolver)
{
Clock = clock
};
@ -61,7 +56,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck
{
AppId = app.Id,
AppId = AppId.Id,
Usage = 1000,
UsageLimit = 3000,
Users = new[] { "1", "2" }
@ -82,7 +77,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck
{
AppId = app.Id,
AppId = AppId.Id,
Usage = 1000,
UsageLimit = 3000,
Users = new[] { "1", "2", "3" }
@ -90,13 +85,13 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user1!, app, 1000, 3000, default))
A.CallTo(() => notificationSender.SendUsageAsync(user1!, App, 1000, 3000, default))
.MustHaveHappened();
A.CallTo(() => notificationSender.SendUsageAsync(user2!, app, 1000, 3000, default))
A.CallTo(() => notificationSender.SendUsageAsync(user2!, App, 1000, 3000, default))
.MustHaveHappened();
A.CallTo(() => notificationSender.SendUsageAsync(user3!, app, 1000, 3000, default))
A.CallTo(() => notificationSender.SendUsageAsync(user3!, App, 1000, 3000, default))
.MustNotHaveHappened();
}
@ -107,7 +102,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck
{
AppId = app.Id,
AppId = AppId.Id,
Usage = 1000,
UsageLimit = 3000,
Users = new[] { "1" }
@ -116,7 +111,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();
}
@ -127,7 +122,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck
{
AppId = app.Id,
AppId = AppId.Id,
Usage = 1000,
UsageLimit = 3000,
Users = new[] { "1" }
@ -139,7 +134,7 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user!, app, 1000, 3000, default))
A.CallTo(() => notificationSender.SendUsageAsync(user!, App, 1000, 3000, default))
.MustHaveHappenedTwiceExactly();
}
@ -155,7 +150,7 @@ public class UsageNotifierWorkerTest
var message = new UsageTrackingCheck
{
AppId = app.Id,
AppId = AppId.Id,
Usage = 1000,
UsageLimit = 3000,
Users = new[] { "1" }
@ -167,7 +162,7 @@ public class UsageNotifierWorkerTest
await sut.HandleAsync(message, default);
A.CallTo(() => notificationSender.SendUsageAsync(user!, app, 1000, 3000, default))
A.CallTo(() => notificationSender.SendUsageAsync(user!, App, 1000, 3000, default))
.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.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
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 CommentsLoader sut;
public CommentsLoaderTests()
{
ct = cts.Token;
sut = new CommentsLoader(domainObjectFactory);
}
@ -39,11 +36,11 @@ public sealed class CommentsLoaderTests
A.CallTo(() => domainObject.GetComments(11))
.Returns(comments);
var actual = await sut.GetCommentsAsync(commentsId, 11, ct);
var actual = await sut.GetCommentsAsync(commentsId, 11, CancellationToken);
Assert.Same(comments, actual);
A.CallTo(() => domainObject.LoadAsync(ct))
A.CallTo(() => domainObject.LoadAsync(CancellationToken))
.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.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared.Users;
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 IUserResolver userResolver = A.Fake<IUserResolver>();
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 commentId = DomainId.NewGuid();
private readonly CommentsCommandMiddleware sut;
public CommentsCommandMiddlewareTests()
{
ct = cts.Token;
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, default))
.Returns(Task.FromResult<IUser?>(null));
@ -44,7 +39,7 @@ public class CommentsCommandMiddlewareTests
var domainObject = A.Fake<CommentsStream>();
A.CallTo(() => domainObject.ExecuteAsync(command, ct))
A.CallTo(() => domainObject.ExecuteAsync(command, CancellationToken))
.Returns(CommandResult.Empty(commentsId, 0, 0));
A.CallTo(() => domainObjectFactory.Create<CommentsStream>(commentsId))
@ -57,7 +52,7 @@ public class CommentsCommandMiddlewareTests
isNextCalled = true;
return Task.CompletedTask;
}, ct);
}, CancellationToken);
Assert.True(isNextCalled);
}
@ -76,7 +71,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct);
await sut.HandleAsync(context, CancellationToken);
Assert.Equal(command.Mentions, new[] { "id1", "id2" });
}
@ -95,7 +90,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct);
await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -112,7 +107,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct);
await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -129,7 +124,7 @@ public class CommentsCommandMiddlewareTests
var context = CrateCommandContext(command);
await sut.HandleAsync(context, ct);
await sut.HandleAsync(context, CancellationToken);
A.CallTo(() => userResolver.FindByIdOrEmailAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -150,10 +145,10 @@ public class CommentsCommandMiddlewareTests
private T CreateCommentsCommand<T>(T command) where T : CommentCommand
{
command.Actor = actor;
command.AppId = appId;
command.AppId = AppId;
command.CommentsId = commentsId;
command.CommentId = commentId;
command.Actor = User;
return command;
}

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

@ -6,19 +6,16 @@
// ==========================================================================
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.TestHelpers;
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> state2;
private readonly IClock clock = A.Fake<IClock>();
private readonly DomainId appId = DomainId.NewGuid();
private readonly string resource1 = "resource1";
private readonly string resource2 = "resource2";
private readonly WatchingService sut;
@ -26,13 +23,11 @@ public class WatchingServiceTests
public WatchingServiceTests()
{
ct = cts.Token;
A.CallTo(() => clock.GetCurrentInstant())
.ReturnsLazily(() => now);
state1 = new TestState<WatchingService.State>($"{appId}_{resource1}");
state2 = new TestState<WatchingService.State>($"{appId}_{resource2}", state1.PersistenceFactory);
state1 = new TestState<WatchingService.State>($"{AppId.Id}_{resource1}");
state2 = new TestState<WatchingService.State>($"{AppId.Id}_{resource2}", state1.PersistenceFactory);
sut = new WatchingService(state1.PersistenceFactory)
{
@ -43,7 +38,7 @@ public class WatchingServiceTests
[Fact]
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);
}
@ -51,11 +46,11 @@ public class WatchingServiceTests
[Fact]
public async Task Should_return_users_watching_on_same_resource()
{
await sut.GetWatchingUsersAsync(appId, resource1, "user1", ct);
await sut.GetWatchingUsersAsync(appId, resource2, "user2", ct);
await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user1", CancellationToken);
await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user2", CancellationToken);
var watching1 = await sut.GetWatchingUsersAsync(appId, resource1, "user3", ct);
var watching2 = await sut.GetWatchingUsersAsync(appId, resource2, "user4", ct);
var watching1 = await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user3", CancellationToken);
var watching2 = await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user4", CancellationToken);
Assert.Equal(new[] { "user1", "user3" }, watching1);
Assert.Equal(new[] { "user2", "user4" }, watching2);
@ -64,24 +59,24 @@ public class WatchingServiceTests
[Fact]
public async Task Should_cleanup_old_users()
{
await sut.GetWatchingUsersAsync(appId, resource1, "user1", ct);
await sut.GetWatchingUsersAsync(appId, resource2, "user2", ct);
await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user1", CancellationToken);
await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user2", CancellationToken);
now = now.Plus(Duration.FromMinutes(2));
await sut.GetWatchingUsersAsync(appId, resource1, "user3", ct);
await sut.GetWatchingUsersAsync(appId, resource2, "user4", ct);
await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user3", CancellationToken);
await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user4", CancellationToken);
var watching1 = await sut.GetWatchingUsersAsync(appId, resource1, "user5", ct);
var watching2 = await sut.GetWatchingUsersAsync(appId, resource2, "user6", ct);
var watching1 = await sut.GetWatchingUsersAsync(AppId.Id, resource1, "user5", CancellationToken);
var watching2 = await sut.GetWatchingUsersAsync(AppId.Id, resource2, "user6", CancellationToken);
Assert.Equal(new[] { "user3", "user5" }, watching1);
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();
A.CallTo(() => state2.Persistence.WriteSnapshotAsync(A<WatchingService.State>._, ct))
A.CallTo(() => state2.Persistence.WriteSnapshotAsync(A<WatchingService.State>._, CancellationToken))
.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.Entities.Backup;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Events.Schemas;
@ -19,19 +20,14 @@ using Squidex.Infrastructure.Json.Objects;
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 Rebuilder rebuilder = A.Fake<Rebuilder>();
private readonly BackupContents sut;
public BackupContentsTests()
{
ct = cts.Token;
sut = new BackupContents(rebuilder, urlGenerator);
}
@ -52,22 +48,22 @@ public class BackupContentsTests
A.CallTo(() => urlGenerator.AssetContentBase())
.Returns(assetsUrl);
A.CallTo(() => urlGenerator.AssetContentBase(appId.Name))
A.CallTo(() => urlGenerator.AssetContentBase(AppId.Name))
.Returns(assetsUrlApp);
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
{
Name = appId.Name
}), context, ct);
Name = AppId.Name
}), context, CancellationToken);
A.CallTo(() => writer.WriteJsonAsync(A<string>._,
A<BackupContents.Urls>.That.Matches(x =>
x.Assets == assetsUrl &&
x.AssetsApp == assetsUrlApp), ct))
x.AssetsApp == assetsUrlApp), CancellationToken))
.MustHaveHappened();
}
@ -87,10 +83,10 @@ public class BackupContentsTests
A.CallTo(() => urlGenerator.AssetContentBase())
.Returns(newAssetsUrl);
A.CallTo(() => urlGenerator.AssetContentBase(appId.Name))
A.CallTo(() => urlGenerator.AssetContentBase(AppId.Name))
.Returns(newAssetsUrlApp);
A.CallTo(() => reader.ReadJsonAsync<BackupContents.Urls>(A<string>._, ct))
A.CallTo(() => reader.ReadJsonAsync<BackupContents.Urls>(A<string>._, CancellationToken))
.Returns(new BackupContents.Urls
{
Assets = oldAssetsUrl,
@ -131,17 +127,17 @@ public class BackupContentsTests
new JsonObject()
.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
{
Name = appId.Name
}), context, ct);
Name = AppId.Name
}), context, CancellationToken);
await sut.RestoreEventAsync(Envelope.Create(new ContentUpdated
{
Data = data
}), context, ct);
}), context, CancellationToken);
Assert.Equal(updateData, data);
}
@ -158,55 +154,55 @@ public class BackupContentsTests
var contentId2 = 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
{
ContentId = contentId1,
SchemaId = schemaId1
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(ContentEvent(new ContentCreated
{
ContentId = contentId2,
SchemaId = schemaId1
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(ContentEvent(new ContentCreated
{
ContentId = contentId3,
SchemaId = schemaId2
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(ContentEvent(new ContentDeleted
{
ContentId = contentId2,
SchemaId = schemaId1
}), context, ct);
}), context, CancellationToken);
await sut.RestoreEventAsync(Envelope.Create(new SchemaDeleted
{
SchemaId = schemaId2
}), context, ct);
}), context, CancellationToken);
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)!));
await sut.RestoreAsync(context, ct);
await sut.RestoreAsync(context, CancellationToken);
Assert.Equal(new HashSet<DomainId>
{
DomainId.Combine(appId, contentId1),
DomainId.Combine(appId, contentId2)
DomainId.Combine(AppId, contentId1),
DomainId.Combine(AppId, contentId2)
}, rebuildContents);
}
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.TestHelpers;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Events.Contents;
@ -23,10 +24,8 @@ using Squidex.Infrastructure.Reflection;
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 IContentLoader contentLoader = A.Fake<IContentLoader>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
@ -36,8 +35,6 @@ public class ContentChangedTriggerHandlerTests
public ContentChangedTriggerHandlerTests()
{
ct = cts.Token;
A.CallTo(() => scriptEngine.Evaluate(A<ScriptVars>._, "true", default))
.Returns(true);
@ -128,14 +125,14 @@ public class ContentChangedTriggerHandlerTests
{
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>
{
new ContentEntity { SchemaId = schemaMatch },
new ContentEntity { SchemaId = schemaNonMatch }
}.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();
@ -160,14 +157,14 @@ public class ContentChangedTriggerHandlerTests
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>
{
new ContentEntity { SchemaId = schemaMatch },
new ContentEntity { SchemaId = schemaMatch }
}.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();
@ -186,10 +183,10 @@ public class ContentChangedTriggerHandlerTests
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()));
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();
@ -213,13 +210,13 @@ public class ContentChangedTriggerHandlerTests
var dataNow = 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 });
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 });
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;
@ -388,13 +385,13 @@ public class ContentChangedTriggerHandlerTests
}
}
private static RuleContext Context(RuleTrigger? trigger = null)
private RuleContext Context(RuleTrigger? trigger = null)
{
trigger ??= new ContentChangedTriggerV2();
return new RuleContext
{
AppId = NamedId.Of(DomainId.NewGuid(), "my-app"),
AppId = AppId,
Rule = new Rule(trigger, A.Fake<RuleAction>()),
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.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents;
public class ContentSchedulerProcessTests
public class ContentSchedulerProcessTests : GivenContext
{
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly IClock clock = A.Fake<IClock>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly ContentSchedulerProcess sut;
public ContentSchedulerProcessTests()
@ -38,14 +38,14 @@ public class ContentSchedulerProcessTests
var content1 = new ContentEntity
{
AppId = appId,
AppId = AppId,
Id = DomainId.NewGuid(),
ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now)
};
var content2 = new ContentEntity
{
AppId = appId,
AppId = AppId,
Id = DomainId.NewGuid(),
ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Draft, null!, now)
};
@ -53,10 +53,10 @@ public class ContentSchedulerProcessTests
A.CallTo(() => clock.GetCurrentInstant())
.Returns(now);
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, default))
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, CancellationToken))
.Returns(new[] { content1, content2 }.ToAsyncEnumerable());
await sut.PublishAsync();
await sut.PublishAsync(CancellationToken);
A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x =>
@ -82,7 +82,7 @@ public class ContentSchedulerProcessTests
var content1 = new ContentEntity
{
AppId = appId,
AppId = AppId,
Id = DomainId.NewGuid(),
ScheduleJob = null
};
@ -90,10 +90,10 @@ public class ContentSchedulerProcessTests
A.CallTo(() => clock.GetCurrentInstant())
.Returns(now);
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, default))
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, CancellationToken))
.Returns(new[] { content1 }.ToAsyncEnumerable());
await sut.PublishAsync();
await sut.PublishAsync(CancellationToken);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, A<CancellationToken>._))
.MustNotHaveHappened();
@ -106,7 +106,7 @@ public class ContentSchedulerProcessTests
var content1 = new ContentEntity
{
AppId = appId,
AppId = AppId,
Id = DomainId.NewGuid(),
ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now)
};
@ -114,13 +114,13 @@ public class ContentSchedulerProcessTests
A.CallTo(() => clock.GetCurrentInstant())
.Returns(now);
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, default))
A.CallTo(() => contentRepository.QueryScheduledWithoutDataAsync(now, CancellationToken))
.Returns(new[] { content1 }.ToAsyncEnumerable());
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._, default))
.Throws(new DomainObjectNotFoundException(content1.Id.ToString()));
await sut.PublishAsync();
await sut.PublishAsync(CancellationToken);
A.CallTo(() => contentRepository.ResetScheduledAsync(content1.UniqueId, default))
.MustHaveHappened();

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

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Security.Claims;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
@ -15,19 +14,14 @@ using Squidex.Domain.Apps.Entities.Search;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Shared;
using Squidex.Shared.Identity;
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 ITextIndex contentIndex = A.Fake<ITextIndex>();
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> schemaId2 = NamedId.Of(DomainId.NewGuid(), "my-schema2");
private readonly NamedId<DomainId> schemaId3 = NamedId.Of(DomainId.NewGuid(), "my-schema3");
@ -35,17 +29,15 @@ public class ContentsSearchSourceTests
public ContentsSearchSourceTests()
{
ct = cts.Token;
A.CallTo(() => appProvider.GetSchemasAsync(appId.Id, ct))
A.CallTo(() => AppProvider.GetSchemasAsync(AppId.Id, CancellationToken))
.Returns(new List<ISchemaEntity>
{
Mocks.Schema(appId, schemaId1),
Mocks.Schema(appId, schemaId2),
Mocks.Schema(appId, schemaId3)
Mocks.Schema(AppId, schemaId1),
Mocks.Schema(AppId, schemaId2),
Mocks.Schema(AppId, schemaId3)
});
sut = new ContentsSearchSource(appProvider, contentQuery, contentIndex, urlGenerator);
sut = new ContentsSearchSource(AppProvider, contentQuery, contentIndex, urlGenerator);
}
[Fact]
@ -152,50 +144,50 @@ public class ContentsSearchSourceTests
[Fact]
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);
A.CallTo(() => contentIndex.SearchAsync(ctx.App, A<TextQuery>._, A<SearchScope>._, A<CancellationToken>._))
A.CallTo(() => contentIndex.SearchAsync(App, A<TextQuery>._, A<SearchScope>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
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>());
var actual = await sut.SearchAsync("query", ctx, ct);
var actual = await sut.SearchAsync("query", requestContext, CancellationToken);
Assert.Empty(actual);
A.CallTo(() => contentQuery.QueryAsync(ctx, A<Q>._, A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(requestContext, A<Q>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
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 };
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);
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));
A.CallTo(() => urlGenerator.ContentUI(appId, schemaId1, content.Id))
A.CallTo(() => urlGenerator.ContentUI(AppId, schemaId1, content.Id))
.Returns("content-url");
var actual = await sut.SearchAsync("query", ctx, ct);
var actual = await sut.SearchAsync("query", requestContext, CancellationToken);
actual.Should().BeEquivalentTo(
new SearchResults()
@ -204,16 +196,13 @@ public class ContentsSearchSourceTests
private Context ContextWithPermissions(params NamedId<DomainId>[] allowedSchemas)
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
var permissions = new List<string>();
foreach (var schemaId in allowedSchemas)
{
var permission = PermissionIds.ForApp(PermissionIds.AppContentsReadOwn, appId.Name, schemaId.Name).Id;
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
permissions.Add(PermissionIds.ForApp(PermissionIds.AppContentsReadOwn, AppId.Name, schemaId.Name).Id);
}
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.Options;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure;
using Squidex.Domain.Apps.Entities.TestHelpers;
namespace Squidex.Domain.Apps.Entities.Contents.Counter;
public class CounterJintExtensionTests
public class CounterJintExtensionTests : GivenContext
{
private readonly ICounterService counterService = A.Fake<ICounterService>();
private readonly JintScriptEngine sut;
@ -35,9 +35,7 @@ public class CounterJintExtensionTests
[Fact]
public void Should_reset_counter()
{
var appId = DomainId.NewGuid();
A.CallTo(() => counterService.ResetAsync(appId, "my", 4, default))
A.CallTo(() => counterService.ResetAsync(AppId.Id, "my", 4, default))
.Returns(3);
const string script = @"
@ -46,7 +44,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars
{
["appId"] = appId
["appId"] = AppId.Id
};
var actual = sut.Execute(vars, script).ToString();
@ -57,9 +55,7 @@ public class CounterJintExtensionTests
[Fact]
public async Task Should_reset_counter_with_callback()
{
var appId = DomainId.NewGuid();
A.CallTo(() => counterService.ResetAsync(appId, "my", 4, A<CancellationToken>._))
A.CallTo(() => counterService.ResetAsync(AppId.Id, "my", 4, A<CancellationToken>._))
.Returns(3);
const string script = @"
@ -70,7 +66,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars
{
["appId"] = appId
["appId"] = AppId.Id
};
var actual = (await sut.ExecuteAsync(vars, script)).ToString();
@ -81,9 +77,7 @@ public class CounterJintExtensionTests
[Fact]
public void Should_increment_counter()
{
var appId = DomainId.NewGuid();
A.CallTo(() => counterService.IncrementAsync(appId, "my", A<CancellationToken>._))
A.CallTo(() => counterService.IncrementAsync(AppId.Id, "my", A<CancellationToken>._))
.Returns(3);
const string script = @"
@ -92,7 +86,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars
{
["appId"] = appId
["appId"] = AppId.Id
};
var actual = sut.Execute(vars, script).ToString();
@ -103,9 +97,7 @@ public class CounterJintExtensionTests
[Fact]
public async Task Should_increment_counter_with_callback()
{
var appId = DomainId.NewGuid();
A.CallTo(() => counterService.IncrementAsync(appId, "my", A<CancellationToken>._))
A.CallTo(() => counterService.IncrementAsync(AppId.Id, "my", A<CancellationToken>._))
.Returns(3);
const string script = @"
@ -116,7 +108,7 @@ public class CounterJintExtensionTests
var vars = new ScriptVars
{
["appId"] = appId
["appId"] = AppId.Id
};
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.Infrastructure;
using Squidex.Infrastructure.TestHelpers;
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 DomainId appId = DomainId.NewGuid();
private readonly CounterService sut;
public CounterServiceTests()
{
ct = cts.Token;
state = new TestState<CounterService.State>(appId);
state = new TestState<CounterService.State>(AppId.Id);
sut = new CounterService(state.PersistenceFactory);
}
@ -39,36 +33,36 @@ public class CounterServiceTests
[Fact]
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();
}
[Fact]
public async Task Should_increment_counters()
{
Assert.Equal(1, await sut.IncrementAsync(appId, "Counter1", ct));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter1", ct));
Assert.Equal(1, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
Assert.Equal(2, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
Assert.Equal(1, await sut.IncrementAsync(appId, "Counter2", ct));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter2", ct));
Assert.Equal(1, await sut.IncrementAsync(AppId.Id, "Counter2", CancellationToken));
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);
}
[Fact]
public async Task Should_reset_counter()
{
Assert.Equal(1, await sut.IncrementAsync(appId, "Counter1", ct));
Assert.Equal(2, await sut.IncrementAsync(appId, "Counter1", ct));
Assert.Equal(1, await sut.IncrementAsync(AppId.Id, "Counter1", CancellationToken));
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);
}
}

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.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
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;
public DefaultWorkflowsValidatorTests()
{
var schema = Mocks.Schema(appId, schemaId, new Schema(schemaId.Name));
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);
sut = new DefaultWorkflowsValidator(AppProvider);
}
[Fact]
@ -42,7 +29,7 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
.Add(DomainId.NewGuid(), "workflow1")
.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());
}
@ -56,10 +43,10 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
var workflows = Workflows.Empty
.Add(id1, "workflow1")
.Add(id2, "workflow2")
.Update(id1, new Workflow(default, null, ReadonlyList.Create(schemaId.Id)))
.Update(id2, 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)));
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());
}
@ -67,18 +54,18 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
[Fact]
public async Task Should_not_generate_error_if_schema_deleted()
{
Schema = null!;
var id1 = DomainId.NewGuid();
var id2 = DomainId.NewGuid();
var oldSchemaId = DomainId.NewGuid();
var workflows = Workflows.Empty
.Add(id1, "workflow1")
.Add(id2, "workflow2")
.Update(id1, new Workflow(default, null, ReadonlyList.Create(oldSchemaId)))
.Update(id2, 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(SchemaId.Id)));
var errors = await sut.ValidateAsync(appId.Id, workflows);
var errors = await sut.ValidateAsync(AppId.Id, workflows);
Assert.Empty(errors);
}
@ -92,9 +79,9 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
var workflows = Workflows.Empty
.Add(id1, "workflow1")
.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);
}
@ -102,7 +89,7 @@ public class DefaultWorkflowsValidatorTests : IClassFixture<TranslationsFixture>
[Fact]
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);
}

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 IDomainObjectFactory domainObjectFactory = A.Fake<IDomainObjectFactory>();
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly DomainId contentId = DomainId.NewGuid();
private readonly Context requestContext;
private readonly ContentCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand
@ -34,16 +32,11 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
public ContentCommandMiddlewareTests()
{
requestContext = Context.Anonymous(Mocks.App(AppNamedId));
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new ContentCommandMiddleware(
domainObjectFactory,
domainObjectCache,
contentEnricher,
contextProvider);
ApiContextProvider);
}
[Fact]
@ -51,7 +44,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
{
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();
}
@ -66,7 +59,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
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();
}
@ -77,7 +70,7 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(actual, true, requestContext, A<CancellationToken>._))
A.CallTo(() => contentEnricher.EnrichAsync(actual, true, ApiContext, CancellationToken))
.Returns(enriched);
var context =
@ -95,12 +88,12 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentDomai
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));
A.CallTo(() => domainObjectFactory.Create<ContentDomainObject>(command.AggregateId))
.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.TestHelpers;
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.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
@ -29,11 +27,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject;
public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.State>
{
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 IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly ISchemaEntity schema;
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly ContentData invalidData =
@ -72,8 +67,6 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
public ContentDomainObjectTests()
{
app = Mocks.App(AppNamedId, Language.DE);
var scripts = new SchemaScripts
{
Change = "<change-script>",
@ -90,15 +83,10 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
new NumberFieldProperties { IsRequired = false })
.SetScripts(scripts);
schema = Mocks.Schema(AppNamedId, SchemaNamedId, schemaDef);
A.CallTo(() => appProvider.GetAppAsync(AppName, false, default))
.Returns(app);
A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId, false, default))
.Returns((app, schema));
A.CallTo(() => Schema.SchemaDef)
.Returns(schemaDef);
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!));
patched = patch.MergeInto(data);
@ -107,7 +95,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
var serviceProvider =
new ServiceCollection()
.AddSingleton(appProvider)
.AddSingleton(AppProvider)
.AddSingleton(A.Fake<ILogger<ContentValidator>>())
.AddSingleton(log)
.AddSingleton(contentWorkflow)
@ -148,9 +136,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -171,9 +159,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -195,9 +183,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
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();
}
@ -248,9 +236,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -271,9 +259,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -295,9 +283,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
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();
}
@ -319,7 +307,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -341,7 +329,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -363,7 +351,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -387,9 +375,9 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
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();
}
@ -443,7 +431,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -467,7 +455,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -484,7 +472,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Assert.Single(LastEvents);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -516,7 +504,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -540,7 +528,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -557,7 +545,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Assert.Single(LastEvents);
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.TransformAsync(A<DataScriptVars>._, "<update-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -579,7 +567,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -601,7 +589,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -624,7 +612,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -633,7 +621,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
{
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);
await ExecuteCreateAsync();
@ -652,7 +640,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -676,7 +664,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -700,7 +688,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -727,7 +715,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -741,7 +729,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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);
var actual = await PublishAsync(command);
@ -755,7 +743,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -769,7 +757,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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);
var actual = await PublishAsync(command);
@ -783,7 +771,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
CreateContentEvent(new ContentSchedulingCancelled())
);
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), default))
A.CallTo(() => scriptEngine.ExecuteAsync(A<DataScriptVars>._, "<change-script>", ScriptOptions(), CancellationToken))
.MustNotHaveHappened();
}
@ -795,7 +783,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
await ExecuteCreateAsync();
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);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
@ -809,7 +797,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
await ExecuteCreateAsync();
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);
await PublishAsync(command);
@ -865,7 +853,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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();
}
@ -883,7 +871,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version);
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();
}
@ -894,7 +882,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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);
await Assert.ThrowsAsync<DomainException>(() => PublishAsync(command));
@ -907,7 +895,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
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);
await PublishAsync(command);
@ -1007,7 +995,7 @@ public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject.Stat
Equals(x["dataOld"], oldData) &&
Equals(x["status"], newStatus) &&
Equals(x["statusOld"], oldStatus) &&
Equals(x["user"], User);
Equals(x["user"], ApiContext.UserPrincipal);
}
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)
{
var actual = await sut.ExecuteAsync(CreateContentCommand(command), default);
var actual = await sut.ExecuteAsync(CreateContentCommand(command), CancellationToken);
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.
// ==========================================================================
using System.Security.Claims;
using Microsoft.Extensions.Logging;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
@ -16,18 +15,14 @@ using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Queries;
using Squidex.Shared;
using Squidex.Shared.Identity;
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 IContextProvider contextProvider = A.Fake<IContextProvider>();
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> schemaCustomId = NamedId.Of(DomainId.NewGuid(), "my-schema2");
private readonly Instant time = Instant.FromDateTimeUtc(DateTime.UtcNow);
@ -35,8 +30,6 @@ public class ContentsBulkUpdateCommandMiddlewareTests
public ContentsBulkUpdateCommandMiddlewareTests()
{
ct = cts.Token;
var log = A.Fake<ILogger<ContentsBulkUpdateCommandMiddleware>>();
sut = new ContentsBulkUpdateCommandMiddleware(contentQuery, contextProvider, log);
@ -92,7 +85,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() &&
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)));
var command = BulkCommand(BulkUpdateContentType.ChangeStatus, new BulkUpdateJob { Query = query });
@ -118,7 +111,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() &&
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)));
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);
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();
}
@ -148,7 +141,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() &&
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(id1),
CreateContent(id2)));
@ -164,11 +157,11 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id2 && x.Exception == null);
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();
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();
}
@ -187,7 +180,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
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();
}
@ -206,7 +199,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
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();
}
@ -225,7 +218,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
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();
}
@ -244,7 +237,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id != default && x.Exception == null);
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();
}
@ -263,7 +256,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
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();
}
@ -300,7 +293,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
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();
}
@ -337,7 +330,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
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();
}
@ -374,7 +367,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
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();
}
@ -393,7 +386,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
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();
}
@ -430,7 +423,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync(
A<ValidateContent>.That.Matches(x => x.ContentId == id), ct))
A<ValidateContent>.That.Matches(x => x.ContentId == id), CancellationToken))
.MustHaveHappened();
}
@ -467,7 +460,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x => x.ContentId == id), ct))
A<DeleteContent>.That.Matches(x => x.ContentId == id), CancellationToken))
.MustHaveHappened();
}
@ -494,8 +487,8 @@ public class ContentsBulkUpdateCommandMiddlewareTests
{
SetupContext(PermissionIds.AppContentsDeleteOwn);
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>._, schemaCustomId.Name, ct))
.Returns(Mocks.Schema(appId, schemaCustomId));
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>._, schemaCustomId.Name, CancellationToken))
.Returns(Mocks.Schema(AppId, schemaCustomId));
var (id, _, _) = CreateTestData(false);
@ -507,7 +500,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception == null);
A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), ct))
A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), CancellationToken))
.MustHaveHappened();
}
@ -529,7 +522,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id && x.Exception is DomainObjectNotFoundException);
A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), ct))
A<DeleteContent>.That.Matches(x => x.SchemaId == schemaCustomId), CancellationToken))
.MustNotHaveHappened();
}
@ -537,7 +530,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
{
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context, ct);
await sut.HandleAsync(context, CancellationToken);
return (context.PlainResult as BulkUpdateResult)!;
}
@ -550,7 +543,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests
return new BulkUpdateContents
{
AppId = appId,
AppId = AppId,
Jobs = new[]
{
job
@ -561,18 +554,9 @@ public class ContentsBulkUpdateCommandMiddlewareTests
private Context SetupContext(string id)
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
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));
var requestContext = CreateContext(false,
PermissionIds.ForApp(id, AppId.Name, schemaId.Name).Id,
PermissionIds.ForApp(id, AppId.Name, schemaCustomId.Name).Id);
A.CallTo(() => contextProvider.Context)
.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;
public class GuardContentTests : IClassFixture<TranslationsFixture>
public class GuardContentTests : GivenContext, IClassFixture<TranslationsFixture>
{
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
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 normalUnpublishedSchema;
private readonly ISchemaEntity singletonSchema;
private readonly ISchemaEntity singletonUnpublishedSchema;
private readonly ISchemaEntity componentSchema;
private readonly RefToken actor = RefToken.User("123");
public GuardContentTests()
{
normalUnpublishedSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name));
Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name));
normalSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name).Publish());
Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name).Publish());
singletonUnpublishedSchema =
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, type: SchemaType.Singleton));
Mocks.Schema(AppId, SchemaId, new Schema(SchemaId.Name, type: SchemaType.Singleton));
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 =
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]
@ -315,7 +312,7 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
[Fact]
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 operation = Operation(CreateContent(Status.Draft), normalSchema, userObject);
@ -330,7 +327,7 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
{
var operation = Operation(CreateContent(Status.Draft), normalSchema);
((ContentEntity)operation.Snapshot).CreatedBy = actor;
((ContentEntity)operation.Snapshot).CreatedBy = User;
operation.MustHavePermission(PermissionIds.AppContentsDelete);
}
@ -360,10 +357,10 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
{
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);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync());
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
}
[Fact]
@ -371,10 +368,10 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
{
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);
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync());
await Assert.ThrowsAsync<DomainException>(() => operation.CheckReferrersAsync(CancellationToken));
}
private ContentOperation Operation(ContentEntity content, ISchemaEntity operationSchema)
@ -392,8 +389,8 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
return new ContentOperation(serviceProvider, () => content)
{
App = Mocks.App(appId),
Command = new CreateContent { User = currentUser, Actor = actor },
App = App,
Command = new CreateContent { User = currentUser, Actor = User },
CommandId = content.Id,
Schema = operationSchema
};
@ -412,10 +409,10 @@ public class GuardContentTests : IClassFixture<TranslationsFixture>
private ContentEntity CreateContentCore(ContentEntity content, DomainId? id = null)
{
content.Id = id ?? DomainId.NewGuid();
content.AppId = appId;
content.AppId = AppId;
content.Created = default;
content.CreatedBy = actor;
content.SchemaId = schemaId;
content.CreatedBy = User;
content.SchemaId = SchemaId;
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.Schemas;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
@ -18,12 +17,8 @@ using Squidex.Infrastructure.Collections;
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 DynamicContentWorkflow sut;
@ -58,8 +53,6 @@ public class DynamicContentWorkflowTests
public DynamicContentWorkflowTests()
{
app = Mocks.App(appId);
var simpleWorkflow = new Workflow(
Status.Draft,
new Dictionary<Status, WorkflowStep>
@ -83,10 +76,7 @@ public class DynamicContentWorkflowTests
var workflows = Workflows.Empty.Set(workflow).Set(DomainId.NewGuid(), simpleWorkflow);
A.CallTo(() => appProvider.GetAppAsync(appId.Id, false, default))
.Returns(app);
A.CallTo(() => app.Workflows)
A.CallTo(() => App.Workflows)
.Returns(workflows);
var scriptEngine = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())),
@ -96,7 +86,7 @@ public class DynamicContentWorkflowTests
TimeoutExecution = TimeSpan.FromSeconds(10)
}));
sut = new DynamicContentWorkflow(scriptEngine, appProvider);
sut = new DynamicContentWorkflow(scriptEngine, AppProvider);
}
[Fact]
@ -122,7 +112,7 @@ public class DynamicContentWorkflowTests
[Fact]
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);
}
@ -130,7 +120,7 @@ public class DynamicContentWorkflowTests
[Fact]
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);
}
@ -138,7 +128,7 @@ public class DynamicContentWorkflowTests
[Fact]
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);
}
@ -148,7 +138,7 @@ public class DynamicContentWorkflowTests
{
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);
}
@ -350,7 +340,7 @@ public class DynamicContentWorkflowTests
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);
}
@ -364,7 +354,7 @@ public class DynamicContentWorkflowTests
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);
}
@ -372,7 +362,8 @@ public class DynamicContentWorkflowTests
[Fact]
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[]
{
@ -381,7 +372,7 @@ public class DynamicContentWorkflowTests
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);
}
@ -389,7 +380,7 @@ public class DynamicContentWorkflowTests
[Fact]
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);
}
@ -413,7 +404,7 @@ public class DynamicContentWorkflowTests
[Fact]
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);
}
@ -425,12 +416,12 @@ public class DynamicContentWorkflowTests
ValidateOnPublish = validateOnPublish
});
return Mocks.Schema(appId, simpleSchemaId, schema);
return Mocks.Schema(AppId, simpleSchemaId, schema);
}
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)
{
@ -438,7 +429,7 @@ public class DynamicContentWorkflowTests
}
else
{
content.SchemaId = schemaId;
content.SchemaId = SchemaId;
}
content.Data =

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

@ -8,7 +8,6 @@
using System.Globalization;
using LoremNET;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
@ -18,9 +17,7 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers;
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.MongoDb.Assets;
using Squidex.Domain.Apps.Entities.MongoDb.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
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.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
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 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;
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);
}
[Fact]
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())
.MustHaveHappened();
@ -52,11 +40,11 @@ public class CalculateTokensTests
[Fact]
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())
.MustHaveHappened();
@ -64,6 +52,11 @@ public class CalculateTokensTests
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