diff --git a/backend/src/Squidex/Areas/Api/Config/OpenApi/ErrorDtoProcessor.cs b/backend/src/Squidex/Areas/Api/Config/OpenApi/ErrorDtoProcessor.cs index cd9d1d0c5..7e5beb08b 100644 --- a/backend/src/Squidex/Areas/Api/Config/OpenApi/ErrorDtoProcessor.cs +++ b/backend/src/Squidex/Areas/Api/Config/OpenApi/ErrorDtoProcessor.cs @@ -12,8 +12,8 @@ using NJsonSchema; using NSwag; using NSwag.Generation.Processors; using NSwag.Generation.Processors.Contexts; -using Squidex.ClientLibrary.Management; using Squidex.Pipeline.OpenApi; +using Squidex.Web; namespace Squidex.Areas.Api.Config.OpenApi { diff --git a/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs b/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs index 24a31aa72..16ffd1f19 100644 --- a/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs +++ b/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs @@ -20,8 +20,8 @@ namespace Squidex.Areas.Api.Config.OpenApi operation.AddQuery("$search", JsonObjectType.String, "Optional OData full text search."); } - operation.AddQuery("$top", JsonObjectType.Number, $"Optional number of {entity} to take."); - operation.AddQuery("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip."); + operation.AddQuery("$top", JsonObjectType.Integer, $"Optional number of {entity} to take."); + operation.AddQuery("$skip", JsonObjectType.Integer, $"Optional number of {entity} to skip."); operation.AddQuery("$orderby", JsonObjectType.String, "Optional OData order definition."); operation.AddQuery("$filter", JsonObjectType.String, "Optional OData filter definition."); } diff --git a/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs b/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs index 01329cbdd..5f4bf73d7 100644 --- a/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs +++ b/backend/src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs @@ -26,7 +26,7 @@ namespace Squidex.Areas.Api.Config.OpenApi public bool Process(OperationProcessorContext context) { - if (context.OperationDescription.Path == supportedPath) + if (context.OperationDescription.Path == supportedPath && context.OperationDescription.Method == "get") { var operation = context.OperationDescription.Operation; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index fff15b861..7bf49768a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -14,7 +13,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; -using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; @@ -179,7 +177,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ProducesResponseType(typeof(AppDto), 201)] [ApiPermission(Permissions.AppUpdateImage)] [ApiCosts(0)] - public async Task UploadImage(string app, [OpenApiIgnore] List file) + public async Task UploadImage(string app, IFormFile file) { var response = await InvokeCommandAsync(CreateCommand(file)); @@ -306,16 +304,16 @@ namespace Squidex.Areas.Api.Controllers.Apps return response; } - private static UploadAppImage CreateCommand(IReadOnlyList file) + private UploadAppImage CreateCommand(IFormFile? file) { - if (file.Count != 1) + if (file == null || Request.Form.Files.Count != 1) { - var error = new ValidationError($"Can only upload one file, found {file.Count} files."); + var error = new ValidationError($"Can only upload one file, found {Request.Form.Files.Count} files."); - throw new ValidationException("Cannot create asset.", error); + throw new ValidationException("Cannot upload image.", error); } - return new UploadAppImage { File = file[0].ToAssetFile() }; + return new UploadAppImage { File = file.ToAssetFile() }; } private static FileStream GetTempStream() diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 86f3f8874..ee695cd7a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Assets.Models; using Squidex.Areas.Api.Controllers.Contents; using Squidex.Domain.Apps.Core.Tags; @@ -20,7 +19,6 @@ using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; -using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Validation; @@ -185,7 +183,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [AssetRequestSizeLimit] [ApiPermission(Permissions.AppAssetsCreate)] [ApiCosts(1)] - public async Task PostAsset(string app, [FromQuery] Guid parentId, [OpenApiIgnore] List file) + public async Task PostAsset(string app, [FromQuery] Guid parentId, IFormFile file) { var assetFile = await CheckAssetFileAsync(file); @@ -215,7 +213,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [ProducesResponseType(typeof(AssetDto), 200)] [ApiPermission(Permissions.AppAssetsUpload)] [ApiCosts(1)] - public async Task PutAssetContent(string app, Guid id, [OpenApiIgnore] List file) + public async Task PutAssetContent(string app, Guid id, IFormFile file) { var assetFile = await CheckAssetFileAsync(file); @@ -311,20 +309,11 @@ namespace Squidex.Areas.Api.Controllers.Assets } } - private async Task CheckAssetFileAsync(IReadOnlyList file) + private async Task CheckAssetFileAsync(IFormFile? file) { - if (file.Count != 1) + if (file == null || Request.Form.Files.Count != 1) { - var error = new ValidationError($"Can only upload one file, found {file.Count} files."); - - throw new ValidationException("Cannot create asset.", error); - } - - var formFile = file[0]; - - if (formFile.Length > assetOptions.MaxSize) - { - var error = new ValidationError($"File cannot be bigger than {assetOptions.MaxSize.ToReadableSize()}."); + var error = new ValidationError($"Can only upload one file, found {Request.Form.Files.Count} files."); throw new ValidationException("Cannot create asset.", error); } @@ -333,14 +322,14 @@ namespace Squidex.Areas.Api.Controllers.Assets var currentSize = await assetStatsRepository.GetTotalSizeAsync(AppId); - if (plan.MaxAssetSize > 0 && plan.MaxAssetSize < currentSize + formFile.Length) + if (plan.MaxAssetSize > 0 && plan.MaxAssetSize < currentSize + file.Length) { var error = new ValidationError("You have reached your max asset size."); throw new ValidationException("Cannot create asset.", error); } - return formFile.ToAssetFile(); + return file.ToAssetFile(); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs index 6404c932e..5fed7180d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Assets; diff --git a/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonConversionTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonConversionTests.cs index 95c702b93..e1c8d9e10 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonConversionTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Queries/QueryJsonConversionTests.cs @@ -361,6 +361,14 @@ namespace Squidex.Infrastructure.Queries AssertQuery(json, "Filter: string == 'Hello'; FullText: 'Hello'; Skip: 10; Take: 20"); } + [Fact] + public void Should_parse_query_with_top() + { + var json = new { skip = 10, top = 20, FullText = "Hello", Filter = new { path = "string", op = "eq", value = "Hello" } }; + + AssertQuery(json, "Filter: string == 'Hello'; FullText: 'Hello'; Skip: 10; Take: 20"); + } + [Fact] public void Should_parse_query_with_sorting() { diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs index eb7fdeb0b..8e9ad41a5 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs @@ -6,8 +6,12 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.IO; +using System.Net.Http; using System.Threading.Tasks; +using Squidex.ClientLibrary; +using Squidex.ClientLibrary.Management; using TestSuite.Fixtures; using Xunit; @@ -32,11 +36,16 @@ namespace TestSuite.ApiTests using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) { - var asset = await _.Assets.CreateAssetAsync(fileName, "image/png", stream); + var file = new FileParameter(stream, fileName, "image/png"); + var asset = await _.Assets.PostAssetAsync(_.AppName, file); + + // Should create metadata. Assert.True(asset.IsImage); Assert.Equal(600, asset.PixelHeight); Assert.Equal(600, asset.PixelWidth); + Assert.Equal(600L, asset.Metadata["pixelHeight"]); + Assert.Equal(600L, asset.Metadata["pixelWidth"]); } } @@ -47,12 +56,212 @@ namespace TestSuite.ApiTests using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) { - var asset = await _.Assets.CreateAssetAsync(fileName, "image/png", stream); + var file = new FileParameter(stream, fileName, "image/png"); + + var asset = await _.Assets.PostAssetAsync(_.AppName, file); + + // Should create metadata. + Assert.True(asset.IsImage); + Assert.Equal(600, asset.PixelHeight); + Assert.Equal(600, asset.PixelWidth); + Assert.Equal(600L, asset.Metadata["pixelHeight"]); + Assert.Equal(600L, asset.Metadata["pixelWidth"]); + } + } + + [Fact] + public async Task Should_replace_asset() + { + var fileName = $"{Guid.NewGuid()}.png"; + + AssetDto asset; + + // STEP 1: Create asset + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var file = new FileParameter(stream, fileName, "image/png"); + + asset = await _.Assets.PostAssetAsync(_.AppName, file); Assert.True(asset.IsImage); Assert.Equal(600, asset.PixelHeight); Assert.Equal(600, asset.PixelWidth); } + + + // STEP 2: Reupload asset + using (var stream = new FileStream("Assets/logo-wide.png", FileMode.Open)) + { + var file = new FileParameter(stream, fileName, "image/png"); + + asset = await _.Assets.PutAssetContentAsync(_.AppName, asset.Id.ToString(), file); + + // Should update metadata. + Assert.True(asset.IsImage); + Assert.Equal(135, asset.PixelHeight); + Assert.Equal(600, asset.PixelWidth); + } + + using (var stream = new FileStream("Assets/logo-wide.png", FileMode.Open)) + { + var downloaded = await DownloadAsync(asset); + + // Should dowload with correct size. + Assert.Equal(stream.Length, downloaded.Length); + } + } + + [Fact] + public async Task Should_annote_asset() + { + var fileName = $"{Guid.NewGuid()}.png"; + + AssetDto asset; + + // STEP 1: Create asset + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var file = new FileParameter(stream, fileName, "image/png"); + + asset = await _.Assets.PostAssetAsync(_.AppName, file); + } + + + // STEP 2: Annotate metadata. + var metadataRequest = new AnnotateAssetDto + { + Metadata = new Dictionary + { + ["pw"] = 100L, + ["ph"] = 20L + } + }; + + asset = await _.Assets.PutAssetAsync(_.AppName, asset.Id.ToString(), metadataRequest); + + // Should provide metadata. + Assert.Equal(metadataRequest.Metadata, asset.Metadata); + + + // STEP 3: Annotate slug. + var slugRequest = new AnnotateAssetDto { Slug = "my-image" }; + + asset = await _.Assets.PutAssetAsync(_.AppName, asset.Id.ToString(), slugRequest); + + // Should provide updated slug. + Assert.Equal(slugRequest.Slug, asset.Slug); + + + // STEP 3: Annotate file name. + var fileNameRequest = new AnnotateAssetDto { FileName = "My Image" }; + + asset = await _.Assets.PutAssetAsync(_.AppName, asset.Id.ToString(), fileNameRequest); + + // Should provide updated file name. + Assert.Equal(fileNameRequest.FileName, asset.FileName); + } + + [Fact] + public async Task Should_protect_asset() + { + var fileName = $"{Guid.NewGuid()}.png"; + + AssetDto asset; + + // STEP 1: Create asset + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var file = new FileParameter(stream, fileName, "image/png"); + + asset = await _.Assets.PostAssetAsync(_.AppName, file); + } + + + // STEP 2: Download asset + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var downloaded = await DownloadAsync(asset); + + // Should dowload with correct size. + Assert.Equal(stream.Length, downloaded.Length); + } + + + // STEP 4: Protect asset + var protectRequest = new AnnotateAssetDto { IsProtected = true }; + + asset = await _.Assets.PutAssetAsync(_.AppName, asset.Id.ToString(), protectRequest); + + + // STEP 5: Download asset with authentication. + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var downloaded = new MemoryStream(); + + using (var assetStream = await _.Assets.GetAssetContentAsync(asset.Id.ToString())) + { + await assetStream.Stream.CopyToAsync(downloaded); + } + + // Should dowload with correct size. + Assert.Equal(stream.Length, downloaded.Length); + } + + + // STEP 5: Download asset without key. + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var ex = await Assert.ThrowsAsync(() => DownloadAsync(asset)); + + // Should return 403 when not authenticated. + Assert.Contains("403", ex.Message); + } + } + + [Fact] + public async Task Should_delete_asset() + { + var fileName = $"{Guid.NewGuid()}.png"; + + AssetDto asset; + + // STEP 1: Create asset + using (var stream = new FileStream("Assets/logo-squared.png", FileMode.Open)) + { + var file = new FileParameter(stream, fileName, "image/png"); + + asset = await _.Assets.PostAssetAsync(_.AppName, file); + } + + + // STEP 2: Delete asset + await _.Assets.DeleteAssetAsync(_.AppName, asset.Id.ToString()); + + // Should return 404 when asset deleted. + var ex = await Assert.ThrowsAsync(() => _.Assets.GetAssetAsync(_.AppName, asset.Id.ToString())); + + Assert.Equal(404, ex.StatusCode); + } + + private async Task DownloadAsync(AssetDto asset) + { + var temp = new MemoryStream(); + + using (var client = new HttpClient()) + { + var url = $"{_.ServerUrl}{asset._links["content"].Href}"; + + var response = await client.GetAsync(url); + + response.EnsureSuccessStatusCode(); + + using (var stream = await response.Content.ReadAsStreamAsync()) + { + await stream.CopyToAsync(temp); + } + } + + return temp; } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/Assets/logo-wide.png b/backend/tools/TestSuite/TestSuite.ApiTests/Assets/logo-wide.png new file mode 100644 index 000000000..9a29fa803 Binary files /dev/null and b/backend/tools/TestSuite/TestSuite.ApiTests/Assets/logo-wide.png differ diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs index 998ce4eb9..1ad8eb3b8 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs @@ -33,7 +33,7 @@ namespace TestSuite.ApiTests [Fact] public async Task Should_query_by_ids() { - var items = await _.Contents.GetAsync(new ODataQuery { OrderBy = "data/number/iv asc" }); + var items = await _.Contents.GetAsync(new ContentQuery { OrderBy = "data/number/iv asc" }); var itemsById = await _.Contents.GetAsync(new HashSet(items.Items.Take(3).Select(x => x.EntityId))); @@ -48,45 +48,138 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_return_all() + public async Task Should_return_all_with_odata() { - var items = await _.Contents.GetAsync(new ODataQuery { OrderBy = "data/number/iv asc" }); + var items = await _.Contents.GetAsync(new ContentQuery { 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() + public async Task Should_return_all_with_json() { - var items = await _.Contents.GetAsync(new ODataQuery { Skip = 5, OrderBy = "data/number/iv asc" }); + var items = await _.Contents.GetAsync(new ContentQuery + { + JsonQuery = new + { + sort = new[] + { + new + { + path = "data.number.iv", order = "ascending" + } + } + } + }); + + AssertItems(items, 10, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + } + + [Fact] + public async Task Should_return_items_with_skip_with_odata() + { + var items = await _.Contents.GetAsync(new ContentQuery { 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() + public async Task Should_return_items_with_skip_with_json() { - var items = await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/number/iv asc" }); + var items = await _.Contents.GetAsync(new ContentQuery + { + JsonQuery = new + { + sort = new[] + { + new + { + path = "data.number.iv", order = "ascending" + } + }, + skip = 5 + } + }); + + AssertItems(items, 10, new[] { 6, 7, 8, 9, 10 }); + } + + [Fact] + public async Task Should_return_items_with_skip_and_top_with_odata() + { + var items = await _.Contents.GetAsync(new ContentQuery { 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() + public async Task Should_return_items_with_skip_and_top_with_json() { - var items = await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/number/iv desc" }); + var items = await _.Contents.GetAsync(new ContentQuery + { + JsonQuery = new + { + sort = new[] + { + new + { + path = "data.number.iv", order = "ascending" + } + }, + skip = 2, top = 5 + } + }); - AssertItems(items, 10, new[] { 8, 7, 6, 5, 4 }); + AssertItems(items, 10, new[] { 3, 4, 5, 6, 7 }); } [Fact] - public async Task Should_return_items_with_filter() + public async Task Should_return_items_with_filter_with_odata() { - var items = await _.Contents.GetAsync(new ODataQuery { Filter = "data/number/iv gt 3 and data/number/iv lt 7", OrderBy = "data/number/iv asc" }); + var items = await _.Contents.GetAsync(new ContentQuery { 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_return_items_with_filter_with_json() + { + var items = await _.Contents.GetAsync(new ContentQuery + { + JsonQuery = new + { + sort = new[] + { + new + { + path = "data.number.iv", order = "ascending" + } + }, + filter = new + { + and = new[] + { + new + { + path = "data.number.iv", + op = "gt", + value = 3 + }, + new + { + path = "data.number.iv", + op = "lt", + value = 7 + } + } + } + } + }); + + AssertItems(items, 3, new[] { 4, 5, 6 }); + } + + [Fact] public async Task Should_query_items_with_graphql() { @@ -94,10 +187,10 @@ namespace TestSuite.ApiTests { query = @" { - queryNumbersContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") { + queryMyReadsContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") { id, data { - value { + number { iv } } @@ -109,7 +202,7 @@ namespace TestSuite.ApiTests var items = result.Items; - Assert.Equal(items.Select(x => x.Data.Value).ToArray(), new[] { 4, 5, 6 }); + Assert.Equal(items.Select(x => x.Data.Number).ToArray(), new[] { 4, 5, 6 }); } [Fact] @@ -119,10 +212,10 @@ namespace TestSuite.ApiTests { query = @" { - queryNumbersContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") { + queryMyReadsContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") { id, data { - value { + number { iv } } @@ -132,14 +225,14 @@ namespace TestSuite.ApiTests var result = await _.Contents.GraphQlAsync(query); - var items = result["queryNumbersContents"]; + var items = result["queryMyReadsContents"]; - Assert.Equal(items.Select(x => x["data"]["value"]["iv"].Value()).ToArray(), new[] { 4, 5, 6 }); + Assert.Equal(items.Select(x => x["data"]["number"]["iv"].Value()).ToArray(), new[] { 4, 5, 6 }); } private sealed class QueryResult { - [JsonProperty("queryNumbersContents")] + [JsonProperty("queryMyReadsContents")] public QueryItem[] Items { get; set; } } @@ -153,7 +246,7 @@ namespace TestSuite.ApiTests private sealed class QueryItemData { [JsonConverter(typeof(InvariantConverter))] - public int Value { get; set; } + public int Number { get; set; } } private void AssertItems(SquidexEntities entities, int total, int[] expected) diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs index b28fc93ea..9e0005739 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs @@ -41,7 +41,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -57,7 +60,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -73,7 +79,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -90,7 +99,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -108,7 +120,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -127,7 +142,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -147,7 +165,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } @@ -167,7 +188,10 @@ namespace TestSuite.ApiTests } finally { - await _.Contents.DeleteAsync(content.Id); + if (content.Id != null) + { + await _.Contents.DeleteAsync(content.Id); + } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/Setup.cs b/backend/tools/TestSuite/TestSuite.ApiTests/Setup.cs new file mode 100644 index 000000000..a9d911028 --- /dev/null +++ b/backend/tools/TestSuite/TestSuite.ApiTests/Setup.cs @@ -0,0 +1,10 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Xunit; + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] \ No newline at end of file diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj b/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj index e0a0f9832..14596b2da 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj +++ b/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj @@ -1,4 +1,4 @@ - + Exe netcoreapp3.0 @@ -29,5 +29,8 @@ PreserveNewest + + PreserveNewest + diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs b/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs index 13961e3e6..8fe47b336 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs +++ b/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs @@ -64,7 +64,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ODataQuery { OrderBy = "data/value/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { OrderBy = "data/value/iv asc" }); }); } @@ -74,7 +74,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ODataQuery { Skip = 5, OrderBy = "data/value/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { Skip = 5, OrderBy = "data/value/iv asc" }); }); } @@ -84,7 +84,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv asc" }); }); } @@ -94,7 +94,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv desc" }); + await _.Contents.GetAsync(new ContentQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv desc" }); }); } @@ -104,7 +104,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ODataQuery { Filter = "data/value/iv gt 3 and data/value/iv lt 7", OrderBy = "data/value/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { Filter = "data/value/iv gt 3 and data/value/iv lt 7", OrderBy = "data/value/iv asc" }); }); } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs index fb6494f18..bffb4fc09 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs @@ -5,17 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.ClientLibrary; +using Squidex.ClientLibrary.Management; namespace TestSuite.Fixtures { public class AssetFixture : CreatedAppFixture { - public SquidexAssetClient Assets { get; } + public IAssetsClient Assets { get; } public AssetFixture() { - Assets = ClientManager.GetAssetClient(); + Assets = ClientManager.CreateAssetsClient(); } } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs index 24ed7890c..9ae4eda75 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentFixture.cs @@ -23,7 +23,12 @@ namespace TestSuite.Fixtures public string FieldString { get; } = "string"; - public ContentFixture(string schemaName = "my-schema") + public ContentFixture() + : this("my-writes") + { + } + + protected ContentFixture(string schemaName) { SchemaName = schemaName; @@ -65,7 +70,7 @@ namespace TestSuite.Fixtures throw; } } - }); + }).Wait(); Contents = ClientManager.GetClient(SchemaName); } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture.cs index 2cf3db8d1..4e8399769 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture.cs @@ -13,7 +13,12 @@ namespace TestSuite.Fixtures { public class ContentQueryFixture : ContentFixture { - public ContentQueryFixture(string schemaName = "my-schema") + public ContentQueryFixture() + : this("my-reads") + { + } + + protected ContentQueryFixture(string schemaName = "my-schema") : base(schemaName) { Task.Run(async () => @@ -37,7 +42,7 @@ namespace TestSuite.Fixtures { await Contents.DeleteAsync(content); } - }); + }).Wait(); } } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index 5d4562fb4..869feea5d 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -4,7 +4,7 @@ - + diff --git a/backend/tools/TestSuite/TestSuite.sln b/backend/tools/TestSuite/TestSuite.sln index ffa2d3b8a..8bc1f5817 100644 --- a/backend/tools/TestSuite/TestSuite.sln +++ b/backend/tools/TestSuite/TestSuite.sln @@ -3,11 +3,11 @@ 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestSuite.LoadTests", "TestSuite.LoadTests\TestSuite.LoadTests.csproj", "{F37572D9-4880-40F4-B3CB-83F58A40CA48}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution