Browse Source

Feature/test suite (#475)

* Started with test suite.

* Unified test suite.

* Backup tests.

* Asset test

* Fix asset metada.

* Fix element removed.
pull/479/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
977adccf2a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs
  2. 23
      backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  3. 46
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs
  5. 52
      backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs
  6. 17
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  7. 2
      backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs
  8. 4
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  9. 5
      backend/src/Squidex/Config/Domain/AssetServices.cs
  10. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs
  11. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs
  12. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs
  13. 6
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs
  14. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeAssetMetadataSourceTests.cs
  15. 38
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs
  16. 25
      backend/tools/LoadTest/LoadTest.sln
  17. 67
      backend/tools/LoadTest/ManyItemsTests.cs
  18. 102
      backend/tools/LoadTest/Model/TestClient.cs
  19. 51
      backend/tools/LoadTest/ReadingFixture.cs
  20. 31
      backend/tools/LoadTest/TestUtils.cs
  21. 70
      backend/tools/TestSuite/TestSuite.ApiTests/AppCreationTests.cs
  22. 175
      backend/tools/TestSuite/TestSuite.ApiTests/AppsTests.cs
  23. 58
      backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs
  24. BIN
      backend/tools/TestSuite/TestSuite.ApiTests/Assets/logo-squared.png
  25. 92
      backend/tools/TestSuite/TestSuite.ApiTests/BackupTests.cs
  26. 165
      backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs
  27. 186
      backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs
  28. 33
      backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj
  29. 71
      backend/tools/TestSuite/TestSuite.LoadTests/ReadingBenchmarks.cs
  30. 34
      backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs
  31. 19
      backend/tools/TestSuite/TestSuite.LoadTests/ReadingFixture.cs
  32. 2
      backend/tools/TestSuite/TestSuite.LoadTests/Run.cs
  33. 10
      backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj
  34. 13
      backend/tools/TestSuite/TestSuite.LoadTests/WritingBenchmarks.cs
  35. 20
      backend/tools/TestSuite/TestSuite.LoadTests/WritingFixture.cs
  36. 21
      backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs
  37. 38
      backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientFixture.cs
  38. 73
      backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs
  39. 43
      backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture.cs
  40. 38
      backend/tools/TestSuite/TestSuite.Shared/Fixtures/CreatedAppFixture.cs
  41. 31
      backend/tools/TestSuite/TestSuite.Shared/Model/TestClient.cs
  42. 2
      backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs
  43. 18
      backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj
  44. 2
      backend/tools/TestSuite/TestSuite.Shared/Utils/RandomHash.cs
  45. 2
      backend/tools/TestSuite/TestSuite.Shared/Utils/RandomString.cs
  46. 2
      backend/tools/TestSuite/TestSuite.Shared/Utils/Run.cs
  47. 37
      backend/tools/TestSuite/TestSuite.sln

4
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs

@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Core.Apps
}
[Pure]
public AppPatterns Add(Guid id, string name, string pattern, string? message)
public AppPatterns Add(Guid id, string name, string pattern, string? message = null)
{
var newPattern = new AppPattern(name, pattern, message);
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Apps
}
[Pure]
public AppPatterns Update(Guid id, string name, string pattern, string? message)
public AppPatterns Update(Guid id, string name, string pattern, string? message = null)
{
Guard.NotNullOrEmpty(name);
Guard.NotNullOrEmpty(pattern);

23
backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup;
@ -155,16 +156,22 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadAssetAsync(Guid appId, IBackupReader reader)
{
await reader.ReadBlobAsync(AvatarFile, async stream =>
try
{
try
{
await appImageStore.UploadAsync(appId, stream);
}
catch (AssetAlreadyExistsException)
await reader.ReadBlobAsync(AvatarFile, async stream =>
{
}
});
try
{
await appImageStore.UploadAsync(appId, stream);
}
catch (AssetAlreadyExistsException)
{
}
});
}
catch (FileNotFoundException)
{
}
}
}
}

46
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -75,15 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
}
}
await EnrichWithMetadataAsync(createAsset, createAsset.Tags);
await HandleCoreAsync(context, next);
var asset = context.Result<IEnrichedAssetEntity>();
context.Complete(new AssetCreatedResult(asset, false));
await assetFileStore.CopyAsync(tempFile, createAsset.AssetId, asset.FileVersion);
await UploadAsync(context, tempFile, createAsset, true, next);
}
finally
{
@ -95,16 +87,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
case UpdateAsset updateAsset:
{
await EnrichWithMetadataAsync(updateAsset);
await EnrichWithHashAndUploadAsync(updateAsset, tempFile);
try
{
await HandleCoreAsync(context, next);
var asset = context.Result<IEnrichedAssetEntity>();
await assetFileStore.CopyAsync(tempFile, updateAsset.AssetId, asset.FileVersion);
await UploadAsync(context, tempFile, updateAsset, false, next);
}
finally
{
@ -115,12 +102,24 @@ namespace Squidex.Domain.Apps.Entities.Assets
}
default:
await HandleCoreAsync(context, next);
await HandleCoreAsync(context, false, next);
break;
}
}
private async Task HandleCoreAsync(CommandContext context, NextDelegate next)
private async Task UploadAsync(CommandContext context, string tempFile, UploadAssetCommand command, bool created, NextDelegate next)
{
await EnrichWithMetadataAsync(command);
var asset = await HandleCoreAsync(context, created, next);
if (asset != null)
{
await assetFileStore.CopyAsync(tempFile, command.AssetId, asset.FileVersion);
}
}
private async Task<IEnrichedAssetEntity?> HandleCoreAsync(CommandContext context, bool created, NextDelegate next)
{
await base.HandleAsync(context, next);
@ -128,8 +127,19 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var enriched = await assetEnricher.EnrichAsync(asset, contextProvider.Context);
context.Complete(enriched);
if (created)
{
context.Complete(new AssetCreatedResult(enriched, false));
}
else
{
context.Complete(enriched);
}
return enriched;
}
return null;
}
private static bool IsDuplicate(IAssetEntity asset, AssetFile file)

2
backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeTagGenerator.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class FileTypeTagGenerator : IAssetMetadataSource
public sealed class FileTypeAssetMetadataSource : IAssetMetadataSource
{
public Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags)
{

52
backend/src/Squidex.Domain.Apps.Entities/Assets/ImageMetadataSource.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs

@ -14,11 +14,11 @@ using Squidex.Infrastructure.Assets;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class ImageMetadataSource : IAssetMetadataSource
public sealed class ImageAssetMetadataSource : IAssetMetadataSource
{
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
public ImageMetadataSource(IAssetThumbnailGenerator assetThumbnailGenerator)
public ImageAssetMetadataSource(IAssetThumbnailGenerator assetThumbnailGenerator)
{
Guard.NotNull(assetThumbnailGenerator);
@ -27,32 +27,35 @@ namespace Squidex.Domain.Apps.Entities.Assets
public async Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags)
{
var imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead());
if (imageInfo != null)
if (command.Type == AssetType.Unknown)
{
command.Type = AssetType.Image;
command.Metadata.SetPixelWidth(imageInfo.PixelWidth);
command.Metadata.SetPixelHeight(imageInfo.PixelHeight);
var imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead());
if (tags != null)
if (imageInfo != null)
{
tags.Add("image");
command.Type = AssetType.Image;
var wh = imageInfo.PixelWidth + imageInfo.PixelHeight;
command.Metadata.SetPixelWidth(imageInfo.PixelWidth);
command.Metadata.SetPixelHeight(imageInfo.PixelHeight);
if (wh > 2000)
{
tags.Add("image/large");
}
else if (wh > 1000)
{
tags.Add("image/medium");
}
else
if (tags != null)
{
tags.Add("image/small");
tags.Add("image");
var wh = imageInfo.PixelWidth + imageInfo.PixelHeight;
if (wh > 2000)
{
tags.Add("image/large");
}
else if (wh > 1000)
{
tags.Add("image/medium");
}
else
{
tags.Add("image/small");
}
}
}
}
@ -60,10 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public IEnumerable<string> Format(IAssetEntity asset)
{
if (asset.Type == AssetType.Image)
{
yield return $"{asset.Metadata.GetPixelWidth()}x{asset.Metadata.GetPixelHeight()}px";
}
yield break;
}
}
}

17
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
@ -202,13 +203,17 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
catch (Exception ex)
{
if (ex is BackupRestoreException backupException)
switch (ex)
{
Log(backupException.Message);
}
else
{
Log("Failed with internal error");
case BackupRestoreException backupException:
Log(backupException.Message);
break;
case FileNotFoundException fileNotFoundException:
Log(fileNotFoundException.Message);
break;
default:
Log("Failed with internal error");
break;
}
await CleanupAsync(handlers);

2
backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs

@ -132,7 +132,7 @@ namespace Squidex.Infrastructure.Collections
var afterIndex = items.Length - index - 1;
Array.Copy(items, 0, result, 0, index);
Array.Copy(items, index, result, index, afterIndex);
Array.Copy(items, index + 1, result, index, afterIndex);
}
return Create<TArray>(result);

4
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -160,10 +160,12 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
public static AssetDto FromAsset(IEnrichedAssetEntity asset, ApiController controller, string app, bool isDuplicate = false)
{
var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() });
var response = SimpleMapper.Map(asset, new AssetDto());
response.Tags = asset.TagNames;
response.FileType = asset.FileName.FileType();
if (isDuplicate)
{
response.Meta = new AssetMeta

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

@ -52,11 +52,14 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AssetUsageTracker>()
.As<IAssetUsageTracker>().As<IEventConsumer>();
services.AddSingletonAs<FileTypeTagGenerator>()
services.AddSingletonAs<FileTypeAssetMetadataSource>()
.As<IAssetMetadataSource>();
services.AddSingletonAs<FileTagAssetMetadataSource>()
.As<IAssetMetadataSource>();
services.AddSingletonAs<ImageAssetMetadataSource>()
.As<IAssetMetadataSource>();
}
public static void AddSquidexAssetInfrastructure(this IServiceCollection services, IConfiguration config)

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

@ -93,9 +93,11 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact]
public void Should_revoke_client()
{
var clients_1 = clients_0.Revoke("1");
var clients_1 = clients_0.Add("2", "secret2");
var clients_2 = clients_1.Add("3", "secret3");
var clients_3 = clients_2.Revoke("2");
Assert.Empty(clients_1);
Assert.Equal(new string[] { "1", "3" }, clients_3.Keys);
}
[Fact]

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

@ -48,9 +48,11 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
public void Should_remove_contributor()
{
var contributors_1 = contributors_0.Assign("1", Role.Developer);
var contributors_2 = contributors_1.Remove("1");
var contributors_2 = contributors_1.Assign("2", Role.Developer);
var contributors_3 = contributors_2.Assign("3", Role.Developer);
var contributors_4 = contributors_3.Remove("2");
Assert.Empty(contributors_2);
Assert.Equal(new string[] { "1", "3" }, contributors_4.Keys);
}
[Fact]

6
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Linq;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Apps;
using Xunit;
@ -68,9 +69,10 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact]
public void Should_remove_pattern()
{
var patterns_1 = patterns_0.Remove(firstId);
var patterns_1 = patterns_0.Add(id, "Name1", "Pattern1");
var patterns_2 = patterns_1.Remove(firstId);
Assert.Empty(patterns_1);
Assert.Equal(id, patterns_2.Keys.Single());
}
[Fact]

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

@ -86,9 +86,11 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact]
public void Should_remove_role()
{
var roles_1 = roles_0.Remove(firstRole);
var roles_1 = roles_0.Add("role1");
var roles_2 = roles_1.Add("role2");
var roles_3 = roles_2.Remove(firstRole);
Assert.Equal(0, roles_1.CustomCount);
Assert.Equal(new string[] { "role1", "role2" }, roles_3.Custom.Select(x => x.Name));
}
[Fact]

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeTagGeneratorTests.cs → backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeAssetMetadataSourceTests.cs

@ -14,10 +14,10 @@ using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class FileTypeTagGeneratorTests
public class FileTypeAssetMetadataSourceTests
{
private readonly HashSet<string> tags = new HashSet<string>();
private readonly FileTypeTagGenerator sut = new FileTypeTagGenerator();
private readonly FileTypeAssetMetadataSource sut = new FileTypeAssetMetadataSource();
[Fact]
public async Task Should_not_add_tag_if_no_file_info()

38
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageMetadataSourceTests.cs → backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs

@ -17,19 +17,30 @@ using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class ImageMetadataSourceTests
public class ImageAssetMetadataSourceTests
{
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly HashSet<string> tags = new HashSet<string>();
private readonly MemoryStream stream = new MemoryStream();
private readonly AssetFile file;
private readonly ImageMetadataSource sut;
private readonly ImageAssetMetadataSource sut;
public ImageMetadataSourceTests()
public ImageAssetMetadataSourceTests()
{
file = new AssetFile("MyImage.png", "image/png", 1024, () => stream);
sut = new ImageMetadataSource(assetThumbnailGenerator);
sut = new ImageAssetMetadataSource(assetThumbnailGenerator);
}
[Fact]
public async Task Should_not_enhance_if_type_already_found()
{
var command = new CreateAsset { File = file, Type = AssetType.Image };
await sut.EnhanceAsync(command, tags);
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A<Stream>.Ignored))
.MustNotHaveHappened();
}
[Fact]
@ -85,24 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
}
[Fact]
public void Should_format_with_dimensions_if_image()
{
var source = new AssetEntity
{
Metadata =
new AssetMetadata()
.SetPixelWidth(800)
.SetPixelHeight(600),
Type = AssetType.Image
};
var formatted = sut.Format(source).First();
Assert.Equal("800x600px", formatted);
}
[Fact]
public void Should_format_to_empty_if_not_an_image()
public void Should_format_to_empty()
{
var source = new AssetEntity();

25
backend/tools/LoadTest/LoadTest.sln

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoadTest", "LoadTest.csproj", "{EC732B4F-576E-4853-880D-AA676966D017}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EC732B4F-576E-4853-880D-AA676966D017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC732B4F-576E-4853-880D-AA676966D017}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC732B4F-576E-4853-880D-AA676966D017}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC732B4F-576E-4853-880D-AA676966D017}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8ABC073D-D6C6-47F0-AFB6-D42F177D468A}
EndGlobalSection
EndGlobal

67
backend/tools/LoadTest/ManyItemsTests.cs

@ -1,67 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using LoadTest.Model;
using LoadTest.Utils;
using Squidex.ClientLibrary;
using Xunit;
namespace LoadTest
{
public class ManyItemsTests
{
private readonly SquidexClient<TestEntity, TestEntityData> client;
public ManyItemsTests()
{
client = TestClient.BuildAsync("multiple").Result;
}
[Fact]
public async Task Should_read_many_async()
{
var contents = await client.GetAsync();
for (var i = contents.Total; i < 20000; i++)
{
await client.CreateAsync(new TestEntityData
{
String = RandomString.Create(1000)
}, publish: true);
}
var found = await client.GetAll2Async();
Assert.Equal(20000, found.Items.Count);
}
}
public static class SquidexClientExtensions
{
public static async Task<SquidexEntities<TEntity, TData>> GetAll2Async<TEntity, TData>(this SquidexClient<TEntity, TData> client, int batchSize = 200)
where TEntity : SquidexEntityBase<TData>
where TData : class, new()
{
var query = new ODataQuery { Top = batchSize, Skip = 0 };
var entities = new SquidexEntities<TEntity, TData>();
do
{
var getResult = await client.GetAsync(query);
entities.Total = getResult.Total;
entities.Items.AddRange(getResult.Items);
query.Skip += getResult.Items.Count;
}
while (query.Skip < entities.Total);
return entities;
}
}
}

102
backend/tools/LoadTest/Model/TestClient.cs

@ -1,102 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Management;
namespace LoadTest.Model
{
public static class TestClient
{
public const string ServerUrl = "http://localhost:5000";
public const string ClientId = "root";
public const string ClientSecret = "xeLd6jFxqbXJrfmNLlO2j1apagGGGSyZJhFnIuHp4I0=";
public const string TestAppName = "integration-tests";
public static readonly SquidexClientManager ClientManager =
new SquidexClientManager(
ServerUrl,
TestAppName,
ClientId,
ClientSecret)
{
ReadResponseAsString = true
};
public static async Task<SquidexClient<TestEntity, TestEntityData>> BuildAsync(string schemaName)
{
await CreateAppIfNotExistsAsync();
await CreateSchemaIfNotExistsAsync(schemaName);
return ClientManager.GetClient<TestEntity, TestEntityData>(schemaName);
}
private static async Task CreateAppIfNotExistsAsync()
{
try
{
var apps = ClientManager.CreateAppsClient();
await apps.PostAppAsync(new CreateAppDto
{
Name = TestAppName
});
}
catch (SquidexManagementException ex)
{
if (ex.StatusCode != 400)
{
throw;
}
}
}
private static async Task CreateSchemaIfNotExistsAsync(string schemaName)
{
try
{
var schemas = ClientManager.CreateSchemasClient();
await schemas.PostSchemaAsync(TestAppName, new CreateSchemaDto
{
Name = schemaName,
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "number",
Properties = new NumberFieldPropertiesDto
{
IsRequired = true
}
},
new UpsertSchemaFieldDto
{
Name = "string",
Properties = new StringFieldPropertiesDto
{
IsRequired = false
}
}
},
IsPublished = true
});
}
catch (SquidexManagementException ex)
{
if (ex.StatusCode != 400)
{
throw;
}
}
}
}
}

