mirror of https://github.com/Squidex/squidex.git
Browse Source
* Bulk endpoint. * Fix in bulk middleware to always query for unpublished content. * Permission fixed.pull/519/head
committed by
GitHub
25 changed files with 877 additions and 271 deletions
@ -0,0 +1,174 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Threading.Tasks.Dataflow; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class BulkUpdateCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IServiceProvider serviceProvider; |
||||
|
private readonly IContentQueryService contentQuery; |
||||
|
private readonly IContextProvider contextProvider; |
||||
|
|
||||
|
public BulkUpdateCommandMiddleware(IServiceProvider serviceProvider, IContentQueryService contentQuery, IContextProvider contextProvider) |
||||
|
{ |
||||
|
Guard.NotNull(serviceProvider); |
||||
|
Guard.NotNull(contentQuery); |
||||
|
Guard.NotNull(contextProvider); |
||||
|
|
||||
|
this.serviceProvider = serviceProvider; |
||||
|
this.contentQuery = contentQuery; |
||||
|
this.contextProvider = contextProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, NextDelegate next) |
||||
|
{ |
||||
|
if (context.Command is BulkUpdateContents bulkUpdates) |
||||
|
{ |
||||
|
if (bulkUpdates.Jobs?.Count > 0) |
||||
|
{ |
||||
|
var requestContext = contextProvider.Context.WithoutContentEnrichment().WithUnpublished(true); |
||||
|
var requestedSchema = bulkUpdates.SchemaId.Name; |
||||
|
|
||||
|
var results = new BulkUpdateResultItem[bulkUpdates.Jobs.Count]; |
||||
|
|
||||
|
var actionBlock = new ActionBlock<int>(async index => |
||||
|
{ |
||||
|
var job = bulkUpdates.Jobs[index]; |
||||
|
|
||||
|
var result = new BulkUpdateResultItem(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var id = await FindIdAsync(requestContext, requestedSchema, job); |
||||
|
|
||||
|
result.ContentId = id; |
||||
|
|
||||
|
switch (job.Type) |
||||
|
{ |
||||
|
case BulkUpdateType.Upsert: |
||||
|
{ |
||||
|
if (id.HasValue) |
||||
|
{ |
||||
|
var command = SimpleMapper.Map(bulkUpdates, new UpdateContent { Data = job.Data, ContentId = id.Value }); |
||||
|
|
||||
|
await context.CommandBus.PublishAsync(command); |
||||
|
|
||||
|
results[index] = new BulkUpdateResultItem { ContentId = id }; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var command = SimpleMapper.Map(bulkUpdates, new CreateContent { Data = job.Data }); |
||||
|
|
||||
|
var content = serviceProvider.GetRequiredService<ContentDomainObject>(); |
||||
|
|
||||
|
content.Setup(command.ContentId); |
||||
|
|
||||
|
await content.ExecuteAsync(command); |
||||
|
|
||||
|
result.ContentId = command.ContentId; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case BulkUpdateType.ChangeStatus: |
||||
|
{ |
||||
|
if (id == null || id == default) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException("NOT DEFINED", typeof(IContentEntity)); |
||||
|
} |
||||
|
|
||||
|
var command = SimpleMapper.Map(bulkUpdates, new ChangeContentStatus { ContentId = id.Value }); |
||||
|
|
||||
|
if (job.Status != null) |
||||
|
{ |
||||
|
command.Status = job.Status.Value; |
||||
|
} |
||||
|
|
||||
|
await context.CommandBus.PublishAsync(command); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case BulkUpdateType.Delete: |
||||
|
{ |
||||
|
if (id == null || id == default) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException("NOT DEFINED", typeof(IContentEntity)); |
||||
|
} |
||||
|
|
||||
|
var command = SimpleMapper.Map(bulkUpdates, new DeleteContent { ContentId = id.Value }); |
||||
|
|
||||
|
await context.CommandBus.PublishAsync(command); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
result.Exception = ex; |
||||
|
} |
||||
|
|
||||
|
results[index] = result; |
||||
|
}, new ExecutionDataflowBlockOptions |
||||
|
{ |
||||
|
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2) |
||||
|
}); |
||||
|
|
||||
|
for (var i = 0; i < bulkUpdates.Jobs.Count; i++) |
||||
|
{ |
||||
|
await actionBlock.SendAsync(i); |
||||
|
} |
||||
|
|
||||
|
actionBlock.Complete(); |
||||
|
|
||||
|
await actionBlock.Completion; |
||||
|
|
||||
|
context.Complete(new BulkUpdateResult(results)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
context.Complete(new BulkUpdateResult()); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await next(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<Guid?> FindIdAsync(Context context, string schema, BulkUpdateJob job) |
||||
|
{ |
||||
|
var id = job.Id; |
||||
|
|
||||
|
if (id == null && job.Query != null) |
||||
|
{ |
||||
|
job.Query.Take = 1; |
||||
|
|
||||
|
var existing = await contentQuery.QueryAsync(context, schema, Q.Empty.WithJsonQuery(job.Query)); |
||||
|
|
||||
|
if (existing.Total > 1) |
||||
|
{ |
||||
|
throw new DomainException("More than one content matches to the query."); |
||||
|
} |
||||
|
|
||||
|
id = existing.FirstOrDefault()?.Id; |
||||
|
} |
||||
|
|
||||
|
return id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public sealed class BulkUpdateResult : List<BulkUpdateResultItem> |
||||
|
{ |
||||
|
public BulkUpdateResult() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public BulkUpdateResult(IEnumerable<BulkUpdateResultItem> source) |
||||
|
: base(source) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Squidex.Infrastructure.Queries; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Commands |
||||
|
{ |
||||
|
public sealed class BulkUpdateJob |
||||
|
{ |
||||
|
public Query<IJsonValue>? Query { get; set; } |
||||
|
|
||||
|
public Guid? Id { get; set; } |
||||
|
|
||||
|
public NamedContentData Data { get; set; } |
||||
|
|
||||
|
public Status? Status { get; set; } |
||||
|
|
||||
|
public BulkUpdateType Type { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,69 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
using Squidex.Infrastructure.Reflection; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents |
|
||||
{ |
|
||||
public sealed class ContentImporterCommandMiddleware : ICommandMiddleware |
|
||||
{ |
|
||||
private readonly IServiceProvider serviceProvider; |
|
||||
|
|
||||
public ContentImporterCommandMiddleware(IServiceProvider serviceProvider) |
|
||||
{ |
|
||||
Guard.NotNull(serviceProvider); |
|
||||
|
|
||||
this.serviceProvider = serviceProvider; |
|
||||
} |
|
||||
|
|
||||
public async Task HandleAsync(CommandContext context, NextDelegate next) |
|
||||
{ |
|
||||
if (context.Command is CreateContents createContents) |
|
||||
{ |
|
||||
var result = new ImportResult(); |
|
||||
|
|
||||
if (createContents.Datas != null && createContents.Datas.Count > 0) |
|
||||
{ |
|
||||
var command = SimpleMapper.Map(createContents, new CreateContent()); |
|
||||
|
|
||||
foreach (var data in createContents.Datas) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
command.ContentId = Guid.NewGuid(); |
|
||||
command.Data = data; |
|
||||
|
|
||||
var content = serviceProvider.GetRequiredService<ContentDomainObject>(); |
|
||||
|
|
||||
content.Setup(command.ContentId); |
|
||||
|
|
||||
await content.ExecuteAsync(command); |
|
||||
|
|
||||
result.Add(new ImportResultItem { ContentId = command.ContentId }); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
result.Add(new ImportResultItem { Exception = ex }); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
context.Complete(result); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
await next(context); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,48 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.Linq; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Contents.Models |
||||
|
{ |
||||
|
public sealed class BulkUpdateDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The contents to update or insert.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public List<BulkUpdateJobDto> Jobs { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// True to automatically publish the content.
|
||||
|
/// </summary>
|
||||
|
public bool Publish { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// True to turn off scripting for faster inserts. Default: true.
|
||||
|
/// </summary>
|
||||
|
public bool DoNotScript { get; set; } = true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// True to turn off costly validation: Unique checks, asset checks and reference checks. Default: true.
|
||||
|
/// </summary>
|
||||
|
public bool OptimizeValidation { get; set; } = true; |
||||
|
|
||||
|
public BulkUpdateContents ToCommand() |
||||
|
{ |
||||
|
var result = SimpleMapper.Map(this, new BulkUpdateContents()); |
||||
|
|
||||
|
result.Jobs = Jobs?.Select(x => x.ToJob())?.ToList(); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Squidex.Infrastructure.Queries; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Contents.Models |
||||
|
{ |
||||
|
public class BulkUpdateJobDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An optional query to identify the content to update.
|
||||
|
/// </summary>
|
||||
|
public Query<IJsonValue>? Query { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An optional id of the content to update.
|
||||
|
/// </summary>
|
||||
|
public Guid? Id { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The data of the content when type is set to 'Upsert'.
|
||||
|
/// </summary>
|
||||
|
public NamedContentData? Data { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The new status when the type is set to 'ChangeStatus'.
|
||||
|
/// </summary>
|
||||
|
public Status? Status { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The update type.
|
||||
|
/// </summary>
|
||||
|
public BulkUpdateType Type { get; set; } |
||||
|
|
||||
|
public BulkUpdateJob ToJob() |
||||
|
{ |
||||
|
return SimpleMapper.Map(this, new BulkUpdateJob()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,394 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Squidex.Infrastructure.Queries; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public class BulkUpdateCommandMiddlewareTests |
||||
|
{ |
||||
|
private readonly IServiceProvider serviceProvider = A.Fake<IServiceProvider>(); |
||||
|
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); |
||||
|
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>(); |
||||
|
private readonly ICommandBus commandBus = A.Dummy<ICommandBus>(); |
||||
|
private readonly Context requestContext = Context.Anonymous(); |
||||
|
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
||||
|
private readonly BulkUpdateCommandMiddleware sut; |
||||
|
|
||||
|
public BulkUpdateCommandMiddlewareTests() |
||||
|
{ |
||||
|
A.CallTo(() => contextProvider.Context) |
||||
|
.Returns(requestContext); |
||||
|
|
||||
|
sut = new BulkUpdateCommandMiddleware(serviceProvider, contentQuery, contextProvider); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_do_nothing_if_jobs_is_null() |
||||
|
{ |
||||
|
var command = new BulkUpdateContents(); |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
Assert.True(context.PlainResult is BulkUpdateResult); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(A<Type>._)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_do_nothing_if_jobs_is_empty() |
||||
|
{ |
||||
|
var command = new BulkUpdateContents { Jobs = new List<BulkUpdateJob>() }; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
Assert.True(context.PlainResult is BulkUpdateResult); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(A<Type>._)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_import_contents_when_no_query_defined() |
||||
|
{ |
||||
|
var (_, data, _) = CreateTestData(false); |
||||
|
|
||||
|
var domainObject = A.Fake<ContentDomainObject>(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
||||
|
.Returns(domainObject); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Upsert, |
||||
|
Data = data |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); |
||||
|
|
||||
|
A.CallTo(() => domainObject.ExecuteAsync(A<CreateContent>.That.Matches(x => x.Data == data))) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
|
||||
|
A.CallTo(() => domainObject.Setup(A<Guid>._)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_import_contents_when_query_returns_no_result() |
||||
|
{ |
||||
|
var (_, data, query) = CreateTestData(false); |
||||
|
|
||||
|
var domainObject = A.Fake<ContentDomainObject>(); |
||||
|
|
||||
|
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
||||
|
.Returns(domainObject); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Upsert, |
||||
|
Data = data, |
||||
|
Query = query |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); |
||||
|
|
||||
|
A.CallTo(() => domainObject.ExecuteAsync(A<CreateContent>.That.Matches(x => x.Data == data))) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
|
||||
|
A.CallTo(() => domainObject.Setup(A<Guid>._)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_update_content_when_id_defined() |
||||
|
{ |
||||
|
var (id, data, _) = CreateTestData(false); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Upsert, |
||||
|
Data = data, |
||||
|
Id = id |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<UpdateContent>.That.Matches(x => x.ContentId == id && x.Data == data))) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_update_content_when_query_defined() |
||||
|
{ |
||||
|
var (id, data, query) = CreateTestData(true); |
||||
|
|
||||
|
A.CallTo(() => contentQuery.QueryAsync(requestContext, A<string>._, A<Q>.That.Matches(x => x.ParsedJsonQuery == query))) |
||||
|
.Returns(ResultList.CreateFrom(1, CreateContent(id))); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Upsert, |
||||
|
Data = data, |
||||
|
Query = query |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<UpdateContent>.That.Matches(x => x.ContentId == id && x.Data == data))) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_exception_when_query_resolves_multiple_contents() |
||||
|
{ |
||||
|
var (id, data, query) = CreateTestData(true); |
||||
|
|
||||
|
A.CallTo(() => contentQuery.QueryAsync(requestContext, A<string>._, A<Q>.That.Matches(x => x.ParsedJsonQuery == query))) |
||||
|
.Returns(ResultList.CreateFrom(2, CreateContent(id), CreateContent(id))); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Upsert, |
||||
|
Data = data, |
||||
|
Query = query |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId == null && x.Exception is DomainException)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_change_content_status() |
||||
|
{ |
||||
|
var (id, _, _) = CreateTestData(false); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.ChangeStatus, |
||||
|
Id = id |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId == id)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<ChangeContentStatus>.That.Matches(x => x.ContentId == id))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_exception_when_content_id_to_change_cannot_be_resolved() |
||||
|
{ |
||||
|
var (_, _, query) = CreateTestData(true); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.ChangeStatus, |
||||
|
Query = query |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId == null && x.Exception is DomainObjectNotFoundException)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_delete_content() |
||||
|
{ |
||||
|
var (id, _, _) = CreateTestData(false); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Delete, |
||||
|
Id = id |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId == id)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<DeleteContent>.That.Matches(x => x.ContentId == id))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_throw_exception_when_content_id_to_delete_cannot_be_resolved() |
||||
|
{ |
||||
|
var (_, _, query) = CreateTestData(true); |
||||
|
|
||||
|
var command = new BulkUpdateContents |
||||
|
{ |
||||
|
Jobs = new List<BulkUpdateJob> |
||||
|
{ |
||||
|
new BulkUpdateJob |
||||
|
{ |
||||
|
Type = BulkUpdateType.Delete, |
||||
|
Query = query |
||||
|
} |
||||
|
}, |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
|
||||
|
var context = new CommandContext(command, commandBus); |
||||
|
|
||||
|
await sut.HandleAsync(context); |
||||
|
|
||||
|
var result = context.Result<BulkUpdateResult>(); |
||||
|
|
||||
|
Assert.Single(result); |
||||
|
Assert.Equal(1, result.Count(x => x.ContentId == null && x.Exception is DomainObjectNotFoundException)); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<ICommand>._)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private static (Guid Id, NamedContentData Data, Query<IJsonValue>? Query) CreateTestData(bool withQuery) |
||||
|
{ |
||||
|
Query<IJsonValue>? query = withQuery ? new Query<IJsonValue>() : null; |
||||
|
|
||||
|
var data = |
||||
|
new NamedContentData() |
||||
|
.AddField("value", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue("iv", JsonValue.Create(1))); |
||||
|
|
||||
|
return (Guid.NewGuid(), data, query); |
||||
|
} |
||||
|
|
||||
|
private static IEnrichedContentEntity CreateContent(Guid id) |
||||
|
{ |
||||
|
return new ContentEntity { Id = id }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,142 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// 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 FakeItEasy; |
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
using Squidex.Infrastructure.Json.Objects; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents |
|
||||
{ |
|
||||
public class ContentImporterCommandMiddlewareTests |
|
||||
{ |
|
||||
private readonly IServiceProvider serviceProvider = A.Fake<IServiceProvider>(); |
|
||||
private readonly ICommandBus commandBus = A.Dummy<ICommandBus>(); |
|
||||
private readonly ContentImporterCommandMiddleware sut; |
|
||||
|
|
||||
public ContentImporterCommandMiddlewareTests() |
|
||||
{ |
|
||||
sut = new ContentImporterCommandMiddleware(serviceProvider); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_do_nothing_if_datas_is_null() |
|
||||
{ |
|
||||
var command = new CreateContents(); |
|
||||
|
|
||||
var context = new CommandContext(command, commandBus); |
|
||||
|
|
||||
await sut.HandleAsync(context); |
|
||||
|
|
||||
Assert.True(context.PlainResult is ImportResult); |
|
||||
|
|
||||
A.CallTo(() => serviceProvider.GetService(A<Type>._)) |
|
||||
.MustNotHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_do_nothing_if_datas_is_empty() |
|
||||
{ |
|
||||
var command = new CreateContents { Datas = new List<NamedContentData>() }; |
|
||||
|
|
||||
var context = new CommandContext(command, commandBus); |
|
||||
|
|
||||
await sut.HandleAsync(context); |
|
||||
|
|
||||
Assert.True(context.PlainResult is ImportResult); |
|
||||
|
|
||||
A.CallTo(() => serviceProvider.GetService(A<Type>._)) |
|
||||
.MustNotHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_import_data() |
|
||||
{ |
|
||||
var data1 = CreateData(1); |
|
||||
var data2 = CreateData(2); |
|
||||
|
|
||||
var domainObject = A.Fake<ContentDomainObject>(); |
|
||||
|
|
||||
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
|
||||
.Returns(domainObject); |
|
||||
|
|
||||
var command = new CreateContents |
|
||||
{ |
|
||||
Datas = new List<NamedContentData> |
|
||||
{ |
|
||||
data1, |
|
||||
data2 |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
var context = new CommandContext(command, commandBus); |
|
||||
|
|
||||
await sut.HandleAsync(context); |
|
||||
|
|
||||
var result = context.Result<ImportResult>(); |
|
||||
|
|
||||
Assert.Equal(2, result.Count); |
|
||||
Assert.Equal(2, result.Count(x => x.ContentId.HasValue && x.Exception == null)); |
|
||||
|
|
||||
A.CallTo(() => domainObject.Setup(A<Guid>._)) |
|
||||
.MustHaveHappenedTwiceExactly(); |
|
||||
|
|
||||
A.CallTo(() => domainObject.ExecuteAsync(A<CreateContent>._)) |
|
||||
.MustHaveHappenedTwiceExactly(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_skip_exception() |
|
||||
{ |
|
||||
var data1 = CreateData(1); |
|
||||
var data2 = CreateData(2); |
|
||||
|
|
||||
var domainObject = A.Fake<ContentDomainObject>(); |
|
||||
|
|
||||
var exception = new InvalidOperationException(); |
|
||||
|
|
||||
A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) |
|
||||
.Returns(domainObject); |
|
||||
|
|
||||
A.CallTo(() => domainObject.ExecuteAsync(A<CreateContent>.That.Matches(x => x.Data == data1))) |
|
||||
.Throws(exception); |
|
||||
|
|
||||
var command = new CreateContents |
|
||||
{ |
|
||||
Datas = new List<NamedContentData> |
|
||||
{ |
|
||||
data1, |
|
||||
data2 |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
var context = new CommandContext(command, commandBus); |
|
||||
|
|
||||
await sut.HandleAsync(context); |
|
||||
|
|
||||
var result = context.Result<ImportResult>(); |
|
||||
|
|
||||
Assert.Equal(2, result.Count); |
|
||||
Assert.Equal(1, result.Count(x => x.ContentId.HasValue && x.Exception == null)); |
|
||||
Assert.Equal(1, result.Count(x => !x.ContentId.HasValue && x.Exception == exception)); |
|
||||
} |
|
||||
|
|
||||
private static NamedContentData CreateData(int value) |
|
||||
{ |
|
||||
return new NamedContentData() |
|
||||
.AddField("value", |
|
||||
new ContentFieldData() |
|
||||
.AddJsonValue("iv", JsonValue.Create(value))); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue