diff --git a/backend/src/Squidex.Web/Pipeline/AppResolver.cs b/backend/src/Squidex.Web/Pipeline/AppResolver.cs index 0e9cdbb52..749f765b5 100644 --- a/backend/src/Squidex.Web/Pipeline/AppResolver.cs +++ b/backend/src/Squidex.Web/Pipeline/AppResolver.cs @@ -33,10 +33,16 @@ namespace Squidex.Web.Pipeline { var user = context.HttpContext.User; - var appName = context.RouteData.Values["app"]?.ToString(); - - if (!string.IsNullOrWhiteSpace(appName)) + if (context.RouteData.Values.TryGetValue("app", out var appValue)) { + var appName = appValue?.ToString(); + + if (string.IsNullOrWhiteSpace(appName)) + { + context.Result = new NotFoundResult(); + return; + } + var isFrontend = user.IsInClient(DefaultClients.Frontend); var app = await appProvider.GetAppAsync(appName, !isFrontend, context.HttpContext.RequestAborted); diff --git a/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs b/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs index 9521ef487..0e075deb4 100644 --- a/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs +++ b/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs @@ -33,10 +33,16 @@ namespace Squidex.Web.Pipeline if (appId != default) { - var schemaIdOrName = context.RouteData.Values["schema"]?.ToString(); - - if (!string.IsNullOrWhiteSpace(schemaIdOrName)) + if (context.RouteData.Values.TryGetValue("schema", out var schemaValue)) { + var schemaIdOrName = schemaValue?.ToString(); + + if (string.IsNullOrWhiteSpace(schemaIdOrName)) + { + context.Result = new NotFoundResult(); + return; + } + var schema = await GetSchemaAsync(appId, schemaIdOrName, context.HttpContext.User); if (schema == null) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs index 285762694..f852742e1 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting identity.AddClaim(new Claim(OpenIdClaims.ClientId, "1")); - AssetUser(identity, "1", true, false); + AssertUser(identity, "1", true, false); } [Fact] @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting identity.AddClaim(new Claim(OpenIdClaims.Subject, "2")); identity.AddClaim(new Claim(OpenIdClaims.Name, "user")); - AssetUser(identity, "2", false, true); + AssertUser(identity, "2", false, true); } [Fact] @@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting Assert.Equal(new[] { "2a", "2b" }, GetValue(identity, script2)); } - private static void AssetUser(ClaimsIdentity identity, string id, bool isClient, bool isUser) + private static void AssertUser(ClaimsIdentity identity, string id, bool isClient, bool isUser) { Assert.Equal(id, GetValue(identity, "user.id")); Assert.Equal(isUser, GetValue(identity, "user.isUser")); diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs index 6c9fa7779..9ce0669a9 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs @@ -121,7 +121,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: initialPosition); + AssertGrainState(isStopped: true, position: initialPosition); A.CallTo(() => eventStore.CreateSubscription(A._, A._, A._)) .MustNotHaveHappened(); @@ -135,7 +135,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: initialPosition); + AssertGrainState(isStopped: false, position: initialPosition); A.CallTo(() => eventStore.CreateSubscription(A._, A._, A._)) .MustHaveHappenedOnceExactly(); @@ -151,7 +151,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: initialPosition); + AssertGrainState(isStopped: false, position: initialPosition); A.CallTo(() => eventStore.CreateSubscription(A._, A._, A._)) .MustHaveHappenedOnceExactly(); @@ -165,7 +165,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: initialPosition); + AssertGrainState(isStopped: false, position: initialPosition); A.CallTo(() => eventStore.CreateSubscription(A._, A._, A._)) .MustHaveHappenedOnceExactly(); @@ -181,7 +181,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: initialPosition); + AssertGrainState(isStopped: true, position: initialPosition); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -200,7 +200,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: null); + AssertGrainState(isStopped: false, position: null); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappened(2, Times.Exactly); @@ -228,7 +228,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: storedEvent.EventPosition, count: 1); + AssertGrainState(isStopped: false, position: storedEvent.EventPosition, count: 1); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -254,7 +254,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: storedEvent.EventPosition, count: 5); + AssertGrainState(isStopped: false, position: storedEvent.EventPosition, count: 5); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappened(5, Times.Exactly); @@ -280,7 +280,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: storedEvent.EventPosition, count: 5); + AssertGrainState(isStopped: false, position: storedEvent.EventPosition, count: 5); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -302,7 +302,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: storedEvent.EventPosition); + AssertGrainState(isStopped: false, position: storedEvent.EventPosition); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -324,7 +324,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: storedEvent.EventPosition); + AssertGrainState(isStopped: false, position: storedEvent.EventPosition); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -343,7 +343,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: initialPosition); + AssertGrainState(isStopped: false, position: initialPosition); A.CallTo(() => eventConsumer.On(envelope)) .MustNotHaveHappened(); @@ -361,7 +361,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); + AssertGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -382,7 +382,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: false, position: initialPosition); + AssertGrainState(isStopped: false, position: initialPosition); A.CallTo(() => grainState.WriteAsync()) .MustNotHaveHappened(); @@ -415,7 +415,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); + AssertGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); A.CallTo(() => grainState.WriteAsync()) .MustHaveHappenedOnceExactly(); @@ -439,7 +439,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); + AssertGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); A.CallTo(() => eventConsumer.On(envelope)) .MustHaveHappened(); @@ -466,7 +466,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); + AssertGrainState(isStopped: true, position: initialPosition, error: ex.ToString()); A.CallTo(() => eventConsumer.On(envelope)) .MustNotHaveHappened(); @@ -497,7 +497,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.StartAsync(); await sut.StartAsync(); - AssetGrainState(isStopped: false, position: initialPosition); + AssertGrainState(isStopped: false, position: initialPosition); A.CallTo(() => eventConsumer.On(envelope)) .MustHaveHappened(); @@ -527,7 +527,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await sut.CompleteAsync(); - AssetGrainState(isStopped: true, position: storedEvent.EventPosition, error: ex.ToString(), 1); + AssertGrainState(isStopped: true, position: storedEvent.EventPosition, error: ex.ToString(), 1); } private Task OnErrorAsync(IEventSubscription subscription, Exception exception) @@ -540,7 +540,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains return sut.OnEventAsync(subscription, ev); } - private void AssetGrainState(bool isStopped = false, string? position = null, string? error = null, int count = 0) + private void AssertGrainState(bool isStopped = false, string? position = null, string? error = null, int count = 0) { var expected = new EventConsumerState { IsStopped = isStopped, Position = position, Error = error, Count = count }; diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs index 5223549fa..150749121 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs @@ -57,6 +57,25 @@ namespace Squidex.Web.Pipeline sut = new AppResolver(appProvider); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task Should_return_not_found_if_app_name_is_null(string? app) + { + SetupUser(); + + actionExecutingContext.RouteData.Values["app"] = app; + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + Assert.IsType(actionExecutingContext.Result); + Assert.False(isNextCalled); + + A.CallTo(() => appProvider.GetAppAsync(A._, false, httpContext.RequestAborted)) + .MustNotHaveHappened(); + } + [Fact] public async Task Should_return_not_found_if_app_not_found() { diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs index dc68a89d2..c11e3ce6b 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs @@ -60,6 +60,22 @@ namespace Squidex.Web.Pipeline sut = new SchemaResolver(appProvider); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task Should_return_not_found_if_schema_name_is_null(string? schema) + { + actionContext.RouteData.Values["schema"] = schema; + + await sut.OnActionExecutionAsync(actionExecutingContext, next); + + AssertNotFound(); + + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A._, true, httpContext.RequestAborted)) + .MustNotHaveHappened(); + } + [Fact] public async Task Should_return_not_found_if_schema_not_published_when_attribute_applied() { @@ -73,7 +89,7 @@ namespace Squidex.Web.Pipeline await sut.OnActionExecutionAsync(actionExecutingContext, next); - AssetNotFound(); + AssertNotFound(); } [Fact] @@ -101,7 +117,7 @@ namespace Squidex.Web.Pipeline await sut.OnActionExecutionAsync(actionExecutingContext, next); - AssetNotFound(); + AssertNotFound(); } [Fact] @@ -193,7 +209,7 @@ namespace Squidex.Web.Pipeline .MustNotHaveHappened(); } - private void AssetNotFound() + private void AssertNotFound() { Assert.IsType(actionExecutingContext.Result); Assert.False(isNextCalled); diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AssetFixture.cs similarity index 97% rename from backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs rename to backend/tools/TestSuite/TestSuite.ApiTests/AssetFixture.cs index 3970e56a4..58ab01b44 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AssetFixture.cs @@ -6,13 +6,12 @@ // ========================================================================== using Squidex.ClientLibrary.Management; +using TestSuite.Fixtures; -namespace TestSuite.Fixtures +namespace TestSuite.ApiTests { public class AssetFixture : CreatedAppFixture { - public IAssetsClient Assets => Squidex.Assets; - public async Task DownloadAsync(AssetDto asset, int? version = null) { var temp = new MemoryStream(); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs index 09521794a..5f5fe45ca 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.ClientLibrary.Management; -using TestSuite.Fixtures; using Xunit; #pragma warning disable SA1300 // Element should begin with upper-case letter diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs index 51ee085a2..8cc6a88a5 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs @@ -7,7 +7,6 @@ using Squidex.Assets; using Squidex.ClientLibrary.Management; -using TestSuite.Fixtures; using Xunit; #pragma warning disable SA1300 // Element should begin with upper-case letter diff --git a/backend/tools/TestSuite/TestSuite.Shared/Utils/RandomString.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentFixture.cs similarity index 51% rename from backend/tools/TestSuite/TestSuite.Shared/Utils/RandomString.cs rename to backend/tools/TestSuite/TestSuite.ApiTests/ContentFixture.cs index 56353bd4d..2a680b24f 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Utils/RandomString.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentFixture.cs @@ -5,22 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace TestSuite.Utils +using TestSuite.Fixtures; + +namespace TestSuite.ApiTests { - public static class RandomString + public sealed class ContentFixture : TestSchemaFixtureBase { - private static readonly Random Random = new Random(); - - public static string Create(int length) + public ContentFixture() + : base("my-writes") { - var chars = new char[length]; - - for (var i = 0; i < length; i++) - { - chars[i] = (char)Random.Next(48, 122); - } - - return new string(chars); } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentLanguageTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentLanguageTests.cs index 73b2982db..d92421fbc 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentLanguageTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentLanguageTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.ClientLibrary; -using TestSuite.Fixtures; using TestSuite.Model; using Xunit; diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryFixture.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryFixture.cs new file mode 100644 index 000000000..0c9cd48c4 --- /dev/null +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryFixture.cs @@ -0,0 +1,47 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.ClientLibrary; +using TestSuite.Fixtures; +using TestSuite.Model; + +namespace TestSuite.ApiTests +{ + public sealed class ContentQueryFixture : TestSchemaFixtureBase + { + public ContentQueryFixture() + : base("my-reads") + { + } + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + + await DisposeAsync(); + + for (var i = 10; i > 0; i--) + { + var data = TestEntity.CreateTestEntry(i); + + await Contents.CreateAsync(data, ContentCreateOptions.AsPublish); + } + } + + public override async Task DisposeAsync() + { + await base.DisposeAsync(); + + var contents = await Contents.GetAsync(); + + foreach (var content in contents.Items) + { + await Contents.DeleteAsync(content); + } + } + } +} diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs index aa0d877cf..bff059f07 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Squidex.ClientLibrary; -using TestSuite.Fixtures; using TestSuite.Model; using Xunit; @@ -16,11 +15,11 @@ using Xunit; namespace TestSuite.ApiTests { - public class ContentQueryTests : IClassFixture + public class ContentQueryTests : IClassFixture { - public ContentQueryFixture1to10 _ { get; } + public ContentQueryFixture _ { get; } - public ContentQueryTests(ContentQueryFixture1to10 fixture) + public ContentQueryTests(ContentQueryFixture fixture) { _ = fixture; } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesFixture.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesFixture.cs new file mode 100644 index 000000000..9506fbc1c --- /dev/null +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesFixture.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using TestSuite.Fixtures; + +namespace TestSuite.ApiTests +{ + public sealed class ContentReferencesFixture : TestSchemaWithReferencesFixtureBase + { + public ContentReferencesFixture() + : base("my-references") + { + } + } +} diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs index a6c5fec39..20b2cbc46 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.ClientLibrary; -using TestSuite.Fixtures; using TestSuite.Model; using Xunit; diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs index da721f696..43f2b81c2 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json.Linq; using Squidex.ClientLibrary; using Squidex.ClientLibrary.Management; -using TestSuite.Fixtures; using TestSuite.Model; using Xunit; diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs index 963a75850..5358d23a3 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json.Linq; using Squidex.ClientLibrary; using Squidex.ClientLibrary.Management; -using TestSuite.Fixtures; using TestSuite.Model; using Xunit; diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/OpenApiTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/OpenApiTests.cs index be3d2ac67..24f9edad7 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/OpenApiTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/OpenApiTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using NSwag; -using TestSuite.Fixtures; using Xunit; #pragma warning disable SA1300 // Element should begin with upper-case letter diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/ReadingFixture.cs b/backend/tools/TestSuite/TestSuite.LoadTests/ReadingFixture.cs index afa2b2c0d..233fbb992 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/ReadingFixture.cs +++ b/backend/tools/TestSuite/TestSuite.LoadTests/ReadingFixture.cs @@ -5,15 +5,43 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.ClientLibrary; using TestSuite.Fixtures; +using TestSuite.Model; namespace TestSuite.LoadTests { - public sealed class ReadingFixture : ContentQueryFixture1to10 + public sealed class ReadingFixture : TestSchemaFixtureBase { public ReadingFixture() : base("benchmark-reading") { } + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + + await DisposeAsync(); + + for (var i = 10; i > 0; i--) + { + var data = TestEntity.CreateTestEntry(i); + + await Contents.CreateAsync(data, ContentCreateOptions.AsPublish); + } + } + + public override async Task DisposeAsync() + { + await base.DisposeAsync(); + + var contents = await Contents.GetAsync(); + + foreach (var content in contents.Items) + { + await Contents.DeleteAsync(content); + } + } } } diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/WritingFixture.cs b/backend/tools/TestSuite/TestSuite.LoadTests/WritingFixture.cs index 1fc527a32..1c5c7d845 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/WritingFixture.cs +++ b/backend/tools/TestSuite/TestSuite.LoadTests/WritingFixture.cs @@ -9,7 +9,7 @@ using TestSuite.Fixtures; namespace TestSuite.LoadTests { - public sealed class WritingFixture : ContentFixture + public sealed class WritingFixture : TestSchemaFixtureBase { public WritingFixture() : base("benchmark_writing") diff --git a/backend/tools/TestSuite/TestSuite.Shared/ClientManagerFactory.cs b/backend/tools/TestSuite/TestSuite.Shared/ClientManagerFactory.cs deleted file mode 100644 index ba307926e..000000000 --- a/backend/tools/TestSuite/TestSuite.Shared/ClientManagerFactory.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace TestSuite -{ - public static class ClientManagerFactory - { - private static Task manager; - - public static Task CreateAsync() - { - if (manager == null) - { - manager = CreateInternalAsync(); - } - - return manager; - } - - private static async Task CreateInternalAsync() - { - var clientManager = new ClientManagerWrapper(); - - await clientManager.ConnectAsync(); - - return clientManager; - } - } -} diff --git a/backend/tools/TestSuite/TestSuite.Shared/ClientManagerWrapper.cs b/backend/tools/TestSuite/TestSuite.Shared/ClientManagerWrapper.cs index e0ff506e1..34c23b869 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/ClientManagerWrapper.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/ClientManagerWrapper.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Lazy; using Microsoft.Extensions.Configuration; using Squidex.ClientLibrary; using Squidex.ClientLibrary.Configuration; @@ -16,40 +15,63 @@ namespace TestSuite { public sealed class ClientManagerWrapper { - private static Task manager; - - public SquidexClientManager ClientManager { get; set; } - - [Lazy] - public IAppsClient Apps => ClientManager.CreateAppsClient(); + private readonly Lazy apps; + private readonly Lazy assets; + private readonly Lazy backups; + private readonly Lazy languages; + private readonly Lazy ping; + private readonly Lazy rules; + private readonly Lazy schemas; + private readonly Lazy templates; + + public SquidexClientManager ClientManager { get; } + + public IAppsClient Apps + { + get => apps.Value; + } - [Lazy] - public IAssetsClient Assets => ClientManager.CreateAssetsClient(); + public IAssetsClient Assets + { + get => assets.Value; + } - [Lazy] - public IBackupsClient Backups => ClientManager.CreateBackupsClient(); + public IBackupsClient Backups + { + get => backups.Value; + } - [Lazy] - public ILanguagesClient Languages => ClientManager.CreateLanguagesClient(); + public ILanguagesClient Languages + { + get => languages.Value; + } - [Lazy] - public IPingClient Ping => ClientManager.CreatePingClient(); + public IPingClient Ping + { + get => ping.Value; + } - [Lazy] - public IRulesClient Rules => ClientManager.CreateRulesClient(); + public IRulesClient Rules + { + get => rules.Value; + } - [Lazy] - public ISchemasClient Schemas => ClientManager.CreateSchemasClient(); + public ISchemasClient Schemas + { + get => schemas.Value; + } - [Lazy] - public ITemplatesClient Templates => ClientManager.CreateTemplatesClient(); + public ITemplatesClient Templates + { + get => templates.Value; + } public ClientManagerWrapper() { - var appName = GetValue("config:app:name", "integration-tests"); - var clientId = GetValue("config:client:id", "root"); - var clientSecret = GetValue("config:client:secret", "xeLd6jFxqbXJrfmNLlO2j1apagGGGSyZJhFnIuHp4I0="); - var serverUrl = GetValue("config:server:url", "https://localhost:5001"); + var appName = TestHelpers.GetValue("config:app:name", "integration-tests"); + var clientId = TestHelpers.GetValue("config:client:id", "root"); + var clientSecret = TestHelpers.GetValue("config:client:secret", "xeLd6jFxqbXJrfmNLlO2j1apagGGGSyZJhFnIuHp4I0="); + var serverUrl = TestHelpers.GetValue("config:server:url", "https://localhost:5001"); ClientManager = new SquidexClientManager(new SquidexOptions { @@ -61,28 +83,49 @@ namespace TestSuite ReadResponseAsString = true, Url = serverUrl }); - } - public static Task CreateAsync() - { - if (manager == null) + apps = new Lazy(() => { - manager = CreateInternalAsync(); - } + return ClientManager.CreateAppsClient(); + }); - return manager; - } + assets = new Lazy(() => + { + return ClientManager.CreateAssetsClient(); + }); - private static async Task CreateInternalAsync() - { - var clientManager = new ClientManagerWrapper(); + backups = new Lazy(() => + { + return ClientManager.CreateBackupsClient(); + }); - await clientManager.ConnectAsync(); + languages = new Lazy(() => + { + return ClientManager.CreateLanguagesClient(); + }); + + ping = new Lazy(() => + { + return ClientManager.CreatePingClient(); + }); + + rules = new Lazy(() => + { + return ClientManager.CreateRulesClient(); + }); + + schemas = new Lazy(() => + { + return ClientManager.CreateSchemasClient(); + }); - return clientManager; + templates = new Lazy(() => + { + return ClientManager.CreateTemplatesClient(); + }); } - public async Task ConnectAsync() + public async Task ConnectAsync() { var waitSeconds = TestHelpers.Configuration.GetValue("config:wait"); @@ -108,27 +151,15 @@ namespace TestSuite } } } - } - else - { - Console.WriteLine("Waiting for server is skipped."); - } - } - - private static string GetValue(string name, string fallback) - { - var value = TestHelpers.Configuration[name]; - if (string.IsNullOrWhiteSpace(value)) - { - value = fallback; + Console.WriteLine("Connected to server."); } else { - Console.WriteLine("Using: {0}={1}", name, value); + Console.WriteLine("Waiting for server is skipped."); } - return value; + return this; } } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Factories.cs b/backend/tools/TestSuite/TestSuite.Shared/Factories.cs new file mode 100644 index 000000000..b76ced839 --- /dev/null +++ b/backend/tools/TestSuite/TestSuite.Shared/Factories.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Concurrent; + +namespace TestSuite +{ + public static class Factories + { + private static readonly ConcurrentDictionary> Instances = new ConcurrentDictionary>(); + + public static async Task CreateAsync(string key, Func> factory) + { + return (T)await Instances.GetOrAdd(key, async (_, f) => await f(), factory); + } + } +} diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientFixture.cs index a011084c3..cff272074 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientFixture.cs @@ -13,6 +13,8 @@ namespace TestSuite.Fixtures { public IAppsClient Apps => Squidex.Apps; + public IAssetsClient Assets => Squidex.Assets; + public IBackupsClient Backups => Squidex.Backups; public ILanguagesClient Languages => Squidex.Languages; diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientManagerFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientManagerFixture.cs index 235b45752..35e2bae51 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientManagerFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientManagerFixture.cs @@ -6,12 +6,13 @@ // ========================================================================== using Squidex.ClientLibrary; +using Xunit; namespace TestSuite.Fixtures { - public class ClientManagerFixture : IDisposable + public class ClientManagerFixture : IAsyncLifetime { - public ClientManagerWrapper Squidex { get; } + public ClientManagerWrapper Squidex { get; private set; } public string AppName => ClientManager.Options.AppName; @@ -23,14 +24,21 @@ namespace TestSuite.Fixtures public SquidexClientManager ClientManager => Squidex.ClientManager; - public ClientManagerFixture() + public virtual async Task InitializeAsync() { - Squidex = ClientManagerWrapper.CreateAsync().Result; + Squidex = await Factories.CreateAsync(nameof(ClientManagerWrapper), async () => + { + var clientManager = new ClientManagerWrapper(); + + await clientManager.ConnectAsync(); + + return clientManager; + }); } - public virtual void Dispose() + public virtual Task DisposeAsync() { - GC.SuppressFinalize(this); + return Task.CompletedTask; } } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs deleted file mode 100644 index 49796873e..000000000 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs +++ /dev/null @@ -1,77 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; -using Newtonsoft.Json.Linq; -using Squidex.ClientLibrary; -using TestSuite.Model; - -namespace TestSuite.Fixtures -{ - public class ContentQueryFixture1to10 : ContentFixture - { - public ContentQueryFixture1to10() - : this("my-reads") - { - } - - protected ContentQueryFixture1to10(string schemaName = "my-schema") - : base(schemaName) - { - Task.Run(async () => - { -#pragma warning disable MA0056 // Do not call overridable members in constructor - Dispose(); -#pragma warning restore MA0056 // Do not call overridable members in constructor - - for (var i = 10; i > 0; i--) - { - var text = i.ToString(CultureInfo.InvariantCulture); - - var data = new TestEntityData - { - String = text, - Json = JObject.FromObject(new - { - nested1 = new - { - nested2 = i - } - }), - Number = i, - }; - - if (i % 2 == 0) - { - data.Geo = new { type = "Point", coordinates = new[] { i, i } }; - } - else - { - data.Geo = new { longitude = i, latitude = i }; - } - - await Contents.CreateAsync(data, ContentCreateOptions.AsPublish); - } - }).Wait(); - } - - public override void Dispose() - { - Task.Run(async () => - { - var contents = await Contents.GetAsync(); - - foreach (var content in contents.Items) - { - await Contents.DeleteAsync(content); - } - }).Wait(); - - GC.SuppressFinalize(this); - } - } -} diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/CreatedAppFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/CreatedAppFixture.cs index ddcb8cdd5..afe323c47 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/CreatedAppFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/CreatedAppFixture.cs @@ -11,58 +11,44 @@ namespace TestSuite.Fixtures { public class CreatedAppFixture : ClientFixture { - private static readonly string[] Contributors = + public override async Task InitializeAsync() { - "hello@squidex.io" - }; + await base.InitializeAsync(); - private static bool isCreated; - - public CreatedAppFixture() - { - if (!isCreated) + await Factories.CreateAsync(AppName, async () => { - Task.Run(async () => + try { - try - { - await Apps.PostAppAsync(new CreateAppDto { Name = AppName }); - } - catch (SquidexManagementException ex) + await Apps.PostAppAsync(new CreateAppDto { - if (ex.StatusCode != 400) - { - throw; - } - } - - var invite = new AssignContributorDto { Invite = true, Role = "Owner" }; - - foreach (var contributor in Contributors) + Name = AppName + }); + } + catch (SquidexManagementException ex) + { + if (ex.StatusCode != 400) { - invite.ContributorId = contributor; - - await Apps.PostContributorAsync(AppName, invite); + throw; } + } - try + try + { + await Apps.PostLanguageAsync(AppName, new AddLanguageDto { - await Apps.PostLanguageAsync(AppName, new AddLanguageDto - { - Language = "de" - }); - } - catch (SquidexManagementException ex) + Language = "de" + }); + } + catch (SquidexManagementException ex) + { + if (ex.StatusCode != 400) { - if (ex.StatusCode != 400) - { - throw; - } + throw; } - }).Wait(); + } - isCreated = true; - } + return true; + }); } } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/TestSchemaFixtureBase.cs similarity index 52% rename from backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs rename to backend/tools/TestSuite/TestSuite.Shared/Fixtures/TestSchemaFixtureBase.cs index fedf7c62e..b00e67d32 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/TestSchemaFixtureBase.cs @@ -11,42 +11,37 @@ using TestSuite.Model; namespace TestSuite.Fixtures { - public class ContentFixture : CreatedAppFixture + public abstract class TestSchemaFixtureBase : CreatedAppFixture { - private static readonly HashSet CreatedSchemas = new HashSet(); - - public IContentsClient Contents { get; } + public IContentsClient Contents { get; private set; } public string SchemaName { get; } - public ContentFixture() - : this("my-writes") + protected TestSchemaFixtureBase(string schemaName) { + SchemaName = schemaName; } - protected ContentFixture(string schemaName) + public override async Task InitializeAsync() { - SchemaName = schemaName; + await base.InitializeAsync(); - if (!CreatedSchemas.Contains(schemaName)) + await Factories.CreateAsync($"{nameof(TestEntity)}_{SchemaName}", async () => { - Task.Run(async () => + try { - try - { - await TestEntity.CreateSchemaAsync(Schemas, AppName, schemaName); - } - catch (SquidexManagementException ex) + await TestEntity.CreateSchemaAsync(Schemas, AppName, SchemaName); + } + catch (SquidexManagementException ex) + { + if (ex.StatusCode != 400) { - if (ex.StatusCode != 400) - { - throw; - } + throw; } - }).Wait(); + } - CreatedSchemas.Add(schemaName); - } + return true; + }); Contents = ClientManager.CreateContentsClient(SchemaName); } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentReferencesFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/TestSchemaWithReferencesFixtureBase.cs similarity index 66% rename from backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentReferencesFixture.cs rename to backend/tools/TestSuite/TestSuite.Shared/Fixtures/TestSchemaWithReferencesFixtureBase.cs index c1e71beaf..33485ae43 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentReferencesFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/TestSchemaWithReferencesFixtureBase.cs @@ -11,15 +11,22 @@ using TestSuite.Model; namespace TestSuite.Fixtures { - public sealed class ContentReferencesFixture : CreatedAppFixture + public abstract class TestSchemaWithReferencesFixtureBase : CreatedAppFixture { - public string SchemaName { get; } = "references"; + public IContentsClient Contents { get; private set; } - public IContentsClient Contents { get; } + public string SchemaName { get; } - public ContentReferencesFixture() + protected TestSchemaWithReferencesFixtureBase(string schemaName) { - Task.Run(async () => + SchemaName = schemaName; + } + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + + await Factories.CreateAsync($"{nameof(TestEntityWithReferences)}_{SchemaName}", async () => { try { @@ -32,7 +39,9 @@ namespace TestSuite.Fixtures throw; } } - }).Wait(); + + return true; + }); Contents = ClientManager.CreateContentsClient(SchemaName); } diff --git a/backend/tools/TestSuite/TestSuite.Shared/FodyWeavers.xml b/backend/tools/TestSuite/TestSuite.Shared/FodyWeavers.xml deleted file mode 100644 index 6ef705866..000000000 --- a/backend/tools/TestSuite/TestSuite.Shared/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs b/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs index 1054dce86..a10d2b186 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Globalization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Squidex.ClientLibrary; @@ -81,6 +82,42 @@ namespace TestSuite.Model return schema; } + + public static TestEntityData CreateTestEntry(int index) + { + var data = new TestEntityData + { + Number = index, + Json = JObject.FromObject(new + { + nested0 = index, + nested1 = new + { + nested2 = index + } + }), + String = index.ToString(CultureInfo.InvariantCulture) + }; + + if (index % 2 == 0) + { + data.Geo = new + { + type = "Point", + coordinates = new[] + { + index, + index + } + }; + } + else + { + data.Geo = new { longitude = index, latitude = index }; + } + + return data; + } } public sealed class TestEntityData diff --git a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index 019cddd16..5f2d0839f 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -6,11 +6,6 @@ enable - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tools/TestSuite/TestSuite.Shared/Utils/RandomHash.cs b/backend/tools/TestSuite/TestSuite.Shared/Utils/RandomHash.cs deleted file mode 100644 index 3d77746f4..000000000 --- a/backend/tools/TestSuite/TestSuite.Shared/Utils/RandomHash.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Security.Cryptography; -using System.Text; - -namespace TestSuite.Utils -{ - public static class RandomHash - { - public static string New() - { - return Guid.NewGuid() - .ToString().Sha256Base64() - .ToLowerInvariant() - .Replace("+", "x", StringComparison.Ordinal) - .Replace("=", "x", StringComparison.Ordinal) - .Replace("/", "x", StringComparison.Ordinal); - } - - public static string Simple() - { - return Guid.NewGuid().ToString().Replace("-", string.Empty, StringComparison.Ordinal); - } - - public static string Sha256Base64(this string value) - { - return Sha256Base64(Encoding.UTF8.GetBytes(value)); - } - - public static string Sha256Base64(this byte[] bytes) - { - using (var sha = SHA256.Create()) - { - var bytesHash = sha.ComputeHash(bytes); - - var result = Convert.ToBase64String(bytesHash); - - return result; - } - } - } -} diff --git a/backend/tools/TestSuite/TestSuite.Shared/Utils/Run.cs b/backend/tools/TestSuite/TestSuite.Shared/Utils/Run.cs deleted file mode 100644 index 2887f413f..000000000 --- a/backend/tools/TestSuite/TestSuite.Shared/Utils/Run.cs +++ /dev/null @@ -1,68 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Concurrent; -using System.Diagnostics; -using Xunit; - -namespace TestSuite.Utils -{ - public static class Run - { - public static async Task Parallel(int numUsers, int numIterationsPerUser, Func action, int expectedAvg = 100) - { - var elapsedMs = new ConcurrentBag(); - - var errors = 0; - - async Task RunAsync() - { - for (var i = 0; i < numIterationsPerUser; i++) - { - try - { - var watch = Stopwatch.StartNew(); - - await action(); - - watch.Stop(); - - elapsedMs.Add(watch.ElapsedMilliseconds); - } - catch - { - Interlocked.Increment(ref errors); - } - } - } - - var tasks = new List(); - - for (var i = 0; i < numUsers; i++) - { - tasks.Add(Task.Run(RunAsync)); - } - - await Task.WhenAll(tasks); - - var count = elapsedMs.Count; - - var max = elapsedMs.Max(); - var min = elapsedMs.Min(); - - var avg = elapsedMs.Average(); - - Assert.Equal(0, errors); - Assert.Equal(count, numUsers * numIterationsPerUser); - - Assert.InRange(max, 0, expectedAvg * 10); - Assert.InRange(min, 0, expectedAvg); - - Assert.InRange(avg, 0, expectedAvg); - } - } -} diff --git a/backend/tools/TestSuite/TestSuite.Shared/Utils/TestHelpers.cs b/backend/tools/TestSuite/TestSuite.Shared/Utils/TestHelpers.cs index 81e50630c..28caecf75 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Utils/TestHelpers.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Utils/TestHelpers.cs @@ -24,5 +24,21 @@ namespace TestSuite.Utils .AddEnvironmentVariables() .Build(); } + + public static string GetValue(string name, string fallback) + { + var value = Configuration[name]; + + if (string.IsNullOrWhiteSpace(value)) + { + value = fallback; + } + else + { + Console.WriteLine("Using: {0}={1}", name, value); + } + + return value; + } } }