Browse Source

Fix surrogate keys.

pull/637/head
Sebastian 5 years ago
parent
commit
91c1532ef7
  1. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  2. 6
      backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs
  3. 160
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs
  4. 6
      backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs
  5. 73
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs
  6. 20
      backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  7. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs
  8. 174
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs
  9. 196
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs

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

@ -161,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
}
public async Task<IAssetEntity?> FindAssetAsync(DomainId appId, string hash, string fileName, long fileSize)
public async Task<IAssetEntity?> FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize)
{
using (Profiler.TraceMethod<MongoAssetRepository>())
{

6
backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs

@ -21,6 +21,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
Task<IEnrichedAssetEntity?> FindByHashAsync(Context context, string hash, string fileName, long fileSize);
Task<IEnrichedAssetEntity?> FindAsync(Context context, DomainId id);
Task<IEnrichedAssetEntity?> FindAsync(Context context, DomainId id, long version = EtagVersion.Any);
Task<IEnrichedAssetEntity?> FindBySlugAsync(Context context, string slug);
Task<IEnrichedAssetEntity?> FindGlobalAsync(Context context, DomainId id);
}
}

160
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs

@ -10,107 +10,195 @@ using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure;
using Squidex.Log;
namespace Squidex.Domain.Apps.Entities.Assets.Queries
{
public sealed class AssetQueryService : IAssetQueryService
{
private static readonly IResultList<IEnrichedAssetEntity> EmptyAssets = ResultList.CreateFrom<IEnrichedAssetEntity>(0);
private readonly IAssetEnricher assetEnricher;
private readonly IAssetRepository assetRepository;
private readonly IAssetLoader assetLoader;
private readonly IAssetFolderRepository assetFolderRepository;
private readonly AssetQueryParser queryParser;
public AssetQueryService(
IAssetEnricher assetEnricher,
IAssetRepository assetRepository,
IAssetLoader assetLoader,
IAssetFolderRepository assetFolderRepository,
AssetQueryParser queryParser)
{
Guard.NotNull(assetEnricher, nameof(assetEnricher));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(assetLoader, nameof(assetLoader));
Guard.NotNull(assetFolderRepository, nameof(assetFolderRepository));
Guard.NotNull(queryParser, nameof(queryParser));
this.assetEnricher = assetEnricher;
this.assetRepository = assetRepository;
this.assetLoader = assetLoader;
this.assetFolderRepository = assetFolderRepository;
this.queryParser = queryParser;
}
public async Task<IEnrichedAssetEntity?> FindByHashAsync(Context context, string hash, string fileName, long fileSize)
public async Task<IReadOnlyList<IAssetFolderEntity>> FindAssetFolderAsync(DomainId appId, DomainId id)
{
Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<AssetQueryService>())
{
var result = new List<IAssetFolderEntity>();
var asset = await assetRepository.FindAssetAsync(context.App.Id, hash, fileName, fileSize);
while (id != DomainId.Empty)
{
var folder = await assetFolderRepository.FindAssetFolderAsync(appId, id);
if (asset != null)
{
return await assetEnricher.EnrichAsync(asset, context);
if (folder == null || result.Any(x => x.Id == folder.Id))
{
result.Clear();
break;
}
result.Insert(0, folder);
id = folder.ParentId;
}
return result;
}
}
public async Task<IResultList<IAssetFolderEntity>> QueryAssetFoldersAsync(Context context, DomainId parentId)
{
using (Profiler.TraceMethod<AssetQueryService>())
{
var assetFolders = await assetFolderRepository.QueryAsync(context.App.Id, parentId);
return null;
return assetFolders;
}
}
public async Task<IEnrichedAssetEntity?> FindAsync(Context context, DomainId id)
public async Task<IEnrichedAssetEntity?> FindByHashAsync(Context context, string hash, string fileName, long fileSize)
{
Guard.NotNull(context, nameof(context));
var asset = await assetRepository.FindAssetAsync(context.App.Id, id);
if (asset != null)
using (Profiler.TraceMethod<AssetQueryService>())
{
return await assetEnricher.EnrichAsync(asset, context);
}
var asset = await assetRepository.FindAssetByHashAsync(context.App.Id, hash, fileName, fileSize);
if (asset == null)
{
return null;
}
return null;
return await TransformAsync(context, asset);
}
}
public async Task<IReadOnlyList<IAssetFolderEntity>> FindAssetFolderAsync(DomainId appId, DomainId id)
public async Task<IEnrichedAssetEntity?> FindBySlugAsync(Context context, string slug)
{
var result = new List<IAssetFolderEntity>();
Guard.NotNull(context, nameof(context));
while (id != DomainId.Empty)
using (Profiler.TraceMethod<AssetQueryService>())
{
var folder = await assetFolderRepository.FindAssetFolderAsync(appId, id);
var asset = await assetRepository.FindAssetBySlugAsync(context.App.Id, slug);
if (folder == null || result.Any(x => x.Id == folder.Id))
if (asset == null)
{
result.Clear();
break;
return null;
}
result.Insert(0, folder);
id = folder.ParentId;
return await TransformAsync(context, asset);
}
}
public async Task<IEnrichedAssetEntity?> FindGlobalAsync(Context context, DomainId id)
{
Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<AssetQueryService>())
{
var asset = await assetRepository.FindAssetAsync(id);
if (asset == null)
{
return null;
}
return result;
return await TransformAsync(context, asset);
}
}
public async Task<IResultList<IAssetFolderEntity>> QueryAssetFoldersAsync(Context context, DomainId parentId)
public async Task<IEnrichedAssetEntity?> FindAsync(Context context, DomainId id, long version = EtagVersion.Any)
{
var assetFolders = await assetFolderRepository.QueryAsync(context.App.Id, parentId);
Guard.NotNull(context, nameof(context));
return assetFolders;
using (Profiler.TraceMethod<AssetQueryService>())
{
IAssetEntity? asset;
if (version > EtagVersion.Empty)
{
asset = await assetLoader.GetAsync(context.App.Id, id, version);
}
else
{
asset = await assetRepository.FindAssetAsync(context.App.Id, id);
}
if (asset == null)
{
return null;
}
return await TransformAsync(context, asset);
}
}
public async Task<IResultList<IEnrichedAssetEntity>> QueryAsync(Context context, DomainId? parentId, Q q)
{
Guard.NotNull(context, nameof(context));
Guard.NotNull(q, nameof(q));
q = await queryParser.ParseQueryAsync(context, q);
var assets = await assetRepository.QueryAsync(context.App.Id, parentId, q);
if (q == null)
{
return EmptyAssets;
}
if (q.Ids != null && q.Ids.Count > 0)
using (Profiler.TraceMethod<AssetQueryService>())
{
assets = assets.SortSet(x => x.Id, q.Ids);
q = await queryParser.ParseQueryAsync(context, q);
var assets = await assetRepository.QueryAsync(context.App.Id, parentId, q);
if (q.Ids != null && q.Ids.Count > 0)
{
assets = assets.SortSet(x => x.Id, q.Ids);
}
return await TransformAsync(context, assets);
}
}
var enriched = await assetEnricher.EnrichAsync(assets, context);
private async Task<IResultList<IEnrichedAssetEntity>> TransformAsync(Context context, IResultList<IAssetEntity> assets)
{
var transformed = await TransformCoreAsync(context, assets);
return ResultList.Create(assets.Total, enriched);
return ResultList.Create(assets.Total, transformed);
}
private async Task<IEnrichedAssetEntity> TransformAsync(Context context, IAssetEntity asset)
{
var transformed = await TransformCoreAsync(context, Enumerable.Repeat(asset, 1));
return transformed[0];
}
private async Task<IReadOnlyList<IEnrichedAssetEntity>> TransformCoreAsync(Context context, IEnumerable<IAssetEntity> assets)
{
using (Profiler.TraceMethod<AssetQueryService>())
{
return await assetEnricher.EnrichAsync(assets, context);
}
}
}
}

6
backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs

@ -21,12 +21,12 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories
Task<IReadOnlyList<DomainId>> QueryChildIdsAsync(DomainId appId, DomainId parentId);
Task<IAssetEntity?> FindAssetAsync(DomainId appId, string hash, string fileName, long fileSize);
Task<IAssetEntity?> FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize);
Task<IAssetEntity?> FindAssetBySlugAsync(DomainId appId, string slug);
Task<IAssetEntity?> FindAssetAsync(DomainId appId);
Task<IAssetEntity?> FindAssetAsync(DomainId appId, DomainId id);
Task<IAssetEntity?> FindAssetBySlugAsync(DomainId appId, string slug);
}
}

73
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs

@ -25,52 +25,47 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
private readonly IAppProvider appProvider;
private readonly IContentEnricher contentEnricher;
private readonly IContentRepository contentRepository;
private readonly IContentLoader contentVersionLoader;
private readonly IContentLoader contentLoader;
private readonly ContentQueryParser queryParser;
public ContentQueryService(
IAppProvider appProvider,
IContentEnricher contentEnricher,
IContentRepository contentRepository,
IContentLoader contentVersionLoader,
IContentLoader assetLoader,
ContentQueryParser queryParser)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(contentVersionLoader, nameof(contentVersionLoader));
Guard.NotNull(assetLoader, nameof(assetLoader));
Guard.NotNull(queryParser, nameof(queryParser));
this.appProvider = appProvider;
this.contentEnricher = contentEnricher;
this.contentRepository = contentRepository;
this.contentVersionLoader = contentVersionLoader;
this.contentLoader = assetLoader;
this.queryParser = queryParser;
this.queryParser = queryParser;
}
public async Task<IEnrichedContentEntity?> FindAsync(Context context, string schemaIdOrName, DomainId id, long version = -1)
public async Task<IEnrichedContentEntity?> FindAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any)
{
Guard.NotNull(context, nameof(context));
if (id == default)
{
throw new DomainObjectNotFoundException(id.ToString());
}
var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName);
using (Profiler.TraceMethod<ContentQueryService>())
{
var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName);
IContentEntity? content;
if (version > EtagVersion.Empty)
{
content = await FindByVersionAsync(context, id, version);
content = await contentLoader.GetAsync(context.App.Id, id, version);
}
else
{
content = await FindCoreAsync(context, id, schema);
content = await contentRepository.FindContentAsync(context.App, schema, id, context.Scope());
}
if (content == null || content.SchemaId.Id != schema.Id)
@ -86,20 +81,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
Guard.NotNull(context, nameof(context));
if (q == null)
using (Profiler.TraceMethod<ContentQueryService>())
{
return EmptyContents;
}
if (q == null)
{
return EmptyContents;
}
var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName);
var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName);
if (!HasPermission(context, schema, Permissions.AppContentsRead))
{
q.CreatedBy = context.User.Token();
}
if (!HasPermission(context, schema, Permissions.AppContentsRead))
{
q.CreatedBy = context.User.Token();
}
using (Profiler.TraceMethod<ContentQueryService>())
{
q = await queryParser.ParseAsync(context, q, schema);
var contents = await contentRepository.QueryAsync(context.App, schema, q, context.Scope());
@ -117,20 +112,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
Guard.NotNull(context, nameof(context));
if (q == null)
using (Profiler.TraceMethod<ContentQueryService>())
{
return EmptyContents;
}
if (q == null)
{
return EmptyContents;
}
var schemas = await GetSchemasAsync(context);
var schemas = await GetSchemasAsync(context);
if (schemas.Count == 0)
{
return EmptyContents;
}
if (schemas.Count == 0)
{
return EmptyContents;
}
using (Profiler.TraceMethod<ContentQueryService>())
{
q = await queryParser.ParseAsync(context, q);
var contents = await contentRepository.QueryAsync(context.App, schemas, q, context.Scope());
@ -218,15 +213,5 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{
return context.Permissions.Allows(permissionId, context.App.Name, schema.SchemaDef.Name);
}
private Task<IContentEntity?> FindCoreAsync(Context context, DomainId id, ISchemaEntity schema)
{
return contentRepository.FindContentAsync(context.App, schema, id, context.Scope());
}
private Task<IContentEntity?> FindByVersionAsync(Context context, DomainId id, long version)
{
return contentVersionLoader.GetAsync(context.App.Id, id, version);
}
}
}

