mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
25 changed files with 729 additions and 121 deletions
@ -0,0 +1,136 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.Assets; |
|||
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Assets; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb |
|||
{ |
|||
public sealed class AssetsQueryFixture |
|||
{ |
|||
private readonly Random random = new Random(); |
|||
private readonly int numValues = 250; |
|||
private readonly IMongoClient mongoClient = new MongoClient("mongodb://localhost"); |
|||
private readonly IMongoDatabase mongoDatabase; |
|||
|
|||
public IAssetRepository AssetRepository { get; } |
|||
|
|||
public NamedId<Guid>[] AppIds { get; } = new[] |
|||
{ |
|||
NamedId.Of(Guid.Parse("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-app1"), |
|||
NamedId.Of(Guid.Parse("4b3672c1-97c6-4e0b-a067-71e9e9a29db9"), "my-app1") |
|||
}; |
|||
|
|||
public AssetsQueryFixture() |
|||
{ |
|||
mongoDatabase = mongoClient.GetDatabase("QueryTests"); |
|||
|
|||
SetupJson(); |
|||
|
|||
var assetRepository = new MongoAssetRepository(mongoDatabase); |
|||
|
|||
Task.Run(async () => |
|||
{ |
|||
await assetRepository.InitializeAsync(); |
|||
|
|||
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 0 }"); |
|||
await mongoDatabase.DropCollectionAsync("system.profile"); |
|||
|
|||
var collection = assetRepository.GetInternalCollection(); |
|||
|
|||
var assetCount = await collection.Find(new BsonDocument()).CountDocumentsAsync(); |
|||
|
|||
if (assetCount == 0) |
|||
{ |
|||
var batch = new List<MongoAssetEntity>(); |
|||
|
|||
async Task ExecuteBatchAsync(MongoAssetEntity? entity) |
|||
{ |
|||
if (entity != null) |
|||
{ |
|||
batch.Add(entity); |
|||
} |
|||
|
|||
if ((entity == null || batch.Count >= 1000) && batch.Count > 0) |
|||
{ |
|||
await collection.InsertManyAsync(batch); |
|||
|
|||
batch.Clear(); |
|||
} |
|||
} |
|||
|
|||
foreach (var appId in AppIds) |
|||
{ |
|||
for (var i = 0; i < numValues; i++) |
|||
{ |
|||
var fileName = i.ToString(); |
|||
|
|||
for (var j = 0; j < numValues; j++) |
|||
{ |
|||
var tag = j.ToString(); |
|||
|
|||
var asset = new MongoAssetEntity |
|||
{ |
|||
Id = Guid.NewGuid(), |
|||
AppId = appId, |
|||
Tags = new HashSet<string> { tag }, |
|||
FileHash = fileName, |
|||
FileName = fileName, |
|||
FileSize = 1024, |
|||
IndexedAppId = appId.Id, |
|||
IsDeleted = false, |
|||
IsProtected = false, |
|||
Metadata = new AssetMetadata |
|||
{ |
|||
["value"] = JsonValue.Create(tag) |
|||
}, |
|||
Slug = fileName, |
|||
}; |
|||
|
|||
await ExecuteBatchAsync(asset); |
|||
} |
|||
} |
|||
} |
|||
|
|||
await ExecuteBatchAsync(null); |
|||
} |
|||
|
|||
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 2 }"); |
|||
}).Wait(); |
|||
|
|||
AssetRepository = assetRepository; |
|||
} |
|||
|
|||
private static void SetupJson() |
|||
{ |
|||
var jsonSerializer = JsonSerializer.Create(JsonHelper.DefaultSettings()); |
|||
|
|||
BsonJsonConvention.Register(jsonSerializer); |
|||
} |
|||
|
|||
public Guid RandomAppId() |
|||
{ |
|||
return AppIds[random.Next(0, AppIds.Length)].Id; |
|||
} |
|||
|
|||
public string RandomValue() |
|||
{ |
|||
return random.Next(0, numValues).ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
// ==========================================================================
|
|||
// 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.Infrastructure; |
|||
using Squidex.Infrastructure.Queries; |
|||
using Xunit; |
|||
using F = Squidex.Infrastructure.Queries.ClrFilter; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb |
|||
{ |
|||
[Trait("Category", "Dependencies")] |
|||
public class AssetsQueryTests : IClassFixture<AssetsQueryFixture> |
|||
{ |
|||
public AssetsQueryFixture _ { get; } |
|||
|
|||
public AssetsQueryTests(AssetsQueryFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_find_asset_by_slug() |
|||
{ |
|||
var asset = await _.AssetRepository.FindAssetBySlugAsync(_.RandomAppId(), _.RandomValue()); |
|||
|
|||
Assert.NotNull(asset); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_asset_by_hash() |
|||
{ |
|||
var assets = await _.AssetRepository.QueryByHashAsync(_.RandomAppId(), _.RandomValue()); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_verify_ids() |
|||
{ |
|||
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet(); |
|||
|
|||
var assets = await _.AssetRepository.QueryIdsAsync(_.RandomAppId(), ids); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ParentIds))] |
|||
public async Task Should_query_assets_by_default(Guid? parentId) |
|||
{ |
|||
var query = new ClrQuery(); |
|||
|
|||
var assets = await QueryAsync(parentId, query); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ParentIds))] |
|||
public async Task Should_query_assets_by_tags(Guid? parentId) |
|||
{ |
|||
var query = new ClrQuery |
|||
{ |
|||
Filter = F.Eq("Tags", _.RandomValue()) |
|||
}; |
|||
|
|||
var assets = await QueryAsync(parentId, query); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ParentIds))] |
|||
public async Task Should_query_assets_by_tags_and_name(Guid? parentId) |
|||
{ |
|||
var query = new ClrQuery |
|||
{ |
|||
Filter = F.And(F.Eq("Tags", _.RandomValue()), F.Contains("FileName", _.RandomValue())) |
|||
}; |
|||
|
|||
var assets = await QueryAsync(parentId, query); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ParentIds))] |
|||
public async Task Should_query_assets_by_fileName(Guid? parentId) |
|||
{ |
|||
var query = new ClrQuery |
|||
{ |
|||
Filter = F.Contains("FileName", _.RandomValue()) |
|||
}; |
|||
|
|||
var assets = await QueryAsync(parentId, query); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(ParentIds))] |
|||
public async Task Should_query_assets_by_fileName_and_tags(Guid? parentId) |
|||
{ |
|||
var query = new ClrQuery |
|||
{ |
|||
Filter = F.And(F.Contains("FileName", _.RandomValue()), F.Eq("Tags", _.RandomValue())) |
|||
}; |
|||
|
|||
var assets = await QueryAsync(parentId, query); |
|||
|
|||
Assert.NotNull(assets); |
|||
} |
|||
|
|||
public static IEnumerable<object?[]> ParentIds() |
|||
{ |
|||
yield return new object?[] { null }; |
|||
yield return new object?[] { Guid.Empty }; |
|||
} |
|||
|
|||
private async Task<IResultList<IAssetEntity>> QueryAsync(Guid? parentId, ClrQuery query) |
|||
{ |
|||
query.Top = 1000; |
|||
|
|||
query.Skip = 100; |
|||
|
|||
query.Sort = new List<SortNode> |
|||
{ |
|||
new SortNode("LastModified", SortOrder.Descending) |
|||
}; |
|||
|
|||
var assets = await _.AssetRepository.QueryAsync(_.RandomAppId(), parentId, query); |
|||
|
|||
return assets; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,211 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Contents.Text; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Contents; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb |
|||
{ |
|||
public sealed class ContentsQueryFixture |
|||
{ |
|||
private readonly Random random = new Random(); |
|||
private readonly int numValues = 10000; |
|||
private readonly IMongoClient mongoClient = new MongoClient("mongodb://localhost"); |
|||
private readonly IMongoDatabase mongoDatabase; |
|||
|
|||
public IContentRepository ContentRepository { get; } |
|||
|
|||
public NamedId<Guid>[] AppIds { get; } = new[] |
|||
{ |
|||
NamedId.Of(Guid.Parse("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-app1"), |
|||
NamedId.Of(Guid.Parse("4b3672c1-97c6-4e0b-a067-71e9e9a29db9"), "my-app1") |
|||
}; |
|||
|
|||
public NamedId<Guid>[] SchemaIds { get; } = new[] |
|||
{ |
|||
NamedId.Of(Guid.Parse("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-schema1"), |
|||
NamedId.Of(Guid.Parse("4b3672c1-97c6-4e0b-a067-71e9e9a29db9"), "my-schema2"), |
|||
NamedId.Of(Guid.Parse("76357c9b-0514-4377-9fcc-a632e7ef960d"), "my-schema3"), |
|||
NamedId.Of(Guid.Parse("164c451e-e5a8-41f8-8aaf-e4b56603d7e7"), "my-schema4"), |
|||
NamedId.Of(Guid.Parse("741e902c-fdfa-41ad-8e5a-b7cb9d6e3d94"), "my-schema5") |
|||
}; |
|||
|
|||
public ContentsQueryFixture() |
|||
{ |
|||
mongoDatabase = mongoClient.GetDatabase("QueryTests"); |
|||
|
|||
SetupJson(); |
|||
|
|||
var contentRepository = |
|||
new MongoContentRepository( |
|||
mongoDatabase, |
|||
CreateAppProvider(), |
|||
CreateTextIndexer(), |
|||
JsonHelper.DefaultSerializer); |
|||
|
|||
Task.Run(async () => |
|||
{ |
|||
await contentRepository.InitializeAsync(); |
|||
|
|||
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 0 }"); |
|||
await mongoDatabase.DropCollectionAsync("system.profile"); |
|||
|
|||
var collection = contentRepository.GetInternalCollection(); |
|||
|
|||
var contentCount = await collection.Find(new BsonDocument()).CountDocumentsAsync(); |
|||
|
|||
if (contentCount == 0) |
|||
{ |
|||
var batch = new List<MongoContentEntity>(); |
|||
|
|||
async Task ExecuteBatchAsync(MongoContentEntity? entity) |
|||
{ |
|||
if (entity != null) |
|||
{ |
|||
batch.Add(entity); |
|||
} |
|||
|
|||
if ((entity == null || batch.Count >= 1000) && batch.Count > 0) |
|||
{ |
|||
await collection.InsertManyAsync(batch); |
|||
|
|||
batch.Clear(); |
|||
} |
|||
} |
|||
|
|||
foreach (var appId in AppIds) |
|||
{ |
|||
foreach (var schemaId in SchemaIds) |
|||
{ |
|||
for (var i = 0; i < numValues; i++) |
|||
{ |
|||
var value = i.ToString(); |
|||
|
|||
var data = |
|||
new IdContentData() |
|||
.AddField(1, |
|||
new ContentFieldData() |
|||
.AddJsonValue(JsonValue.Create(value))); |
|||
|
|||
var content = new MongoContentEntity |
|||
{ |
|||
Id = Guid.NewGuid(), |
|||
AppId = appId, |
|||
DataByIds = data, |
|||
DataDraftByIds = data, |
|||
IndexedAppId = appId.Id, |
|||
IndexedSchemaId = schemaId.Id, |
|||
IsDeleted = false, |
|||
IsPending = false, |
|||
SchemaId = schemaId, |
|||
Status = Status.Published |
|||
}; |
|||
|
|||
await ExecuteBatchAsync(content); |
|||
} |
|||
} |
|||
} |
|||
|
|||
await ExecuteBatchAsync(null); |
|||
} |
|||
|
|||
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 2 }"); |
|||
}).Wait(); |
|||
|
|||
ContentRepository = contentRepository; |
|||
} |
|||
|
|||
private static IAppProvider CreateAppProvider() |
|||
{ |
|||
var appProvider = A.Fake<IAppProvider>(); |
|||
|
|||
A.CallTo(() => appProvider.GetSchemaAsync(A<Guid>.Ignored, A<Guid>.Ignored, false)) |
|||
.ReturnsLazily(x => Task.FromResult<ISchemaEntity?>(CreateSchema(x.GetArgument<Guid>(0)!, x.GetArgument<Guid>(1)!))); |
|||
|
|||
return appProvider; |
|||
} |
|||
|
|||
private static ITextIndexer CreateTextIndexer() |
|||
{ |
|||
var textIndexer = A.Fake<ITextIndexer>(); |
|||
|
|||
A.CallTo(() => textIndexer.SearchAsync(A<string>.Ignored, A<IAppEntity>.Ignored, A<Guid>.Ignored, A<Scope>.Ignored)) |
|||
.Returns(new List<Guid> { Guid.NewGuid() }); |
|||
|
|||
return textIndexer; |
|||
} |
|||
|
|||
private static void SetupJson() |
|||
{ |
|||
var jsonSerializer = JsonSerializer.Create(JsonHelper.DefaultSettings()); |
|||
|
|||
BsonJsonConvention.Register(jsonSerializer); |
|||
} |
|||
|
|||
public Guid RandomAppId() |
|||
{ |
|||
return AppIds[random.Next(0, AppIds.Length)].Id; |
|||
} |
|||
|
|||
public IAppEntity RandomApp() |
|||
{ |
|||
return CreateApp(RandomAppId()); |
|||
} |
|||
|
|||
public Guid RandomSchemaId() |
|||
{ |
|||
return SchemaIds[random.Next(0, SchemaIds.Length)].Id; |
|||
} |
|||
|
|||
public ISchemaEntity RandomSchema() |
|||
{ |
|||
return CreateSchema(RandomAppId(), RandomSchemaId()); |
|||
} |
|||
|
|||
public string RandomValue() |
|||
{ |
|||
return random.Next(0, numValues).ToString(); |
|||
} |
|||
|
|||
private static IAppEntity CreateApp(Guid appId) |
|||
{ |
|||
return Mocks.App(NamedId.Of(appId, "my-app")); |
|||
} |
|||
|
|||
private static ISchemaEntity CreateSchema(Guid appId, Guid schemaId) |
|||
{ |
|||
var schemaDef = |
|||
new Schema("my-schema") |
|||
.AddField(Fields.String(1, "value", Partitioning.Invariant)); |
|||
|
|||
var schema = |
|||
Mocks.Schema( |
|||
NamedId.Of(appId, "my-app"), |
|||
NamedId.Of(schemaId, "my-schema"), |
|||
schemaDef); |
|||
|
|||
return schema; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
// ==========================================================================
|
|||
// 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 NodaTime; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Queries; |
|||
using Squidex.Infrastructure.Tasks; |
|||
using Xunit; |
|||
using F = Squidex.Infrastructure.Queries.ClrFilter; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb |
|||
{ |
|||
[Trait("Category", "Dependencies")] |
|||
public class ContentsQueryTests : IClassFixture<ContentsQueryFixture> |
|||
{ |
|||
public ContentsQueryFixture _ { get; } |
|||
|
|||
public ContentsQueryTests(ContentsQueryFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_verify_ids() |
|||
{ |
|||
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet(); |
|||
|
|||
var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), ids); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_contents_by_ids() |
|||
{ |
|||
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet(); |
|||
|
|||
var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), new[] { Status.Published }, ids, true); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_contents_by_ids_and_schema() |
|||
{ |
|||
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet(); |
|||
|
|||
var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), _.RandomSchema(), new[] { Status.Published }, ids, true); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_contents_by_filter() |
|||
{ |
|||
var filter = F.Eq("data.value.iv", _.RandomValue()); |
|||
|
|||
var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), _.RandomSchemaId(), filter); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_contents_scheduled() |
|||
{ |
|||
var time = SystemClock.Instance.GetCurrentInstant(); |
|||
|
|||
await _.ContentRepository.QueryScheduledWithoutDataAsync(time, _ => TaskHelper.Done); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Statuses))] |
|||
public async Task Should_query_contents_by_default(Status[]? status) |
|||
{ |
|||
var query = new ClrQuery(); |
|||
|
|||
var contents = await QueryAsync(status, query); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Statuses))] |
|||
public async Task Should_query_contents_with_query_fulltext(Status[]? status) |
|||
{ |
|||
var query = new ClrQuery |
|||
{ |
|||
FullText = "hello" |
|||
}; |
|||
|
|||
var contents = await QueryAsync(status, query); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Statuses))] |
|||
public async Task Should_query_contents_with_query_filter(Status[]? status) |
|||
{ |
|||
var query = new ClrQuery |
|||
{ |
|||
Filter = F.Eq("data.value.iv", _.RandomValue()) |
|||
}; |
|||
|
|||
var contents = await QueryAsync(status, query); |
|||
|
|||
Assert.NotNull(contents); |
|||
} |
|||
|
|||
public static IEnumerable<object?[]> Statuses() |
|||
{ |
|||
yield return new object?[] { null }; |
|||
yield return new object?[] { new[] { Status.Published } }; |
|||
} |
|||
|
|||
private async Task<IResultList<IContentEntity>> QueryAsync(Status[]? status, ClrQuery query) |
|||
{ |
|||
query.Top = 1000; |
|||
|
|||
query.Skip = 100; |
|||
|
|||
query.Sort = new List<SortNode> |
|||
{ |
|||
new SortNode("LastModified", SortOrder.Descending) |
|||
}; |
|||
|
|||
var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), _.RandomSchema(), status, true, query, true); |
|||
|
|||
return contents; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue