diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 39928580c6..9c289ff665 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -35,7 +35,7 @@ internal sealed class ContentEditingService return await Task.FromResult(content); } - public async Task> CreateAsync(ContentCreateModel createModel, int userId) + public async Task> CreateAsync(ContentCreateModel createModel, int userId = Constants.Security.SuperUserId) { Attempt result = await MapCreate(createModel); if (result.Success == false) @@ -56,7 +56,7 @@ internal sealed class ContentEditingService : Attempt.FailWithStatus(operationStatus, content); } - public async Task> UpdateAsync(IContent content, ContentUpdateModel updateModel, int userId) + public async Task> UpdateAsync(IContent content, ContentUpdateModel updateModel, int userId = Constants.Security.SuperUserId) { Attempt result = await MapUpdate(content, updateModel); if (result.Success == false) @@ -76,10 +76,10 @@ internal sealed class ContentEditingService : Attempt.FailWithStatus(operationStatus, content); } - public async Task> MoveToRecycleBinAsync(Guid id, int userId) + public async Task> MoveToRecycleBinAsync(Guid id, int userId = Constants.Security.SuperUserId) => await HandleDeletionAsync(id, content => ContentService.MoveToRecycleBin(content, userId)); - public async Task> DeleteAsync(Guid id, int userId) + public async Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId) => await HandleDeletionAsync(id, content => ContentService.Delete(content, userId)); protected override IContent Create(string? name, int parentId, IContentType contentType) => new Content(name, parentId, contentType); diff --git a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs index ba5d00932c..93bf43c264 100644 --- a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs @@ -156,6 +156,19 @@ public abstract class ContentEditingServiceBase c.Key).ToArray() + ?? Array.Empty(); + + if (allowedContentTypeKeys.Contains(contentType.Key) == false) + { + operationStatus = ContentEditingOperationStatus.NotAllowed; + return null; + } + } + operationStatus = ContentEditingOperationStatus.Success; return parent; } diff --git a/src/Umbraco.Core/Services/IContentEditingService.cs b/src/Umbraco.Core/Services/IContentEditingService.cs index c38e15f436..512a0fb3ad 100644 --- a/src/Umbraco.Core/Services/IContentEditingService.cs +++ b/src/Umbraco.Core/Services/IContentEditingService.cs @@ -8,9 +8,11 @@ public interface IContentEditingService { Task GetAsync(Guid id); - Task> CreateAsync(ContentCreateModel createModel, int userId); + Task> CreateAsync(ContentCreateModel createModel, int userId = Constants.Security.SuperUserId); - Task> UpdateAsync(IContent content, ContentUpdateModel updateMode, int userId); + Task> UpdateAsync(IContent content, ContentUpdateModel updateModel, int userId = Constants.Security.SuperUserId); - Task> MoveToRecycleBinAsync(Guid id, int userId); + Task> MoveToRecycleBinAsync(Guid id, int userId = Constants.Security.SuperUserId); + + Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId); } diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs index f8cdd22cfb..7040f30bae 100644 --- a/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs @@ -10,7 +10,7 @@ public enum ContentEditingOperationStatus ParentNotFound, NotAllowed, TemplateNotFound, + TemplateNotAllowed, PropertyTypeNotFound, - Unknown, - TemplateNotAllowed + Unknown } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Create.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Create.cs new file mode 100644 index 0000000000..8711a6dfde --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Create.cs @@ -0,0 +1,428 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Tests.Common.Builders; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +public partial class ContentEditingServiceTests +{ + [TestCase(true)] + [TestCase(false)] + public async Task Create_At_Root(bool allowedAtRoot) + { + var template = TemplateBuilder.CreateTextPageTemplate(); + await TemplateService.CreateAsync(template); + + var contentType = ContentTypeBuilder.CreateTextPageContentType(defaultTemplateId: template.Id); + contentType.AllowedAsRoot = allowedAtRoot; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + TemplateKey = template.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The title value" }, + new PropertyValueModel { Alias = "bodyText", Value = "The body text" } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + + if (allowedAtRoot) + { + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + VerifyCreate(result.Result); + + // re-get and re-test + VerifyCreate(await ContentEditingService.GetAsync(result.Result!.Key)); + + void VerifyCreate(IContent? createdContent) + { + Assert.IsNotNull(createdContent); + Assert.AreNotEqual(Guid.Empty, createdContent.Key); + Assert.IsTrue(createdContent.HasIdentity); + Assert.AreEqual("Test Create", createdContent.Name); + Assert.AreEqual("The title value", createdContent.GetValue("title")); + Assert.AreEqual("The body text", createdContent.GetValue("bodyText")); + } + } + else + { + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.NotAllowed, result.Status); + Assert.IsNull(result.Result); + } + } + + [TestCase(true)] + [TestCase(false)] + public async Task Create_As_Child(bool allowedAsChild) + { + var template = TemplateBuilder.CreateTextPageTemplate(); + await TemplateService.CreateAsync(template); + + var childContentType = ContentTypeBuilder.CreateTextPageContentType(defaultTemplateId: template.Id); + childContentType.AllowedAsRoot = false; + ContentTypeService.Save(childContentType); + + var rootContentType = ContentTypeBuilder.CreateBasicContentType(); + rootContentType.AllowedAsRoot = true; + if (allowedAsChild) + { + rootContentType.AllowedContentTypes = new[] + { + new ContentTypeSort(new Lazy(() => childContentType.Id), childContentType.Key, 1, childContentType.Alias) + }; + } + ContentTypeService.Save(rootContentType); + + var rootKey = (await ContentEditingService.CreateAsync(new ContentCreateModel + { + ContentTypeKey = rootContentType.Key, InvariantName = "Root", ParentKey = Constants.System.RootKey, + })).Result.Key; + + var createModel = new ContentCreateModel + { + ContentTypeKey = childContentType.Key, + TemplateKey = template.Key, + ParentKey = rootKey, + InvariantName = "Test Create Child", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The child title value" }, + new PropertyValueModel { Alias = "bodyText", Value = "The child body text" } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + + if (allowedAsChild) + { + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + + var createdContent = result.Result; + Assert.NotNull(createdContent); + Assert.AreNotEqual(Guid.Empty, createdContent.Key); + Assert.IsTrue(createdContent.HasIdentity); + Assert.AreEqual("Test Create Child", createdContent.Name); + Assert.AreEqual("The child title value", createdContent.GetValue("title")); + Assert.AreEqual("The child body text", createdContent.GetValue("bodyText")); + } + else + { + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.NotAllowed, result.Status); + Assert.IsNull(result.Result); + } + } + + [Test] + public async Task Can_Create_Without_Template() + { + var contentType = ContentTypeBuilder.CreateContentMetaContentType(); + contentType.AllowedTemplates = null; + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The title value" } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.Result.HasIdentity); + Assert.AreEqual("The title value", result.Result.GetValue("title")); + } + + [Test] + public async Task Can_Create_Without_Properties() + { + var template = TemplateBuilder.CreateTextPageTemplate(); + await TemplateService.CreateAsync(template); + + var contentType = ContentTypeBuilder.CreateTextPageContentType(defaultTemplateId: template.Id); + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create" + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.Result.HasIdentity); + Assert.AreEqual(null, result.Result.GetValue("title")); + Assert.AreEqual(null, result.Result.GetValue("bodyText")); + } + + [Test] + public async Task Cannot_Create_With_Non_Existing_Parent() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Guid.NewGuid(), + InvariantName = "Test Create" + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.ParentNotFound, result.Status); + Assert.IsNull(result.Result); + } + + [Test] + public async Task Cannot_Create_Without_Content_Type() + { + var createModel = new ContentCreateModel + { + ContentTypeKey = Guid.NewGuid(), + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create" + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.ContentTypeNotFound, result.Status); + Assert.IsNull(result.Result); + } + + [Test] + public async Task Cannot_Create_With_Invalid_Template() + { + var template = TemplateBuilder.CreateTextPageTemplate(); + await TemplateService.CreateAsync(template); + + var contentType = ContentTypeBuilder.CreateBasicContentType(); + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + TemplateKey = template.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create" + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.TemplateNotAllowed, result.Status); + Assert.IsNotNull(result.Result); + Assert.IsFalse(result.Result.HasIdentity); + } + + [Test] + public async Task Cannot_Create_With_Non_Existing_Template() + { + var contentType = ContentTypeBuilder.CreateBasicContentType(); + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + TemplateKey = Guid.NewGuid(), + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create" + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.TemplateNotFound, result.Status); + Assert.IsNotNull(result.Result); + Assert.IsFalse(result.Result.HasIdentity); + } + + [Test] + public async Task Cannot_Create_With_Non_Existing_Properties() + { + var contentType = ContentTypeBuilder.CreateContentMetaContentType(); + contentType.AllowedTemplates = null; + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The title value" }, + new PropertyValueModel { Alias = "no_such_property", Value = "No such property value" }, + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.PropertyTypeNotFound, result.Status); + Assert.IsNull(result.Result); + } + + [Test] + public async Task Cannot_Create_Invariant_Content_Without_Name() + { + var contentType = ContentTypeBuilder.CreateContentMetaContentType(); + contentType.AllowedTemplates = null; + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantName = null, + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The title value" } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.ContentTypeCultureVarianceMismatch, result.Status); + Assert.IsNull(result.Result); + } + + [Test] + public async Task Cannot_Create_With_Variant_Property_Value_For_Invariant_Content() + { + var contentType = ContentTypeBuilder.CreateContentMetaContentType(); + contentType.AllowedTemplates = null; + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Test Create", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The title value" } + }, + Variants = new [] + { + new VariantModel + { + Culture = "en-US", + Name = "The English Name", + Properties = new [] + { + new PropertyValueModel { Alias = "bodyText", Value = "The body text value" } + } + } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.ContentTypeCultureVarianceMismatch, result.Status); + Assert.IsNull(result.Result); + } + + [Test] + public async Task Can_Create_Culture_Variant() + { + var contentType = await CreateVariantContentType(); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "invariantTitle", Value = "The Invariant Title" } + }, + Variants = new[] + { + new VariantModel + { + Culture = "en-US", + Name = "The English Name", + Properties = new[] + { + new PropertyValueModel { Alias = "variantTitle", Value = "The English Title" } + } + }, + new VariantModel + { + Culture = "da-DK", + Name = "The Danish Name", + Properties = new[] + { + new PropertyValueModel { Alias = "variantTitle", Value = "The Danish Title" } + } + } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + VerifyCreate(result.Result); + + // re-get and re-test + VerifyCreate(await ContentEditingService.GetAsync(result.Result!.Key)); + + void VerifyCreate(IContent? createdContent) + { + Assert.IsNotNull(createdContent); + Assert.AreEqual("The English Name", createdContent.GetCultureName("en-US")); + Assert.AreEqual("The Danish Name", createdContent.GetCultureName("da-DK")); + Assert.AreEqual("The Invariant Title", createdContent.GetValue("invariantTitle")); + Assert.AreEqual("The English Title", createdContent.GetValue("variantTitle", "en-US")); + Assert.AreEqual("The Danish Title", createdContent.GetValue("variantTitle", "da-DK")); + } + } + + [Test] + public async Task Cannot_Create_With_Invariant_Property_Value_For_Variant_Content() + { + var contentType = await CreateVariantContentType(); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "invariantTitle", Value = "The Invariant Title" }, + new PropertyValueModel { Alias = "variantTitle", Value = "The Variant Title" } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.PropertyTypeNotFound, result.Status); + Assert.IsNull(result.Result); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Delete.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Delete.cs new file mode 100644 index 0000000000..4cd39d42d0 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Delete.cs @@ -0,0 +1,30 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +public partial class ContentEditingServiceTests +{ + [TestCase(true)] + [TestCase(true)] + public async Task Can_Delete(bool variant) + { + var content = await (variant ? CreateVariantContent() : CreateInvariantContent()); + + var result = await ContentEditingService.DeleteAsync(content.Key); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + + // re-get and verify deletion + content = await ContentEditingService.GetAsync(content.Key); + Assert.IsNull(content); + } + + [Test] + public async Task Cannot_Delete_Non_Existing() + { + var result = await ContentEditingService.DeleteAsync(Guid.NewGuid()); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.NotFound, result.Status); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs new file mode 100644 index 0000000000..f162e2d56b --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs @@ -0,0 +1,24 @@ +using NUnit.Framework; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +public partial class ContentEditingServiceTests +{ + [TestCase(true)] + [TestCase(true)] + public async Task Can_Get(bool variant) + { + var content = await (variant ? CreateVariantContent() : CreateInvariantContent()); + + var result = await ContentEditingService.GetAsync(content.Key); + Assert.IsNotNull(result); + Assert.AreEqual(content.Key, result.Key); + } + + [Test] + public async Task Cannot_Get_Non_Existing() + { + var result = await ContentEditingService.GetAsync(Guid.NewGuid()); + Assert.IsNull(result); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs new file mode 100644 index 0000000000..f769868a22 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs @@ -0,0 +1,255 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +public partial class ContentEditingServiceTests +{ + [Test] + public async Task Can_Update_Invariant() + { + var content = await CreateInvariantContent(); + + var updateModel = new ContentUpdateModel + { + InvariantName = "Updated Name", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The updated title" }, + new PropertyValueModel { Alias = "text", Value = "The updated text" } + } + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + VerifyUpdate(result.Result); + + // re-get and re-test + VerifyUpdate(await ContentEditingService.GetAsync(content.Key)); + + void VerifyUpdate(IContent? updatedContent) + { + Assert.IsNotNull(updatedContent); + Assert.AreEqual("Updated Name", updatedContent.Name); + Assert.AreEqual("The updated title", updatedContent.GetValue("title")); + Assert.AreEqual("The updated text", updatedContent.GetValue("text")); + } + } + + [Test] + public async Task Can_Update_Variant() + { + var content = await CreateVariantContent(); + + var updateModel = new ContentUpdateModel + { + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" } + }, + Variants = new [] + { + new VariantModel + { + Culture = "en-US", + Name = "Updated English Name", + Properties = new [] + { + new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" } + } + }, + new VariantModel + { + Culture = "da-DK", + Name = "Updated Danish Name", + Properties = new [] + { + new PropertyValueModel { Alias = "variantTitle", Value = "The updated Danish title" } + } + } + } + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + VerifyUpdate(result.Result); + + // re-get and re-test + VerifyUpdate(await ContentEditingService.GetAsync(content.Key)); + + void VerifyUpdate(IContent? updatedContent) + { + Assert.IsNotNull(updatedContent); + Assert.AreEqual("Updated English Name", updatedContent.GetCultureName("en-US")); + Assert.AreEqual("Updated Danish Name", updatedContent.GetCultureName("da-DK")); + Assert.AreEqual("The updated invariant title", updatedContent.GetValue("invariantTitle")); + Assert.AreEqual("The updated English title", updatedContent.GetValue("variantTitle", "en-US")); + Assert.AreEqual("The updated Danish title", updatedContent.GetValue("variantTitle", "da-DK")); + } + } + + [Test] + public async Task Can_Update_Template() + { + var templateOne = new TemplateBuilder().WithAlias("textPageOne").WithName("Text page one").Build(); + var templateTwo = new TemplateBuilder().WithAlias("textPageTwo").WithName("Text page two").Build(); + await TemplateService.CreateAsync(templateOne); + await TemplateService.CreateAsync(templateTwo); + + var content = await CreateInvariantContent(templateOne, templateTwo); + Assert.AreEqual(templateOne.Id, content.TemplateId); + + var updateModel = new ContentUpdateModel + { + InvariantName = "Updated Name", + TemplateKey = templateTwo.Key + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + VerifyUpdate(result.Result); + + // re-get and re-test + VerifyUpdate(await ContentEditingService.GetAsync(content.Key)); + + void VerifyUpdate(IContent? updatedContent) + { + Assert.IsNotNull(updatedContent); + Assert.AreEqual("Updated Name", updatedContent.Name); + Assert.AreEqual(templateTwo.Id, updatedContent.TemplateId); + } + } + + [Test] + public async Task Can_Remove_Template() + { + var templateOne = new TemplateBuilder().WithAlias("textPageOne").WithName("Text page one").Build(); + await TemplateService.CreateAsync(templateOne); + + var content = await CreateInvariantContent(templateOne); + Assert.AreEqual(templateOne.Id, content.TemplateId); + + var updateModel = new ContentUpdateModel + { + InvariantName = "Updated Name", + TemplateKey = null + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + VerifyUpdate(result.Result); + + // re-get and re-test + VerifyUpdate(await ContentEditingService.GetAsync(content.Key)); + + void VerifyUpdate(IContent? updatedContent) + { + Assert.IsNotNull(updatedContent); + Assert.AreEqual("Updated Name", updatedContent.Name); + Assert.AreEqual(null, updatedContent.TemplateId); + } + } + + [Test] + public async Task Can_Remove_Property_Value() + { + var content = await CreateInvariantContent(); + + var updateModel = new ContentUpdateModel + { + InvariantName = "Updated Name", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The updated title" } + } + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + VerifyUpdate(result.Result); + + // re-get and re-test + VerifyUpdate(await ContentEditingService.GetAsync(content.Key)); + + void VerifyUpdate(IContent? updatedContent) + { + Assert.IsNotNull(updatedContent); + Assert.AreEqual("Updated Name", updatedContent.Name); + Assert.AreEqual("The updated title", updatedContent.GetValue("title")); + Assert.AreEqual(null, updatedContent.GetValue("text")); + } + } + + [Test] + public async Task Cannot_Update_With_Variant_Property_Value_For_Invariant_Content() + { + var content = await CreateInvariantContent(); + + var updateModel = new ContentUpdateModel + { + InvariantName = "Updated Name", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The updated title" }, + }, + Variants = new[] + { + new VariantModel + { + Culture = "en-US", + Name = "Updated English Name", + Properties = new[] + { + new PropertyValueModel { Alias = "text", Value = "The updated text" } + } + } + } + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.ContentTypeCultureVarianceMismatch, result.Status); + + // re-get and validate + content = await ContentEditingService.GetAsync(content.Key); + Assert.IsNotNull(content); + Assert.AreEqual("Initial Name", content.Name); + Assert.AreEqual("The initial title", content.GetValue("title")); + Assert.AreEqual("The initial text", content.GetValue("text")); + } + + [Test] + public async Task Cannot_Update_With_Invariant_Property_Value_For_Variant_Content() + { + var content = await CreateVariantContent(); + + var updateModel = new ContentUpdateModel + { + InvariantName = "Updated Name", + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }, + new PropertyValueModel { Alias = "variantTitle", Value = "The updated variant title" } + } + }; + + var result = await ContentEditingService.UpdateAsync(content, updateModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.PropertyTypeNotFound, result.Status); + Assert.IsNotNull(result.Result); + + // re-get and validate + content = await ContentEditingService.GetAsync(content.Key); + Assert.IsNotNull(content); + Assert.AreEqual("Initial English Name", content.GetCultureName("en-US")); + Assert.AreEqual("The initial invariant title", content.GetValue("invariantTitle")); + Assert.AreEqual("The initial English title", content.GetValue("variantTitle", "en-US")); + Assert.AreEqual("The initial Danish title", content.GetValue("variantTitle", "da-DK")); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.cs new file mode 100644 index 0000000000..6a792de915 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.cs @@ -0,0 +1,158 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +[TestFixture] +[UmbracoTest( + Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true)] +public partial class ContentEditingServiceTests : UmbracoIntegrationTestWithContent +{ + [SetUp] + public void Setup() => ContentRepositoryBase.ThrowOnWarning = true; + + private ITemplateService TemplateService => GetRequiredService(); + + private ILanguageService LanguageService => GetRequiredService(); + + private IContentEditingService ContentEditingService => GetRequiredService(); + + private IContentType CreateInvariantContentType(params ITemplate[] templates) + { + var contentTypeBuilder = new ContentTypeBuilder() + .WithAlias("invariantTest") + .WithName("Invariant Test") + .WithContentVariation(ContentVariation.Nothing) + .AddPropertyType() + .WithAlias("title") + .WithName("Title") + .WithVariations(ContentVariation.Nothing) + .Done() + .AddPropertyType() + .WithAlias("text") + .WithName("Text") + .WithVariations(ContentVariation.Nothing) + .Done(); + + foreach (var template in templates) + { + contentTypeBuilder + .AddAllowedTemplate() + .WithId(template.Id) + .WithAlias(template.Alias) + .WithName(template.Name ?? template.Alias) + .Done(); + } + + if (templates.Any()) + { + contentTypeBuilder.WithDefaultTemplateId(templates.First().Id); + } + + var contentType = contentTypeBuilder.Build(); + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + + return contentType; + } + + private async Task CreateVariantContentType() + { + var language = new LanguageBuilder() + .WithCultureInfo("da-DK") + .Build(); + await LanguageService.CreateAsync(language); + + var contentType = new ContentTypeBuilder() + .WithAlias("cultureVariationTest") + .WithName("Culture Variation Test") + .WithContentVariation(ContentVariation.Culture) + .AddPropertyType() + .WithAlias("variantTitle") + .WithName("Variant Title") + .WithVariations(ContentVariation.Culture) + .Done() + .AddPropertyType() + .WithAlias("invariantTitle") + .WithName("Invariant Title") + .WithVariations(ContentVariation.Nothing) + .Done() + .Build(); + contentType.AllowedAsRoot = true; + ContentTypeService.Save(contentType); + return contentType; + } + + private async Task CreateInvariantContent(params ITemplate[] templates) + { + var contentType = CreateInvariantContentType(templates); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantName = "Initial Name", + TemplateKey = templates.FirstOrDefault()?.Key, + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "title", Value = "The initial title" }, + new PropertyValueModel { Alias = "text", Value = "The initial text" } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsTrue(result.Success); + return result.Result!; + } + + private async Task CreateVariantContent() + { + var contentType = await CreateVariantContentType(); + + var createModel = new ContentCreateModel + { + ContentTypeKey = contentType.Key, + ParentKey = Constants.System.RootKey, + InvariantProperties = new[] + { + new PropertyValueModel { Alias = "invariantTitle", Value = "The initial invariant title" } + }, + Variants = new [] + { + new VariantModel + { + Culture = "en-US", + Name = "Initial English Name", + Properties = new [] + { + new PropertyValueModel { Alias = "variantTitle", Value = "The initial English title" } + } + }, + new VariantModel + { + Culture = "da-DK", + Name = "Initial Danish Name", + Properties = new [] + { + new PropertyValueModel { Alias = "variantTitle", Value = "The initial Danish title" } + } + } + } + }; + + var result = await ContentEditingService.CreateAsync(createModel); + Assert.IsTrue(result.Success); + return result.Result!; + } + } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 89624c0658..293ba6eed9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -49,5 +49,17 @@ + + ContentEditingServiceTests.cs + + + ContentEditingServiceTests.cs + + + ContentEditingServiceTests.cs + + + ContentEditingServiceTests.cs +