diff --git a/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs b/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs index 1eb43b95749..d95309fa421 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentTypes/RecipeSteps/ContentDefinitionStep.cs @@ -58,6 +58,11 @@ private Task UpdateContentTypeAsync(ContentTypeDefinition type, ContentTypeDefin foreach (var part in record.ContentTypePartDefinitionRecords) { + if (string.IsNullOrEmpty(part.PartName)) + { + throw new InvalidOperationException($"Unable to add content-part to the '{type.Name}' content-type. The part name cannot be null or empty."); + } + builder.WithPart(part.Name, part.PartName, partBuilder => partBuilder.MergeSettings(part.Settings)); } }); diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj index 97879b76cfe..28995c4eb9f 100644 --- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj +++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj @@ -29,6 +29,7 @@ + @@ -44,6 +45,7 @@ + diff --git a/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs b/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs index 3bcdd2d742d..979b6f350a3 100644 --- a/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs +++ b/test/OrchardCore.Tests/Recipes/RecipeExecutorTests.cs @@ -8,6 +8,7 @@ using OrchardCore.Recipes.Models; using OrchardCore.Recipes.Services; using OrchardCore.Scripting; +using OrchardCore.Tests.Apis.Context; namespace OrchardCore.Recipes { @@ -50,6 +51,27 @@ public async Task ShouldTrimValidScriptExpression(string recipeName, string expe }); } + [Fact] + public async Task ContentDefinitionStep_WhenPartNameIsMissing_ThrowInvalidOperationException() + { + var context = new BlogContext(); + await context.InitializeAsync(); + await context.UsingTenantScopeAsync(async scope => + { + var recipeExecutor = scope.ServiceProvider.GetRequiredService(); + // Act + var executionId = Guid.NewGuid().ToString("n"); + var recipeDescriptor = new RecipeDescriptor { RecipeFileInfo = GetRecipeFileInfo("recipe6") }; + + var exception = await Assert.ThrowsAsync(async () => + { + await recipeExecutor.ExecuteAsync(executionId, recipeDescriptor, new Dictionary(), CancellationToken.None); + }); + + Assert.Equal("Unable to add content-part to the 'Message' content-type. The part name cannot be null or empty.", exception.Message); + }); + } + private static Task GetScopeAsync() => ShellScope.Context.CreateScopeAsync(); private static ShellContext CreateShellContext() => new() diff --git a/test/OrchardCore.Tests/Recipes/RecipeFiles/recipe6.json b/test/OrchardCore.Tests/Recipes/RecipeFiles/recipe6.json new file mode 100644 index 00000000000..c113645d8f0 --- /dev/null +++ b/test/OrchardCore.Tests/Recipes/RecipeFiles/recipe6.json @@ -0,0 +1,27 @@ +{ + "name": "Recipe6", + "displayName": "Recipe 6", + "description": "This recipe is designed to uncover potential bugs during the content-definition import process. It serves as a validation for the import functionality.", + "author": "Tony Han", + "website": "", + "version": "1.0.0", + "issetuprecipe": false, + "categories": [ "test" ], + "steps": [ + { + "name": "ContentDefinition", + "ContentTypes": [ + { + "Name": "Message", + "DisplayName": "Message", + "ContentTypePartDefinitionRecords": [ + { + // "PartName": "TitlePart", // Test PartName validation. + "Name": "TitlePart" + } + ] + } + ] + } + ] +}