Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/Umbraco.Core/Services/PropertyValidationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
}

/// <inheritdoc />
public bool IsPropertyDataValid(IPublishableContentBase content, out IProperty[] invalidProperties, CultureImpact? impact)

Check failure on line 132 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHw_&open=AaHKD2jBK4qQ3lLsaHw_&pullRequest=24559
{
// select invalid properties
invalidProperties = content.Properties.Where(x =>
Expand All @@ -141,6 +141,11 @@
return false;
}

if (HasInvalidSegmentValue(content, x, impact))
{
return true;
}

Check warning on line 148 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v19/dev)

❌ Getting worse: Complex Method

IsPropertyDataValid increases in cyclomatic complexity from 10 to 11, threshold = 9 This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
// impacts invariant = validate invariant property, invariant culture
if (impact.ImpactsOnlyInvariantCulture)
{
Expand Down Expand Up @@ -190,13 +195,68 @@
return invalidProperties.Length == 0;
}

/// <summary>
/// Determines whether the property holds an invalid value in a non-default segment of the cultures the impact publishes.
/// </summary>
private bool HasInvalidSegmentValue(IPublishableContentBase content, IProperty property, CultureImpact impact)

Check failure on line 201 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxF&open=AaHKD2jBK4qQ3lLsaHxF&pullRequest=24559
{
if (property.PropertyType.VariesBySegment() is false)

Check warning on line 203 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary Boolean literal(s).

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxA&open=AaHKD2jBK4qQ3lLsaHxA&pullRequest=24559
{
return false;
}

var variesByCulture = property.PropertyType.VariesByCulture();
IEnumerable<string> culturesBeingValidated = [];

if (impact.ImpactsAllCultures is false)

Check warning on line 211 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary Boolean literal(s).

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxB&open=AaHKD2jBK4qQ3lLsaHxB&pullRequest=24559
{
if (variesByCulture)
{
if (impact.ImpactsOnlyInvariantCulture)
{
return false;
}
}
else if (impact.ImpactsExplicitCulture)
{
if (GetDataEditor(property.PropertyType)?.CanMergePartialPropertyValues(property.PropertyType) is true)
{
culturesBeingValidated = [impact.Culture!];
}
else if (impact.ImpactsAlsoInvariantProperties is false && content.Published)

Check warning on line 226 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary Boolean literal(s).

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxC&open=AaHKD2jBK4qQ3lLsaHxC&pullRequest=24559
{
return false;
}
}
}

var culture = variesByCulture ? impact.Culture : null;

// a culture invariant value applies to every culture, so when all cultures are impacted it is validated for all of them
var validateInvariantValueForAllCultures = variesByCulture is false && impact.ImpactsAllCultures;

Check warning on line 236 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary Boolean literal(s).

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHPAlJXoXftYBUx96Oo&open=AaHPAlJXoXftYBUx96Oo&pullRequest=24559

return property.Values.Any(value =>
value.Segment.IsNullOrWhiteSpace() is false

Check warning on line 239 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary Boolean literal(s).

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxD&open=AaHKD2jBK4qQ3lLsaHxD&pullRequest=24559
&& property.PropertyType.SupportsVariation(value.Culture, value.Segment, true)
&& (impact.ImpactsAllCultures || value.Culture.InvariantEquals(culture))
&& IsValidPropertyValue(
property,
value.EditedValue,
new PropertyValidationContext
{
Culture = validateInvariantValueForAllCultures ? "*" : value.Culture,
Segment = value.Segment,
CulturesBeingValidated = culturesBeingValidated,
}) is false);

Check warning on line 250 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary Boolean literal(s).

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxE&open=AaHKD2jBK4qQ3lLsaHxE&pullRequest=24559
}

Check warning on line 251 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v19/dev)

❌ New issue: Complex Method

HasInvalidSegmentValue has a cyclomatic complexity of 16, threshold = 9 This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 251 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v19/dev)

❌ New issue: Bumpy Road Ahead

HasInvalidSegmentValue has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

/// <inheritdoc />
// TODO (V20): Make this internal rather than removing it entirely in V20, so the
// referencing unit tests can continue to exercise this code.
// Also remove the obsolete code warning suppressions added around the various
// callers to this method (including the unit tests).
[Obsolete("Property level validation is not going to be supported moving forward. Please use content level validation with IsPropertyDataValid instead. Scheduled for removal in Umbraco 20.")]
public bool IsPropertyValid(IProperty property, PropertyValidationContext validationContext)

Check warning on line 259 in src/Umbraco.Core/Services/PropertyValidationService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'public' members should come before 'private' members

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2jBK4qQ3lLsaHxG&open=AaHKD2jBK4qQ3lLsaHxG&pullRequest=24559
{
// NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there.
// The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using NUnit.Framework;

Check warning on line 1 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Validation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v19/dev)

❌ Getting worse: Low Cohesion

