Browse Source

Fix referencing queries and improve tests. (#942)

* Fix referencing queries and improve tests.

* Another test.

* Tests fixed.
pull/943/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
cac87d84f6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  2. 14
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  3. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs
  4. 24
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs
  5. 8
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs
  6. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs
  7. 161
      backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs
  8. 2
      backend/tools/TestSuite/TestSuite.ApiTests/ContentFixture.cs
  9. 5
      backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryFixture.cs
  10. 8
      backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs
  11. 195
      backend/tools/TestSuite/TestSuite.ApiTests/GraphQLFixture.cs
  12. 277
      backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs
  13. 10
      backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj
  14. 4
      backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj
  15. 44
      backend/tools/TestSuite/TestSuite.Shared/Model/Geography.cs
  16. 16
      backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj

13
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -89,6 +89,9 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
{
try
{
// We need to translate the query names to the document field names in MongoDB.
var query = q.Query.AdjustToModel(appId);
if (q.Ids is { Count: > 0 })
{
var filter = BuildFilter(appId, q.Ids.ToHashSet());
@ -98,10 +101,10 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
.SortByDescending(x => x.LastModified).ThenBy(x => x.Id)
.QueryLimit(q.Query)
.QuerySkip(q.Query)
.ToListRandomAsync(Collection, q.Query.Random, ct);
.ToListRandomAsync(Collection, query.Random, ct);
long assetTotal = assetEntities.Count;
if (assetEntities.Count >= q.Query.Take || q.Query.Skip > 0)
if (assetEntities.Count >= query.Take || query.Skip > 0)
{
if (q.NoTotal)
{
@ -117,8 +120,6 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
else
{
var query = q.Query.AdjustToModel(appId);
// Default means that no other filters are applied and we only query by app.
var (filter, isDefault) = query.BuildFilter(appId, parentId);
@ -130,9 +131,9 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
.ToListRandomAsync(Collection, query.Random, ct);
long assetTotal = assetEntities.Count;
if (assetEntities.Count >= q.Query.Take || q.Query.Skip > 0)
if (assetEntities.Count >= query.Take || query.Skip > 0)
{
var isDefaultQuery = q.Query.Filter == null;
var isDefaultQuery = query.Filter == null;
if (q.NoTotal || (q.NoSlowTotal && !isDefaultQuery))
{

14
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -180,17 +180,17 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
return await queryScheduled.QueryAsync(app, new List<ISchemaEntity> { schema }, q, ct);
}
if (q.Referencing == default)
if (q.Referencing != default)
{
if (queryInDedicatedCollection != null)
{
return await queryInDedicatedCollection.QueryAsync(schema, q, ct);
}
return await queryReferences.QueryAsync(app, new List<ISchemaEntity> { schema }, q, ct);
}
return await queryByQuery.QueryAsync(schema, q, ct);
if (queryInDedicatedCollection != null)
{
return await queryInDedicatedCollection.QueryAsync(schema, q, ct);
}
return ResultList.Empty<IContentEntity>();
return await queryByQuery.QueryAsync(schema, q, ct);
}
catch (MongoCommandException ex) when (ex.Code == 96)
{

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs

@ -106,8 +106,8 @@ public static class Extensions
var result =
collection.Find(filter)
.QuerySort(query)
.QueryLimit(query)
.QuerySkip(query)
.QueryLimit(query)
.ToListRandomAsync(collection, query.Random, ct);
return await result;

24
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs

@ -28,7 +28,8 @@ internal sealed class QueryByIds : OperationBase
return ReadonlyList.Empty<ContentIdStatus>();
}
var filter = CreateFilter(appId, null, ids);
// Create a filter from the Ids and ensure that the content ids match to the app ID.
var filter = CreateFilter(appId, null, ids, null);
var contentEntities = await Collection.FindStatusAsync(filter, ct);
@ -43,12 +44,16 @@ internal sealed class QueryByIds : OperationBase
return ResultList.Empty<IContentEntity>();
}
var filter = CreateFilter(app.Id, schemas.Select(x => x.Id), q.Ids.ToHashSet());
// We need to translate the query names to the document field names in MongoDB.
var query = q.Query.AdjustToModel(app.Id);
var contentEntities = await FindContentsAsync(q.Query, filter, ct);
// Create a filter from the Ids and ensure that the content ids match to the schema IDs.
var filter = CreateFilter(app.Id, schemas.Select(x => x.Id), q.Ids.ToHashSet(), query.Filter);
var contentEntities = await FindContentsAsync(query, filter, ct);
var contentTotal = (long)contentEntities.Count;
if (contentTotal >= q.Query.Take || q.Query.Skip > 0)
if (contentTotal >= query.Take || query.Skip > 0)
{
if (q.NoTotal)
{
@ -68,14 +73,16 @@ internal sealed class QueryByIds : OperationBase
{
var result =
Collection.Find(filter)
.QueryLimit(query)
.QuerySort(query)
.QuerySkip(query)
.QueryLimit(query)
.ToListRandomAsync(Collection, query.Random, ct);
return await result;
}
private static FilterDefinition<MongoContentEntity> CreateFilter(DomainId appId, IEnumerable<DomainId>? schemaIds, HashSet<DomainId> ids)
private static FilterDefinition<MongoContentEntity> CreateFilter(DomainId appId, IEnumerable<DomainId>? schemaIds, HashSet<DomainId> ids,
FilterNode<ClrValue>? filter)
{
var filters = new List<FilterDefinition<MongoContentEntity>>();
@ -101,6 +108,11 @@ internal sealed class QueryByIds : OperationBase
filters.Add(Filter.Ne(x => x.IsDeleted, true));
if (filter != null)
{
filters.Add(filter.BuildFilter<MongoContentEntity>());
}
return Filter.And(filters);
}
}

8
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs

@ -66,9 +66,9 @@ internal sealed class QueryByQuery : OperationBase
var contentEntities = await Collection.QueryContentsAsync(filter, query, ct);
var contentTotal = (long)contentEntities.Count;
if (contentTotal >= q.Query.Take || q.Query.Skip > 0)
if (contentTotal >= query.Take || query.Skip > 0)
{
if (q.NoTotal || (q.NoSlowTotal && q.Query.Filter != null))
if (q.NoTotal || (q.NoSlowTotal && query.Filter != null))
{
contentTotal = -1;
}
@ -98,9 +98,9 @@ internal sealed class QueryByQuery : OperationBase
var contentEntities = await Collection.QueryContentsAsync(filter, query, ct);
var contentTotal = (long)contentEntities.Count;
if (contentTotal >= q.Query.Take || q.Query.Skip > 0)
if (contentTotal >= query.Take || query.Skip > 0)
{
if (q.NoTotal || (q.NoSlowTotal && q.Query.Filter != null))
if (q.NoTotal || (q.NoSlowTotal && query.Filter != null))
{
contentTotal = -1;
}

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs

@ -91,9 +91,9 @@ internal sealed class QueryInDedicatedCollection : MongoBase<MongoContentEntity>
var contentEntities = await contentCollection.QueryContentsAsync(filter, query, ct);
var contentTotal = (long)contentEntities.Count;
if (contentTotal >= q.Query.Take || q.Query.Skip > 0)
if (contentTotal >= query.Take || query.Skip > 0)
{
if (q.NoTotal || (q.NoSlowTotal && q.Query.Filter != null))
if (q.NoTotal || (q.NoSlowTotal && query.Filter != null))
{
contentTotal = -1;
}

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

@ -6,11 +6,9 @@
// ==========================================================================
using System.Net;
using System.Runtime.Intrinsics.X86;
using Squidex.Assets;
using Squidex.ClientLibrary.Management;
using TestSuite.Fixtures;
using Xunit.Sdk;
#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
@ -80,44 +78,12 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
var fileParameter = FileParameter.FromPath("Assets/SampleVideo_1280x720_1mb.mp4");
var pausingStream = new PauseStream(fileParameter.Data, 0.25);
var pausingFile = new FileParameter(pausingStream, fileParameter.FileName, fileParameter.ContentType);
var numUploads = 0;
var numConflicts = 0;
await using (pausingFile.Data)
{
using var cts = new CancellationTokenSource(5000);
while (progress.Asset == null)
{
// When the previous request is still in progress we just give it another try.
if (progress.Exception is SquidexManagementException { StatusCode: 409 } && numConflicts < 3)
{
numConflicts++;
progress.ResetException();
// Wait a little bit to finish the request on the server.
await Task.Delay(100, cts.Token);
}
else if (progress.Exception != null)
{
break;
}
pausingStream.Reset();
await _.Assets.UploadAssetAsync(_.AppName, pausingFile, progress.AsOptions(), cts.Token);
numUploads++;
}
}
await UploadInChunksAsync(fileParameter);
Assert.Null(progress.Exception);
Assert.NotEmpty(progress.Progress);
Assert.NotNull(progress.Asset);
Assert.True(numUploads > 1);
Assert.True(progress.Uploads.Count > 1);
await using (var stream = new FileStream("Assets/SampleVideo_1280x720_1mb.mp4", FileMode.Open))
{
@ -242,7 +208,7 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
[Fact]
public async Task Should_replace_asset_using_tus_in_chunks()
{
for (var i = 0; i < 5; i++)
for (var i = 0; i < 1; i++)
{
// STEP 1: Create asset
var asset_1 = await _.Assets.UploadFileAsync(_.AppName, "Assets/logo-squared.png", "image/png");
@ -253,45 +219,12 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
var fileParameter = FileParameter.FromPath("Assets/SampleVideo_1280x720_1mb.mp4");
var pausingStream = new PauseStream(fileParameter.Data, 0.25);
var pausingFile = new FileParameter(pausingStream, fileParameter.FileName, fileParameter.ContentType);
var numUploads = 0;
var numConflicts = 0;
await using (pausingFile.Data)
{
using var cts = new CancellationTokenSource(5000);
// When the previous request is still in progress we just give it another try.
while (progress.Asset == null)
{
// When the previous request is still in progress we just give it another try.
if (progress.Exception is SquidexManagementException { StatusCode: 409 } && numConflicts < 3)
{
numConflicts++;
progress.ResetException();
// Wait a little bit to finish the request on the server.
await Task.Delay(100, cts.Token);
}
else if (progress.Exception != null)
{
break;
}
pausingStream.Reset();
await _.Assets.UploadAssetAsync(_.AppName, pausingFile, progress.AsOptions(asset_1.Id), cts.Token);
numUploads++;
}
}
await UploadInChunksAsync(fileParameter, asset_1.Id);
Assert.Null(progress.Exception);
Assert.NotEmpty(progress.Progress);
Assert.NotNull(progress.Asset);
Assert.True(numUploads > 1);
Assert.True(progress.Uploads.Count > 1);
await using (var stream = new FileStream("Assets/SampleVideo_1280x720_1mb.mp4", FileMode.Open))
{
@ -699,12 +632,36 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
Assert.NotEqual(asset_1.FileSize, asset_2.FileSize);
}
private async Task UploadInChunksAsync(FileParameter fileParameter, string id = null)
{
var pausingStream = new PauseStream(fileParameter.Data, 0.25);
var pausingFile = new FileParameter(pausingStream, fileParameter.FileName, fileParameter.ContentType)
{
ContentLength = fileParameter.Data.Length
};
await using (pausingFile.Data)
{
using var cts = new CancellationTokenSource(5000);
while (progress.Asset == null && progress.Exception == null && !cts.IsCancellationRequested)
{
pausingStream.Reset();
await _.Assets.UploadAssetAsync(_.AppName, pausingFile, progress.AsOptions(id), cts.Token);
progress.Uploaded();
}
}
}
public class ProgressHandler : IAssetProgressHandler
{
public string FileId { get; private set; }
public string FileId { get; private set; } = Guid.NewGuid().ToString();
public List<int> Progress { get; } = new List<int>();
public List<int> Uploads { get; } = new List<int>();
public Exception Exception { get; private set; }
public AssetDto Asset { get; private set; }
@ -719,17 +676,14 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
return options;
}
public void ResetException()
public void Uploaded()
{
Exception = null;
Uploads.Add(Progress.LastOrDefault());
}
public Task OnCompletedAsync(AssetUploadCompletedEvent @event,
CancellationToken ct)
{
// This is a previous exception, so we can unset it.
ResetException();
Asset = @event.Asset;
return Task.CompletedTask;
}
@ -751,28 +705,44 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
public Task OnFailedAsync(AssetUploadExceptionEvent @event,
CancellationToken ct)
{
if (@event.Exception.InnerException is not PauseException)
{
Exception = @event.Exception;
}
Exception = @event.Exception;
return Task.CompletedTask;
}
}
private sealed class PauseException : Exception
public class PauseStream : DelegateStream
{
}
private readonly int maxLength;
private long totalRead;
private long totalRemaining;
private long seekStart;
private sealed class PauseStream : DelegateStream
{
private readonly double pauseAfter = 1;
private int totalRead;
public override long Length
{
get => Math.Min(maxLength, totalRemaining);
}
public override long Position
{
get => base.Position - seekStart;
set => throw new NotSupportedException();
}
public PauseStream(Stream innerStream, double pauseAfter)
: base(innerStream)
{
this.pauseAfter = pauseAfter;
maxLength = (int)Math.Floor(innerStream.Length * pauseAfter) + 1;
totalRemaining = innerStream.Length;
}
public override long Seek(long offset, SeekOrigin origin)
{
var position = seekStart = base.Seek(offset, origin);
totalRemaining = base.Length - position;
return position;
}
public void Reset()
@ -783,9 +753,16 @@ public class AssetTests : IClassFixture<CreatedAppFixture>
public override async ValueTask<int> ReadAsync(Memory<byte> buffer,
CancellationToken cancellationToken = default)
{
if (totalRead > Length * pauseAfter)
var remaining = Length - totalRead;
if (remaining <= 0)
{
return 0;
}
if (remaining < buffer.Length)
{
throw new PauseException();
buffer = buffer[..(int)remaining];
}
var bytesRead = await base.ReadAsync(buffer, cancellationToken);

2
backend/tools/TestSuite/TestSuite.ApiTests/ContentFixture.cs

@ -9,7 +9,7 @@ using TestSuite.Fixtures;
namespace TestSuite.ApiTests;
public sealed class ContentFixture : TestSchemaFixtureBase
public class ContentFixture : TestSchemaFixtureBase
{
public ContentFixture()
: base("my-writes")

5
backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryFixture.cs

@ -38,7 +38,10 @@ public sealed class ContentQueryFixture : TestSchemaFixtureBase
nested2 = index
}
}),
Geo = GeoJson.Point(index, index, oldFormat: index % 2 == 1),
Geo = GeoJson.Point(
index + 100,
index,
oldFormat: index % 2 == 1),
Localized = new Dictionary<string, string>
{
["en"] = index.ToString(CultureInfo.InvariantCulture)

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

@ -361,7 +361,7 @@ public class ContentQueryTests : IClassFixture<ContentQueryFixture>
[Fact]
public async Task Should_query_by_near_location_with_odata()
{
var q = new ContentQuery { Filter = "geo.distance(data/geo/iv, geography'POINT(3 3)') lt 1000" };
var q = new ContentQuery { Filter = "geo.distance(data/geo/iv, geography'POINT(103 3)') lt 1000" };
var items = await _.Contents.WaitForContentAsync(q, x => true, TimeSpan.FromSeconds(30));
@ -381,7 +381,7 @@ public class ContentQueryTests : IClassFixture<ContentQueryFixture>
op = "lt",
value = new
{
longitude = 3,
longitude = 103,
latitude = 3,
distance = 1000
}
@ -397,7 +397,7 @@ public class ContentQueryTests : IClassFixture<ContentQueryFixture>
[Fact]
public async Task Should_query_by_near_geoson_location_with_odata()
{
var q = new ContentQuery { Filter = "geo.distance(data/geo/iv, geography'POINT(4 4)') lt 1000" };
var q = new ContentQuery { Filter = "geo.distance(data/geo/iv, geography'POINT(104 4)') lt 1000" };
var items = await _.Contents.WaitForContentAsync(q, x => true, TimeSpan.FromSeconds(30));
@ -466,7 +466,7 @@ public class ContentQueryTests : IClassFixture<ContentQueryFixture>
op = "lt",
value = new
{
longitude = 4,
longitude = 104,
latitude = 4,
distance = 1000
}

195
backend/tools/TestSuite/TestSuite.ApiTests/GraphQLFixture.cs

@ -0,0 +1,195 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Management;
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
namespace TestSuite.ApiTests;
public sealed class GraphQLFixture : ContentFixture
{
public sealed class DynamicEntity : Content<object>
{
}
public override async Task InitializeAsync()
{
await base.InitializeAsync();
await CreateSchemasAsync();
await CreateContentsAsync();
}
private async Task CreateSchemasAsync()
{
async Task<string> CreateSchemaAsync(CreateSchemaDto request)
{
try
{
var response = await Schemas.PostSchemaAsync(AppName, request);
return response.Id;
}
catch (SquidexManagementException ex)
{
if (ex.StatusCode != 400)
{
throw;
}
var schema = await Schemas.GetSchemaAsync(AppName, request.Name);
return schema.Id;
}
}
// STEP 1: Create cities schema.
var createCitiesRequest = new CreateSchemaDto
{
Name = "cities",
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "name",
Properties = new StringFieldPropertiesDto()
}
},
IsPublished = true
};
var citiesId = await CreateSchemaAsync(createCitiesRequest);
// STEP 2: Create states schema.
var createStatesRequest = new CreateSchemaDto
{
Name = "states",
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "name",
Properties = new StringFieldPropertiesDto()
},
new UpsertSchemaFieldDto
{
Name = "cities",
Properties = new ReferencesFieldPropertiesDto
{
SchemaIds = new List<string> { citiesId }
}
}
},
IsPublished = true
};
var statesId = await CreateSchemaAsync(createStatesRequest);
// STEP 3: Create countries schema.
var createCountriesRequest = new CreateSchemaDto
{
Name = "countries",
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "name",
Properties = new StringFieldPropertiesDto()
},
new UpsertSchemaFieldDto
{
Name = "states",
Properties = new ReferencesFieldPropertiesDto
{
SchemaIds = new List<string> { statesId }
}
}
},
IsPublished = true
};
await CreateSchemaAsync(createCountriesRequest);
}
private async Task CreateContentsAsync()
{
var countriesClient = ClientManager.CreateContentsClient<DynamicEntity, object>("countries");
var countriesResponse = await countriesClient.GetAsync();
if (countriesResponse.Total > 0)
{
return;
}
async Task<string> CreateCityAsync(string name)
{
var citySAData = new
{
name = new
{
iv = name
}
};
var citiesClient = ClientManager.CreateContentsClient<DynamicEntity, object>("cities");
var city = await citiesClient.CreateAsync(citySAData, ContentCreateOptions.AsPublish);
return city.Id;
}
async Task<string> CreateStateAsync(string name, string cityId)
{
var citySAData = new
{
name = new
{
iv = name
},
cities = new
{
iv = new[] { cityId }
}
};
var statesClient = ClientManager.CreateContentsClient<DynamicEntity, object>("states");
var state = await statesClient.CreateAsync(citySAData, ContentCreateOptions.AsPublish);
return state.Id;
}
// STEP 1: Create state 1
var sachsenCapital = await CreateCityAsync("Leipzig");
var sachstenState = await CreateStateAsync("Sachsen", sachsenCapital);
// STEP 1: Create state 2
var badenWCapital = await CreateCityAsync("Stuttgart");
var badenWState = await CreateStateAsync("Baden Württemberg", badenWCapital);
// STEP 3: Create country
var countryData = new
{
name = new
{
iv = "Germany"
},
states = new
{
iv = new[] { sachstenState, badenWState }
}
};
await countriesClient.CreateAsync(countryData, ContentCreateOptions.AsPublish);
}
}

277
backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs

@ -7,7 +7,6 @@
using Newtonsoft.Json.Linq;
using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Management;
using TestSuite.Model;
#pragma warning disable SA1300 // Element should begin with upper-case letter
@ -15,53 +14,15 @@ using TestSuite.Model;
namespace TestSuite.ApiTests;
public sealed class GraphQLTests : IClassFixture<ContentFixture>
public sealed class GraphQLTests : IClassFixture<GraphQLFixture>
{
public ContentFixture _ { get; }
public GraphQLFixture _ { get; }
public GraphQLTests(ContentFixture fixture)
public GraphQLTests(GraphQLFixture fixture)
{
_ = fixture;
}
public sealed class DynamicEntity : Content<object>
{
}
public sealed class Country
{
public CountryData Data { get; set; }
}
public sealed class CountryData
{
public string Name { get; set; }
public List<State> States { get; set; }
}
public sealed class State
{
public StateData Data { get; set; }
}
public sealed class StateData
{
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public sealed class City
{
public CityData Data { get; set; }
}
public sealed class CityData
{
public string Name { get; set; }
}
[Fact]
public async Task Should_query_json()
{
@ -92,38 +53,20 @@ public sealed class GraphQLTests : IClassFixture<ContentFixture>
}".Replace("<ID>", content_0.Id, StringComparison.Ordinal)
};
var result1 = await _.SharedContents.GraphQlAsync<JToken>(query);
var result = await _.SharedContents.GraphQlAsync<JToken>(query);
Assert.Equal(1, result1["findMyWritesContent"]["flatData"]["json"]["value"].Value<int>());
Assert.Equal(2, result1["findMyWritesContent"]["flatData"]["json"]["obj"]["value"].Value<int>());
Assert.Equal(1, result["findMyWritesContent"]["flatData"]["json"]["value"].Value<int>());
Assert.Equal(2, result["findMyWritesContent"]["flatData"]["json"]["obj"]["value"].Value<int>());
}
[Fact]
public async Task Should_create_and_query_with_graphql()
public async Task Should_query_graphql_reference_selectors()
{
try
{
await CreateSchemasAsync();
}
catch
{
// Do nothing
}
try
{
await CreateContentsAsync();
}
catch
{
// Do nothing
}
var query = new
{
query = @"
{
queryCountriesContents {
countries: queryCountriesContents {
data: flatData {
name,
states {
@ -141,134 +84,144 @@ public sealed class GraphQLTests : IClassFixture<ContentFixture>
}"
};
var result1 = await _.SharedContents.GraphQlAsync<JToken>(query);
var result = await _.SharedContents.GraphQlAsync<JToken>(query);
var typed = result1["queryCountriesContents"].ToObject<List<Country>>();
var cityNames =
result["countries"].ToObject<List<Country>>()[0].Data.States
.SelectMany(x => x.Data.Cities)
.Select(x => x.Data.Name)
.Order();
Assert.Equal("Leipzig", typed[0].Data.States[0].Data.Cities[0].Data.Name);
Assert.Equal(new[] { "Leipzig", "Stuttgart" }, cityNames);
}
private async Task CreateSchemasAsync()
[Fact]
public async Task Should_query_graphql_reference_operator()
{
// STEP 1: Create cities schema.
var createCitiesRequest = new CreateSchemaDto
var query = new
{
Name = "cities",
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
query = @"
{
Name = "name",
Properties = new StringFieldPropertiesDto()
}
},
IsPublished = true
countries: queryCountriesContents {
data: flatData {
name,
states {
data: flatData {
name
},
cities: referencesCitiesContents {
data: flatData {
name
}
}
}
}
}
}"
};
var cities = await _.Schemas.PostSchemaAsync(_.AppName, createCitiesRequest);
var result = await _.SharedContents.GraphQlAsync<JToken>(query);
var cityNames =
result["countries"]
.SelectMany(x => x["data"]["states"])
.SelectMany(x => x["cities"])
.Select(x => x["data"]["name"].Value<string>())
.Order();
Assert.Equal(new[] { "Leipzig", "Stuttgart" }, cityNames);
}
// STEP 2: Create states schema.
var createStatesRequest = new CreateSchemaDto
[Fact]
public async Task Should_query_graphql_reference_operator_with_filter()
{
var query = new
{
Name = "states",
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "name",
Properties = new StringFieldPropertiesDto()
},
new UpsertSchemaFieldDto
query = @"
{
Name = "cities",
Properties = new ReferencesFieldPropertiesDto
{
SchemaIds = new List<string> { cities.Id }
countries: queryCountriesContents {
data: flatData {
name,
states {
data: flatData {
name
},
cities: referencesCitiesContents(filter: ""data/name/iv eq 'Leipzig'"") {
data: flatData {
name
}
}
}
}
}
}
},
IsPublished = true
}"
};
var states = await _.Schemas.PostSchemaAsync(_.AppName, createStatesRequest);
var result = await _.SharedContents.GraphQlAsync<JToken>(query);
// STEP 3: Create countries schema.
var createCountriesRequest = new CreateSchemaDto
{
Name = "countries",
Fields = new List<UpsertSchemaFieldDto>
{
new UpsertSchemaFieldDto
{
Name = "name",
Properties = new StringFieldPropertiesDto()
},
new UpsertSchemaFieldDto
{
Name = "states",
Properties = new ReferencesFieldPropertiesDto
{
SchemaIds = new List<string> { states.Id }
}
}
},
IsPublished = true
};
var cityNames =
result["countries"]
.SelectMany(x => x["data"]["states"])
.SelectMany(x => x["cities"])
.Select(x => x["data"]["name"].Value<string>())
.Order();
await _.Schemas.PostSchemaAsync(_.AppName, createCountriesRequest);
Assert.Equal(new[] { "Leipzig" }, cityNames);
}
private async Task CreateContentsAsync()
[Fact]
public async Task Should_query_graphql_referencing_operator()
{
// STEP 1: Create city
var cityData = new
var query = new
{
name = new
{
iv = "Leipzig"
}
query = @"
{
cities: queryCitiesContents {
states: referencingStatesContents {
data: flatData {
name
}
}
}
}"
};
var citiesClient = _.ClientManager.CreateContentsClient<DynamicEntity, object>("cities");
var result = await _.SharedContents.GraphQlAsync<JToken>(query);
var city = await citiesClient.CreateAsync(cityData, ContentCreateOptions.AsPublish);
var stateNames =
result["cities"]
.SelectMany(x => x["states"])
.Select(x => x["data"]["name"].Value<string>())
.Order();
Assert.Equal(new[] { "Baden Württemberg", "Sachsen" }, stateNames);
}
// STEP 2: Create city
var stateData = new
[Fact]
public async Task Should_query_graphql_referencing_operator_with_filter()
{
var query = new
{
name = new
{
iv = "Saxony"
},
cities = new
{
iv = new[] { city.Id }
}
query = @"
{
cities: queryCitiesContents {
states: referencingStatesContents(filter: ""data/name/iv eq 'Sachsen'"") {
data: flatData {
name
}
}
}
}"
};
var statesClient = _.ClientManager.CreateContentsClient<DynamicEntity, object>("states");
var state = await statesClient.CreateAsync(stateData, ContentCreateOptions.AsPublish);
// STEP 3: Create country
var countryData = new
{
name = new
{
iv = "Germany"
},
states = new
{
iv = new[] { state.Id }
}
};
var result = await _.SharedContents.GraphQlAsync<JToken>(query);
var countriesClient = _.ClientManager.CreateContentsClient<DynamicEntity, object>("countries");
var stateNames =
result["cities"]
.SelectMany(x => x["states"])
.Select(x => x["data"]["name"].Value<string>())
.Order();
await countriesClient.CreateAsync(countryData, ContentCreateOptions.AsPublish);
Assert.Equal(new[] { "Sachsen" }, stateNames);
}
}

10
backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj

@ -15,14 +15,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Codeuctivity.ImageSharpCompare" Version="2.0.76" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.704">
<PackageReference Include="Meziantou.Analyzer" Version="1.0.750">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="NSwag.Core" Version="13.16.1" />
<PackageReference Include="PuppeteerSharp" Version="7.1.0" />
<PackageReference Include="Squidex.Assets" Version="4.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="NSwag.Core" Version="13.18.0" />
<PackageReference Include="PuppeteerSharp" Version="8.0.0" />
<PackageReference Include="Squidex.Assets" Version="5.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="Verify.Xunit" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />

4
backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj

@ -6,11 +6,11 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="1.0.704">
<PackageReference Include="Meziantou.Analyzer" Version="1.0.750">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<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.5">

44
backend/tools/TestSuite/TestSuite.Shared/Model/Geography.cs

@ -0,0 +1,44 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
#pragma warning disable MA0048 // File name must match type name
namespace TestSuite.Model;
public sealed class Country
{
public CountryData Data { get; set; }
}
public sealed class CountryData
{
public string Name { get; set; }
public List<State> States { get; set; }
}
public sealed class State
{
public StateData Data { get; set; }
}
public sealed class StateData
{
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public sealed class City
{
public CityData Data { get; set; }
}
public sealed class CityData
{
public string Name { get; set; }
}

16
backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj

@ -6,18 +6,18 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="1.0.704">
<PackageReference Include="Meziantou.Analyzer" Version="1.0.750">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.ClientLibrary" Version="12.3.0" />
<PackageReference Include="Squidex.ClientLibrary.ServiceExtensions" Version="12.3.0" />
<PackageReference Include="Squidex.ClientLibrary" Version="12.5.0" />
<PackageReference Include="Squidex.ClientLibrary.ServiceExtensions" Version="12.5.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="Verify" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />

Loading…
Cancel
Save