Only apply validation on content update to variant cultures where the editor has permission for the culture (#18778)

* Only apply validation on content update to variant cultures where the editor has permission for the culture.

* Remove inadvertent comment updates.

* Fixed failing integration test.
This commit is contained in:
Andy Butland
2025-04-03 22:09:40 +02:00
committed by GitHub
parent 7d41791543
commit 3e6b9313e5
9 changed files with 389 additions and 55 deletions

View File

@@ -1,11 +1,14 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -16,15 +19,32 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
{
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
private readonly IContentEditingService _contentEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public ValidateCreateDocumentController(
IAuthorizationService authorizationService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
IContentEditingService contentEditingService)
: this(
authorizationService,
documentEditingPresentationFactory,
contentEditingService,
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
{
}
[ActivatorUtilitiesConstructor]
public ValidateCreateDocumentController(
IAuthorizationService authorizationService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
IContentEditingService contentEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_documentEditingPresentationFactory = documentEditingPresentationFactory;
_contentEditingService = contentEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPost("validate")]
@@ -36,7 +56,10 @@ public class ValidateCreateDocumentController : CreateDocumentControllerBase
=> await HandleRequest(requestModel, async () =>
{
ContentCreateModel model = _documentEditingPresentationFactory.MapCreateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateCreateAsync(model);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
await _contentEditingService.ValidateCreateAsync(
model,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()

View File

@@ -1,11 +1,14 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -17,15 +20,32 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
{
private readonly IContentEditingService _contentEditingService;
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public ValidateUpdateDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory)
: this(
authorizationService,
contentEditingService,
documentEditingPresentationFactory,
StaticServiceProvider.Instance.GetRequiredService<IBackOfficeSecurityAccessor>())
{
}
[ActivatorUtilitiesConstructor]
public ValidateUpdateDocumentController(
IAuthorizationService authorizationService,
IContentEditingService contentEditingService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_contentEditingService = contentEditingService;
_documentEditingPresentationFactory = documentEditingPresentationFactory;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPut("{id:guid}/validate")]
@@ -62,7 +82,11 @@ public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
=> await HandleRequest(id, requestModel, async () =>
{
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
await _contentEditingService.ValidateUpdateAsync(
id,
model,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()

View File

@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
@@ -64,7 +64,7 @@ internal sealed class ContentEditingService
return await Task.FromResult(content);
}
[Obsolete("Please use the validate update method that is not obsoleted. Will be removed in V16.")]
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in V16.")]
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ContentUpdateModel updateModel)
{
IContent? content = ContentService.GetById(key);
@@ -73,16 +73,50 @@ internal sealed class ContentEditingService
: Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
}
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in V17.")]
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel)
=> await ValidateUpdateAsync(key, updateModel, Guid.Empty);
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel, Guid userKey)
{
IContent? content = ContentService.GetById(key);
return content is not null
? await ValidateCulturesAndPropertiesAsync(updateModel, content.ContentType.Key, updateModel.Cultures)
? await ValidateCulturesAndPropertiesAsync(updateModel, content.ContentType.Key, await GetCulturesToValidate(updateModel.Cultures, userKey))
: Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
}
[Obsolete("Please use the validate create method that is not obsoleted. Scheduled for removal in V17.")]
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel)
=> await ValidateCulturesAndPropertiesAsync(createModel, createModel.ContentTypeKey, createModel.Variants.Select(variant => variant.Culture));
=> await ValidateCreateAsync(createModel, Guid.Empty);
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel, Guid userKey)
=> await ValidateCulturesAndPropertiesAsync(createModel, createModel.ContentTypeKey, await GetCulturesToValidate(createModel.Variants.Select(variant => variant.Culture), userKey));
private async Task<IEnumerable<string?>?> GetCulturesToValidate(IEnumerable<string?>? cultures, Guid userKey)
{
// Cultures to validate can be provided by the calling code, but if the editor is restricted to only have
// access to certain languages, we don't want to validate by any they aren't allowed to edit.
// TODO: Remove this check once the obsolete overloads to ValidateCreateAsync and ValidateUpdateAsync that don't provide a user key are removed.
// We only have this to ensure backwards compatibility with the obsolete overloads.
if (userKey == Guid.Empty)
{
return cultures;
}
HashSet<string>? allowedCultures = await GetAllowedCulturesForEditingUser(userKey);
if (cultures == null)
{
// If no cultures are provided, we are asking to validate all cultures. But if the user doesn't have access to all, we
// should only validate the ones they do.
var allCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToList();
return allowedCultures.Count == allCultures.Count ? null : allowedCultures;
}
// If explicit cultures are provided, we should only validate the ones the user has access to.
return cultures.Where(x => !string.IsNullOrEmpty(x) && allowedCultures.Contains(x)).ToList();
}
public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateAsync(ContentCreateModel createModel, Guid userKey)
{
@@ -127,16 +161,7 @@ internal sealed class ContentEditingService
IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key);
IUser? user = await _userService.GetAsync(userKey);
if (user is null)
{
return contentWithPotentialUnallowedChanges;
}
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
HashSet<string>? allowedCultures = await GetAllowedCulturesForEditingUser(userKey);
ILanguage? defaultLanguage = await _languageService.GetDefaultLanguageAsync();
@@ -211,6 +236,16 @@ internal sealed class ContentEditingService
return contentWithPotentialUnallowedChanges;
}
private async Task<HashSet<string>> GetAllowedCulturesForEditingUser(Guid userKey)
{
IUser? user = await _userService.GetAsync(userKey)
?? throw new InvalidOperationException($"Could not find user by key {userKey} when editing or validating content.");
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
return (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
}
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey)
{
IContent? content = ContentService.GetById(key);

View File

@@ -1,4 +1,4 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -8,13 +8,25 @@ public interface IContentEditingService
{
Task<IContent?> GetAsync(Guid key);
[Obsolete("Please use the validate create method that is not obsoleted. Scheduled for removal in Umbraco 17.")]
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel);
[Obsolete("Please use the validate update method that is not obsoleted. Will be removed in V16.")]
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel, Guid userKey)
#pragma warning disable CS0618 // Type or member is obsolete
=> ValidateCreateAsync(createModel);
#pragma warning restore CS0618 // Type or member is obsolete
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in Umbraco 16.")]
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ContentUpdateModel updateModel);
[Obsolete("Please use the validate update method that is not obsoleted. Scheduled for removal in Umbraco 17.")]
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel);
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel, Guid userKey)
#pragma warning disable CS0618 // Type or member is obsolete
=> ValidateUpdateAsync(key, updateModel);
#pragma warning restore CS0618 // Type or member is obsolete
Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateAsync(ContentCreateModel createModel, Guid userKey);
Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey);

