diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs new file mode 100644 index 0000000000..fa42f710e3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.DataType; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.DataType; + +public class CopyDataTypeController : DataTypeControllerBase +{ + private readonly IDataTypeService _dataTypeService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public CopyDataTypeController(IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _dataTypeService = dataTypeService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpPost("{key:guid}/copy")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Copy(Guid key, DataTypeCopyModel dataTypeCopyModel) + { + IDataType? source = await _dataTypeService.GetAsync(key); + if (source is null) + { + return NotFound(); + } + + Attempt result = await _dataTypeService.CopyAsync(source, dataTypeCopyModel.TargetKey, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result.Key) + : DataTypeOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs index 7f34c0fc3c..3e21544c57 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs @@ -29,6 +29,7 @@ public abstract class DataTypeControllerBase : ManagementApiControllerBase .WithTitle("Cancelled by notification") .WithDetail("A notification handler prevented the data type operation.") .Build()), + DataTypeOperationStatus.ParentNotFound => NotFound("The targeted parent for the data type operation was not found."), _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown data type operation status") }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs new file mode 100644 index 0000000000..27b8ccc4b1 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/MoveDataTypeController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.DataType; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.DataType; + +public class MoveDataTypeController : DataTypeControllerBase +{ + private readonly IDataTypeService _dataTypeService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public MoveDataTypeController(IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _dataTypeService = dataTypeService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpPost("{key:guid}/move")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Move(Guid key, DataTypeMoveModel dataTypeMoveModel) + { + IDataType? source = await _dataTypeService.GetAsync(key); + if (source is null) + { + return NotFound(); + } + + Attempt result = await _dataTypeService.MoveAsync(source, dataTypeMoveModel.TargetKey, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? Ok() + : DataTypeOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index a316e1c1c4..42262ac2d2 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -234,6 +234,100 @@ } } }, + "/umbraco/management/api/v1/data-type/{key}/copy": { + "post": { + "tags": [ + "Data Type" + ], + "operationId": "PostDataTypeByKeyCopy", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DataTypeCopyModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created" + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + } + } + } + }, + "/umbraco/management/api/v1/data-type/{key}/move": { + "post": { + "tags": [ + "Data Type" + ], + "operationId": "PostDataTypeByKeyMove", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DataTypeMoveModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + } + } + } + }, "/umbraco/management/api/v1/data-type/{key}/references": { "get": { "tags": [ @@ -5745,6 +5839,17 @@ }, "additionalProperties": false }, + "DataTypeCopyModel": { + "type": "object", + "properties": { + "targetKey": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, "DataTypeCreateModel": { "type": "object", "allOf": [ @@ -5807,6 +5912,17 @@ }, "additionalProperties": false }, + "DataTypeMoveModel": { + "type": "object", + "properties": { + "targetKey": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, "DataTypePropertyModel": { "type": "object", "properties": { diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeCopyModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeCopyModel.cs new file mode 100644 index 0000000000..b8d6942cbe --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeCopyModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.DataType; + +public class DataTypeCopyModel +{ + public Guid? TargetKey { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeMoveModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeMoveModel.cs new file mode 100644 index 0000000000..691af4092f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeMoveModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.DataType; + +public class DataTypeMoveModel +{ + public Guid? TargetKey { get; set; } +} diff --git a/src/Umbraco.Core/CompatibilitySuppressions.xml b/src/Umbraco.Core/CompatibilitySuppressions.xml index 400de0b096..ad50b77190 100644 --- a/src/Umbraco.Core/CompatibilitySuppressions.xml +++ b/src/Umbraco.Core/CompatibilitySuppressions.xml @@ -455,6 +455,13 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.IDataTypeService.CopyAsync(Umbraco.Cms.Core.Models.IDataType,System.Nullable{System.Guid},System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.Services.IDataTypeService.CreateAsync(Umbraco.Cms.Core.Models.IDataType,System.Int32) @@ -497,6 +504,13 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.IDataTypeService.MoveAsync(Umbraco.Cms.Core.Models.IDataType,System.Nullable{System.Guid},System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.Services.IDataTypeService.UpdateAsync(Umbraco.Cms.Core.Models.IDataType,System.Int32) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 2e2104eb5f..d143337c6f 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -355,50 +355,73 @@ namespace Umbraco.Cms.Core.Services.Implement public Attempt?> Move(IDataType toMove, int parentId) { - EventMessages evtMsgs = EventMessagesFactory.Get(); - if (toMove.ParentId == parentId) + Guid? containerKey = null; + if (parentId > 0) { - return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, evtMsgs); + // mimic obsolete Copy method behavior + EntityContainer? container = GetContainer(parentId); + if (container is null) + { + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + } + + containerKey = container.Key; } - var moveInfo = new List>(); + Attempt result = MoveAsync(toMove, containerKey).GetAwaiter().GetResult(); + + // mimic old service behavior + EventMessages evtMsgs = EventMessagesFactory.Get(); + return result.Status switch + { + DataTypeOperationStatus.Success => OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs), + DataTypeOperationStatus.CancelledByNotification => OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs), + DataTypeOperationStatus.ParentNotFound => OperationResult.Attempt.Fail(MoveOperationStatusType.FailedParentNotFound, evtMsgs), + _ => OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, evtMsgs, new InvalidOperationException($"Invalid operation status: {result.Status}")), + }; + } + + public async Task> MoveAsync(IDataType toMove, Guid? containerKey, int userId = Constants.Security.SuperUserId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + EntityContainer? container = null; + var parentId = Constants.System.Root; + if (containerKey.HasValue && containerKey.Value != Guid.Empty) + { + container = await _dataTypeContainerService.GetAsync(containerKey.Value); + if (container is null) + { + return Attempt.FailWithStatus(DataTypeOperationStatus.ParentNotFound, toMove); + } - var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, evtMsgs); + parentId = container.Id; + } + + if (toMove.ParentId == parentId) + { + return Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, toMove); + } + + var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, eventMessages); if (scope.Notifications.PublishCancelable(movingDataTypeNotification)) { scope.Complete(); - return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); + return Attempt.FailWithStatus(DataTypeOperationStatus.CancelledByNotification, toMove); } - try - { - EntityContainer? container = null; - if (parentId > 0) - { - container = _dataTypeContainerRepository.Get(parentId); - if (container == null) - { - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback - } - } - moveInfo.AddRange(_dataTypeRepository.Move(toMove, container)); + _dataTypeRepository.Move(toMove, container); - scope.Notifications.Publish(new DataTypeMovedNotification(moveEventInfo, evtMsgs).WithStateFrom(movingDataTypeNotification)); + scope.Notifications.Publish(new DataTypeMovedNotification(moveEventInfo, eventMessages).WithStateFrom(movingDataTypeNotification)); - scope.Complete(); - } - catch (DataOperationException ex) - { - scope.Complete(); // TODO: what are we doing here exactly? - return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); - } + Audit(AuditType.Move, userId, toMove.Id); + scope.Complete(); } - return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); + return Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, toMove); } [Obsolete("Use the method which specifies the userId parameter")] @@ -409,32 +432,50 @@ namespace Umbraco.Cms.Core.Services.Implement public Attempt?> Copy(IDataType copying, int containerId, int userId = Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); - - IDataType copy; - try + Guid? containerKey = null; + if (containerId > 0) { - if (containerId > 0) + // mimic obsolete Copy method behavior + EntityContainer? container = GetContainer(containerId); + if (container is null) { - var container = GetContainer(containerId); - if (container is null) - { - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback - } + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); } - copy = copying.DeepCloneWithResetIdentities(); - copy.Name += " (copy)"; // might not be unique - copy.ParentId = containerId; - - Save(copy, userId); + containerKey = container.Key; } - catch (DataOperationException ex) + + Attempt result = CopyAsync(copying, containerKey, userId).GetAwaiter().GetResult(); + + // mimic old service behavior + EventMessages evtMsgs = EventMessagesFactory.Get(); + return result.Status switch { - return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback + DataTypeOperationStatus.Success => OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, result.Result), + DataTypeOperationStatus.CancelledByNotification => OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs, result.Result), + DataTypeOperationStatus.ParentNotFound => OperationResult.Attempt.Fail(MoveOperationStatusType.FailedParentNotFound, evtMsgs, result.Result), + _ => OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, evtMsgs, result.Result, new InvalidOperationException($"Invalid operation status: {result.Status}")), + }; + } + + /// + public async Task> CopyAsync(IDataType toCopy, Guid? containerKey, int userId = Constants.Security.SuperUserId) + { + EntityContainer? container = null; + if (containerKey.HasValue && containerKey.Value != Guid.Empty) + { + container = await _dataTypeContainerService.GetAsync(containerKey.Value); + if (container is null) + { + return Attempt.FailWithStatus(DataTypeOperationStatus.ParentNotFound, toCopy); + } } - return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy); + IDataType copy = toCopy.DeepCloneWithResetIdentities(); + copy.Name += " (copy)"; // might not be unique + copy.ParentId = container?.Id ?? Constants.System.Root; + + return await SaveAsync(copy, () => DataTypeOperationStatus.Success, userId, AuditType.Copy); } /// @@ -442,6 +483,7 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// to save /// Id of the user issuing the save + [Obsolete("Please use CreateAsync or UpdateAsync. Will be removed in V15.")] public void Save(IDataType dataType, int userId = Constants.Security.SuperUserId) { // mimic old service behavior diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 5bd431a0a5..5edbad40ab 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -175,21 +175,32 @@ public interface IDataTypeService : IService /// Collection of configured for the property editor Task> GetByEditorAliasAsync(string propertyEditorAlias); + [Obsolete("Please use MoveAsync instead. Will be removed in V15")] Attempt?> Move(IDataType toMove, int parentId); - [Obsolete("Use the method which specifies the userId parameter")] + /// + /// Moves a to a given container + /// + /// The data type that will be moved + /// The container key where the data type will be moved to. + /// The user that did the Move action + /// + Task> MoveAsync(IDataType toMove, Guid? containerKey, int userId = Constants.Security.SuperUserId); + + [Obsolete("Please use CopyASync instead. Will be removed in V15")] Attempt?> Copy(IDataType copying, int containerId) => Copy(copying, containerId, Constants.Security.SuperUserId); + [Obsolete("Please use CopyASync instead. Will be removed in V15")] + Attempt?> Copy(IDataType copying, int containerId, int userId = Constants.Security.SuperUserId) => throw new NotImplementedException(); + /// - /// Copies the give to a given container - /// We have the default implementation here to avoid breaking changes for the user + /// Copies a to a given container /// - /// The data type that will be copied - /// The container ID under where the data type will be copied + /// The data type that will be copied + /// The container key where the data type will be copied to. /// The user that did the Copy action /// - /// - Attempt?> Copy(IDataType copying, int containerId, int userId = Constants.Security.SuperUserId) => throw new NotImplementedException(); + Task> CopyAsync(IDataType toCopy, Guid? containerKey, int userId = Constants.Security.SuperUserId); /// /// Performs validation for the configuration data of a given data type. diff --git a/src/Umbraco.Core/Services/OperationStatus/DataTypeOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/DataTypeOperationStatus.cs index dd627a76e7..3e3750bc87 100644 --- a/src/Umbraco.Core/Services/OperationStatus/DataTypeOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/DataTypeOperationStatus.cs @@ -7,5 +7,6 @@ public enum DataTypeOperationStatus InvalidConfiguration, InvalidName, InvalidId, - NotFound + NotFound, + ParentNotFound } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs index 239f0f75a9..a16fa11436 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs @@ -22,8 +22,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; public class DataTypeServiceTests : UmbracoIntegrationTest { private IDataValueEditorFactory DataValueEditorFactory => GetRequiredService(); + private IDataTypeService DataTypeService => GetRequiredService(); + private IDataTypeContainerService DataTypeContainerService => GetRequiredService(); + private IContentTypeService ContentTypeService => GetRequiredService(); private IFileService FileService => GetRequiredService(); @@ -167,6 +170,180 @@ public class DataTypeServiceTests : UmbracoIntegrationTest Assert.That(contentType.PropertyTypes.Count(), Is.EqualTo(1)); } + [Test] + public async Task Can_Create_DataType_In_Container() + { + var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" })).Result; + + var result = await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext, + ParentId = container.Id + }); + + Assert.True(result.Success); + Assert.IsNotNull(result.Result); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + + var dataType = await DataTypeService.GetAsync(result.Result.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(container.Id, dataType.ParentId); + } + + [Test] + public async Task Can_Move_DataType_To_Container() + { + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext + })).Result; + + var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" })).Result; + + dataType = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(Constants.System.Root, dataType.ParentId); + + var result = await DataTypeService.MoveAsync(dataType, container.Key); + Assert.IsTrue(result.Success); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + + dataType = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(container.Id, dataType.ParentId); + } + + [Test] + public async Task Can_Move_DataType_To_Root() + { + var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" })).Result; + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext, + ParentId = container.Id + })).Result; + + dataType = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(container.Id, dataType.ParentId); + + var result = await DataTypeService.MoveAsync(dataType, null); + Assert.IsTrue(result.Success); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + + dataType = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(Constants.System.Root, dataType.ParentId); + } + + [Test] + public async Task Can_Copy_DataType_To_Root() + { + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext + })).Result; + + var result = await DataTypeService.CopyAsync(dataType, null); + Assert.IsTrue(result.Success); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + Assert.IsNotNull(result.Result); + Assert.AreNotEqual(dataType.Key, result.Result.Key); + Assert.AreNotEqual(dataType.Name, result.Result.Name); + + IDataType copy = await DataTypeService.GetAsync(result.Result.Key); + Assert.IsNotNull(copy); + Assert.AreEqual(Constants.System.Root, copy.ParentId); + } + + [Test] + public async Task Can_Copy_DataType_To_Container() + { + var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" })).Result; + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext + })).Result; + + var result = await DataTypeService.CopyAsync(dataType, container.Key); + Assert.IsTrue(result.Success); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + Assert.IsNotNull(result.Result); + Assert.AreNotEqual(dataType.Key, result.Result.Key); + Assert.AreNotEqual(dataType.Name, result.Result.Name); + + IDataType copy = await DataTypeService.GetAsync(result.Result.Key); + Assert.IsNotNull(copy); + Assert.AreEqual(container.Id, copy.ParentId); + } + + [Test] + public async Task Can_Copy_DataType_Between_Containers() + { + var container1 = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 1" })).Result; + var container2 = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 2" })).Result; + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext, + ParentId = container1.Id + })).Result; + + var result = await DataTypeService.CopyAsync(dataType, container2.Key); + Assert.IsTrue(result.Success); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + Assert.IsNotNull(result.Result); + Assert.AreNotEqual(dataType.Key, result.Result.Key); + Assert.AreNotEqual(dataType.Name, result.Result.Name); + + IDataType original = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(original); + Assert.AreEqual(container1.Id, original.ParentId); + + IDataType copy = await DataTypeService.GetAsync(result.Result.Key); + Assert.IsNotNull(copy); + Assert.AreEqual(container2.Id, copy.ParentId); + } + + [Test] + public async Task Can_Copy_DataType_From_Container_To_Root() + { + var container1 = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 1" })).Result; + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext, + ParentId = container1.Id + })).Result; + + var result = await DataTypeService.CopyAsync(dataType, null); + Assert.IsTrue(result.Success); + Assert.AreEqual(DataTypeOperationStatus.Success, result.Status); + Assert.IsNotNull(result.Result); + Assert.AreNotEqual(dataType.Key, result.Result.Key); + Assert.AreNotEqual(dataType.Name, result.Result.Name); + + IDataType original = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(original); + Assert.AreEqual(container1.Id, original.ParentId); + + IDataType copy = await DataTypeService.GetAsync(result.Result.Key); + Assert.IsNotNull(copy); + Assert.AreEqual(Constants.System.Root, copy.ParentId); + } + [Test] public async Task Cannot_Create_DataType_With_Empty_Name() { @@ -219,4 +396,27 @@ public class DataTypeServiceTests : UmbracoIntegrationTest // Act & Assert Assert.Throws(() => DataTypeService.Save(dataTypeDefinition)); } + + [Test] + public async Task Cannot_Move_DataType_To_Non_Existing_Container() + { + var dataType = (await DataTypeService.CreateAsync( + new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) + { + Name = "Testing Textfield", + DatabaseType = ValueStorageType.Ntext + })).Result; + + dataType = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(Constants.System.Root, dataType.ParentId); + + var result = await DataTypeService.MoveAsync(dataType, Guid.NewGuid()); + Assert.IsFalse(result.Success); + Assert.AreEqual(DataTypeOperationStatus.ParentNotFound, result.Status); + + dataType = await DataTypeService.GetAsync(dataType.Key); + Assert.IsNotNull(dataType); + Assert.AreEqual(Constants.System.Root, dataType.ParentId); + } }