Add "move" to dictionary API (#13810)

* Sanitize dictionary overview and export actions

* Amend dictionary services with async and attempt pattern + isolate temporary file handling in its own service.

* Update OpenAPI schema to match new dictionary bulk actions

* Implement move API for dictionary items.

* Add unit tests for dictionary item move

* Fix merge

* Update OpenAPI json after merge
This commit is contained in:
Kenn Jacobsen
2023-02-10 08:32:24 +01:00
committed by GitHub
parent 42f8cde259
commit 36dc35f8aa
10 changed files with 594 additions and 240 deletions

View File

@@ -26,6 +26,10 @@ public abstract class DictionaryControllerBase : ManagementApiControllerBase
.WithTitle("Cancelled by notification")
.WithDetail("A notification handler prevented the dictionary item operation.")
.Build()),
DictionaryItemOperationStatus.InvalidParent => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Invalid parent")
.WithDetail("The targeted parent dictionary item is not valid for this dictionary item operation.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown dictionary operation status")
};
}

View File

@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
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.Dictionary;
public class MoveDictionaryController : DictionaryControllerBase
{
private readonly IDictionaryItemService _dictionaryItemService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public MoveDictionaryController(IDictionaryItemService dictionaryItemService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_dictionaryItemService = dictionaryItemService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPost("{key:guid}/move")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Move(Guid key, DictionaryMoveModel dictionaryMoveModel)
{
IDictionaryItem? source = await _dictionaryItemService.GetAsync(key);
if (source == null)
{
return NotFound();
}
Attempt<IDictionaryItem, DictionaryItemOperationStatus> result = await _dictionaryItemService.MoveAsync(
source,
dictionaryMoveModel.TargetKey,
CurrentUserId(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: DictionaryItemOperationStatusResult(result.Status);
}
}

View File

@@ -638,8 +638,8 @@
"201": {
"description": "Created"
},
"400": {
"description": "Bad Request",
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
@@ -648,8 +648,8 @@
}
}
},
"404": {
"description": "Not Found",
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
@@ -863,6 +863,63 @@
}
}
},
"/umbraco/management/api/v1/dictionary/{key}/move": {
"post": {
"tags": [
"Dictionary"
],
"operationId": "PostDictionaryByKeyMove",
"parameters": [
{
"name": "key",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/DictionaryMoveModel"
}
]
}
}
}
},
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
},
"/umbraco/management/api/v1/dictionary/import": {
"post": {
"tags": [
@@ -916,9 +973,7 @@
],
"operationId": "PostDictionaryUpload",
"requestBody": {
"content": {
}
"content": { }
},
"responses": {
"200": {
@@ -1338,16 +1393,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
@@ -1357,6 +1402,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
}
}
}
@@ -1388,16 +1443,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
@@ -1407,6 +1452,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
}
}
}
@@ -1641,6 +1696,16 @@
}
],
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -1654,16 +1719,6 @@
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
@@ -1685,6 +1740,16 @@
}
],
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -1698,16 +1763,6 @@
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
@@ -1732,6 +1787,16 @@
}
},
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -1745,16 +1810,6 @@
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
@@ -1806,16 +1861,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedHelpPageModel"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
@@ -1825,6 +1870,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedHelpPageModel"
}
}
}
}
}
}
@@ -1884,6 +1939,16 @@
}
],
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -1897,16 +1962,6 @@
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
@@ -1928,16 +1983,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OkResultModel"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
@@ -1947,6 +1992,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OkResultModel"
}
}
}
}
}
}
@@ -1958,20 +2013,6 @@
],
"operationId": "GetInstallSettings",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/InstallSettingsModel"
}
]
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
@@ -1991,6 +2032,20 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/InstallSettingsModel"
}
]
}
}
}
}
}
}
@@ -2015,9 +2070,6 @@
}
},
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
@@ -2037,6 +2089,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
}
@@ -2061,9 +2116,6 @@
}
},
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
@@ -2073,6 +2125,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
}
@@ -2135,8 +2190,15 @@
}
},
"responses": {
"201": {
"description": "Created"
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"400": {
"description": "Bad Request",
@@ -2148,15 +2210,8 @@
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
"201": {
"description": "Created"
}
}
}
@@ -2178,6 +2233,16 @@
}
],
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -2191,16 +2256,6 @@
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
},
@@ -2220,9 +2275,6 @@
}
],
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
@@ -2242,6 +2294,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
},
@@ -2274,8 +2329,15 @@
}
},
"responses": {
"200": {
"description": "Success"
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResultModel"
}
}
}
},
"400": {
"description": "Bad Request",
@@ -2287,15 +2349,8 @@
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResultModel"
}
}
}
"200": {
"description": "Success"
}
}
}
@@ -2365,9 +2420,6 @@
}
],
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
@@ -2377,6 +2429,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
}
@@ -2504,16 +2559,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedLogTemplateModel"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
@@ -2523,6 +2568,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedLogTemplateModel"
}
}
}
}
}
}
@@ -2585,9 +2640,6 @@
}
},
"responses": {
"201": {
"description": "Created"
},
"400": {
"description": "Bad Request",
"content": {
@@ -2597,6 +2649,9 @@
}
}
}
},
"201": {
"description": "Created"
}
}
}
@@ -2618,6 +2673,16 @@
}
],
"responses": {
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResultModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -2631,16 +2696,6 @@
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFoundResultModel"
}
}
}
}
}
},
@@ -2660,9 +2715,6 @@
}
],
"responses": {
"200": {
"description": "Success"
},
"404": {
"description": "Not Found",
"content": {
@@ -2672,6 +2724,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
}
@@ -2701,9 +2756,6 @@
}
],
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
@@ -2713,6 +2765,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
}
@@ -2899,16 +2954,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
@@ -2918,6 +2963,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
}
}
}
@@ -2949,16 +3004,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
@@ -2968,6 +3013,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRecycleBinItemModel"
}
}
}
}
}
}
@@ -3636,16 +3691,6 @@
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRedirectUrlModel"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
@@ -3655,6 +3700,16 @@
}
}
}
},
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagedRedirectUrlModel"
}
}
}
}
}
}
@@ -4213,6 +4268,16 @@
],
"operationId": "GetServerStatus",
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -4226,16 +4291,6 @@
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
@@ -4247,6 +4302,16 @@
],
"operationId": "GetServerVersion",
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
},
"200": {
"description": "Success",
"content": {
@@ -4260,16 +4325,6 @@
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetailsModel"
}
}
}
}
}
}
@@ -4606,9 +4661,6 @@
}
},
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
@@ -4618,6 +4670,9 @@
}
}
}
},
"200": {
"description": "Success"
}
}
}
@@ -6031,6 +6086,17 @@
},
"additionalProperties": false
},
"DictionaryMoveModel": {
"type": "object",
"properties": {
"targetKey": {
"type": "string",
"format": "uuid",
"nullable": true
}
},
"additionalProperties": false
},
"DictionaryOverviewModel": {
"type": "object",
"properties": {
@@ -6783,9 +6849,7 @@
},
"providerProperties": {
"type": "object",
"additionalProperties": {
},
"additionalProperties": { },
"nullable": true
}
},
@@ -8437,9 +8501,7 @@
"nullable": true
}
},
"additionalProperties": {
}
"additionalProperties": { }
},
"ProfilingStatusModel": {
"type": "object",
@@ -9919,9 +9981,7 @@
"authorizationCode": {
"authorizationUrl": "/umbraco/management/api/v1.0/security/back-office/authorize",
"tokenUrl": "/umbraco/management/api/v1.0/security/back-office/token",
"scopes": {
}
"scopes": { }
}
}
}
@@ -9929,9 +9989,7 @@
},
"security": [
{
"OAuth": [
]
"OAuth": [ ]
}
]
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
public class DictionaryMoveModel
{
public Guid? TargetKey { get; set; }
}