51
backend/tools/LoadTest/ReadingFixture.cs

@ -1,51 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using LoadTest.Model;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Management;
namespace LoadTest
{
public sealed class ReadingFixture : IDisposable
{
public SquidexClient<TestEntity, TestEntityData> Client { get; private set; }
public IAppsClient AppsClient { get; private set; }
public ReadingFixture()
{
Task.Run(async () =>
{
Client = await TestClient.BuildAsync("reading");
var contents = await Client.GetAllAsync();
if (contents.Total != 10)
{
foreach (var content in contents.Items)
{
await Client.DeleteAsync(content);
}
for (var i = 10; i > 0; i--)
{
await Client.CreateAsync(new TestEntityData { Number = i }, true);
}
}
AppsClient = TestClient.ClientManager.CreateAppsClient();
}).Wait();
}
public void Dispose()
{
}
}
}

31
backend/tools/LoadTest/TestUtils.cs

@ -1,31 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using LoadTest.Model;
using Squidex.ClientLibrary.Management;
using Xunit;
namespace LoadTest
{
public sealed class TestUtils
{
[Fact]
public async Task GenerateAppManyContributorsAsync()
{
var client = TestClient.ClientManager.CreateAppsClient();
for (var i = 0; i < 200; i++)
{
await client.PostContributorAsync("test", new AssignContributorDto
{
ContributorId = $"hello{i}@squidex.io", Invite = true, Role = "Editor"
});
}
}
}
}

70
backend/tools/TestSuite/TestSuite.ApiTests/AppCreationTests.cs

@ -0,0 +1,70 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.ClientLibrary.Management;
using TestSuite.Fixtures;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public class AppCreationTests : IClassFixture<ClientFixture>
{
public ClientFixture _ { get; }
public AppCreationTests(ClientFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_create_app()
{
var appName = Guid.NewGuid().ToString();
var createRequest = new CreateAppDto { Name = appName };
var app = await _.Apps.PostAppAsync(createRequest);
// Should return create app with correct name.
Assert.Equal(appName, app.Name);
var apps = await _.Apps.GetAppsAsync();
// Should provide new app when apps are queried.
Assert.Contains(apps, x => x.Name == appName);
var contributors = await _.Apps.GetContributorsAsync(appName);
// Should not client itself as a contributor.
Assert.Empty(contributors.Items);
var clients = await _.Apps.GetClientsAsync(appName);
// Should create default client.
Assert.Contains(clients.Items, x => x.Id == "default");
}
[Fact]
public async Task Should_remove_app()
{
var appName = Guid.NewGuid().ToString();
await _.Apps.PostAppAsync(new CreateAppDto { Name = appName });
await _.Apps.DeleteAppAsync(appName);
var apps = await _.Apps.GetAppsAsync();
// Should not provide deleted app when apps are queried.
Assert.DoesNotContain(apps, x => x.Name == appName);
}
}
}

175
backend/tools/TestSuite/TestSuite.ApiTests/AppsTests.cs

@ -0,0 +1,175 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.ClientLibrary.Management;
using TestSuite.Fixtures;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public sealed class AppsTests : IClassFixture<CreatedAppFixture>
{
public CreatedAppFixture _ { get; }
public AppsTests(CreatedAppFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_manage_clients()
{
var clientId = "my-client";
var clientName = "My Client";
var clientRole = "Owner";
// STEP 1: Create client
var createRequest = new CreateClientDto { Id = clientId };
var clients1 = await _.Apps.PostClientAsync(_.AppName, createRequest);
// Should return client with correct name and id.
Assert.Contains(clients1.Items, x => x.Id == clientId && x.Name == clientId && x.Role == "Editor");
// STEP 2: Update client name.
var updateNameRequest = new UpdateClientDto { Name = clientName };
var clients2 = await _.Apps.PutClientAsync(_.AppName, clientId, updateNameRequest);
// Should update client name.
Assert.Contains(clients2.Items, x => x.Id == clientId && x.Name == clientName && x.Role == "Editor");
// STEP 3: Update client role.
var updateRoleRequest = new UpdateClientDto { Role = clientRole };
var clients3 = await _.Apps.PutClientAsync(_.AppName, clientId, updateRoleRequest);
// Should update client role.
Assert.Contains(clients3.Items, x => x.Id == clientId && x.Name == clientName && x.Role == clientRole);
// STEP 4: Delete client
var clients4 = await _.Apps.DeleteClientAsync(_.AppName, clientId);
// Should not return deleted client.
Assert.DoesNotContain(clients4.Items, x => x.Id == clientId);
}
[Fact]
public async Task Should_manage_contributors()
{
var contributorEmail = "hello@squidex.io";
var contributorRole = "Owner";
// STEP 0: Do not invite contributors when flag is false.
var createRequest = new AssignContributorDto { ContributorId = "test@squidex.io" };
var ex = await Assert.ThrowsAsync<SquidexManagementException>(() =>
{
return _.Apps.PostContributorAsync(_.AppName, createRequest);
});
Assert.Equal(404, ex.StatusCode);
// STEP 1: Assign contributor.
var createInviteRequest = new AssignContributorDto { ContributorId = contributorEmail, Invite = true };
var contributors1 = await _.Apps.PostContributorAsync(_.AppName, createInviteRequest);
var id = contributors1.Items.FirstOrDefault(x => x.ContributorName == contributorEmail).ContributorId;
// Should return contributor with correct email.
Assert.Contains(contributors1.Items, x => x.ContributorName == contributorEmail && x.Role == "Developer");
// STEP 2: Update contributor role.
var updateRequest = new AssignContributorDto { ContributorId = contributorEmail, Role = contributorRole };
var contributors2 = await _.Apps.PostContributorAsync(_.AppName, updateRequest);
// Should return contributor with correct role.
Assert.Contains(contributors2.Items, x => x.ContributorId == id && x.Role == contributorRole);
// STEP 3: Remove contributor.
var contributors3 = await _.Apps.DeleteContributorAsync(_.AppName, id);
// Should not return deleted contributor.
Assert.DoesNotContain(contributors3.Items, x => x.ContributorId == id);
}
[Fact]
public async Task Should_manage_roles()
{
var roleName = Guid.NewGuid().ToString();
var roleClient = Guid.NewGuid().ToString();
var roleContributor = "role@squidex.io";
// STEP 1: Add role.
var createRequest = new AddRoleDto { Name = roleName };
var roles1 = await _.Apps.PostRoleAsync(_.AppName, createRequest);
// Should return role with correct name.
Assert.Contains(roles1.Items, x => x.Name == roleName && x.Permissions.Count == 0);
// STEP 2: Update role.
var updateRequest = new UpdateRoleDto { Permissions = new List<string> { "a", "b" } };
var roles2 = await _.Apps.PutRoleAsync(_.AppName, roleName, updateRequest);
// Should return role with correct name.
Assert.Contains(roles2.Items, x => x.Name == roleName && x.Permissions.SequenceEqual(updateRequest.Permissions));
// STEP 3: Assign client and contributor.
await _.Apps.PostClientAsync(_.AppName, new CreateClientDto { Id = roleClient });
await _.Apps.PutClientAsync(_.AppName, roleClient, new UpdateClientDto { Role = roleName });
await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor, Role = roleName, Invite = true });
var roles3 = await _.Apps.GetRolesAsync(_.AppName);
// Should return role with correct number of users and clients.
Assert.Contains(roles3.Items, x => x.Name == roleName && x.NumClients == 1 && x.NumContributors == 1);
// STEP 4: Try to delete role.
var ex = await Assert.ThrowsAsync<SquidexManagementException<ErrorDto>>(() =>
{
return _.Apps.DeleteRoleAsync(_.AppName, roleName);
});
Assert.Equal(400, ex.StatusCode);
// Step 5: Remove after client and contributor removed.
var fallbackRole = "Developer";
await _.Apps.PutClientAsync(_.AppName, roleClient, new UpdateClientDto { Role = fallbackRole });
await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor, Role = fallbackRole });
await _.Apps.DeleteRoleAsync(_.AppName, roleName);
var roles4 = await _.Apps.GetRolesAsync(_.AppName);
// Should not return deleted role.
Assert.DoesNotContain(roles4.Items, x => x.Name == roleName);
}
}
}