View File

@@ -3,6 +3,7 @@
using Moq;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Models.Membership.Permissions;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
@@ -27,6 +28,8 @@ public class UserGroupBuilder<TParent>
{
private string _alias;
private IEnumerable<string> _allowedSections = Enumerable.Empty<string>();
private IEnumerable<int> _allowedLanguages = Enumerable.Empty<int>();
private IEnumerable<IGranularPermission> _granularPermissions = Enumerable.Empty<IGranularPermission>();
private string _icon;
private int? _id;
private Guid? _key;
@@ -95,13 +98,24 @@ public class UserGroupBuilder<TParent>
return this;
}
public UserGroupBuilder<TParent> WithAllowedSections(IList<string> allowedSections)
{
_allowedSections = allowedSections;
return this;
}
public UserGroupBuilder<TParent> WithAllowedLanguages(IList<int> allowedLanguages)
{
_allowedLanguages = allowedLanguages;
return this;
}
public UserGroupBuilder<TParent> WithGranularPermissions(IList<IGranularPermission> granularPermissions)
{
_granularPermissions = granularPermissions;
return this;
}
public UserGroupBuilder<TParent> WithStartContentId(int startContentId)
{
_startContentId = startContentId;
@@ -144,17 +158,40 @@ public class UserGroupBuilder<TParent>
Id = id,
Key = key,
StartContentId = startContentId,
StartMediaId = startMediaId
StartMediaId = startMediaId,
Permissions = _permissions
};
userGroup.Permissions = _permissions;
BuildAllowedSections(userGroup);
BuildAllowedLanguages(userGroup);
BuildGranularPermissions(userGroup);
return userGroup;
}
private void BuildAllowedSections(UserGroup userGroup)
{
foreach (var section in _allowedSections)
{
userGroup.AddAllowedSection(section);
}
}
return userGroup;
private void BuildAllowedLanguages(UserGroup userGroup)
{
foreach (var language in _allowedLanguages)
{
userGroup.AddAllowedLanguage(language);
}
}
private void BuildGranularPermissions(UserGroup userGroup)
{
foreach (var permission in _granularPermissions)
{
userGroup.GranularPermissions.Add(permission);
}
}
public static UserGroup CreateUserGroup(

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
@@ -346,6 +346,7 @@ public partial class ContentEditingServiceTests
InvariantName = "Updated Name",
InvariantProperties = new[]
{
new PropertyValueModel { Alias = "title", Value = "The initial title" },
new PropertyValueModel { Alias = "label", Value = "The updated label value" }
}
};
@@ -390,6 +391,7 @@ public partial class ContentEditingServiceTests
Name = "Updated English Name",
Properties = new []
{
new PropertyValueModel { Alias = "variantTitle", Value = "The initial English title" },
new PropertyValueModel { Alias = "variantLabel", Value = "The updated English label value" }
}
},
@@ -399,6 +401,7 @@ public partial class ContentEditingServiceTests
Name = "Updated Danish Name",
Properties = new []
{
new PropertyValueModel { Alias = "variantTitle", Value = "The initial Danish title" },
new PropertyValueModel { Alias = "variantLabel", Value = "The updated Danish label value" }
}
}

