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)
{
@@ -32,6 +36,7 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
.AddPropertyType()
.WithAlias("title")
.WithName("Title")
.WithMandatory(true)
.WithVariations(ContentVariation.Nothing)
.Done()
.AddPropertyType()
@@ -83,6 +88,7 @@ public abstract class ContentEditingServiceTestsBase : UmbracoIntegrationTestWit
.AddPropertyType()
.WithAlias("variantTitle")
.WithName("Variant Title")
.WithMandatory(true)
.WithVariations(ContentVariation.Culture)
.Done()
.AddPropertyType()

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>