58
backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs

@ -0,0 +1,58 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Threading.Tasks;
using TestSuite.Fixtures;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public class AssetTests : IClassFixture<AssetFixture>
{
public AssetFixture _ { get; }
public AssetTests(AssetFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_upload_image()
{
var fileName = $"{Guid.NewGuid()}.png";
using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open))
{
var asset = await _.Assets.CreateAssetAsync(fileName, "image/png", stream);
Assert.True(asset.IsImage);
Assert.Equal(600, asset.PixelHeight);
Assert.Equal(600, asset.PixelWidth);
}
}
[Fact]
public async Task Should_upload_image_without_extension()
{
var fileName = $"{Guid.NewGuid()}.png";
using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open))
{
var asset = await _.Assets.CreateAssetAsync(fileName, "image/png", stream);
Assert.True(asset.IsImage);
Assert.Equal(600, asset.PixelHeight);
Assert.Equal(600, asset.PixelWidth);
}
}
}
}

BIN
backend/tools/TestSuite/TestSuite.ApiTests/Assets/logo-squared.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

92
backend/tools/TestSuite/TestSuite.ApiTests/BackupTests.cs

@ -0,0 +1,92 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Squidex.ClientLibrary.Management;
using TestSuite.Fixtures;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public class BackupTests : IClassFixture<ClientFixture>
{
public ClientFixture _ { get; }
public BackupTests(ClientFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_backup_and_restore_app()
{
var appName = Guid.NewGuid().ToString();
var appNameRestore = $"{appName}-restore";
// STEP 1: Create app
var createRequest = new CreateAppDto { Name = appName };
await _.Apps.PostAppAsync(createRequest);
// STEP 2: Create backup
await _.Backups.PostBackupAsync(appName);
BackupJobDto backup = null;
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20)))
{
while (true)
{
cts.Token.ThrowIfCancellationRequested();
await Task.Delay(1000);
var backups = await _.Backups.GetBackupsAsync(appName);
if (backups.Items.Count > 0)
{
backup = backups.Items.FirstOrDefault();
break;
}
}
}
// STEP 3: Restore backup
var uri = new Uri($"{_.ServerUrl}{backup._links["download"].Href}");
var restoreRequest = new RestoreRequestDto { Url = uri, Name = appNameRestore };
await _.Backups.PostRestoreJobAsync(restoreRequest);
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)))
{
while (true)
{
cts.Token.ThrowIfCancellationRequested();
await Task.Delay(1000);
var job = await _.Backups.GetRestoreJobAsync();
if (job != null && job.Url == uri && job.Status == JobStatus.Completed)
{
break;
}
}
}
}
}
}

165
backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs

@ -0,0 +1,165 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.ClientLibrary;
using TestSuite.Fixtures;
using TestSuite.Model;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public class ContentQueryTests : IClassFixture<ContentQueryFixture>
{
public ContentQueryFixture _ { get; }
public ContentQueryTests(ContentQueryFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_query_by_ids()
{
var items = await _.Contents.GetAsync(new ODataQuery { OrderBy = "data/number/iv asc" });
var itemsById = await _.Contents.GetAsync(new HashSet<Guid>(items.Items.Take(3).Select(x => x.EntityId)));
Assert.Equal(3, itemsById.Items.Count);
Assert.Equal(3, itemsById.Total);
foreach (var item in itemsById.Items)
{
Assert.Equal(_.AppName, item.AppName);
Assert.Equal(_.SchemaName, item.SchemaName);
}
}
[Fact]
public async Task Should_return_all()
{
var items = await _.Contents.GetAsync(new ODataQuery { OrderBy = "data/number/iv asc" });
AssertItems(items, 10, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
}
[Fact]
public async Task Should_return_items_with_skip()
{
var items = await _.Contents.GetAsync(new ODataQuery { Skip = 5, OrderBy = "data/number/iv asc" });
AssertItems(items, 10, new[] { 6, 7, 8, 9, 10 });
}
[Fact]
public async Task Should_return_items_with_skip_and_top()
{
var items = await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/number/iv asc" });
AssertItems(items, 10, new[] { 3, 4, 5, 6, 7 });
}
[Fact]
public async Task Should_return_items_with_ordering()
{
var items = await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/number/iv desc" });
AssertItems(items, 10, new[] { 8, 7, 6, 5, 4 });
}
[Fact]
public async Task Should_return_items_with_filter()
{
var items = await _.Contents.GetAsync(new ODataQuery { Filter = "data/number/iv gt 3 and data/number/iv lt 7", OrderBy = "data/number/iv asc" });
AssertItems(items, 3, new[] { 4, 5, 6 });
}
[Fact]
public async Task Should_query_items_with_graphql()
{
var query = new
{
query = @"
{
queryNumbersContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") {
id,
data {
value {
iv
}
}
}
}"
};
var result = await _.Contents.GraphQlAsync<QueryResult>(query);
var items = result.Items;
Assert.Equal(items.Select(x => x.Data.Value).ToArray(), new[] { 4, 5, 6 });
}
[Fact]
public async Task Should_query_items_with_graphql_with_dynamic()
{
var query = new
{
query = @"
{
queryNumbersContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") {
id,
data {
value {
iv
}
}
}
}"
};
var result = await _.Contents.GraphQlAsync<JObject>(query);
var items = result["queryNumbersContents"];
Assert.Equal(items.Select(x => x["data"]["value"]["iv"].Value<int>()).ToArray(), new[] { 4, 5, 6 });
}
private sealed class QueryResult
{
[JsonProperty("queryNumbersContents")]
public QueryItem[] Items { get; set; }
}
private sealed class QueryItem
{
public Guid Id { get; set; }
public QueryItemData Data { get; set; }
}
private sealed class QueryItemData
{
[JsonConverter(typeof(InvariantConverter))]
public int Value { get; set; }
}
private void AssertItems(SquidexEntities<TestEntity, TestEntityData> entities, int total, int[] expected)
{
Assert.Equal(total, entities.Total);
Assert.Equal(expected, entities.Items.Select(x => x.Data.Number).ToArray());
}
}
}

186
backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs

@ -0,0 +1,186 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.ClientLibrary;
using TestSuite.Fixtures;
using TestSuite.Model;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests
{
public class ContentUpdateTests : IClassFixture<ContentFixture>
{
public ContentFixture _ { get; }
public ContentUpdateTests(ContentFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_create_strange_text()
{
const string text = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36";
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { String = text }, true);
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(text, updated.Data.String);
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_not_return_not_published_item()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 });
await Assert.ThrowsAsync<SquidexException>(() => _.Contents.GetAsync(content.Id));
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_return_item_published_with_creation()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, true);
await _.Contents.GetAsync(content.Id);
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_return_item_published_item()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 });
await _.Contents.ChangeStatusAsync(content.Id, Status.Published);
await _.Contents.GetAsync(content.Id);
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_not_return_archived_item()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, true);
await _.Contents.ChangeStatusAsync(content.Id, Status.Archived);
await Assert.ThrowsAsync<SquidexException>(() => _.Contents.GetAsync(content.Id));
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_not_return_unpublished_item()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 });
await _.Contents.ChangeStatusAsync(content.Id, Status.Published);
await _.Contents.ChangeStatusAsync(content.Id, Status.Draft);
await Assert.ThrowsAsync<SquidexException>(() => _.Contents.GetAsync(content.Id));
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_update_item()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, true);
await _.Contents.UpdateAsync(content.Id, new TestEntityData { Number = 2 });
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(2, updated.Data.Number);
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_patch_item()
{
TestEntity content = null;
try
{
content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, true);
await _.Contents.PatchAsync(content.Id, new TestEntityData { Number = 2 });
var updated = await _.Contents.GetAsync(content.Id);
Assert.Equal(2, updated.Data.Number);
}
finally
{
await _.Contents.DeleteAsync(content.Id);
}
}
[Fact]
public async Task Should_delete_item()
{
var content = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, true);
await _.Contents.DeleteAsync(content.Id);
var updated = await _.Contents.GetAsync();
Assert.DoesNotContain(updated.Items, x => x.Id == content.Id);
}
}
}

33
backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\..\Squidex.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TestSuite.Shared\TestSuite.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Assets\" />
</ItemGroup>
<ItemGroup>
<None Update="Assets\logo-squared.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

71
backend/tools/TestSuite/TestSuite.LoadTests/ReadingBenchmarks.cs

@ -0,0 +1,71 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using TestSuite.Fixtures;
using Xunit;
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.LoadTests
{
public class ReadingBenchmarks : IClassFixture<CreatedAppFixture>
{
public CreatedAppFixture _ { get; }
public ReadingBenchmarks(CreatedAppFixture fixture)
{
_ = fixture;
}
public static IEnumerable<object[]> Loads()
{
int[] users =
{
1,
5,
10,
20,
50,
100
};
int[] loads =
{
1,
5,
10,
20,
50,
100,
1000
};
foreach (var user in users)
{
foreach (var load in loads)
{
yield return new object[] { user, load };
}
}
yield return new object[] { 1, 20000 };
}
[Theory]
[MemberData(nameof(Loads))]
public async Task Should_return_clients(int numUsers, int numIterationsPerUser)
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await _.Apps.GetClientsAsync(_.AppName);
});
}
}
}

34
backend/tools/LoadTest/ReadingBenchmarks.cs → backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs

@ -7,19 +7,21 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using LoadTest.Model;
using Squidex.ClientLibrary;
using Xunit;
namespace LoadTest
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.LoadTests
{
public class ReadingBenchmarks : IClassFixture<ReadingFixture>
public class ReadingContentBenchmarks : IClassFixture<ReadingFixture>
{
public ReadingFixture Fixture { get; }
public ReadingFixture _ { get; }
public ReadingBenchmarks(ReadingFixture fixture)
public ReadingContentBenchmarks(ReadingFixture fixture)
{
Fixture = fixture;
_ = fixture;
}
public static IEnumerable<object[]> Loads()
@ -62,7 +64,7 @@ namespace LoadTest
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.Client.GetAsync(new ODataQuery { OrderBy = "data/value/iv asc" });
await _.Contents.GetAsync(new ODataQuery { OrderBy = "data/value/iv asc" });
});
}
@ -72,7 +74,7 @@ namespace LoadTest
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.Client.GetAsync(new ODataQuery { Skip = 5, OrderBy = "data/value/iv asc" });
await _.Contents.GetAsync(new ODataQuery { Skip = 5, OrderBy = "data/value/iv asc" });
});
}
@ -82,7 +84,7 @@ namespace LoadTest
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.Client.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv asc" });
await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv asc" });
});
}
@ -92,7 +94,7 @@ namespace LoadTest
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.Client.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv desc" });
await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv desc" });
});
}
@ -102,17 +104,7 @@ namespace LoadTest
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.Client.GetAsync(new ODataQuery { Filter = "data/value/iv gt 3 and data/value/iv lt 7", OrderBy = "data/value/iv asc" });
});
}
[Theory]
[MemberData(nameof(Loads))]
public async Task Should_return_clients(int numUsers, int numIterationsPerUser)
{
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.AppsClient.GetClientsAsync(TestClient.TestAppName);
await _.Contents.GetAsync(new ODataQuery { Filter = "data/value/iv gt 3 and data/value/iv lt 7", OrderBy = "data/value/iv asc" });
});
}
}

19
backend/tools/TestSuite/TestSuite.LoadTests/ReadingFixture.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.LoadTests
{
public sealed class ReadingFixture : ContentFixture
{
public ReadingFixture()
: base("benchmark_reading")
{
}
}
}

2
backend/tools/LoadTest/Run.cs → backend/tools/TestSuite/TestSuite.LoadTests/Run.cs