View File

@@ -0,0 +1,191 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Membership;
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_Validate_Valid_Invariant_Content()
{
var content = await CreateInvariantContent();
var validateContentUpdateModel = new ValidateContentUpdateModel
{
InvariantName = "Updated Name",
InvariantProperties =
[
new PropertyValueModel { Alias = "title", Value = "The updated title" },
new PropertyValueModel { Alias = "text", Value = "The updated text" }
]
};
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
}
[Test]
public async Task Will_Fail_Invalid_Invariant_Content()
{
var content = await CreateInvariantContent();
var validateContentUpdateModel = new ValidateContentUpdateModel
{
InvariantName = "Updated Name",
InvariantProperties =
[
new PropertyValueModel { Alias = "title", Value = null },
new PropertyValueModel { Alias = "text", Value = "The updated text" }
]
};
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentEditingOperationStatus.PropertyValidationError, result.Status);
Assert.AreEqual(1, result.Result.ValidationErrors.Count());
Assert.AreEqual("#validation_invalidNull", result.Result.ValidationErrors.Single(x => x.Alias == "title").ErrorMessages[0]);
}
[Test]
public async Task Can_Validate_Valid_Variant_Content()
{
var content = await CreateVariantContent();
var validateContentUpdateModel = new ValidateContentUpdateModel
{
InvariantProperties =
[
new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }
],
Variants =
[
new VariantModel
{
Culture = "en-US",
Name = "Updated English Name",
Properties =
[
new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" }
]
},
new VariantModel
{
Culture = "da-DK",
Name = "Updated Danish Name",
Properties =
[
new PropertyValueModel { Alias = "variantTitle", Value = "The updated Danish title" }
]
}
],
};
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
}
[Test]
public async Task Will_Fail_Invalid_Variant_Content()
{
var content = await CreateVariantContent();
var validateContentUpdateModel = new ValidateContentUpdateModel
{
InvariantProperties =
[
new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }
],
Variants =
[
new VariantModel
{
Culture = "en-US",
Name = "Updated English Name",
Properties =
[
new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" }
]
},
new VariantModel
{
Culture = "da-DK",
Name = "Updated Danish Name",
Properties =
[
new PropertyValueModel { Alias = "variantTitle", Value = null }
]
}
],
};
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentEditingOperationStatus.PropertyValidationError, result.Status);
Assert.AreEqual(1, result.Result.ValidationErrors.Count());
Assert.AreEqual("#validation_invalidNull", result.Result.ValidationErrors.Single(x => x.Alias == "variantTitle" && x.Culture == "da-DK").ErrorMessages[0]);
}
[Test]
public async Task Will_Succeed_For_Invalid_Variant_Content_Without_Access_To_Edited_Culture()
{
var content = await CreateVariantContent();
IUser englishEditor = await CreateEnglishLanguageOnlyEditor();
var validateContentUpdateModel = new ValidateContentUpdateModel
{
InvariantProperties =
[
new PropertyValueModel { Alias = "invariantTitle", Value = "The updated invariant title" }
],
Variants =
[
new VariantModel
{
Culture = "en-US",
Name = "Updated English Name",
Properties =
[
new PropertyValueModel { Alias = "variantTitle", Value = "The updated English title" }
]
}
],
};
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await ContentEditingService.ValidateUpdateAsync(content.Key, validateContentUpdateModel, englishEditor.Key);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
}
private async Task<IUser> CreateEnglishLanguageOnlyEditor()
{
var enUSLanguage = await LanguageService.GetAsync("en-US");
var userGroup = new UserGroupBuilder()
.WithName("English Editors")
.WithAlias("englishEditors")
.WithAllowedLanguages([enUSLanguage.Id])
.Build();
var createUserGroupResult = await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
Assert.IsTrue(createUserGroupResult.Success);
var createUserAttempt = await UserService.CreateAsync(Constants.Security.SuperUserKey, new UserCreateModel
{
Email = "english-editor@test.com",
Name = "Test English Editor",
UserName = "english-editor@test.com",
UserGroupKeys = new[] { userGroup.Key }.ToHashSet(),
});
Assert.IsTrue(createUserAttempt.Success);
return await UserService.GetAsync(createUserAttempt.Result.CreatedUser.Key);
}
}

View File

@@ -21,7 +21,11 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
protected IContentBlueprintEditingService ContentBlueprintEditingService => GetRequiredService<IContentBlueprintEditingService>();
private ILanguageService LanguageService => GetRequiredService<ILanguageService>();
protected ILanguageService LanguageService => GetRequiredService<ILanguageService>();
protected IUserService UserService => GetRequiredService<IUserService>();
protected IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
protected IContentType CreateInvariantContentType(params ITemplate[] templates)
{
@@ -30,22 +34,23 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
.WithName("Invariant Test")
.WithContentVariation(ContentVariation.Nothing)
.AddPropertyType()
.WithAlias("title")
.WithName("Title")
.WithVariations(ContentVariation.Nothing)
.Done()
.WithAlias("title")
.WithName("Title")
.WithMandatory(true)
.WithVariations(ContentVariation.Nothing)
.Done()
.AddPropertyType()
.WithAlias("text")
.WithName("Text")
.WithVariations(ContentVariation.Nothing)
.Done()
.WithAlias("text")
.WithName("Text")
.WithVariations(ContentVariation.Nothing)
.Done()
.AddPropertyType()
.WithAlias("label")
.WithName("Label")
.WithDataTypeId(Constants.DataTypes.LabelString)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
.WithVariations(ContentVariation.Nothing)
.Done();
.WithAlias("label")
.WithName("Label")
.WithDataTypeId(Constants.DataTypes.LabelString)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
.WithVariations(ContentVariation.Nothing)
.Done();
foreach (var template in templates)
{
@@ -81,22 +86,23 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
.WithName("Culture Variation Test")
.WithContentVariation(ContentVariation.Culture)
.AddPropertyType()
.WithAlias("variantTitle")
.WithName("Variant Title")
.WithVariations(ContentVariation.Culture)
.Done()
.WithAlias("variantTitle")
.WithName("Variant Title")
.WithMandatory(true)
.WithVariations(ContentVariation.Culture)
.Done()
.AddPropertyType()
.WithAlias("invariantTitle")
.WithName("Invariant Title")
.WithVariations(ContentVariation.Nothing)
.Done()
.WithAlias("invariantTitle")
.WithName("Invariant Title")
.WithVariations(ContentVariation.Nothing)
.Done()
.AddPropertyType()
.WithAlias("variantLabel")
.WithName("Variant Label")
.WithDataTypeId(Constants.DataTypes.LabelString)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
.WithVariations(ContentVariation.Culture)
.Done()
.WithAlias("variantLabel")
.WithName("Variant Label")
.WithDataTypeId(Constants.DataTypes.LabelString)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label)
.WithVariations(ContentVariation.Culture)
.Done()
.Build();
contentType.AllowedAsRoot = true;
ContentTypeService.Save(contentType);

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsTestProject>true</IsTestProject>
<PackageId>Umbraco.Cms.Tests.Integration</PackageId>
@@ -97,6 +97,9 @@
<Compile Update="Umbraco.Infrastructure\Services\ContentEditingServiceTests.Update.cs">
<DependentUpon>ContentEditingServiceTests.cs</DependentUpon>
</Compile>
<Compile Update="Umbraco.Infrastructure\Services\ContentEditingServiceTests.Validate.cs">
<DependentUpon>ContentEditingServiceTests.cs</DependentUpon>
</Compile>
<Compile Update="Umbraco.Infrastructure\Services\ContentPublishingServiceTests.Publish.cs">
<DependentUpon>ContentPublishingServiceTests.cs</DependentUpon>
</Compile>