20
backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

@ -17,7 +17,6 @@ using Squidex.Areas.Api.Controllers.Assets.Models;
using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Log;
@ -34,23 +33,20 @@ namespace Squidex.Areas.Api.Controllers.Assets
public sealed class AssetContentController : ApiController
{
private readonly IAssetFileStore assetFileStore;
private readonly IAssetRepository assetRepository;
private readonly IAssetLoader assetLoader;
private readonly IAssetQueryService assetQuery;
private readonly IAssetStore assetStore;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
public AssetContentController(
ICommandBus commandBus,
IAssetFileStore assetFileStore,
IAssetRepository assetRepository,
IAssetLoader assetLoader,
IAssetQueryService assetQuery,
IAssetStore assetStore,
IAssetThumbnailGenerator assetThumbnailGenerator)
: base(commandBus)
{
this.assetFileStore = assetFileStore;
this.assetRepository = assetRepository;
this.assetLoader = assetLoader;
this.assetQuery = assetQuery;
this.assetStore = assetStore;
this.assetThumbnailGenerator = assetThumbnailGenerator;
}
@ -74,11 +70,11 @@ namespace Squidex.Areas.Api.Controllers.Assets
[AllowAnonymous]
public async Task<IActionResult> GetAssetContentBySlug(string app, string idOrSlug, [FromQuery] AssetContentQueryDto queries, string? more = null)
{
var asset = await assetRepository.FindAssetAsync(AppId, DomainId.Create(idOrSlug));
var asset = await assetQuery.FindAsync(Context, DomainId.Create(idOrSlug));
if (asset == null)
{
asset = await assetRepository.FindAssetBySlugAsync(AppId, idOrSlug);
asset = await assetQuery.FindBySlugAsync(Context, idOrSlug);
}
return await DeliverAssetAsync(asset, queries);
@ -102,7 +98,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
[Obsolete("Use overload with app name")]
public async Task<IActionResult> GetAssetContent(DomainId id, [FromQuery] AssetContentQueryDto queries)
{
var asset = await assetRepository.FindAssetAsync(id);
var asset = await assetQuery.FindGlobalAsync(Context, id);
return await DeliverAssetAsync(asset, queries);
}
@ -123,9 +119,9 @@ namespace Squidex.Areas.Api.Controllers.Assets
return StatusCode(403);
}
if (asset != null && queries.Version > EtagVersion.Any && asset.Version != queries.Version)
if (asset != null && queries.Version > EtagVersion.Any && asset.Version != queries.Version && Context.App != null)
{
asset = await assetLoader.GetAsync(asset.AppId.Id, asset.Id, queries.Version);
asset = await assetQuery.FindAsync(Context, asset.Id, queries.Version);
}
if (asset == null)

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
{
var random = _.RandomValue();
var assets = await _.AssetRepository.FindAssetAsync(_.RandomAppId(), random, random, 1024);
var assets = await _.AssetRepository.FindAssetByHashAsync(_.RandomAppId(), random, random, 1024);
Assert.NotNull(assets);
}

174
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs

@ -12,6 +12,7 @@ using FakeItEasy;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets.Queries
@ -20,6 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
{
private readonly IAssetEnricher assetEnricher = A.Fake<IAssetEnricher>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAssetLoader assetLoader = A.Fake<IAssetLoader>();
private readonly IAssetFolderRepository assetFolderRepository = A.Fake<IAssetFolderRepository>();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly Context requestContext;
@ -30,76 +32,167 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
{
requestContext = new Context(Mocks.FrontendUser(), Mocks.App(appId));
SetupEnricher();
A.CallTo(() => queryParser.ParseQueryAsync(requestContext, A<Q>._))
.ReturnsLazily(c => Task.FromResult(c.GetArgument<Q>(1)!));
sut = new AssetQueryService(assetEnricher, assetRepository, assetFolderRepository, queryParser);
sut = new AssetQueryService(assetEnricher, assetRepository, assetLoader, assetFolderRepository, queryParser);
}
[Fact]
public async Task Should_find_asset_by_slug_and_enrich_it()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetBySlugAsync(appId.Id, "slug"))
.Returns(asset);
var result = await sut.FindBySlugAsync(requestContext, "slug");
AssertAsset(asset, result);
}
[Fact]
public async Task Should_return_null_if_asset_by_slug_cannot_be_found()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetBySlugAsync(appId.Id, "slug"))
.Returns(Task.FromResult<IAssetEntity?>(null));
var result = await sut.FindBySlugAsync(requestContext, "slug");
Assert.Null(result);
}
[Fact]
public async Task Should_find_asset_by_id_and_enrich_it()
{
var found = new AssetEntity { Id = DomainId.NewGuid() };
var asset = CreateAsset(DomainId.NewGuid());
var enriched = new AssetEntity();
A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, asset.Id))
.Returns(asset);
A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, found.Id))
.Returns(found);
var result = await sut.FindAsync(requestContext, asset.Id);
A.CallTo(() => assetEnricher.EnrichAsync(found, requestContext))
.Returns(enriched);
AssertAsset(asset, result);
}
var result = await sut.FindAsync(requestContext, found.Id);
[Fact]
public async Task Should_return_null_if_asset_by_id_cannot_be_found()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, asset.Id))
.Returns(Task.FromResult<IAssetEntity?>(null));
Assert.Same(enriched, result);
var result = await sut.FindAsync(requestContext, asset.Id);
Assert.Null(result);
}
[Fact]
public async Task Should_find_assets_by_hash_and_and_enrich_it()
public async Task Should_find_asset_by_id_and_version_and_enrich_it()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetLoader.GetAsync(appId.Id, asset.Id, 2))
.Returns(asset);
var result = await sut.FindAsync(requestContext, asset.Id, 2);
AssertAsset(asset, result);
}
[Fact]
public async Task Should_return_null_if_asset_by_id_and_version_cannot_be_found()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetLoader.GetAsync(appId.Id, asset.Id, 2))
.Returns(Task.FromResult<IAssetEntity?>(null));
var result = await sut.FindAsync(requestContext, asset.Id, 2);
Assert.Null(result);
}
[Fact]
public async Task Should_find_global_asset_by_id_and_enrich_it()
{
var found = new AssetEntity { Id = DomainId.NewGuid() };
var asset = CreateAsset(DomainId.NewGuid());
var enriched = new AssetEntity();
A.CallTo(() => assetRepository.FindAssetAsync(asset.Id))
.Returns(asset);
A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, "hash", "name", 123))
.Returns(found);
var result = await sut.FindGlobalAsync(requestContext, asset.Id);
A.CallTo(() => assetEnricher.EnrichAsync(found, requestContext))
.Returns(enriched);
AssertAsset(asset, result);
}
[Fact]
public async Task Should_return_null_if_global_asset_by_id_cannot_be_found()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetAsync(asset.Id))
.Returns(Task.FromResult<IAssetEntity?>(null));
var result = await sut.FindGlobalAsync(requestContext, asset.Id);
Assert.Null(result);
}
[Fact]
public async Task Should_find_assets_by_hash_and_and_enrich_it()
{
var asset = CreateAsset(DomainId.NewGuid());
A.CallTo(() => assetRepository.FindAssetByHashAsync(appId.Id, "hash", "name", 123))
.Returns(asset);
var result = await sut.FindByHashAsync(requestContext, "hash", "name", 123);
Assert.Same(enriched, result);
AssertAsset(asset, result);
}
[Fact]
public async Task Should_load_assets_with_query_and_resolve_tags()
public async Task Should_return_null_if_asset_by_hash_cannot_be_found()
{
var found1 = new AssetEntity { Id = DomainId.NewGuid() };
var found2 = new AssetEntity { Id = DomainId.NewGuid() };
var asset = CreateAsset(DomainId.NewGuid());
var enriched1 = new AssetEntity();
var enriched2 = new AssetEntity();
A.CallTo(() => assetRepository.FindAssetByHashAsync(appId.Id, "hash", "name", 123))
.Returns(Task.FromResult<IAssetEntity?>(null));
var result = await sut.FindByHashAsync(requestContext, "hash", "name", 123);
Assert.Null(result);
}
[Fact]
public async Task Should_query_assets_and_enrich_it()
{
var asset1 = CreateAsset(DomainId.NewGuid());
var asset2 = CreateAsset(DomainId.NewGuid());
var parentId = DomainId.NewGuid();
var q = Q.Empty.WithODataQuery("fileName eq 'Name'");
A.CallTo(() => assetRepository.QueryAsync(appId.Id, parentId, q))
.Returns(ResultList.CreateFrom(8, found1, found2));
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2), requestContext))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
.Returns(ResultList.CreateFrom(8, asset1, asset2));
var result = await sut.QueryAsync(requestContext, parentId, q);
Assert.Equal(8, result.Total);
Assert.Equal(new[] { enriched1, enriched2 }, result.ToArray());
AssertAsset(asset1, result[0]);
AssertAsset(asset2, result[1]);
}
[Fact]
public async Task Should_load_assets_folders_from_repository()
public async Task Should_query_asset_folders()
{
var parentId = DomainId.NewGuid();
@ -114,7 +207,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
}
[Fact]
public async Task Should_resolve_folder_path_from_root()
public async Task Should_find_asset_folder_with_path()
{
var folderId1 = DomainId.NewGuid();
var folder1 = CreateFolder(folderId1);
@ -205,6 +298,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
Assert.Empty(result);
}
private static void AssertAsset(IAssetEntity source, IEnrichedAssetEntity? result)
{
Assert.NotNull(result);
Assert.NotSame(source, result);
Assert.Equal(source.AssetId, result?.AssetId);
}
private static IAssetFolderEntity CreateFolder(DomainId id, DomainId parentId = default)
{
var assetFolder = A.Fake<IAssetFolderEntity>();
@ -214,5 +314,21 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
return assetFolder;
}
private static AssetEntity CreateAsset(DomainId id)
{
return new AssetEntity { Id = id };
}
private void SetupEnricher()
{
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>._, A<Context>._))
.ReturnsLazily(x =>
{
var input = x.GetArgument<IEnumerable<IAssetEntity>>(0)!;
return Task.FromResult<IReadOnlyList<IEnrichedAssetEntity>>(input.Select(c => SimpleMapper.Map(c, new AssetEntity())).ToList());
});
}
}
}