View File

@@ -0,0 +1,17 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Notifications;
public class DictionaryItemMovedNotification : MovedNotification<IDictionaryItem>
{
public DictionaryItemMovedNotification(MoveEventInfo<IDictionaryItem> target, EventMessages messages)
: base(target, messages)
{
}
public DictionaryItemMovedNotification(IEnumerable<MoveEventInfo<IDictionaryItem>> target, EventMessages messages)
: base(target, messages)
{
}
}

View File

@@ -0,0 +1,17 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Notifications;
public class DictionaryItemMovingNotification : MovingNotification<IDictionaryItem>
{
public DictionaryItemMovingNotification(MoveEventInfo<IDictionaryItem> target, EventMessages messages)
: base(target, messages)
{
}
public DictionaryItemMovingNotification(IEnumerable<MoveEventInfo<IDictionaryItem>> target, EventMessages messages)
: base(target, messages)
{
}
}

View File

@@ -171,6 +171,67 @@ internal sealed class DictionaryItemService : RepositoryService, IDictionaryItem
}
}
/// <inheritdoc/>
public async Task<Attempt<IDictionaryItem, DictionaryItemOperationStatus>> MoveAsync(
IDictionaryItem dictionaryItem,
Guid? parentId,
int userId = Constants.Security.SuperUserId)
{
// same parent? then just ignore this operation, assume success.
if (dictionaryItem.ParentId == parentId)
{
return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem);
}
// cannot move a dictionary item underneath itself
if (dictionaryItem.Key == parentId)
{
return Attempt.FailWithStatus(DictionaryItemOperationStatus.InvalidParent, dictionaryItem);
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
IDictionaryItem? parent = parentId.HasValue ? _dictionaryRepository.Get(parentId.Value) : null;
// validate parent if applicable
if (parentId.HasValue && parent == null)
{
return Attempt.FailWithStatus(DictionaryItemOperationStatus.ParentNotFound, dictionaryItem);
}
// ensure we don't move a dictionary item underneath one of its own descendants
if (parent != null)
{
IEnumerable<IDictionaryItem> descendants = _dictionaryRepository.GetDictionaryItemDescendants(dictionaryItem.Key);
if (descendants.Any(item => item.Key == parent.Key))
{
return Attempt.FailWithStatus(DictionaryItemOperationStatus.InvalidParent, dictionaryItem);
}
}
dictionaryItem.ParentId = parentId;
EventMessages eventMessages = EventMessagesFactory.Get();
var moveEventInfo = new MoveEventInfo<IDictionaryItem>(dictionaryItem, string.Empty, parent?.Id ?? Constants.System.Root);
var movingNotification = new DictionaryItemMovingNotification(moveEventInfo, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(movingNotification))
{
scope.Complete();
return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem);
}
_dictionaryRepository.Save(dictionaryItem);
scope.Notifications.Publish(
new DictionaryItemMovedNotification(moveEventInfo, eventMessages).WithStateFrom(movingNotification));
Audit(AuditType.Move, "Move DictionaryItem", userId, dictionaryItem.Id, nameof(DictionaryItem));
scope.Complete();
return await Task.FromResult(Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem));
}
}
private async Task<Attempt<IDictionaryItem, DictionaryItemOperationStatus>> SaveAsync(
IDictionaryItem dictionaryItem,
Func<DictionaryItemOperationStatus> operationValidation,

View File

@@ -90,4 +90,12 @@ public interface IDictionaryItemService
/// <param name="id">The ID of the <see cref="IDictionaryItem" /> to delete</param>
/// <param name="userId">Optional id of the user deleting the dictionary item</param>
Task<Attempt<IDictionaryItem?, DictionaryItemOperationStatus>> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId);
/// <summary>
/// Moves a <see cref="IDictionaryItem" /> object
/// </summary>
/// <param name="dictionaryItem"><see cref="IDictionaryItem" /> to move</param>
/// <param name="parentId">Id of the new <see cref="IDictionaryItem" /> parent, null if the item should be moved to the root</param>
/// <param name="userId">Optional id of the user moving the dictionary item</param>
Task<Attempt<IDictionaryItem, DictionaryItemOperationStatus>> MoveAsync(IDictionaryItem dictionaryItem, Guid? parentId, int userId = Constants.Security.SuperUserId);
}

