Files
Umbraco-CMS/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentPublishingServiceTests.Publish.cs
Kenn Jacobsen 3bd66b89e1 Merge branch 'v15/dev' into v16/dev (#18971)
* Only prevent the unpublish or delete of a related item when configured to do so if it is related as a child, not as a parent (#18886)

* Only prevent the unpubkish or delete of a related item when configured to do so if it is related as a child, not as a parent.

* Fixed incorect parameter names.

* Fixed failing integration tests.

* Use using variable instead to reduce nesting

* Applied suggestions from code review.

* Used simple using statement throughout RelationService for consistency.

* Applied XML header comments consistently.

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>

* Feature: highlight invariant doc with variant blocks is unsupported (#18806)

* mark variant blocks in invariant docs as invalid

* implement RTE Blocks

* Fix pagination for users restricted by start nodes (#18907)

* Fix pagination for users restricted by start nodes

* Default implementation to avoid breakage

* Review comments

* Fix failing test

* Add media start node tests

* Fix issue preventing blueprint derived values from being scaffolded (#18917)

* Fix issue preventing blueprint derived values from being scaffolded.

* fix manipulating frooen array

* compare with variantId as well

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>

* ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com

* ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com

* ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com

* Remove admin permission on user configuration, allowing users with user section access only to manaage users and groups. (#18848)

* Tiptap RTE: Style Menu extension kind (#18918)

* Adds 'styleMenu' Tiptap toolbar extension kind

* Adds icons for `<h4>` and `<p>` tags

* Adds commands to HTML Global Attributes extension

for setting the `class` and `id` attributes.

* Renamed "default-tiptap-toolbar-element.api.ts" file

The "element" part was confusing.

* Toolbar Menu: uses correct `item` value

* Cascading Menu: adds localization for the label

* Adds `label` attribute to UUI components

for accessibility.

* Toolbar Menu: uses correct `appearance` value

* Removed unrequired `api` from Style Select

* Destructs the `item.data` object

* Ensure has children reflects only items with folder children when folders only are queried. (#18790)

* Ensure has children reflects only items with folder children when folders only are queried.

* Added supression for change to integration test public code.

---------

Co-authored-by: Migaroez <geusens@gmail.com>

* 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.

* Adds ancestor ID details on document tree and collection responses (#18909)

* Populate ancestor keys on document tree response items.

* Populate ancestor keys on document collection response items.

* Update OpenApi.json

* Use array of objects rather than Ids for the ancestor collection.

* Update OpenApi.json.

* Move publish with descendants to a background task with polling (#18497)

* Use background queue for database cache rebuild and track rebuilding status.

* Updated OpenApi.json and client-side types.

* Updated client to poll for completion of database rebuild.

* Move IBackgroundTaskQueue to core and prepare publish branch to run as background task.

* Endpoints for retrieval of status and result from branch publish operations.

* Poll and retrieve result for publish with descendants.

* Handled issues from testing.

* Rework to single controller for status and result.

* Updated client side sdk.

* OpenApi post dev merge gen

---------

Co-authored-by: Migaroez <geusens@gmail.com>

* Clear roots before rebuilding navigation dictionary (#18766)

* Clear roots before rebuilding navigation dictionary.

* Added tests to verify fix.

* Correct test implementation.

* Convert integration tests with method overloads into test cases.

* Integration test compatibility supressions.

* Fixes save of empty, invariant block list on variant content. (#18932)

* remove unnecessary code (#18927)

* V15/bugfix/fix route issue from 18859 (#18931)

* unique check

* unique for workspace empty path

* more unique routes

* Bump vite from 6.2.3 to 6.2.4 in /src/Umbraco.Web.UI.Client

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* removes autogenerated workflows

* make getHasUnpersistedChanges public (#18929)

* Added management API endpoint, service and repository for retrieval of references from the recycle bin (#18882)

* Added management API endpoint, service and repository for retrieval of references from the recycle bin.

* Update src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/ReferencedByDocumentRecycleBinController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Removed unused code.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Updated management API endpoint and model for data type references to align with that used for documents, media etc. (#18905)

* Updated management API endpoint and model for data type references to align with that used for documents, media etc.

* Refactoring.

* Update src/Umbraco.Core/Constants-ReferenceTypes.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed typos.

* Added id to tracked reference content type response.

* Updated OpenApi.json.

* Added missing updates.

* Renamed model and constants from code review feedback.

* Fix typo

* Fix multiple enumeration

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>

* Skip lock tests

* Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined (#18763)

* Look-up redirect in content finder for multi-lingual sites using path and legacy route prefixed with the integer ID of the node with domains defined.

* Added tests to verify functionality.

* Added reference to previous PR.

* Referenced second PR.

* Assemble URLs for all cultures, not just the default.

* Revert previous update.

* Display an original URL if we have one.

* Bump vite from 6.2.4 to 6.2.5 in /src/Umbraco.Web.UI.Client

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.4 to 6.2.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Add raw value validation to multiple text strings property editor (#18936)

* Add raw value validation to multiple text strings property editor

* Added additional assert on unit test and comment on validation logic.

* Don't remove items to obtain a valid value

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Integration tests for content publishing with ancestor unpublished (#18941)

* Resolved warnings in test class.

* Refactor regions into partial classes.

* Aligned test names.

* Variable name refactoring.

* Added tests for unpublished paths.

* Adjust tests to verify current behaviour.

* Cleaned up project file.

* fix circular icon import (#18952)

* remove segment toggle for elements (#18949)

* Fix modal route registration circular import (#18953)

* fix modal route registration circular import

* Update modal-route-registration.controller.ts

* V15/fix/18595 (#18925)

* fix for #18595

* updates the en.ts

* Avoid unneeded Dictionary operations (#18890)

* Avoid some heap allocations

* Remove unneeded double seek

* Avoid allocating new empty arrays, reuse existing empty array

* Avoid allocating strings for parsing comma separated int values (#18199)

* Data type References UI: Workspace + Delete (#18914)

* Updated management API endpoint and model for data type references to align with that used for documents, media etc.

* Refactoring.

* Update src/Umbraco.Core/Constants-ReferenceTypes.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed typos.

* generate server models

* add extension slot

* register data type reference info app

* add reference data mappers

* Added id to tracked reference content type response.

* Updated OpenApi.json.

* Added missing updates.

* generate new models

* update models

* register ref item

* remove debugger

* render types

* register member type property type ref

* register media type property type ref

* Renamed model and constants from code review feedback.

* register reference workspace info app kind

* use kind for document references

* use kind for media references

* use kind for member references

* use deleteWithRelation kind when deleting data types

* fix manifest types

* fix types

* Update types.gen.ts

* update code to fit new server models

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Feature: discard changes for block workspace (#18930)

* make getHasUnpersistedChanges public

* Discard changes impl for Block Workspace

* fix 18367 (#18956)

* Merge commit from fork

* Prevent path traveral vulnerability with upload of temporary files.

* Used BadRequest instead of NotFound for invalid file name response.

* V15 QA Fixing the failing media acceptance tests (#18881)

* Fixed the function name due to test helper changes

* Updated assertion steps due to UI changes

* Added more waits

* Bumped version

* Increase timeout

* Reverted

---------

Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>

* V15 QA added clipboard test for not being able to copy to root when block is not allowed at root (#18937)

* Added clipboard test

* Bumped version

* Updated to use the name

* Run all tests on the pipeline

* Reverted command

* build: adjusts circular ref number to 4

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>
Co-authored-by: Migaroez <geusens@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jacob Welander Jensen <64834767+Welander1994@users.noreply.github.com>
Co-authored-by: Henrik <hg@impact.dk>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com>
2025-04-09 09:58:01 +02:00

760 lines
34 KiB
C#

using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
public partial class ContentPublishingServiceTests
{
[Test]
public async Task Can_Publish_Root()
{
VerifyIsNotPublished(Textpage.Key);
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, result.Status);
VerifyIsPublished(Textpage.Key);
}
[Test]
public async Task Publish_Single_Item_Does_Not_Publish_Children()
{
await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
VerifyIsPublished(Textpage.Key);
VerifyIsNotPublished(Subpage.Key);
}
[Test]
public async Task Can_Publish_Child_Of_Root()
{
await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
var result = await ContentPublishingService.PublishAsync(Subpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, result.Status);
VerifyIsPublished(Subpage.Key);
}
[TestCase(PublishBranchFilter.Default)]
[TestCase(PublishBranchFilter.IncludeUnpublished)]
[TestCase(PublishBranchFilter.ForceRepublish)]
[TestCase(PublishBranchFilter.All)]
public async Task Publish_Branch_Does_Not_Publish_Unpublished_Children_Unless_Instructed_To(PublishBranchFilter publishBranchFilter)
{
var result = await ContentPublishingService.PublishBranchAsync(Textpage.Key, _allCultures, publishBranchFilter, Constants.Security.SuperUserKey, false);
Assert.IsTrue(result.Success);
VerifyIsPublished(Textpage.Key);
if (publishBranchFilter.HasFlag(PublishBranchFilter.IncludeUnpublished))
{
AssertBranchResultSuccess(result.Result, Textpage.Key, Subpage.Key, Subpage2.Key, Subpage3.Key);
VerifyIsPublished(Subpage.Key);
VerifyIsPublished(Subpage2.Key);
VerifyIsPublished(Subpage3.Key);
}
else
{
AssertBranchResultSuccess(result.Result, Textpage.Key);
VerifyIsNotPublished(Subpage.Key);
VerifyIsNotPublished(Subpage2.Key);
VerifyIsNotPublished(Subpage3.Key);
}
}
[Test]
public async Task Can_Publish_Branch_Beneath_Root()
{
await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
var subpage2Subpage = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 2-2", Subpage2.Id);
ContentService.Save(subpage2Subpage, -1);
VerifyIsNotPublished(Subpage2.Key);
var result = await ContentPublishingService.PublishBranchAsync(Subpage2.Key, _allCultures, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsTrue(result.Success);
AssertBranchResultSuccess(result.Result, Subpage2.Key, subpage2Subpage.Key);
VerifyIsPublished(Subpage2.Key);
VerifyIsPublished(subpage2Subpage.Key);
VerifyIsNotPublished(Subpage.Key);
}
[Test]
public async Task Can_Cancel_Publishing_With_Notification()
{
ContentNotificationHandler.PublishingContent = notification => notification.Cancel = true;
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.CancelledByEvent, result.Status);
}
[Test]
public async Task Can_Publish_Variant_Content()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langEn.IsoCode, langDa.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, result.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(2, content.PublishedCultures.Count());
Assert.IsTrue(content.PublishedCultures.InvariantContains(langEn.IsoCode));
Assert.IsTrue(content.PublishedCultures.InvariantContains(langDa.IsoCode));
}
[Test]
public async Task Can_Publish_All_Variants_And_Unpublish_All_Variants_And_Publish_A_Single_Variant()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(content);
var publishResult = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langEn.IsoCode, langDa.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsTrue(publishResult.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, publishResult.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(2, content.PublishedCultures.Count());
Assert.IsTrue(content.PublishedCultures.InvariantContains(langEn.IsoCode));
Assert.IsTrue(content.PublishedCultures.InvariantContains(langDa.IsoCode));
var unpublishResult = await ContentPublishingService.UnpublishAsync(content.Key, new HashSet<string>() { "*" }, Constants.Security.SuperUserKey);
Assert.IsTrue(unpublishResult.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, unpublishResult.Result);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(0, content.PublishedCultures.Count());
publishResult = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langDa.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsTrue(publishResult.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, publishResult.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(1, content.PublishedCultures.Count());
Assert.IsTrue(content.PublishedCultures.InvariantContains(langDa.IsoCode));
}
[Test]
public async Task Can_Publish_Branch_Of_Variant_Content()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent root = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
root.SetValue("title", "EN root title", culture: langEn.IsoCode);
root.SetValue("title", "DA root title", culture: langDa.IsoCode);
ContentService.Save(root);
IContent child = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN child")
.WithCultureName(langDa.IsoCode, "DA child")
.WithParent(root)
.Build();
child.SetValue("title", "EN child title", culture: langEn.IsoCode);
child.SetValue("title", "DA child title", culture: langDa.IsoCode);
ContentService.Save(child);
var result = await ContentPublishingService.PublishBranchAsync(root.Key, new[] { langEn.IsoCode, langDa.IsoCode }, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsTrue(result.Success);
AssertBranchResultSuccess(result.Result, root.Key, child.Key);
root = ContentService.GetById(root.Key)!;
Assert.AreEqual(2, root.PublishedCultures.Count());
Assert.IsTrue(root.PublishedCultures.InvariantContains(langEn.IsoCode));
Assert.IsTrue(root.PublishedCultures.InvariantContains(langDa.IsoCode));
child = ContentService.GetById(child.Key)!;
Assert.AreEqual(2, child.PublishedCultures.Count());
Assert.IsTrue(child.PublishedCultures.InvariantContains(langEn.IsoCode));
Assert.IsTrue(child.PublishedCultures.InvariantContains(langDa.IsoCode));
}
[Test]
public async Task Can_Publish_Culture_With_Other_Culture_Invalid()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", null, culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langEn.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, result.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(1, content.PublishedCultures.Count());
Assert.IsTrue(content.PublishedCultures.First().InvariantEquals(langEn.IsoCode));
}
[Test]
public async Task Can_Publish_Culture_Branch_With_Other_Culture_Invalid()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent root = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
root.SetValue("title", "EN title", culture: langEn.IsoCode);
root.SetValue("title", null, culture: langDa.IsoCode);
ContentService.Save(root);
IContent child = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN child")
.WithCultureName(langDa.IsoCode, "DA child")
.WithParent(root)
.Build();
child.SetValue("title", "EN child title", culture: langEn.IsoCode);
child.SetValue("title", "DA child title", culture: langDa.IsoCode);
ContentService.Save(child);
var result = await ContentPublishingService.PublishBranchAsync(root.Key, new[] { langEn.IsoCode }, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsTrue(result.Success);
AssertBranchResultSuccess(result.Result, root.Key, child.Key);
root = ContentService.GetById(root.Key)!;
Assert.AreEqual(1, root.PublishedCultures.Count());
Assert.IsTrue(root.PublishedCultures.InvariantContains(langEn.IsoCode));
child = ContentService.GetById(child.Key)!;
Assert.AreEqual(1, child.PublishedCultures.Count());
Assert.IsTrue(child.PublishedCultures.InvariantContains(langEn.IsoCode));
}
[Test]
public async Task Can_Publish_Culture_Branch_Without_Other_Culture()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent root = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
root.SetValue("title", "EN title", culture: langEn.IsoCode);
root.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(root);
root = ContentService.GetById(root.Key)!;
IContent child = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN child")
.WithCultureName(langDa.IsoCode, "DA child")
.WithParent(root)
.Build();
child.SetValue("title", "EN child title", culture: langEn.IsoCode);
child.SetValue("title", "DA child title", culture: langDa.IsoCode);
ContentService.Save(child);
var result = await ContentPublishingService.PublishBranchAsync(root.Key, new[] { langEn.IsoCode }, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsTrue(result.Success);
AssertBranchResultSuccess(result.Result, root.Key, child.Key);
root = ContentService.GetById(root.Key)!;
Assert.AreEqual(1, root.PublishedCultures.Count());
Assert.IsTrue(root.PublishedCultures.InvariantContains(langEn.IsoCode));
child = ContentService.GetById(child.Key)!;
Assert.AreEqual(1, child.PublishedCultures.Count());
Assert.IsTrue(child.PublishedCultures.InvariantContains(langEn.IsoCode));
}
[Test]
public async Task Can_Publish_Variant_Content_With_Mandatory_Culture()
{
var (langEn, langDa, contentType) = await SetupVariantTest(true);
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langEn.IsoCode, langDa.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, result.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(2, content.PublishedCultures.Count());
}
[TestCase(true, "da-DK")]
[TestCase(false, "en-US")]
[TestCase(false, "en-US", "da-DK")]
public async Task Publish_Invalid_Invariant_Property_WithoutAllowEditInvariantFromNonDefault(bool expectedSuccess, params string[] culturesToRepublish)
=> await Publish_Invalid_Invariant_Property(expectedSuccess, culturesToRepublish);
[TestCase(false, "da-DK")]
[TestCase(false, "en-US")]
[TestCase(false, "en-US", "da-DK")]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowEditInvariantFromNonDefaultTrue))]
public async Task Publish_Invalid_Invariant_Property_WithAllowEditInvariantFromNonDefault(bool expectedSuccess, params string[] culturesToRepublish)
=> await Publish_Invalid_Invariant_Property(expectedSuccess, culturesToRepublish);
private async Task Publish_Invalid_Invariant_Property(bool expectedSuccess, params string[] culturesToRepublish)
{
var contentType = await SetupVariantInvariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName("en-US", "EN")
.WithCultureName("da-DK", "DA")
.Build();
content.SetValue("variantValue", "EN value", culture: "en-US");
content.SetValue("variantValue", "DA value", culture: "da-DK");
content.SetValue("invariantValue", "Invariant value");
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string> { "en-US", "da-DK" }), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
content = ContentService.GetById(content.Key)!;
content.SetValue("variantValue", "EN value updated", culture: "en-US");
content.SetValue("variantValue", "DA value updated", culture: "da-DK");
content.SetValue("invariantValue", null);
ContentService.Save(content);
result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>(culturesToRepublish)), Constants.Security.SuperUserKey);
content = ContentService.GetById(content.Key)!;
Assert.Multiple(() =>
{
Assert.AreEqual(null, content.GetValue("invariantValue", published: false));
Assert.AreEqual("EN value updated", content.GetValue("variantValue", culture: "en-US", published: false));
Assert.AreEqual("DA value updated", content.GetValue("variantValue", culture: "da-DK", published: false));
Assert.AreEqual("Invariant value", content.GetValue("invariantValue", published: true));
});
if (expectedSuccess)
{
Assert.Multiple(() =>
{
Assert.IsTrue(result.Success);
var expectedPublishedEnglishValue = culturesToRepublish.Contains("en-US")
? "EN value updated"
: "EN value";
var expectedPublishedDanishValue = culturesToRepublish.Contains("da-DK")
? "DA value updated"
: "DA value";
Assert.AreEqual(expectedPublishedEnglishValue, content.GetValue("variantValue", culture: "en-US", published: true));
Assert.AreEqual(expectedPublishedDanishValue, content.GetValue("variantValue", culture: "da-DK", published: true));
});
}
else
{
Assert.Multiple(() =>
{
Assert.IsFalse(result.Success);
Assert.AreEqual("EN value", content.GetValue("variantValue", culture: "en-US", published: true));
Assert.AreEqual("DA value", content.GetValue("variantValue", culture: "da-DK", published: true));
});
}
}
[Test]
public async Task Cannot_Publish_Non_Existing_Content()
{
var result = await ContentPublishingService.PublishAsync(Guid.NewGuid(), MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.ContentNotFound, result.Status);
}
[Test]
public async Task Cannot_Publish_Branch_Of_Non_Existing_Content()
{
var key = Guid.NewGuid();
var result = await ContentPublishingService.PublishBranchAsync(key, _allCultures, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsFalse(result);
AssertBranchResultFailed(result.Result, (key, ContentPublishingOperationStatus.ContentNotFound));
}
[Test]
public async Task Cannot_Publish_Invalid_Content()
{
var content = await CreateInvalidContent();
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result);
Assert.AreEqual(ContentPublishingOperationStatus.ContentInvalid, result.Status);
var invalidPropertyAliases = result.Result.InvalidPropertyAliases.ToArray();
Assert.AreEqual(3, invalidPropertyAliases.Length);
Assert.Contains("title", invalidPropertyAliases);
Assert.Contains("bodyText", invalidPropertyAliases);
Assert.Contains("author", invalidPropertyAliases);
VerifyIsNotPublished(content.Key);
}
[Test]
public async Task Cannot_Publish_Branch_With_Invalid_Parent()
{
var content = await CreateInvalidContent(Textpage);
var child = ContentBuilder.CreateSimpleContent(ContentType, "Child page", content.Id);
ContentService.Save(child, -1);
Assert.AreEqual(content.Id, ContentService.GetById(child.Key)!.ParentId);
var result = await ContentPublishingService.PublishBranchAsync(Textpage.Key, _allCultures, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsFalse(result.Success);
AssertBranchResultSuccess(result.Result, Textpage.Key, Subpage.Key, Subpage2.Key, Subpage3.Key);
AssertBranchResultFailed(result.Result, (content.Key, ContentPublishingOperationStatus.ContentInvalid));
VerifyIsPublished(Textpage.Key);
VerifyIsPublished(Subpage.Key);
VerifyIsPublished(Subpage2.Key);
VerifyIsPublished(Subpage3.Key);
VerifyIsNotPublished(content.Key);
VerifyIsNotPublished(child.Key);
}
[Test]
public async Task Cannot_Publish_Invalid_Variant_Content()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", null, culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langEn.IsoCode, langDa.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.ContentInvalid, result.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(0, content.PublishedCultures.Count());
}
[Test]
public async Task Cannot_Publish_Variant_Content_Without_Mandatory_Culture()
{
var (langEn, langDa, contentType) = await SetupVariantTest(true);
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { langDa.IsoCode }), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.MandatoryCultureMissing, result.Status);
content = ContentService.GetById(content.Key)!;
Assert.AreEqual(0, content.PublishedCultures.Count());
}
[Test]
public async Task Cannot_Publish_Culture_Branch_With_Invalid_Culture()
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent root = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
root.SetValue("title", "EN title", culture: langEn.IsoCode);
root.SetValue("title", null, culture: langDa.IsoCode);
ContentService.Save(root);
IContent child = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN child")
.WithCultureName(langDa.IsoCode, "DA child")
.WithParent(root)
.Build();
child.SetValue("title", "EN child title", culture: langEn.IsoCode);
child.SetValue("title", "DA child title", culture: langDa.IsoCode);
ContentService.Save(child);
var result = await ContentPublishingService.PublishBranchAsync(root.Key, new[] { langEn.IsoCode, langDa.IsoCode }, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsFalse(result.Success);
AssertBranchResultFailed(result.Result, (root.Key, ContentPublishingOperationStatus.ContentInvalid));
root = ContentService.GetById(root.Key)!;
Assert.AreEqual(0, root.PublishedCultures.Count());
child = ContentService.GetById(child.Key)!;
Assert.AreEqual(0, child.PublishedCultures.Count());
}
[Test]
public async Task Cannot_Publish_Child_Of_Unpublished_Parent()
{
VerifyIsNotPublished(Textpage.Key);
var result = await ContentPublishingService.PublishAsync(Subpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.PathNotPublished, result.Status);
VerifyIsNotPublished(Subpage.Key);
}
[Test]
public async Task Cannot_Publish_From_Trash()
{
ContentService.MoveToRecycleBin(Subpage);
Assert.IsTrue(ContentService.GetById(Subpage.Key)!.Trashed);
var result = await ContentPublishingService.PublishAsync(Subpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InTrash, result.Status);
VerifyIsNotPublished(Subpage.Key);
}
[Test]
public async Task Cannot_Republish_Content_After_Adding_Validation_To_Existing_Property()
{
Textpage.SetValue("title", string.Empty);
Textpage.SetValue("author", "This is not a number");
ContentService.Save(Textpage);
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
VerifyIsPublished(Textpage.Key);
ContentType.PropertyTypes.First(pt => pt.Alias == "title").Mandatory = true;
ContentType.PropertyTypes.First(pt => pt.Alias == "author").ValidationRegExp = "^\\d*$";
await ContentTypeService.SaveAsync(ContentType, Constants.Security.SuperUserKey);
result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.ContentInvalid, result.Status);
var invalidPropertyAliases = result.Result.InvalidPropertyAliases.ToArray();
Assert.AreEqual(2, invalidPropertyAliases.Length);
Assert.Contains("title", invalidPropertyAliases);
Assert.Contains("author", invalidPropertyAliases);
// despite the failure to publish, the page should remain published
VerifyIsPublished(Textpage.Key);
}
[Test]
public async Task Cannot_Republish_Content_After_Adding_Mandatory_Property()
{
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
VerifyIsPublished(Textpage.Key);
ContentType.AddPropertyType(
new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar)
{
Alias = "mandatoryProperty", Name = "Mandatory Property", Mandatory = true
});
await ContentTypeService.SaveAsync(ContentType, Constants.Security.SuperUserKey);
result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.ContentInvalid, result.Status);
var invalidPropertyAliases = result.Result.InvalidPropertyAliases.ToArray();
Assert.AreEqual(1, invalidPropertyAliases.Length);
Assert.Contains("mandatoryProperty", invalidPropertyAliases);
// despite the failure to publish, the page should remain published
VerifyIsPublished(Textpage.Key);
}
[Test]
public async Task Cannot_Republish_Branch_After_Adding_Mandatory_Property()
{
var result = await ContentPublishingService.PublishBranchAsync(Textpage.Key, _allCultures, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsTrue(result.Success);
VerifyIsPublished(Textpage.Key);
VerifyIsPublished(Subpage.Key);
VerifyIsPublished(Subpage2.Key);
VerifyIsPublished(Subpage3.Key);
// force an update on the child pages so they will be subject to branch republishing
foreach (var key in new [] { Subpage.Key, Subpage2.Key, Subpage3.Key })
{
var content = ContentService.GetById(key)!;
content.SetValue("title", "Updated");
ContentService.Save(content);
}
ContentType.AddPropertyType(
new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar)
{
Alias = "mandatoryProperty", Name = "Mandatory Property", Mandatory = true
});
await ContentTypeService.SaveAsync(ContentType, Constants.Security.SuperUserKey);
// force an update on the root page so it is valid (and also subject to branch republishing).
// if we didn't do this, the children would never be considered for branch publishing, as the publish logic
// stops at the first invalid parent.
// as an added bonus, this lets us test a partially successful branch publish :)
var textPage = ContentService.GetById(Textpage.Key)!;
textPage.SetValue("mandatoryProperty", "This is a valid value");
ContentService.Save(textPage);
result = await ContentPublishingService.PublishBranchAsync(Textpage.Key, _allCultures, PublishBranchFilter.IncludeUnpublished, Constants.Security.SuperUserKey, false);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.FailedBranch, result.Status);
AssertBranchResultSuccess(result.Result, Textpage.Key);
AssertBranchResultFailed(
result.Result,
(Subpage.Key, ContentPublishingOperationStatus.ContentInvalid),
(Subpage2.Key, ContentPublishingOperationStatus.ContentInvalid),
(Subpage3.Key, ContentPublishingOperationStatus.ContentInvalid));
// despite the failure to publish, the entier branch should remain published
VerifyIsPublished(Textpage.Key);
VerifyIsPublished(Subpage.Key);
VerifyIsPublished(Subpage2.Key);
VerifyIsPublished(Subpage3.Key);
}
[TestCase("en-us")]
[TestCase("da-dk")]
public async Task Cannot_Publish_Incorrect_Culture_Code(string cultureCode)
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { cultureCode }), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, result.Status);
}
[TestCase("de-DE")]
[TestCase("es-ES")]
public async Task Cannot_Publish_Non_Existing_Culture(string cultureCode)
{
var (langEn, langDa, contentType) = await SetupVariantTest();
IContent content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName(langEn.IsoCode, "EN root")
.WithCultureName(langDa.IsoCode, "DA root")
.Build();
content.SetValue("title", "EN title", culture: langEn.IsoCode);
content.SetValue("title", "DA title", culture: langDa.IsoCode);
ContentService.Save(content);
var result = await ContentPublishingService.PublishAsync(content.Key, MakeModel(new HashSet<string>() { cultureCode }), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, result.Status);
}
[Test]
public async Task Can_Publish_Invariant_Content_With_Cultures_Provided_If_The_Default_Culture_Is_Exclusively_Provided()
{
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(new HashSet<string>() { "en-US" }), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
}
[Test]
public async Task Can_Publish_Invariant_Content_With_Cultures_Provided_If_The_Default_Culture_Is_Provided_With_Other_Cultures()
{
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(new HashSet<string>() { "en-US", "da-DK" }), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
}
[Test]
public async Task Cannot_Publish_Invariant_Content_With_Cultures_Provided_That_Do_Not_Include_The_Default_Culture()
{
var result = await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(new HashSet<string>() { "da-DK" }), Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, result.Status);
}
private void AssertBranchResultSuccess(ContentPublishingBranchResult result, params Guid[] expectedKeys)
{
var items = result.SucceededItems.ToArray();
Assert.AreEqual(expectedKeys.Length, items.Length);
foreach (var key in expectedKeys)
{
var item = items.FirstOrDefault(i => i.Key == key);
Assert.IsNotNull(item);
Assert.AreEqual(ContentPublishingOperationStatus.Success, item.OperationStatus);
}
}
private void AssertBranchResultFailed(ContentPublishingBranchResult result, params (Guid, ContentPublishingOperationStatus)[] expectedFailures)
{
var items = result.FailedItems.ToArray();
Assert.AreEqual(expectedFailures.Length, items.Length);
foreach (var (key, status) in expectedFailures)
{
var item = items.FirstOrDefault(i => i.Key == key);
Assert.IsNotNull(item);
Assert.AreEqual(status, item.OperationStatus);
}
}
}