mirror of https://github.com/Squidex/squidex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
11 KiB
368 lines
11 KiB
// ==========================================================================
|
|
// Squidex Headless CMS
|
|
// ==========================================================================
|
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
// All rights reserved. Licensed under the MIT license.
|
|
// ==========================================================================
|
|
|
|
using System.Globalization;
|
|
using Squidex.Domain.Apps.Core.Assets;
|
|
using Squidex.Domain.Apps.Entities;
|
|
using Squidex.Domain.Apps.Entities.Assets.Repositories;
|
|
using Squidex.Domain.Apps.Entities.TestHelpers;
|
|
using Squidex.Infrastructure;
|
|
using Squidex.Infrastructure.Json.Objects;
|
|
using Squidex.Infrastructure.Queries;
|
|
using Squidex.Infrastructure.States;
|
|
|
|
#pragma warning disable xUnit1044 // Avoid using TheoryData type arguments that are not serializable
|
|
#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one
|
|
|
|
namespace Squidex.Shared;
|
|
|
|
public abstract class AssetRepositoryTests : GivenContext
|
|
{
|
|
private const int NumValues = 250;
|
|
private static readonly DomainId ParentId = DomainId.Create("3b5ba909-e5a5-4858-9d0d-df4ff922d451");
|
|
private static readonly NamedId<DomainId>[] AppIds =
|
|
[
|
|
NamedId.Of(DomainId.Create("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-app1"),
|
|
NamedId.Of(DomainId.Create("3b5ba909-e5a5-4858-9d0d-df4ff922d453"), "my-app2"),
|
|
];
|
|
|
|
private readonly string randomValue = Random.Shared.Next(NumValues).ToString(CultureInfo.InvariantCulture);
|
|
private readonly DomainId appId;
|
|
|
|
protected AssetRepositoryTests()
|
|
{
|
|
appId = AppIds[Random.Shared.Next(AppIds.Length)].Id;
|
|
}
|
|
|
|
protected abstract Task<IAssetRepository> CreateSutAsync();
|
|
|
|
protected async Task<IAssetRepository> CreateAndPrepareSutAsync()
|
|
{
|
|
var sut = await CreateSutAsync();
|
|
if (sut is not ISnapshotStore<Asset> store)
|
|
{
|
|
return sut;
|
|
}
|
|
|
|
if (await sut.StreamAll(AppIds[0].Id).AnyAsync())
|
|
{
|
|
return sut;
|
|
}
|
|
|
|
var batch = new List<SnapshotWriteJob<Asset>>();
|
|
|
|
async Task ExecuteBatchAsync(Asset? entity)
|
|
{
|
|
if (entity != null)
|
|
{
|
|
batch.Add(new SnapshotWriteJob<Asset>(entity.UniqueId, entity, 0));
|
|
}
|
|
|
|
if ((entity == null || batch.Count >= 1000) && batch.Count > 0)
|
|
{
|
|
await store.WriteManyAsync(batch);
|
|
batch.Clear();
|
|
}
|
|
}
|
|
|
|
foreach (var forAppId in AppIds)
|
|
{
|
|
for (var i = 0; i < NumValues; i++)
|
|
{
|
|
var fileName = i.ToString(CultureInfo.InvariantCulture);
|
|
|
|
for (var j = 0; j < NumValues; j++)
|
|
{
|
|
var asset = CreateAsset() with
|
|
{
|
|
AppId = forAppId,
|
|
FileHash = fileName,
|
|
FileName = fileName,
|
|
Metadata = new AssetMetadata
|
|
{
|
|
["value"] = JsonValue.Create($"value_{j}"),
|
|
},
|
|
Tags =
|
|
[
|
|
$"tag_{j}",
|
|
],
|
|
Slug = fileName,
|
|
};
|
|
|
|
await ExecuteBatchAsync(asset);
|
|
}
|
|
}
|
|
|
|
var byParent = CreateAsset() with
|
|
{
|
|
AppId = forAppId,
|
|
ParentId = ParentId,
|
|
FileHash = "0",
|
|
FileName = "0",
|
|
Metadata = new AssetMetadata
|
|
{
|
|
["value"] = JsonValue.Create("0"),
|
|
},
|
|
Tags =
|
|
[
|
|
"tag_0",
|
|
],
|
|
Slug = "0",
|
|
};
|
|
|
|
await ExecuteBatchAsync(byParent);
|
|
}
|
|
|
|
await ExecuteBatchAsync(null);
|
|
return sut;
|
|
}
|
|
|
|
public static readonly TheoryData<DomainId?> ParentIds = new TheoryData<DomainId?>
|
|
{
|
|
{ null as DomainId? },
|
|
{ DomainId.Empty },
|
|
};
|
|
|
|
[Fact]
|
|
public async Task Should_find_asset_by_id_only()
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var asset1 = await sut.StreamAll(appId).FirstAsync();
|
|
var asset2 = await sut.FindAssetAsync(asset1.Id);
|
|
|
|
// The Slug is random here, as it does not really matter.
|
|
Assert.NotNull(asset2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_find_asset_by_id()
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var asset1 = await sut.StreamAll(appId).FirstAsync();
|
|
var asset2 = await sut.FindAssetAsync(appId, asset1.Id, false);
|
|
|
|
// The ID is random here, as it does not really matter.
|
|
Assert.NotNull(asset2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_find_asset_by_hash()
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var asset = await sut.FindAssetByHashAsync(appId, randomValue, randomValue, 1024);
|
|
|
|
// The Hash is random here, as it does not really matter.
|
|
Assert.NotNull(asset);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_find_asset_by_slug()
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var asset = await sut.FindAssetBySlugAsync(appId, randomValue, false);
|
|
|
|
// The Slug is random here, as it does not really matter.
|
|
Assert.NotNull(asset);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_query_ids()
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var assetIds = await sut.StreamAll(appId).Take(100).Select(x => x.Id).ToHashSetAsync();
|
|
var assets = await sut.QueryIdsAsync(appId, assetIds);
|
|
|
|
// The IDs are valid.
|
|
Assert.Equal(assetIds.Count, assets.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Should_query_child_ids()
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var assets = await sut.QueryChildIdsAsync(appId, ParentId);
|
|
|
|
// No pagination is going on here.
|
|
Assert.Single(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery();
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// Default page size is 1000.
|
|
Assert.Equal(1000, assets.Count);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_with_random_count(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Random = 40,
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// Default page size is 1000, so we expect less elements.
|
|
Assert.Equal(40, assets.Count);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_tags(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Filter = ClrFilter.Eq("tags", $"tag_{randomValue}"),
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// The tag is random here, as it does not really matter.
|
|
Assert.NotNull(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_tags_in_query(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Filter = ClrFilter.In("tags", new List<string> { randomValue, "other" }),
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// The tag is random here, as it does not really matter.
|
|
Assert.NotNull(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_tags_and_fileName(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Filter =
|
|
ClrFilter.And(
|
|
ClrFilter.Eq("tags", $"tag_{randomValue}"),
|
|
ClrFilter.Contains("fileName", randomValue)),
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// The filter is a random value from the expected result set.
|
|
Assert.NotEmpty(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_fileName(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Filter = ClrFilter.Contains("fileName", randomValue),
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// The filter is a random value from the expected result set.
|
|
Assert.NotEmpty(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_metadata(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Filter = ClrFilter.Contains("metadata.value", $"value_{randomValue}"),
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// The filter is a random value from the expected result set.
|
|
Assert.NotEmpty(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_fileName_and_tags(DomainId? parentId)
|
|
{
|
|
var query = new ClrQuery
|
|
{
|
|
Filter =
|
|
ClrFilter.And(
|
|
ClrFilter.Contains("fileName", randomValue),
|
|
ClrFilter.Eq("tags", $"tag_{randomValue}")),
|
|
};
|
|
|
|
var assets = await QueryAsync(parentId, query);
|
|
|
|
// The filter is a random value from the expected result set.
|
|
Assert.NotEmpty(assets);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ParentIds))]
|
|
public async Task Should_query_assets_by_ids(DomainId? parentId)
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
var q =
|
|
Q.Empty
|
|
.WithIds(await sut.StreamAll(appId).Take(100).Select(x => x.Id).ToHashSetAsync());
|
|
|
|
var assets = await sut.QueryAsync(appId, parentId, q);
|
|
|
|
// The filter is a random value from the expected result set.
|
|
Assert.NotEmpty(assets);
|
|
}
|
|
|
|
private async Task<IResultList<Asset>> QueryAsync(DomainId? parentId,
|
|
ClrQuery clrQuery,
|
|
int top = 1000,
|
|
int skip = 0)
|
|
{
|
|
var sut = await CreateAndPrepareSutAsync();
|
|
|
|
clrQuery.Top = top;
|
|
clrQuery.Skip = skip;
|
|
clrQuery.Sort ??= [];
|
|
|
|
if (clrQuery.Sort.Count == 0)
|
|
{
|
|
clrQuery.Sort.Add(new SortNode("lastModified", SortOrder.Descending));
|
|
}
|
|
|
|
if (!clrQuery.Sort.Exists(x => x.Path.Equals("id")))
|
|
{
|
|
clrQuery.Sort.Add(new SortNode("id", SortOrder.Ascending));
|
|
}
|
|
|
|
var q =
|
|
Q.Empty
|
|
.WithoutTotal()
|
|
.WithQuery(clrQuery);
|
|
|
|
var assets = await sut.QueryAsync(appId, parentId, q);
|
|
|
|
return assets;
|
|
}
|
|
}
|
|
|