The number of different responsibilities increases from 14 to 15, threshold = 3 Cohesion is calculated using the LCOM4 metric. Low cohesion means that the module/class has multiple unrelated responsibilities, doing too many things and breaking the Single Responsibility Principle.
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
Expand All @@ -14,6 +14,42 @@
{
private IContentValidationService ContentValidationService => GetRequiredService<IContentValidationService>();

/// <summary>
/// Tests whether validating for all cultures includes the culture variant block values held in a non-default
/// segment of a culture invariant block property.
/// </summary>
[TestCase("Valid value in Danish", true)]
[TestCase("Invalid value in Danish", false)]
public async Task Validates_Culture_Variant_Block_Values_In_Non_Default_Segments_For_All_Cultures(string segmentValueInDanish, bool expectedValid)

Check warning on line 23 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Validation.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Element return value should be documented

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHPAlcPoXftYBUx96Oq&open=AaHPAlcPoXftYBUx96Oq&pullRequest=24559
{
var elementType = await CreateElementTypeWithValidationAsync();
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = await CreateContentType(ContentVariation.CultureAndSegment, blockListDataType, ContentVariation.Segment);

var content = CreateContent(
contentType,
elementType,
[BlockPropertyForSegment(null, "Valid value in Danish"), BlockPropertyForSegment("s1", segmentValueInDanish)],
false);

var isValid = GetRequiredService<IPropertyValidationService>().IsPropertyDataValid(content, out IProperty[] invalidProperties, CultureImpact.All);

Assert.AreEqual(expectedValid, isValid);
Assert.AreEqual(expectedValid ? 0 : 1, invalidProperties.Length);

BlockProperty BlockPropertyForSegment(string? segment, string valueInDanish)
{
return new BlockProperty(BlockValues(), BlockValues(), null, segment);

List<BlockPropertyValue> BlockValues() =>
[
new() { Alias = "invariantText", Value = "Valid invariant value" },
new() { Alias = "variantText", Value = "Valid value in English", Culture = "en-US" },
new() { Alias = "variantText", Value = valueInDanish, Culture = "da-DK" },
];
}
}

[Test]
public async Task Can_Validate_Invalid_Properties()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using NUnit.Framework;

Check warning on line 1 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v19/dev)

❌ New issue: Primitive Obsession

In this module, 61.1% of all function arguments are primitive types, threshold = 30.0% The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.

Check notice on line 1 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v19/dev)

✅ No longer an issue: String Heavy Function Arguments

The ratio of strings in function arguments is no longer above the threshold
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
Expand Down Expand Up @@ -779,6 +779,79 @@
VerifyIsPublished(content.Key);
}

[TestCase(ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment)]
public async Task Cannot_Publish_Branch_Descendant_With_Invalid_Value_In_Non_Default_Segment(ContentVariation variation)
{
var contentType = await SetupSegmentVariantTest(variation);
var culture = variation.VariesByCulture() ? "en-US" : null;

IContent root = BuildSegmentVariantContent(contentType, culture, "Root");
root.SetValue("title", "Valid default value", culture);
ContentService.Save(root);

IContent child = BuildSegmentVariantContent(contentType, culture, "Child", root);
child.SetValue("title", "Valid default value", culture);
child.SetValue("title", "Invalid seg-1 value", culture, "seg-1");
ContentService.Save(child);

var result = await ContentPublishingService.PublishBranchAsync(
root.Key,
culture is null ? _allCultures : [culture],
PublishBranchFilter.IncludeUnpublished,
Constants.Security.SuperUserKey,
false);

Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.FailedBranch, result.Status);
AssertBranchResultSuccess(result.Result, root.Key);
AssertBranchResultFailed(result.Result, (child.Key, ContentPublishingOperationStatus.ContentInvalid));
VerifyIsPublished(root.Key);
VerifyIsNotPublished(child.Key);
}

[TestCase(ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment)]
public async Task Scheduled_Publish_Does_Not_Publish_Content_With_Invalid_Value_In_Non_Default_Segment(ContentVariation variation)
{
var contentType = await SetupSegmentVariantTest(variation);
var culture = variation.VariesByCulture() ? "en-US" : null;

IContent content = BuildSegmentVariantContent(contentType, culture, "Segment Test");
content.SetValue("title", "Valid default value", culture);
content.SetValue("title", "Valid seg-1 value", culture, "seg-1");
ContentService.Save(content);

var scheduleResult = await ContentPublishingService.PublishAsync(
content.Key,
[
new CulturePublishScheduleModel
{
Culture = culture,
Schedule = new ContentScheduleModel { PublishDate = DateTimeOffset.UtcNow.AddDays(1) },
}

Check warning on line 832 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use trailing comma in multi-line initializers and lists

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2vHK4qQ3lLsaHxJ&open=AaHKD2vHK4qQ3lLsaHxJ&pullRequest=24559
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleResult.Success);

content = ContentService.GetById(content.Key)!;
content.SetValue("title", "Invalid seg-1 value", culture, "seg-1");
ContentService.Save(content);

var results = ContentService.PerformScheduledPublish(DateTime.UtcNow.AddDays(2)).ToArray();

var contentResult = results.Single(r => r.Content.Key == content.Key);
Assert.AreEqual(PublishResultType.FailedPublishContentInvalid, contentResult.Result);
VerifyIsNotPublished(content.Key);
}

private static IContent BuildSegmentVariantContent(IContentType contentType, string? culture, string name, IContent? parent = null)

Check warning on line 848 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change return type of method 'BuildSegmentVariantContent' from 'Umbraco.Cms.Core.Models.IContent' to 'Umbraco.Cms.Core.Models.Content' for improved performance

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2vHK4qQ3lLsaHxK&open=AaHKD2vHK4qQ3lLsaHxK&pullRequest=24559
{
ContentBuilder builder = new ContentBuilder().WithContentType(contentType);
builder = culture is null ? builder.WithName(name) : builder.WithCultureName(culture, name);
return parent is null ? builder.Build() : builder.WithParent(parent).Build();
}

[TestCase(PublishBranchFilter.Default, true)]
[TestCase(PublishBranchFilter.ForceRepublish, false)]
public async Task Publish_Branch_Validates_Unchanged_Published_Root_Only_When_Republishing(PublishBranchFilter publishBranchFilter, bool expectSuccess)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,79 @@
Assert.AreEqual(4, invalid.Length);
}

