Elements: Split content type validation for create and update (#21906)
* Split content type validation for create and update to allow saving elements no longer permitted in library * Add integration test for element update after AllowedInLibrary toggle Verify that ElementEditingService.UpdateAsync succeeds when the content type's AllowedInLibrary flag is set to false after the element was created, covering the split validation introduced for create vs update. * Move content type validation into TryGetAndValidateContentType override Eliminate redundant content type lookups in CreateAsync and UpdateAsync by moving the IsElement/AllowedInLibrary check into the TryGetAndValidateContentType override, which distinguishes create from update by checking if the model is a ContentCreationModelBase. * Use Assert.Multiple for element property assertions in update test * Extract IsAllowedLibraryElement static method for readability
This commit is contained in:
@@ -86,40 +86,37 @@ internal sealed class ElementEditingService
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateElementUpdateModel updateModel, Guid userKey)
|
||||
{
|
||||
IElement? content = _elementService.GetById(key);
|
||||
return content is not null
|
||||
? await ValidateCulturesAndPropertiesAsync(
|
||||
updateModel,
|
||||
content.ContentType.Key,
|
||||
updateModel.Cultures,
|
||||
userKey)
|
||||
: Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
|
||||
if (content is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
|
||||
}
|
||||
|
||||
return await ValidateCulturesAndPropertiesAsync(
|
||||
updateModel,
|
||||
content.ContentType.Key,
|
||||
updateModel.Cultures,
|
||||
userKey);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ElementCreateModel createModel, Guid userKey)
|
||||
=> await ValidateCulturesAndPropertiesAsync(
|
||||
createModel,
|
||||
createModel.ContentTypeKey,
|
||||
createModel.Variants.Select(variant => variant.Culture),
|
||||
userKey);
|
||||
|
||||
protected override IContentType? TryGetAndValidateContentType(
|
||||
Guid contentTypeKey, ContentEditingModelBase contentEditingModelBase,
|
||||
out ContentEditingOperationStatus operationStatus)
|
||||
{
|
||||
IContentType? contentType = base.TryGetAndValidateContentType(contentTypeKey, contentEditingModelBase, out operationStatus);
|
||||
IContentType? contentType = ContentTypeService.Get(createModel.ContentTypeKey);
|
||||
if (contentType is null)
|
||||
{
|
||||
return null;
|
||||
return Attempt.FailWithStatus(ContentEditingOperationStatus.ContentTypeNotFound, new ContentValidationResult());
|
||||
}
|
||||
|
||||
if (contentType.IsElement is false || contentType.AllowedInLibrary is false)
|
||||
{
|
||||
operationStatus = ContentEditingOperationStatus.NotAllowed;
|
||||
return null;
|
||||
return Attempt.FailWithStatus(ContentEditingOperationStatus.NotAllowed, new ContentValidationResult());
|
||||
}
|
||||
|
||||
return contentType;
|
||||
return await ValidateCulturesAndPropertiesAsync(
|
||||
createModel,
|
||||
createModel.ContentTypeKey,
|
||||
createModel.Variants.Select(variant => variant.Culture),
|
||||
userKey);
|
||||
}
|
||||
|
||||
public async Task<Attempt<ElementCreateResult, ContentEditingOperationStatus>> CreateAsync(ElementCreateModel createModel, Guid userKey)
|
||||
@@ -192,6 +189,27 @@ internal sealed class ElementEditingService
|
||||
protected override IElement New(string? name, int parentId, IContentType contentType)
|
||||
=> new Element(name, parentId, contentType);
|
||||
|
||||
protected override IContentType? TryGetAndValidateContentType(
|
||||
Guid contentTypeKey, ContentEditingModelBase contentEditingModelBase,
|
||||
out ContentEditingOperationStatus operationStatus)
|
||||
{
|
||||
IContentType? contentType = base.TryGetAndValidateContentType(contentTypeKey, contentEditingModelBase, out operationStatus);
|
||||
if (contentType is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only enforce IsElement + AllowedInLibrary on create; updates only need the content type to exist
|
||||
if (contentEditingModelBase is ContentCreationModelBase
|
||||
&& IsAllowedLibraryElement(contentType) is false)
|
||||
{
|
||||
operationStatus = ContentEditingOperationStatus.NotAllowed;
|
||||
return null;
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
protected override async Task<(int? ParentId, ContentEditingOperationStatus OperationStatus)> TryGetAndValidateParentIdAsync(Guid? parentKey, IContentType contentType)
|
||||
{
|
||||
if (parentKey.HasValue is false)
|
||||
@@ -497,4 +515,7 @@ internal sealed class ElementEditingService
|
||||
return ContentEditingOperationStatus.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllowedLibraryElement(IContentType contentType)
|
||||
=> contentType.IsElement && contentType.AllowedInLibrary;
|
||||
}
|
||||
|
||||
+37
@@ -126,6 +126,43 @@ public partial class ElementEditingServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Update_Element_After_ContentType_Disallowed_From_Library()
|
||||
{
|
||||
var elementType = await CreateInvariantElementType();
|
||||
var element = await CreateInvariantElement(contentTypeKey: elementType.Key);
|
||||
|
||||
// Disallow the content type from the library after the element has been created
|
||||
elementType.AllowedInLibrary = false;
|
||||
await ContentTypeService.UpdateAsync(elementType, Constants.Security.SuperUserKey);
|
||||
|
||||
var updateModel = new ElementUpdateModel
|
||||
{
|
||||
Variants =
|
||||
[
|
||||
new VariantModel { Name = "Updated Name" }
|
||||
],
|
||||
Properties =
|
||||
[
|
||||
new PropertyValueModel { Alias = "title", Value = "The updated title" },
|
||||
new PropertyValueModel { Alias = "text", Value = "The updated text" }
|
||||
],
|
||||
};
|
||||
|
||||
var result = await ElementEditingService.UpdateAsync(element.Key, updateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
|
||||
|
||||
var updatedElement = result.Result.Content;
|
||||
Assert.IsNotNull(updatedElement);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("Updated Name", updatedElement.Name);
|
||||
Assert.AreEqual("The updated title", updatedElement.GetValue<string>("title"));
|
||||
Assert.AreEqual("The updated text", updatedElement.GetValue<string>("text"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Update_Culture_And_Segment_Variant()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user