@ -14,7 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace LoadTest
namespace TestSuite.LoadTests
{
public static class Run
{

10
backend/tools/LoadTest/LoadTest.csproj → backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
@ -6,7 +6,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.ClientLibrary" Version="4.0.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
@ -15,9 +14,12 @@
</PackageReference>
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRuleSet>..\..\..\Squidex.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
<AdditionalFiles Include="..\..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TestSuite.Shared\TestSuite.Shared.csproj" />
</ItemGroup>
</Project>

13
backend/tools/LoadTest/WritingBenchmarks.cs → backend/tools/TestSuite/TestSuite.LoadTests/WritingBenchmarks.cs

@ -8,18 +8,21 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LoadTest.Model;
using TestSuite.Model;
using Xunit;
namespace LoadTest
#pragma warning disable SA1300 // Element should begin with upper-case letter
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.LoadTests
{
public class WritingBenchmarks : IClassFixture<WritingFixture>
{
public WritingFixture Fixture { get; }
public WritingFixture _ { get; }
public WritingBenchmarks(WritingFixture fixture)
{
Fixture = fixture;
_ = fixture;
}
public static IEnumerable<object[]> Loads()
@ -62,7 +65,7 @@ namespace LoadTest
await Run.Parallel(numUsers, numIterationsPerUser, async () =>
{
await Fixture.Client.CreateAsync(new TestEntityData { Number = random.Next() }, true);
await _.Contents.CreateAsync(new TestEntityData { Number = random.Next() }, true);
});
}
}

20
backend/tools/LoadTest/WritingFixture.cs → backend/tools/TestSuite/TestSuite.LoadTests/WritingFixture.cs

@ -5,26 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using LoadTest.Model;
using Squidex.ClientLibrary;
using TestSuite.Fixtures;
namespace LoadTest
namespace TestSuite.LoadTests
{
public sealed class WritingFixture : IDisposable
public sealed class WritingFixture : ContentFixture
{
public SquidexClient<TestEntity, TestEntityData> Client { get; private set; }
public WritingFixture()
{
Task.Run(async () =>
{
Client = await TestClient.BuildAsync("writing");
}).Wait();
}
public void Dispose()
: base("benchmark_writing")
{
}
}

21
backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.ClientLibrary;
namespace TestSuite.Fixtures
{
public class AssetFixture : CreatedAppFixture
{
public SquidexAssetClient Assets { get; }
public AssetFixture()
{
Assets = ClientManager.GetAssetClient();
}
}
}

38
backend/tools/TestSuite/TestSuite.Shared/Fixtures/ClientFixture.cs

@ -0,0 +1,38 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Management;
using TestSuite.Model;
namespace TestSuite.Fixtures
{
public class ClientFixture : IDisposable
{
public SquidexClientManager ClientManager { get; }
public IAppsClient Apps { get; }
public IBackupsClient Backups { get; }
public string ServerUrl { get; } = TestClient.ServerUrl;
public ClientFixture()
{
ClientManager = TestClient.ClientManager;
Apps = ClientManager.CreateAppsClient();
Backups = ClientManager.CreateBackupsClient();
}
public virtual void Dispose()
{
}
}
}

73
backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs

@ -0,0 +1,73 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Management;
using TestSuite.Model;
namespace TestSuite.Fixtures
{
public class ContentFixture : CreatedAppFixture
{
public SquidexClient<TestEntity, TestEntityData> Contents { get; }
public string SchemaName { get; }
public string FieldNumber { get; } = "number";
public string FieldString { get; } = "string";
public ContentFixture(string schemaName = "my-schema")
{
SchemaName = schemaName;
Task.Run(async () =>
{
try
{
var schemas = ClientManager.CreateSchemasClient();
await schemas.PostSchemaAsync(TestClient.TestAppName, new CreateSchemaDto
{
Name = SchemaName,
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = FieldNumber,
Properties = new NumberFieldPropertiesDto
{
IsRequired = true
}
},
new UpsertSchemaFieldDto
{
Name = FieldString,
Properties = new StringFieldPropertiesDto
{
IsRequired = false
}
}
},
IsPublished = true
});
}
catch (SquidexManagementException ex)
{
if (ex.StatusCode != 400)
{
throw;
}
}
});
Contents = ClientManager.GetClient<TestEntity, TestEntityData>(SchemaName);
}
}
}

43
backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture.cs

@ -0,0 +1,43 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.ClientLibrary;
using TestSuite.Model;
namespace TestSuite.Fixtures
{
public class ContentQueryFixture : ContentFixture
{
public ContentQueryFixture(string schemaName = "my-schema")
: base(schemaName)
{
Task.Run(async () =>
{
Dispose();
for (var i = 10; i > 0; i--)
{
await Contents.CreateAsync(new TestEntityData { Number = i }, true);
}
}).Wait();
}
public override void Dispose()
{
Task.Run(async () =>
{
var contents = await Contents.GetAllAsync();
foreach (var content in contents.Items)
{
await Contents.DeleteAsync(content);
}
});
}
}
}

38
backend/tools/TestSuite/TestSuite.Shared/Fixtures/CreatedAppFixture.cs

@ -0,0 +1,38 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.ClientLibrary.Management;
using TestSuite.Model;
namespace TestSuite.Fixtures
{
public class CreatedAppFixture : ClientFixture
{
public string AppName { get; } = TestClient.TestAppName;
public CreatedAppFixture()
{
Task.Run(async () =>
{
try
{
await Apps.PostAppAsync(new CreateAppDto { Name = AppName });
}
catch (SquidexManagementException ex)
{
if (ex.StatusCode != 400)
{
throw;
}
}
await Apps.PostContributorAsync(AppName, new AssignContributorDto { ContributorId = "sebastian@squidex.io", Invite = true, Role = "Owner" });
}).Wait();
}
}
}

31
backend/tools/TestSuite/TestSuite.Shared/Model/TestClient.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.ClientLibrary;
namespace TestSuite.Model
{
internal static class TestClient
{
public const string ServerUrl = "http://localhost:5000";
public const string ClientId = "root";
public const string ClientSecret = "xeLd6jFxqbXJrfmNLlO2j1apagGGGSyZJhFnIuHp4I0=";
public const string TestAppName = "integration-tests";
public static readonly SquidexClientManager ClientManager =
new SquidexClientManager(
ServerUrl,
TestAppName,
ClientId,
ClientSecret)
{
ReadResponseAsString = true
};
}
}

2
backend/tools/LoadTest/Model/TestEntity.cs → backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs

@ -8,7 +8,7 @@
using Newtonsoft.Json;
using Squidex.ClientLibrary;
namespace LoadTest.Model
namespace TestSuite.Model
{
public sealed class TestEntity : SquidexEntityBase<TestEntityData>
{

18
backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.ClientLibrary" Version="4.0.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\..\Squidex.ruleset</CodeAnalysisRuleSet>
<RootNamespace>TestSuite</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

2
backend/tools/LoadTest/Utils/RandomHash.cs → backend/tools/TestSuite/TestSuite.Shared/Utils/RandomHash.cs

@ -9,7 +9,7 @@ using System;
using System.Security.Cryptography;
using System.Text;
namespace LoadTest.Utils
namespace TestSuite.Utils
{
public static class RandomHash
{

2
backend/tools/LoadTest/Utils/RandomString.cs → backend/tools/TestSuite/TestSuite.Shared/Utils/RandomString.cs

@ -7,7 +7,7 @@
using System;
namespace LoadTest.Utils
namespace TestSuite.Utils
{
public static class RandomString
{

2
backend/tools/LoadTest/Utils/Run.cs → backend/tools/TestSuite/TestSuite.Shared/Utils/Run.cs

@ -14,7 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace LoadTest.Utils
namespace TestSuite.Utils
{
public static class Run
{

37
backend/tools/TestSuite/TestSuite.sln

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestSuite.Shared", "TestSuite.Shared\TestSuite.Shared.csproj", "{37484845-5542-4E52-AB00-C4576B84FE75}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestSuite.ApiTests", "TestSuite.ApiTests\TestSuite.ApiTests.csproj", "{E5F048CB-5307-4E4C-8DAB-2F1C0E5CACF3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestSuite.LoadTests", "TestSuite.LoadTests\TestSuite.LoadTests.csproj", "{F37572D9-4880-40F4-B3CB-83F58A40CA48}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{37484845-5542-4E52-AB00-C4576B84FE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37484845-5542-4E52-AB00-C4576B84FE75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37484845-5542-4E52-AB00-C4576B84FE75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37484845-5542-4E52-AB00-C4576B84FE75}.Release|Any CPU.Build.0 = Release|Any CPU
{E5F048CB-5307-4E4C-8DAB-2F1C0E5CACF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5F048CB-5307-4E4C-8DAB-2F1C0E5CACF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5F048CB-5307-4E4C-8DAB-2F1C0E5CACF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5F048CB-5307-4E4C-8DAB-2F1C0E5CACF3}.Release|Any CPU.Build.0 = Release|Any CPU
{F37572D9-4880-40F4-B3CB-83F58A40CA48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F37572D9-4880-40F4-B3CB-83F58A40CA48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F37572D9-4880-40F4-B3CB-83F58A40CA48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F37572D9-4880-40F4-B3CB-83F58A40CA48}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F1CDBED-7D91-4B46-B4C5-0FE086E29285}
EndGlobalSection
EndGlobal
Loading…
Cancel
Save