196
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs

@ -31,11 +31,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IContentLoader contentVersionLoader = A.Fake<IContentLoader>();
private readonly ISchemaEntity schema;
private readonly DomainId contentId = DomainId.NewGuid();
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
private readonly ContentData contentData = new ContentData();
private readonly ContentData contentTransformed = new ContentData();
private readonly ContentQueryParser queryParser = A.Fake<ContentQueryParser>();
private readonly ContentQueryService sut;
@ -67,66 +65,70 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}
[Fact]
public async Task GetSchemaOrThrowAsync_should_return_schema_from_guid_string()
public async Task Should_get_schema_from_guid_string()
{
var input = schemaId.Id.ToString();
var ctx = CreateContext(isFrontend: false, allowSchema: true);
var requestContext = CreateContext();
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, true))
.Returns(schema);
var result = await sut.GetSchemaOrThrowAsync(ctx, input);
var result = await sut.GetSchemaOrThrowAsync(requestContext, input);
Assert.Equal(schema, result);
}
[Fact]
public async Task GetSchemaOrThrowAsync_should_return_schema_from_name()
public async Task Should_get_schema_from_name()
{
var input = schemaId.Name;
var ctx = CreateContext(isFrontend: false, allowSchema: true);
var requestContext = CreateContext();
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name, true))
.Returns(schema);
var result = await sut.GetSchemaOrThrowAsync(ctx, input);
var result = await sut.GetSchemaOrThrowAsync(requestContext, input);
Assert.Equal(schema, result);
}
[Fact]
public async Task GetSchemaOrThrowAsync_should_throw_404_if_not_found()
public async Task Should_throw_notfound_exception_if_schema_to_get_not_found()
{
var ctx = CreateContext(isFrontend: false, allowSchema: true);
var requestContext = CreateContext();
A.CallTo(() => appProvider.GetSchemaAsync(A<DomainId>._, A<string>._, true))
.Returns((ISchemaEntity?)null);
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaOrThrowAsync(ctx, schemaId.Name));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaOrThrowAsync(requestContext, schemaId.Name));
}
[Fact]
public async Task FindContentAsync_should_throw_exception_if_user_has_no_permission()
public async Task Should_throw_permission_exception_if_content_to_find_is_restricted()
{
var ctx = CreateContext(isFrontend: false, allowSchema: false);
var requestContext = CreateContext(allowSchema: false);
A.CallTo(() => contentRepository.FindContentAsync(ctx.App, schema, contentId, A<SearchScope>._))
.Returns(CreateContent(contentId));
var content = CreateContent(DomainId.NewGuid());
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindAsync(ctx, schemaId.Name, contentId));
A.CallTo(() => contentRepository.FindContentAsync(requestContext.App, schema, content.Id, A<SearchScope>._))
.Returns(CreateContent(DomainId.NewGuid()));
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindAsync(requestContext, schemaId.Name, content.Id));
}
[Fact]
public async Task FindContentAsync_should_return_null_if_not_found()
public async Task Should_return_null_if_content_by_id_dannot_be_found()
{
var ctx = CreateContext(isFrontend: false, allowSchema: true);
var requestContext = CreateContext();
var content = CreateContent(DomainId.NewGuid());
A.CallTo(() => contentRepository.FindContentAsync(ctx.App, schema, contentId, A<SearchScope>._))
A.CallTo(() => contentRepository.FindContentAsync(requestContext.App, schema, content.Id, A<SearchScope>._))
.Returns<IContentEntity?>(null);
Assert.Null(await sut.FindAsync(ctx, schemaId.Name, contentId));
Assert.Null(await sut.FindAsync(requestContext, schemaId.Name, content.Id));
}
[Theory]
@ -134,45 +136,41 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task FindContentAsync_should_return_content(int isFrontend, int unpublished, SearchScope scope)
public async Task Should_return_content_by_id(int isFrontend, int unpublished, SearchScope scope)
{
var ctx =
CreateContext(isFrontend: isFrontend == 1, allowSchema: true)
.WithUnpublished(unpublished == 1);
var requestContext = CreateContext(isFrontend, isUnpublished: unpublished);
var content = CreateContent(contentId);
var content = CreateContent(DomainId.NewGuid());
A.CallTo(() => contentRepository.FindContentAsync(ctx.App, schema, contentId, scope))
A.CallTo(() => contentRepository.FindContentAsync(requestContext.App, schema, content.Id, scope))
.Returns(content);
var result = await sut.FindAsync(ctx, schemaId.Name, contentId);
var result = await sut.FindAsync(requestContext, schemaId.Name, content.Id);
Assert.Equal(contentTransformed, result!.Data);
Assert.Equal(content.Id, result.Id);
AssertContent(content, result);
}
[Fact]
public async Task FindContentAsync_should_return_content_by_version()
public async Task Should_return_content_by_id_and_version()
{
var ctx = CreateContext(isFrontend: false, allowSchema: true);
var requestContext = CreateContext();
var content = CreateContent(contentId);
var content = CreateContent(DomainId.NewGuid());
A.CallTo(() => contentVersionLoader.GetAsync(appId.Id, contentId, 13))
A.CallTo(() => contentVersionLoader.GetAsync(appId.Id, content.Id, 13))
.Returns(content);
var result = await sut.FindAsync(ctx, schemaId.Name, contentId, 13);
var result = await sut.FindAsync(requestContext, schemaId.Name, content.Id, 13);
Assert.Equal(contentTransformed, result!.Data);
Assert.Equal(content.Id, result.Id);
AssertContent(content, result);
}
[Fact]
public async Task QueryAsync_should_throw_if_user_has_no_permission()
public async Task Should_throw_exception_if_user_has_no_permission_to_query_content()
{
var ctx = CreateContext(isFrontend: false, allowSchema: false);
var requestContext = CreateContext(allowSchema: false);
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.QueryAsync(ctx, schemaId.Name, Q.Empty));
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.QueryAsync(requestContext, schemaId.Name, Q.Empty));
}
[Theory]
@ -180,89 +178,95 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task QueryAsync_should_return_contents(int isFrontend, int unpublished, SearchScope scope)
public async Task Should_query_contents(int isFrontend, int unpublished, SearchScope scope)
{
var ctx =
CreateContext(isFrontend: isFrontend == 1, allowSchema: true)
.WithUnpublished(unpublished == 1);
var requestContext = CreateContext(isFrontend, isUnpublished: unpublished);
var content = CreateContent(contentId);
var content1 = CreateContent(DomainId.NewGuid());
var content2 = CreateContent(DomainId.NewGuid());
var q = Q.Empty.WithReference(DomainId.NewGuid());
A.CallTo(() => contentRepository.QueryAsync(ctx.App, schema, q, scope))
.Returns(ResultList.CreateFrom(5, content));
var result = await sut.QueryAsync(ctx, schemaId.Name, q);
A.CallTo(() => contentRepository.QueryAsync(requestContext.App, schema, q, scope))
.Returns(ResultList.CreateFrom(5, content1, content2));
Assert.Equal(contentData, result[0].Data);
Assert.Equal(contentId, result[0].Id);
var result = await sut.QueryAsync(requestContext, schemaId.Name, q);
Assert.Equal(5, result.Total);
AssertContent(content1, result[0]);
AssertContent(content2, result[1]);
}
[Fact]
public async Task QueryAll_should_not_return_contents_if_user_has_no_permission()
[Theory]
[InlineData(1, 0, SearchScope.All)]
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task Should_query_contents_by_ids(int isFrontend, int unpublished, SearchScope scope)
{
var ctx = CreateContext(isFrontend: false, allowSchema: false);
var requestContext = CreateContext(isFrontend, isUnpublished: unpublished);
var ids = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList();
var contents = ids.Select(CreateContent).ToList();
var q = Q.Empty.WithIds(ids);
A.CallTo(() => contentRepository.QueryAsync(ctx.App, A<List<ISchemaEntity>>.That.Matches(x => x.Count == 0), q, SearchScope.All))
.Returns(ResultList.Create(0, ids.Select(CreateContent)));
A.CallTo(() => contentRepository.QueryAsync(requestContext.App,
A<List<ISchemaEntity>>.That.Matches(x => x.Count == 1), q, scope))
.Returns(ResultList.Create(5, contents));
var result = await sut.QueryAsync(ctx, q);
var result = await sut.QueryAsync(requestContext, q);
Assert.Empty(result);
Assert.Equal(5, result.Total);
for (var i = 0; i < contents.Count; i++)
{
AssertContent(contents[i], result[i]);
}
}
[Fact]
public async Task QueryAll_should_only_query_only_users_contents_if_no_permission()
public async Task Should_query_contents_with_matching_permissions()
{
var ctx =
CreateContext(true, true, Permissions.AppContentsReadOwn);
var requestContext = CreateContext(allowSchema: false);
await sut.QueryAsync(ctx, schemaId.Name, Q.Empty);
var ids = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList();
A.CallTo(() => contentRepository.QueryAsync(ctx.App, schema, A<Q>.That.Matches(x => x.CreatedBy!.Equals(ctx.User.Token())), SearchScope.All))
.MustHaveHappened();
var q = Q.Empty.WithIds(ids);
A.CallTo(() => contentRepository.QueryAsync(requestContext.App,
A<List<ISchemaEntity>>.That.Matches(x => x.Count == 0), q, SearchScope.All))
.Returns(ResultList.Create(0, ids.Select(CreateContent)));
var result = await sut.QueryAsync(requestContext, q);
Assert.Empty(result);
}
[Fact]
public async Task QueryAll_should_query_all_contents_if_user_has_permission()
public async Task Should_query_contents_from_user_if_user_has_only_own_permission()
{
var ctx =
CreateContext(true, true, Permissions.AppContentsRead);
var requestContext = CreateContext(permissionId: Permissions.AppContentsReadOwn);
await sut.QueryAsync(ctx, schemaId.Name, Q.Empty);
await sut.QueryAsync(requestContext, schemaId.Name, Q.Empty);
A.CallTo(() => contentRepository.QueryAsync(ctx.App, schema, A<Q>.That.Matches(x => x.CreatedBy == null), SearchScope.All))
A.CallTo(() => contentRepository.QueryAsync(requestContext.App, schema,
A<Q>.That.Matches(x => Equals(x.CreatedBy, requestContext.User.Token())), SearchScope.Published))
.MustHaveHappened();
}
[Theory]
[InlineData(1, 0, SearchScope.All)]
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task QueryAll_should_return_contents(int isFrontend, int unpublished, SearchScope scope)
[Fact]
public async Task Should_query_all_contents_if_user_has_read_permission()
{
var ctx =
CreateContext(isFrontend: isFrontend == 1, allowSchema: true)
.WithUnpublished(unpublished == 1);
var requestContext = CreateContext(permissionId: Permissions.AppContentsRead);
var ids = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList();
var q = Q.Empty.WithIds(ids);
A.CallTo(() => contentRepository.QueryAsync(ctx.App, A<List<ISchemaEntity>>.That.Matches(x => x.Count == 1), q, scope))
.Returns(ResultList.Create(5, ids.Select(CreateContent)));
var result = await sut.QueryAsync(ctx, q);
await sut.QueryAsync(requestContext, schemaId.Name, Q.Empty);
Assert.Equal(ids, result.Select(x => x.Id).ToList());
A.CallTo(() => contentRepository.QueryAsync(requestContext.App, schema,
A<Q>.That.Matches(x => x.CreatedBy == null), SearchScope.Published))
.MustHaveHappened();
}
private void SetupEnricher()
@ -276,12 +280,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
});
}
private Context CreateContext(bool isFrontend, bool allowSchema, string permissionId = Permissions.AppContentsRead)
private Context CreateContext(
int isFrontend = 0,
int isUnpublished = 0,
bool allowSchema = true,
string permissionId = Permissions.AppContentsRead)
{
var claimsIdentity = new ClaimsIdentity();
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
if (isFrontend)
claimsIdentity.AddClaim(new Claim(OpenIdClaims.Subject, "user1"));
if (isFrontend == 1)
{
claimsIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, DefaultClients.Frontend));
}
@ -293,7 +303,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
claimsIdentity.AddClaim(new Claim(SquidexClaimTypes.Permissions, concretePermission));
}
return new Context(claimsPrincipal, Mocks.App(appId));
return new Context(claimsPrincipal, Mocks.App(appId)).WithUnpublished(isUnpublished == 1);
}
private static void AssertContent(IContentEntity source, IEnrichedContentEntity? result)
{
Assert.NotNull(result);
Assert.NotSame(source, result);
Assert.Same(source.Data, result?.Data);
Assert.Equal(source.Id, result?.Id);
}
private IContentEntity CreateContent(DomainId id)

Loading…
Cancel
Save