View File

@@ -8,5 +8,6 @@ public enum DictionaryItemOperationStatus
ItemNotFound,
ParentNotFound,
InvalidId,
DuplicateKey
DuplicateKey,
InvalidParent
}

View File

@@ -174,6 +174,39 @@ public class DictionaryItemServiceTests : UmbracoIntegrationTest
Assert.AreEqual(1, item.Translations.Count());
}
[Test]
public async Task Can_Create_DictionaryItem_Under_Parent_DictionaryItem()
{
var english = await LanguageService.GetAsync("en-US");
var result = await DictionaryItemService.CreateAsync(
new DictionaryItem("Testing123")
{
Translations = new List<IDictionaryTranslation> { new DictionaryTranslation(english, "Hello parent") }
});
Assert.True(result.Success);
var parentKey = result.Result.Key;
result = await DictionaryItemService.CreateAsync(
new DictionaryItem("Testing456")
{
Translations = new List<IDictionaryTranslation> { new DictionaryTranslation(english, "Hello child") },
ParentId = parentKey
});
Assert.True(result.Success);
// re-get
var item = await DictionaryItemService.GetAsync(result.Result!.Key);
Assert.NotNull(item);
Assert.Greater(item.Id, 0);
Assert.IsTrue(item.HasIdentity);
Assert.IsTrue(item.ParentId.HasValue);
Assert.AreEqual("Testing456", item.ItemKey);
Assert.AreEqual(1, item.Translations.Count());
Assert.AreEqual(parentKey, item.ParentId);
}
[Test]
public async Task Can_Create_DictionaryItem_At_Root_With_All_Languages()
{
@@ -331,6 +364,68 @@ public class DictionaryItemServiceTests : UmbracoIntegrationTest
}
}
[Test]
public async Task Can_Move_DictionaryItem_To_Root()
{
var rootOneKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootOne"))).Result.Key;
var childKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("ChildOne") { ParentId = rootOneKey })).Result.Key;
var child = await DictionaryItemService.GetAsync(childKey);
Assert.AreEqual(rootOneKey, child.ParentId);
var result = await DictionaryItemService.MoveAsync(child, null);
Assert.IsTrue(result.Success);
Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status);
child = await DictionaryItemService.GetAsync(childKey);
Assert.AreEqual(null, child.ParentId);
var rootItemKeys = (await DictionaryItemService.GetAtRootAsync()).Select(item => item.Key);
Assert.True(rootItemKeys.Contains(childKey));
var rootOneChildren = await DictionaryItemService.GetChildrenAsync(rootOneKey);
Assert.AreEqual(0, rootOneChildren.Count());
}
[Test]
public async Task Can_Move_DictionaryItem_To_Other_Parent()
{
var rootOneKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootOne"))).Result.Key;
var rootTwoKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootTwo"))).Result.Key;
var childKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("ChildOne") { ParentId = rootOneKey })).Result.Key;
var child = await DictionaryItemService.GetAsync(childKey);
Assert.AreEqual(rootOneKey, child.ParentId);
var result = await DictionaryItemService.MoveAsync(child, rootTwoKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status);
child = await DictionaryItemService.GetAsync(childKey);
Assert.AreEqual(rootTwoKey, child.ParentId);
}
[Test]
public async Task Can_Move_DictionaryItem_From_Root()
{
var rootOneKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootOne"))).Result.Key;
var rootTwoKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootTwo"))).Result.Key;
var childKey = (await DictionaryItemService.CreateAsync(new DictionaryItem("ChildOne") { ParentId = rootOneKey })).Result.Key;
var rootTwo = await DictionaryItemService.GetAsync(rootTwoKey);
Assert.IsNull(rootTwo.ParentId);
var result = await DictionaryItemService.MoveAsync(rootTwo, childKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status);
rootTwo = await DictionaryItemService.GetAsync(rootTwoKey);
Assert.AreEqual(childKey, rootTwo.ParentId);
var rootItemKeys = (await DictionaryItemService.GetAtRootAsync()).Select(item => item.Key);
Assert.IsFalse(rootItemKeys.Contains(rootTwoKey));
}
[Test]
public async Task Cannot_Add_Duplicate_DictionaryItem_ItemKey()
{
@@ -450,6 +545,48 @@ public class DictionaryItemServiceTests : UmbracoIntegrationTest
Assert.AreEqual(DictionaryItemOperationStatus.InvalidId, result.Status);
}
[Test]
public async Task Cannot_Move_DictionaryItem_To_Itself()
{
var root = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootOne"))).Result;
var result = await DictionaryItemService.MoveAsync(root, root.Key);
Assert.IsFalse(result.Success);
Assert.AreEqual(DictionaryItemOperationStatus.InvalidParent, result.Status);
root = await DictionaryItemService.GetAsync(root.Key);
Assert.IsNull(root.ParentId);
}
[Test]
public async Task Cannot_Move_DictionaryItem_To_Child()
{
var root = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootOne"))).Result;
var child = (await DictionaryItemService.CreateAsync(new DictionaryItem("ChildOne") { ParentId = root.Key })).Result;
var result = await DictionaryItemService.MoveAsync(root, child.Key);
Assert.IsFalse(result.Success);
Assert.AreEqual(DictionaryItemOperationStatus.InvalidParent, result.Status);
root = await DictionaryItemService.GetAsync(root.Key);
Assert.IsNull(root.ParentId);
}
[Test]
public async Task Cannot_Move_DictionaryItem_To_Descendant()
{
var root = (await DictionaryItemService.CreateAsync(new DictionaryItem("RootOne"))).Result;
var child = (await DictionaryItemService.CreateAsync(new DictionaryItem("ChildOne") { ParentId = root.Key })).Result;
var grandChild = (await DictionaryItemService.CreateAsync(new DictionaryItem("GrandChildOne") { ParentId = child.Key })).Result;
var result = await DictionaryItemService.MoveAsync(root, grandChild.Key);
Assert.IsFalse(result.Success);
Assert.AreEqual(DictionaryItemOperationStatus.InvalidParent, result.Status);
root = await DictionaryItemService.GetAsync(root.Key);
Assert.IsNull(root.ParentId);
}
private async Task CreateTestData()
{
var languageDaDk = new LanguageBuilder()