diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs index 40dd52f76..1ae5c25fa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs @@ -20,17 +20,14 @@ 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) + public BulkUpdateCommandMiddleware(IContentQueryService contentQuery, IContextProvider contextProvider) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); Guard.NotNull(contentQuery, nameof(contentQuery)); Guard.NotNull(contextProvider, nameof(contextProvider)); - this.serviceProvider = serviceProvider; this.contentQuery = contentQuery; this.contextProvider = contextProvider; } @@ -62,23 +59,16 @@ namespace Squidex.Domain.Apps.Entities.Contents { case BulkUpdateType.Upsert: { - if (id.HasValue) - { - var command = SimpleMapper.Map(bulkUpdates, new UpdateContent { Data = job.Data, ContentId = id.Value }); - - await context.CommandBus.PublishAsync(command); + var command = SimpleMapper.Map(bulkUpdates, new UpsertContent { Data = job.Data }); - results[index] = new BulkUpdateResultItem { ContentId = id }; - } - else + if (id != null && id != DomainId.Empty) { - var command = SimpleMapper.Map(bulkUpdates, new CreateContent { Data = job.Data }); - - await InsertAsync(command); - - result.ContentId = command.ContentId; + command.ContentId = id.Value; } + result.ContentId = command.ContentId; + + await context.CommandBus.PublishAsync(command); break; } @@ -147,15 +137,6 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private async Task InsertAsync(CreateContent command) - { - var content = serviceProvider.GetRequiredService(); - - content.Setup(command.ContentId); - - await content.ExecuteAsync(command); - } - private async Task FindIdAsync(Context context, string schema, BulkUpdateJob job) { var id = job.Id; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs new file mode 100644 index 000000000..f40ff04b0 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpsertContent.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents.Commands +{ + public sealed class UpsertContent : ContentDataCommand, ISchemaCommand + { + public bool Publish { get; set; } + + public UpsertContent() + { + ContentId = DomainId.NewGuid(); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index a9a93c765..ea8ddb950 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents protected override bool CanAcceptCreation(ICommand command) { - return command is ContentCommand; + return command is CreateContent; } protected override bool CanAccept(ICommand command) @@ -64,6 +64,22 @@ namespace Squidex.Domain.Apps.Entities.Contents switch (command) { + case UpsertContent uspertContent: + { + if (Version > EtagVersion.Empty) + { + var updateContent = SimpleMapper.Map(uspertContent, new UpdateContent()); + + return ExecuteAsync(updateContent); + } + else + { + var createContent = SimpleMapper.Map(uspertContent, new CreateContent()); + + return ExecuteAsync(createContent); + } + } + case CreateContent createContent: return CreateReturnAsync(createContent, async c => { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs index bd13a7e1a..fd1b0c19e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -39,18 +39,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = $"update{schemaType}Content", - Arguments = ContentActions.UpdateOrPatch.Arguments(inputType), + Arguments = ContentActions.Update.Arguments(inputType), ResolvedType = contentType, - Resolver = ContentActions.UpdateOrPatch.Update(appId, schemaId), + Resolver = ContentActions.Update.Resolver(appId, schemaId), Description = $"Update an {schemaName} content by id." }); + AddField(new FieldType + { + Name = $"upsert{schemaType}Content", + Arguments = ContentActions.Upsert.Arguments(inputType), + ResolvedType = contentType, + Resolver = ContentActions.Upsert.Resolver(appId, schemaId), + Description = $"Upsert an {schemaName} content by id." + }); + AddField(new FieldType { Name = $"patch{schemaType}Content", - Arguments = ContentActions.UpdateOrPatch.Arguments(inputType), + Arguments = ContentActions.Patch.Arguments(inputType), ResolvedType = contentType, - Resolver = ContentActions.UpdateOrPatch.Patch(appId, schemaId), + Resolver = ContentActions.Patch.Resolver(appId, schemaId), Description = $"Patch an {schemaName} content by id." }); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetActions.cs index 9032ee7a6..beee05833 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetActions.cs @@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types new QueryArgument(AllTypes.None) { Name = "id", - Description = "The id of the asset (GUID).", + Description = "The id of the asset (usually GUID).", DefaultValue = string.Empty, ResolvedType = AllTypes.NonNullDomainId } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index 160777083..ceb5b58e9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "id", - ResolvedType = AllTypes.NonNullGuid, + ResolvedType = AllTypes.NonNullString, Resolver = EntityResolvers.Id, Description = "The id of the asset." }); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentActions.cs index 1b5f09331..a0c246ffe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentActions.cs @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types new QueryArgument(AllTypes.None) { Name = "id", - Description = "The id of the content (GUID).", + Description = "The id of the content (usually GUID).", DefaultValue = string.Empty, ResolvedType = AllTypes.NonNullDomainId } @@ -160,6 +160,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Description = "Set to true to autopublish content.", DefaultValue = false, ResolvedType = AllTypes.Boolean + }, + new QueryArgument(AllTypes.None) + { + Name = "id", + Description = "The optional custom content id.", + DefaultValue = null, + ResolvedType = AllTypes.String } }; } @@ -170,13 +177,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var contentPublish = c.GetArgument("publish"); var contentData = GetContentData(c); + var contentId = c.GetArgument("id"); - return new CreateContent { Data = contentData, Publish = contentPublish }; + var command = new CreateContent { Data = contentData, Publish = contentPublish }; + + if (!string.IsNullOrWhiteSpace(contentId)) + { + command.ContentId = contentId; + } + + return command; }); } } - public static class UpdateOrPatch + public static class Upsert { public static QueryArguments Arguments(IGraphType inputType) { @@ -187,7 +202,57 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = "id", Description = "The id of the content (GUID)", DefaultValue = string.Empty, - ResolvedType = AllTypes.NonNullGuid + ResolvedType = AllTypes.NonNullString + }, + new QueryArgument(AllTypes.None) + { + Name = "data", + Description = "The data for the content.", + DefaultValue = null, + ResolvedType = new NonNullGraphType(inputType), + }, + new QueryArgument(AllTypes.None) + { + Name = "publish", + Description = "Set to true to autopublish content on create.", + DefaultValue = false, + ResolvedType = AllTypes.Boolean + }, + new QueryArgument(AllTypes.None) + { + Name = "expectedVersion", + Description = "The expected version", + DefaultValue = EtagVersion.Any, + ResolvedType = AllTypes.Int + } + }; + } + + public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + { + return ResolveAsync(appId, schemaId, c => + { + var contentPublish = c.GetArgument("publish"); + var contentData = GetContentData(c); + var contentId = c.GetArgument("id"); + + return new UpsertContent { ContentId = contentId, Data = contentData, Publish = contentPublish }; + }); + } + } + + public static class Update + { + public static QueryArguments Arguments(IGraphType inputType) + { + return new QueryArguments + { + new QueryArgument(AllTypes.None) + { + Name = "id", + Description = "The id of the content (usually GUID)", + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullString }, new QueryArgument(AllTypes.None) { @@ -206,7 +271,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }; } - public static IFieldResolver Update(NamedId appId, NamedId schemaId) + public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) { return ResolveAsync(appId, schemaId, c => { @@ -216,8 +281,39 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return new UpdateContent { ContentId = contentId, Data = contentData }; }); } + } - public static IFieldResolver Patch(NamedId appId, NamedId schemaId) + public static class Patch + { + public static QueryArguments Arguments(IGraphType inputType) + { + return new QueryArguments + { + new QueryArgument(AllTypes.None) + { + Name = "id", + Description = "The id of the content (usually GUID)", + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullString + }, + new QueryArgument(AllTypes.None) + { + Name = "data", + Description = "The data for the content.", + DefaultValue = null, + ResolvedType = new NonNullGraphType(inputType), + }, + new QueryArgument(AllTypes.None) + { + Name = "expectedVersion", + Description = "The expected version", + DefaultValue = EtagVersion.Any, + ResolvedType = AllTypes.Int + } + }; + } + + public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) { return ResolveAsync(appId, schemaId, c => { @@ -236,9 +332,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types new QueryArgument(AllTypes.None) { Name = "id", - Description = "The id of the content (GUID)", + Description = "The id of the content (usually GUID)", DefaultValue = string.Empty, - ResolvedType = AllTypes.NonNullGuid + ResolvedType = AllTypes.NonNullString }, new QueryArgument(AllTypes.None) { @@ -283,7 +379,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types new QueryArgument(AllTypes.None) { Name = "id", - Description = "The id of the content (GUID)", + Description = "The id of the content (usually GUID)", DefaultValue = string.Empty, ResolvedType = AllTypes.NonNullGuid }, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index 05ba7fb4b..952ee944f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs @@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "id", - ResolvedType = AllTypes.NonNullGuid, + ResolvedType = AllTypes.NonNullString, Resolver = EntityResolvers.Id, Description = $"The id of the {schemaName} content." }); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentInterfaceGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentInterfaceGraphType.cs index 9afae5c5b..f796e9018 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentInterfaceGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentInterfaceGraphType.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "id", - ResolvedType = AllTypes.NonNullGuid, + ResolvedType = AllTypes.NonNullString, Resolver = EntityResolvers.Id, Description = "The id of the content." }); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs index 01ff81ab3..eb60d3009 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs @@ -22,6 +22,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains private readonly IEventSubscription eventSubscription; private readonly IDataflowBlock pipelineEnd; + public object Sender + { + get { return eventSubscription.Sender!; } + } + private sealed class Job { public StoredEvent? StoredEvent { get; set; } @@ -91,11 +96,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains if (exception != null) { - await grain.OnErrorAsync(exception); + await grain.OnErrorAsync(Sender, exception); } else { - await grain.OnEventsAsync(GetEvents(jobsBySender), GetPosition(jobsBySender)); + await grain.OnEventsAsync(Sender, GetEvents(jobsBySender), GetPosition(jobsBySender)); } } } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs index 84cf9fd80..a45052bb3 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs @@ -81,8 +81,13 @@ namespace Squidex.Infrastructure.EventSourcing.Grains return State.ToInfo(eventConsumer!.Name).AsImmutable(); } - public Task OnEventsAsync(IReadOnlyList> events, string position) + public Task OnEventsAsync(object sender, IReadOnlyList> events, string position) { + if (!ReferenceEquals(sender, currentSubscriber?.Sender)) + { + return Task.CompletedTask; + } + return DoAndUpdateStateAsync(async () => { await DispatchAsync(events); @@ -91,8 +96,13 @@ namespace Squidex.Infrastructure.EventSourcing.Grains }); } - public Task OnErrorAsync(Exception exception) + public Task OnErrorAsync(object sender, Exception exception) { + if (!ReferenceEquals(sender, currentSubscriber?.Sender)) + { + return Task.CompletedTask; + } + return DoAndUpdateStateAsync(() => { Unsubscribe(); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index f1a77e749..e9d0072da 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -162,7 +162,7 @@ namespace Squidex.Areas.Api.Controllers.Apps } /// - /// Get the app image. + /// Upload the app image. /// /// The name of the app to update. /// The file to upload. diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index 2fda68966..775e77bcc 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -71,7 +71,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [ApiPermission] [ApiCosts(0.5)] [AllowAnonymous] - public async Task GetAssetContentBySlug(string app, string idOrSlug, string more, [FromQuery] AssetContentQueryDto queries) + public async Task GetAssetContentBySlug(string app, string idOrSlug, [FromQuery] AssetContentQueryDto queries, string? more = null) { IAssetEntity? asset; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 3f4625c1a..84dfeecce 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -319,6 +319,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// The name of the schema. /// The full data for the content item. /// True to automatically publish the content. + /// The optional custom content id. /// /// 201 => Content created. /// 404 => Content, schema or app not found. @@ -332,10 +333,15 @@ namespace Squidex.Areas.Api.Controllers.Contents [ProducesResponseType(typeof(ContentsDto), 201)] [ApiPermissionOrAnonymous(Permissions.AppContentsCreate)] [ApiCosts(1)] - public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) + public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false, [FromQuery] string? id = null) { var command = new CreateContent { Data = request.ToCleaned(), Publish = publish }; + if (!string.IsNullOrWhiteSpace(id)) + { + command.ContentId = id; + } + var response = await InvokeCommandAsync(command); return CreatedAtAction(nameof(GetContent), new { app, name, id = command.ContentId }, response); @@ -403,6 +409,36 @@ namespace Squidex.Areas.Api.Controllers.Contents return Ok(response); } + /// + /// Upsert a content item. + /// + /// The name of the app. + /// The name of the schema. + /// The id of the content item to update. + /// True to automatically publish the content. + /// The full data for the content item. + /// + /// 200 => Content updated. + /// 404 => Content references, schema or app not found. + /// 400 => Content data is not valid. + /// + /// + /// You can read the generated documentation for your app at /api/content/{appName}/docs. + /// + [HttpPost] + [Route("content/{app}/{name}/{id}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] + [ApiPermissionOrAnonymous(Permissions.AppContentsUpdate)] + [ApiCosts(1)] + public async Task PostContent(string app, string name, string id, [FromBody] NamedContentData request, [FromQuery] bool publish = false) + { + var command = new UpsertContent { ContentId = id, Data = request.ToCleaned(), Publish = publish }; + + var response = await InvokeCommandAsync(command); + + return Ok(response); + } + /// /// Update a content item. /// diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs index f5b925f82..d9533f1de 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs @@ -125,6 +125,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs); operation.AddQuery("publish", JsonObjectType.Boolean, "True to automatically publish the content."); + operation.AddQuery("id", JsonObjectType.String, "The optional custom content id."); operation.AddResponse("201", $"{schemaName} content created.", contentSchema); operation.AddResponse("400", $"{schemaName} content not valid."); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs index b44fe7871..b9790f051 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BulkUpdateCommandMiddlewareTests.cs @@ -22,7 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Contents { public class BulkUpdateCommandMiddlewareTests { - private readonly IServiceProvider serviceProvider = A.Fake(); private readonly IContentQueryService contentQuery = A.Fake(); private readonly IContextProvider contextProvider = A.Fake(); private readonly ICommandBus commandBus = A.Dummy(); @@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contextProvider.Context) .Returns(requestContext); - sut = new BulkUpdateCommandMiddleware(serviceProvider, contentQuery, contextProvider); + sut = new BulkUpdateCommandMiddleware(contentQuery, contextProvider); } [Fact] @@ -48,9 +47,6 @@ namespace Squidex.Domain.Apps.Entities.Contents await sut.HandleAsync(context); Assert.True(context.PlainResult is BulkUpdateResult); - - A.CallTo(() => serviceProvider.GetService(A._)) - .MustNotHaveHappened(); } [Fact] @@ -63,21 +59,13 @@ namespace Squidex.Domain.Apps.Entities.Contents await sut.HandleAsync(context); Assert.True(context.PlainResult is BulkUpdateResult); - - A.CallTo(() => serviceProvider.GetService(A._)) - .MustNotHaveHappened(); } [Fact] - public async Task Should_import_contents_when_no_query_defined() + public async Task Should_upsert_content_with_random_id_if_no_query_and_id_defined() { var (_, data, _) = CreateTestData(false); - var domainObject = A.Fake(); - - A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) - .Returns(domainObject); - var command = new BulkUpdateContents { Jobs = new List @@ -100,23 +88,16 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Single(result); Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); - A.CallTo(() => domainObject.ExecuteAsync(A.That.Matches(x => x.Data == data))) - .MustHaveHappenedOnceExactly(); - - A.CallTo(() => domainObject.Setup(A._)) + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => x.Data == data && x.ContentId.ToString().Length == 36))) .MustHaveHappenedOnceExactly(); } [Fact] - public async Task Should_import_contents_when_query_returns_no_result() + public async Task Should_upsert_content_with_random_id_if_query_returns_no_result() { var (_, data, query) = CreateTestData(false); - var domainObject = A.Fake(); - - A.CallTo(() => serviceProvider.GetService(typeof(ContentDomainObject))) - .Returns(domainObject); - var command = new BulkUpdateContents { Jobs = new List @@ -140,15 +121,13 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Single(result); Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); - A.CallTo(() => domainObject.ExecuteAsync(A.That.Matches(x => x.Data == data))) - .MustHaveHappenedOnceExactly(); - - A.CallTo(() => domainObject.Setup(A._)) + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => x.Data == data && x.ContentId.ToString().Length == 36))) .MustHaveHappenedOnceExactly(); } [Fact] - public async Task Should_update_content_when_id_defined() + public async Task Should_upsert_content_when_id_defined() { var (id, data, _) = CreateTestData(false); @@ -175,12 +154,13 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Single(result); Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); - A.CallTo(() => commandBus.PublishAsync(A.That.Matches(x => x.ContentId == id && x.Data == data))) + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => x.Data == data && x.ContentId == id))) .MustHaveHappenedOnceExactly(); } [Fact] - public async Task Should_update_content_when_query_defined() + public async Task Should_upsert_content_with_custom_id() { var (id, data, query) = CreateTestData(true); @@ -210,7 +190,8 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Single(result); Assert.Equal(1, result.Count(x => x.ContentId != default && x.Exception == null)); - A.CallTo(() => commandBus.PublishAsync(A.That.Matches(x => x.ContentId == id && x.Data == data))) + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => x.Data == data && x.ContentId == id))) .MustHaveHappenedOnceExactly(); } @@ -338,7 +319,8 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Single(result); Assert.Equal(1, result.Count(x => x.ContentId == id)); - A.CallTo(() => commandBus.PublishAsync(A.That.Matches(x => x.ContentId == id))) + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => x.ContentId == id))) .MustHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index 495b1665b..dbfb26cd7 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -173,6 +173,50 @@ namespace Squidex.Domain.Apps.Entities.Contents await Assert.ThrowsAsync(() => PublishAsync(CreateContentCommand(command))); } + [Fact] + public async Task Upsert_should_create_contnet_when_not_found() + { + var command = new UpsertContent { Data = data }; + + var result = await PublishAsync(CreateContentCommand(command)); + + result.ShouldBeEquivalent(sut.Snapshot); + + Assert.Same(data, sut.Snapshot.CurrentVersion.Data); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) + ); + + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft), "", ScriptOptions())) + .MustHaveHappened(); + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions())) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Upsert_should_update_contnet_when_found() + { + var command = new UpsertContent { Data = otherData }; + + await ExecuteCreateAsync(); + + var result = await PublishAsync(CreateContentCommand(command)); + + result.ShouldBeEquivalent(sut.Snapshot); + + Assert.Equal(otherData, sut.Snapshot.CurrentVersion.Data); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentUpdated { Data = otherData }) + ); + + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(otherData, data, Status.Draft), "", ScriptOptions())) + .MustHaveHappened(); + } + [Fact] public async Task Update_should_create_events_and_update_data() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index ba6f06897..a793571fd 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var query = @" mutation { - createMySchemaContent(data: ) { + createMySchemaContent(data: , publish: true) { } }".Replace("", GetDataString()).Replace("", TestContent.AllFields); @@ -64,6 +64,41 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.That.Matches(x => x.SchemaId.Equals(schemaId) && x.ExpectedVersion == EtagVersion.Any && + x.Publish && + x.Data.Equals(content.Data)))) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_return_single_content_when_creating_content_with_custom_id() + { + var query = @" + mutation { + createMySchemaContent(data: , id: ""123"", publish: true) { + + } + }".Replace("", GetDataString()).Replace("", TestContent.AllFields); + + commandContext.Complete(content); + + var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + createMySchemaContent = TestContent.Response(content) + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.SchemaId.Equals(schemaId) && + x.ExpectedVersion == EtagVersion.Any && + x.ContentId == DomainId.Create("123") && + x.Publish && x.Data.Equals(content.Data)))) .MustHaveHappened(); } @@ -73,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var query = @" mutation OP($data: MySchemaDataInputDto!) { - createMySchemaContent(data: $data) { + createMySchemaContent(data: $data, publish: true) { } }".Replace("", TestContent.AllFields); @@ -96,6 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.That.Matches(x => x.SchemaId.Equals(schemaId) && x.ExpectedVersion == EtagVersion.Any && + x.Publish && x.Data.Equals(content.Data)))) .MustHaveHappened(); } @@ -164,6 +200,72 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL .MustHaveHappened(); } + [Fact] + public async Task Should_return_single_content_when_upserting_content() + { + var query = @" + mutation { + upsertMySchemaContent(id: """", data: , publish: true, expectedVersion: 10) { + + } + }".Replace("", contentId.ToString()).Replace("", GetDataString()).Replace("", TestContent.AllFields); + + commandContext.Complete(content); + + var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + upsertMySchemaContent = TestContent.Response(content) + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.ContentId == content.Id && + x.ExpectedVersion == 10 && + x.Publish && + x.Data.Equals(content.Data)))) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_return_single_content_when_upserting_content_with_variable() + { + var query = @" + mutation OP($data: MySchemaDataInputDto!) { + upsertMySchemaContent(id: """", data: $data, publish: true, expectedVersion: 10) { + + } + }".Replace("", contentId.ToString()).Replace("", TestContent.AllFields); + + commandContext.Complete(content); + + var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query, Inputs = GetInput() }); + + var expected = new + { + data = new + { + upsertMySchemaContent = TestContent.Response(content) + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.ContentId == content.Id && + x.ExpectedVersion == 10 && + x.Publish && + x.Data.Equals(content.Data)))) + .MustHaveHappened(); + } + [Fact] public async Task Should_return_single_content_when_patching_content() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/CachingTextIndexerStateTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/CachingTextIndexerStateTests.cs index 393647812..3e630f2c9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/CachingTextIndexerStateTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/CachingTextIndexerStateTests.cs @@ -18,7 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text public class CachingTextIndexerStateTests { private readonly ITextIndexerState inner = A.Fake(); - private readonly DomainId appId = DomainId.NewGuid(); private readonly DomainId contentId = DomainId.NewGuid(); private readonly CachingTextIndexerState sut; diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs index b3c87ee39..c180e5608 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs @@ -27,8 +27,6 @@ namespace Squidex.Infrastructure.EventSourcing mongoClient = new MongoClient(connectionString); mongoDatabase = mongoClient.GetDatabase($"EventStoreTest"); - Cleanup(); - BsonJsonConvention.Register(JsonSerializer.Create(JsonHelper.DefaultSettings())); EventStore = new MongoEventStore(mongoDatabase, notifier); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index e05102a70..c281db978 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -287,7 +287,7 @@ namespace TestSuite.ApiTests // STEP 2: Update pattern. var updateRequest = new UpdatePatternDto { Name = patternName, Pattern = patternRegex2 }; - var patterns_2 = await _.Apps.PutPatternAsync(_.AppName, pattern_1.Id.ToString(), updateRequest); + var patterns_2 = await _.Apps.PutPatternAsync(_.AppName, pattern_1.Id, updateRequest); var pattern_2 = patterns_2.Items.Single(x => x.Name == patternName); // Should return pattern with correct regex. @@ -295,7 +295,7 @@ namespace TestSuite.ApiTests // STEP 3: Remove pattern. - var patterns_3 = await _.Apps.DeletePatternAsync(_.AppName, pattern_2.Id.ToString()); + var patterns_3 = await _.Apps.DeletePatternAsync(_.AppName, pattern_2.Id); // Should not return deleted pattern. Assert.DoesNotContain(patterns_3.Items, x => x.Id == pattern_2.Id); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs index 7e07cc505..72ab36d39 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs @@ -13,6 +13,7 @@ using Xunit; #pragma warning disable SA1300 // Element should begin with upper-case letter #pragma warning disable SA1507 // Code should not contain multiple blank lines in a row +#pragma warning disable CS0612 // Type or member is obsolete namespace TestSuite.ApiTests { diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs index e0247a67f..e12ddd24f 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AssetTests.cs @@ -65,7 +65,7 @@ namespace TestSuite.ApiTests } }; - var asset_2 = await _.Assets.PutAssetAsync(_.AppName, asset_1.Id.ToString(), metadataRequest); + var asset_2 = await _.Assets.PutAssetAsync(_.AppName, asset_1.Id, metadataRequest); // Should provide metadata. Assert.Equal(metadataRequest.Metadata, asset_2.Metadata); @@ -74,7 +74,7 @@ namespace TestSuite.ApiTests // STEP 3: Annotate slug. var slugRequest = new AnnotateAssetDto { Slug = "my-image" }; - var asset_3 = await _.Assets.PutAssetAsync(_.AppName, asset_2.Id.ToString(), slugRequest); + var asset_3 = await _.Assets.PutAssetAsync(_.AppName, asset_2.Id, slugRequest); // Should provide updated slug. Assert.Equal(slugRequest.Slug, asset_3.Slug); @@ -83,7 +83,7 @@ namespace TestSuite.ApiTests // STEP 3: Annotate file name. var fileNameRequest = new AnnotateAssetDto { FileName = "My Image" }; - var asset_4 = await _.Assets.PutAssetAsync(_.AppName, asset_3.Id.ToString(), fileNameRequest); + var asset_4 = await _.Assets.PutAssetAsync(_.AppName, asset_3.Id, fileNameRequest); // Should provide updated file name. Assert.Equal(fileNameRequest.FileName, asset_4.FileName); @@ -111,7 +111,7 @@ namespace TestSuite.ApiTests // STEP 4: Protect asset var protectRequest = new AnnotateAssetDto { IsProtected = true }; - var asset_2 = await _.Assets.PutAssetAsync(_.AppName, asset_1.Id.ToString(), protectRequest); + var asset_2 = await _.Assets.PutAssetAsync(_.AppName, asset_1.Id, protectRequest); // STEP 5: Download asset with authentication. @@ -119,7 +119,7 @@ namespace TestSuite.ApiTests { var downloaded = new MemoryStream(); - using (var assetStream = await _.Assets.GetAssetContentAsync(asset_2.Id.ToString())) + using (var assetStream = await _.Assets.GetAssetContentBySlugAsync(_.AppName, asset_2.Id)) { await assetStream.Stream.CopyToAsync(downloaded); } @@ -147,10 +147,10 @@ namespace TestSuite.ApiTests // STEP 2: Delete asset - await _.Assets.DeleteAssetAsync(_.AppName, asset_1.Id.ToString()); + await _.Assets.DeleteAssetAsync(_.AppName, asset_1.Id); // Should return 404 when asset deleted. - var ex = await Assert.ThrowsAsync(() => _.Assets.GetAssetAsync(_.AppName, asset_1.Id.ToString())); + var ex = await Assert.ThrowsAsync(() => _.Assets.GetAssetAsync(_.AppName, asset_1.Id)); Assert.Equal(404, ex.StatusCode); } @@ -174,7 +174,7 @@ namespace TestSuite.ApiTests // STEP 3: Add custom metadata. asset_1.Metadata["custom"] = "foo"; - await _.Assets.PutAssetAsync(_.AppName, asset_1.Id.ToString(), new AnnotateAssetDto + await _.Assets.PutAssetAsync(_.AppName, asset_1.Id, new AnnotateAssetDto { Metadata = asset_1.Metadata }); @@ -209,7 +209,7 @@ namespace TestSuite.ApiTests // STEP 4: Delete folder. - await _.Assets.DeleteAssetFolderAsync(_.AppName, folder_1.Id.ToString()); + await _.Assets.DeleteAssetFolderAsync(_.AppName, folder_1.Id); // STEP 5: Wait for recursive deleter to delete the asset. diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs index 1d63a206e..659215630 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs @@ -35,7 +35,7 @@ namespace TestSuite.ApiTests { var items = await _.Contents.GetAsync(new ContentQuery { OrderBy = "data/number/iv asc" }); - var itemsById = await _.Contents.GetAsync(new HashSet(items.Items.Take(3).Select(x => x.Id))); + var itemsById = await _.Contents.GetAsync(new HashSet(items.Items.Take(3).Select(x => x.Id))); Assert.Equal(3, itemsById.Items.Count); Assert.Equal(3, itemsById.Total); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs index fe67504d9..cc185a90a 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs @@ -55,7 +55,7 @@ namespace TestSuite.ApiTests // STEP 5: Query new item again var contentB_3 = await _.Contents.GetAsync(contentB_1.Id); - Assert.Equal(new Guid[] { contentA_1.Id }, contentB_3.Data.References); + Assert.Equal(new string[] { contentA_1.Id }, contentB_3.Data.References); } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs index 306a60e47..07977edf7 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Threading.Tasks; using Squidex.ClientLibrary; using TestSuite.Fixtures; @@ -25,6 +26,69 @@ namespace TestSuite.ApiTests _ = fixture; } + [Fact] + public async Task Should_return_item_published_item() + { + TestEntity content = null; + try + { + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); + + await _.Contents.ChangeStatusAsync(content.Id, Status.Published); + await _.Contents.GetAsync(content.Id); + } + finally + { + if (content != null) + { + await _.Contents.DeleteAsync(content.Id); + } + } + } + + [Fact] + public async Task Should_not_return_archived_item() + { + TestEntity content = null; + try + { + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, true); + + await _.Contents.ChangeStatusAsync(content.Id, Status.Archived); + + await Assert.ThrowsAsync(() => _.Contents.GetAsync(content.Id)); + } + finally + { + if (content != null) + { + await _.Contents.DeleteAsync(content.Id); + } + } + } + + [Fact] + public async Task Should_not_return_unpublished_item() + { + TestEntity content = null; + try + { + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); + + await _.Contents.ChangeStatusAsync(content.Id, Status.Published); + await _.Contents.ChangeStatusAsync(content.Id, Status.Draft); + + await Assert.ThrowsAsync(() => _.Contents.GetAsync(content.Id)); + } + finally + { + if (content != null) + { + await _.Contents.DeleteAsync(content.Id); + } + } + } + [Fact] public async Task Should_create_strange_text() { @@ -49,7 +113,7 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_not_return_not_published_item() + public async Task Should_create_non_published_item() { TestEntity content = null; try @@ -68,7 +132,7 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_return_item_published_with_creation() + public async Task Should_create_published_item() { TestEntity content = null; try @@ -87,15 +151,16 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_return_item_published_item() + public async Task Should_create_item_with_custom_id() { TestEntity content = null; try { - content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); + var id = Guid.NewGuid().ToString(); - await _.Contents.ChangeStatusAsync(content.Id, Status.Published); - await _.Contents.GetAsync(content.Id); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, id, true); + + Assert.Equal(id, content.Id); } finally { @@ -107,16 +172,16 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_not_return_archived_item() + public async Task Should_create_item_with_custom_id_and_upsert() { TestEntity content = null; try { - content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, true); + var id = Guid.NewGuid().ToString(); - await _.Contents.ChangeStatusAsync(content.Id, Status.Archived); + content = await _.Contents.UpsertAsync(id, new TestEntityData { Number = 1 }, true); - await Assert.ThrowsAsync(() => _.Contents.GetAsync(content.Id)); + Assert.Equal(id, content.Id); } finally { @@ -128,17 +193,20 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_not_return_unpublished_item() + public async Task Should_upsert_item() { TestEntity content = null; try { - content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); + var id = Guid.NewGuid().ToString(); - await _.Contents.ChangeStatusAsync(content.Id, Status.Published); - await _.Contents.ChangeStatusAsync(content.Id, Status.Draft); + content = await _.Contents.UpsertAsync(id, new TestEntityData { Number = 1 }, true); - await Assert.ThrowsAsync(() => _.Contents.GetAsync(content.Id)); + await _.Contents.UpsertAsync(id, new TestEntityData { Number = 2 }); + + var updated = await _.Contents.GetAsync(content.Id); + + Assert.Equal(2, updated.Data.Number); } finally { diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs index 70906467e..1d87f2e1f 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs @@ -148,7 +148,7 @@ namespace TestSuite.ApiTests Name = "cities", Properties = new ReferencesFieldPropertiesDto { - SchemaIds = new List { cities.Id } + SchemaIds = new List { cities.Id } } } }, @@ -174,7 +174,7 @@ namespace TestSuite.ApiTests Name = "states", Properties = new ReferencesFieldPropertiesDto { - SchemaIds = new List { states.Id } + SchemaIds = new List { states.Id } } } }, diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs index c5d0ccd64..10934a882 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/AssetFixture.cs @@ -46,11 +46,11 @@ namespace TestSuite.Fixtures { var upload = new FileParameter(stream, fileName ?? RandomName(fileInfo), asset.MimeType); - return await Assets.PutAssetContentAsync(AppName, asset.Id.ToString(), upload); + return await Assets.PutAssetContentAsync(AppName, asset.Id, upload); } } - public async Task UploadFileAsync(string path, string mimeType, string fileName = null, Guid? parentId = null) + public async Task UploadFileAsync(string path, string mimeType, string fileName = null, string parentId = null) { var fileInfo = new FileInfo(path); @@ -58,7 +58,7 @@ namespace TestSuite.Fixtures { var upload = new FileParameter(stream, fileName ?? RandomName(fileInfo), mimeType); - return await Assets.PostAssetAsync(AppName, upload, parentId); + return await Assets.PostAssetAsync(AppName, parentId?.ToString(), upload); } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntityWithReferences.cs b/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntityWithReferences.cs index a009cd68b..e442e6520 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntityWithReferences.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntityWithReferences.cs @@ -42,6 +42,6 @@ namespace TestSuite.Model public sealed class TestEntityWithReferencesData { [JsonConverter(typeof(InvariantConverter))] - public Guid[] References { get; set; } + public string[] References { get; set; } } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index 19af566bb..29b89f46a 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -9,7 +9,7 @@ - +