[TestCase(ContentVariation.Segment, null, "Invalid", false)]
[TestCase(ContentVariation.Segment, null, "Valid", true)]
[TestCase(ContentVariation.CultureAndSegment, "en-US", "Invalid", false)]
[TestCase(ContentVariation.CultureAndSegment, "en-US", "Valid", true)]
public void Validate_Values_In_Non_Default_Segments(ContentVariation variation, string? culture, string segmentValue, bool expectedValid)
Comment thread
Zeegaan marked this conversation as resolved.
Resolved
{
MockObjects(out var validationService, out var dataType);

var property = new Property(
new PropertyType(ShortStringHelper, dataType, "test1")
{
ValidationRegExp = "^Valid.*$",
Variations = variation,
});
property.SetValue("Valid", culture);
property.SetValue(segmentValue, culture, "segment-1");

var content = Mock.Of<IContent>(x => x.Properties == new PropertyCollection(new[] { property }));
CultureImpact impact = culture is null ? CultureImpact.Invariant : CultureImpact.Explicit(culture, true);

Check warning on line 314 in tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'CultureImpact.Explicit(string?, bool)' is obsolete: 'Use ICultureImpactService instead.'

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2sWK4qQ3lLsaHxH&open=AaHKD2sWK4qQ3lLsaHxH&pullRequest=24559

var result = validationService.IsPropertyDataValid(content, out var invalid, impact);

Assert.AreEqual(expectedValid, result);
Assert.AreEqual(expectedValid ? 0 : 1, invalid.Length);
}

[TestCase(ContentVariation.Segment, null)]
[TestCase(ContentVariation.CultureAndSegment, "en-US")]
public void Does_Not_Require_Values_In_Non_Default_Segments_Of_Mandatory_Properties(ContentVariation variation, string? culture)
{
MockObjects(out var validationService, out var dataType);

var property = new Property(
new PropertyType(ShortStringHelper, dataType, "test1")
{
Mandatory = true,
Variations = variation,
});
property.SetValue("Hello", culture);
property.SetValue(string.Empty, culture, "segment-1");

var content = Mock.Of<IContent>(x => x.Properties == new PropertyCollection(new[] { property }));
CultureImpact impact = culture is null ? CultureImpact.Invariant : CultureImpact.Explicit(culture, true);

Check warning on line 338 in tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'CultureImpact.Explicit(string?, bool)' is obsolete: 'Use ICultureImpactService instead.'

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHPAlV1oXftYBUx96Op&open=AaHPAlV1oXftYBUx96Op&pullRequest=24559

var result = validationService.IsPropertyDataValid(content, out var invalid, impact);

Assert.IsTrue(result);
Assert.IsEmpty(invalid);
}

[Test]
public void Does_Not_Validate_Segment_Values_Of_Cultures_Not_Impacted()
{
MockObjects(out var validationService, out var dataType);

var property = new Property(
new PropertyType(ShortStringHelper, dataType, "test1")
{
ValidationRegExp = "^Valid.*$",
Variations = ContentVariation.CultureAndSegment,
});
property.SetValue("Valid", "en-US");
property.SetValue("Valid", "en-US", "segment-1");
property.SetValue("Invalid", "da-DK", "segment-1");

var content = Mock.Of<IContent>(x => x.Properties == new PropertyCollection(new[] { property }));

var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", true));

Check warning on line 363 in tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'CultureImpact.Explicit(string?, bool)' is obsolete: 'Use ICultureImpactService instead.'

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AaHKD2sWK4qQ3lLsaHxI&open=AaHKD2sWK4qQ3lLsaHxI&pullRequest=24559

Assert.IsTrue(result);
Assert.IsEmpty(invalid);
}

[TestCase(null)]
[TestCase(24)]
[TestCase("test")]
Expand Down
Loading