diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs index deeb8d8f0d..9a93a6814e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Security.OperationStatus; @@ -20,7 +20,7 @@ public abstract class ClientCredentialsUserControllerBase : UserControllerBase .Build()), BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder .WithTitle("Invalid client ID") - .WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 255 characters.") + .WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 100 characters.") .Build()), _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder .WithTitle("Unknown client credentials operation status.") diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 581159ab64..c76e89381a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1267,7 +1267,7 @@ public class ContentService : RepositoryService, IContentService // we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications if (HasUnsavedChanges(content)) { - return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, EventMessagesFactory.Get(), content); + return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, evtMsgs, content); } if (content.Name != null && content.Name.Length > 255) @@ -1324,13 +1324,8 @@ public class ContentService : RepositoryService, IContentService // Change state to publishing content.PublishedState = PublishedState.Publishing; - var publishingNotification = new ContentPublishingNotification(content, evtMsgs); - if (scope.Notifications.PublishCancelable(publishingNotification)) - { - return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - } - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, publishingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, new Dictionary(), userId); scope.Complete(); return result; } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index e92a5b0cd0..659a98ea07 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -2680,7 +2680,7 @@ internal partial class UserService : RepositoryService, IUserService } } - [GeneratedRegex(@"^[\w\d\-\._~]{1,255}$")] + [GeneratedRegex(@"^[\w\d\-\._~]{1,100}$")] private static partial Regex ValidClientId(); #endregion diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts index 4541209e21..459e01e2ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts @@ -179,7 +179,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin `, )} -
+
`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts index 6c66cc56bf..8338b7bf43 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts @@ -30,6 +30,8 @@ export class UmbTiptapToolbarElement extends UmbLitElement { override connectedCallback(): void { super.connectedCallback(); + this.setAttribute('data-mark', 'tiptap-toolbar'); + this.#attached = true; this.#observeExtensions(); } @@ -51,7 +53,12 @@ export class UmbTiptapToolbarElement extends UmbLitElement { [], (manifest) => this.toolbar.flat(2).includes(manifest.alias), (extensionControllers) => { - this._lookup = new Map(extensionControllers.map((ext) => [ext.alias, ext.component])); + this._lookup = new Map( + extensionControllers.map((ext) => { + (ext.component as HTMLElement)?.setAttribute('data-mark', `action:tiptap-toolbar:${ext.alias}`); + return [ext.alias, ext.component]; + }), + ); }, undefined, undefined, diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index 46525299dc..159a040f77 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -165,6 +165,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName); + textStringDataTypeId = textStringDataType.id; + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(blockName); + await umbracoApi.dataType.ensureNameNotExists(blockGridName); +}); + +test('invariant document type with invariant block grid with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, textStringName, textStringDataTypeId); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('invariant document type with invariant block grid with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +// Remove fixme when this test works. Currently, the textstring value is not saved when saving / publishing the document +test('invariant document type with invariant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName) + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with variant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName, true); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block grid with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton() + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithBlockList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts similarity index 100% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithBlockList.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/ContentWithBlockList.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts new file mode 100644 index 0000000000..1ad6625c86 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/BlockList/VariantBlockList.spec.ts @@ -0,0 +1,198 @@ +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; + +// Document Type +const documentTypeName = 'DocumentTypeName'; +let documentTypeId = null; +const documentTypeGroupName = 'DocumentGroup'; + +// Block List +const blockListName = 'BlockListName'; +let blockListId = null; + +// Element Type +const blockName = 'BlockName'; +let elementTypeId = null; +const elementGroupName = 'ElementGroup'; + +// Text String +const textStringName = 'TextStringName'; +let textStringDataTypeId = null; +const textStringDataTypeName = 'Textstring'; +const textStringText = 'ThisIsATextString'; + +// Content Name +const contentName = 'ContentName'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName); + textStringDataTypeId = textStringDataType.id; + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(blockName); + await umbracoApi.dataType.ensureNameNotExists(blockListName); +}); + +test('invariant document type with invariant block list with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, textStringName, textStringDataTypeId); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('invariant document type with invariant block list with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +// Remove fixme when this test works. Currently the textstring value is is not saved when saving / publishing the document +test.fixme('invariant document type with invariant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName) + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with variant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName, true); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block list with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickAddBlockElementButton() + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts index 3c567fd688..a81b98e84f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts @@ -101,7 +101,7 @@ test('can create content with names and content that vary by culture', async ({u const danishTextContent = 'Dette er testtekst'; const dataTypeName = 'Textstring'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', true); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', true, true); await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, textContent, dataTypeName, true); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts similarity index 100% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithTiptap.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts new file mode 100644 index 0000000000..21f8a99295 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/VariantTipTapBlocks.spec.ts @@ -0,0 +1,199 @@ +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; + +// Document Type +const documentTypeName = 'DocumentTypeName'; +let documentTypeId = null; +const documentTypeGroupName = 'DocumentGroup'; + +// Tip Tap +const tipTapName = 'TipTapTest'; +let tipTapId = null; + +// Element Type +const blockName = 'BlockName'; +let elementTypeId = null; +const elementGroupName = 'ElementGroup'; + +// Text String +const textStringName = 'TextStringName'; +let textStringDataTypeId = null; +const textStringDataTypeName = 'Textstring'; +const textStringText = 'ThisIsATextString'; + +// Content Name +const contentName = 'ContentName'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName); + textStringDataTypeId = textStringDataType.id; + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.language.ensureIsoCodeNotExists('da'); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(blockName); + await umbracoApi.dataType.ensureNameNotExists(tipTapName); +}); + +test('invariant document type with invariant tiptap RTE with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, textStringName, textStringDataTypeId); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('invariant document type with invariant tiptap RTE with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +// Remove fixme when this test works. Currently the textstring value is is not saved when saving / publishing the document +test.fixme('invariant document type with invariant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with variant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName, true); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant tiptap RTE with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); + +test('variant document type with invariant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => { + // Arrange + elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true); + tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, documentTypeGroupName, true, false); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickInsertBlockButton(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.enterTextstring(textStringText); + await umbracoUi.content.clickCreateModalButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + + await umbracoUi.reloadPage(); + await umbracoUi.content.clickBlockElementWithName(blockName); + await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts index e04ed42622..f0ce32091b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts @@ -397,7 +397,7 @@ test('can enable validation for a property in a document type', async ({umbracoA test('can allow vary by culture for a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, false); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, true); await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); // Act diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts index 34966f2466..2242683de9 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts @@ -49,7 +49,7 @@ test.beforeEach(async ({umbracoApi}) => { await umbracoApi.language.createVietnameseLanguage(); const dataType = await umbracoApi.dataType.getByName(dataTypeName); dataTypeId = dataType.id; - documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId, 'TestGroup', true); + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId, 'TestGroup', true, true); await umbracoApi.document.createDocumentWithMultipleVariants(documentName, documentTypeId, AliasHelper.toAlias(dataTypeName), cultureVariants); }); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs index 92829fdf37..755b17d1c3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; using System.Text; using NUnit.Framework; @@ -17,7 +15,6 @@ using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -302,57 +299,109 @@ public class UserServiceTests : UmbracoIntegrationTest [Test] public void Calculate_Permissions_For_User_For_Path() { - // see: http://issues.umbraco.org/issue/U4-10075#comment=67-40085 - // for an overview of what this is testing + // Details of what this is testing were originally at http://issues.umbraco.org/issue/U4-10075#comment=67-40085 (and available + // now at https://web.archive.org/web/20211021021851/http://issues.umbraco.org/issue/U4-10075). + // Here is the same information copied and reformatted: + + // Here's a mockup of how user group permissions will work with inheritance. + // User (UserX), belongs to these groups which have the following default permission characters applied: + + // | Groups | Defaults | + // |--------|----------| + // | G1 | sdf | + // | G2 | sdgk | + // | G3 | fg | + + // Thus the aggregate defaults for UserX is "sdfgk" + + // As an example node structure, here is the node tree and where any explicit permissions are assigned. + // It shows you the resulting permissions for each node that the user has: + + // | Nodes | G1 | G2 | G3 | Result | + // |-------------|------|------|------|-------------------------------------------| + // | - A | | | | sdfgk (Defaults) | + // | - - B | | fr | | sdfgr (Defaults + Explicit) | + // | - - - C | | | qz | sdfrqz (Defaults + Explicit + Inherited) | + // | - - - - D | | | | sdfrqz (Inherited) | + // | - - - - - E | | | | sdfrqz (Inherited) | + + // Notice that 'k' is no longer in the result for node B and below. + // Notice that 'g' is no longer in the result for node C and below. + const string path = "-1,1,2,3,4"; var pathIds = path.GetIdsFromPathReversed(); + // Setup: 3 groups with different default permissions. + // - Group A has permissions S, D, F. + // - Group B has permissions S, D, G, K. + // - Group C has permissions F, G. const int groupA = 7; const int groupB = 8; const int groupC = 9; - var userGroups = new Dictionary> { - {groupA, new[] {"S", "D", "F"}.ToHashSet()}, {groupB, new[] {"S", "D", "G", "K"}.ToHashSet()}, {groupC, new[] {"F", "G"}.ToHashSet()} + { groupA, new[] {"S", "D", "F"}.ToHashSet() }, + { groupB, new[] {"S", "D", "G", "K"}.ToHashSet() }, + { groupC, new[] {"F", "G"}.ToHashSet() } }; + // Setup: combination of default and specific permissions for each group and document. EntityPermission[] permissions = { - new(groupA, 1, userGroups[groupA], true), new(groupA, 2, userGroups[groupA], true), - new(groupA, 3, userGroups[groupA], true), new(groupA, 4, userGroups[groupA], true), - new(groupB, 1, userGroups[groupB], true), new(groupB, 2, new[] {"F", "R"}.ToHashSet(), false), - new(groupB, 3, userGroups[groupB], true), new(groupB, 4, userGroups[groupB], true), - new(groupC, 1, userGroups[groupC], true), new(groupC, 2, userGroups[groupC], true), - new(groupC, 3, new[] {"Q", "Z"}.ToHashSet(), false), new(groupC, 4, userGroups[groupC], true) + new(groupA, 1, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupA, 2, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupA, 3, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupA, 4, userGroups[groupA], true), // Comes from the default permission on group A. + new(groupB, 1, userGroups[groupB], true), // Comes from the default permission on group B. + new(groupB, 2, new[] {"F", "R"}.ToHashSet(), false), // Group B has a specific permission on document 2 of of F, R. + new(groupB, 3, userGroups[groupB], true), // Comes from the default permission on group B. + new(groupB, 4, userGroups[groupB], true), // Comes from the default permission on group B. + new(groupC, 1, userGroups[groupC], true), // Comes from the default permission on group C. + new(groupC, 2, userGroups[groupC], true), // Comes from the default permission on group C. + new(groupC, 3, new[] {"Q", "Z"}.ToHashSet(), false), // Group C has a specific permission on document 3 of of Q, Z. + new(groupC, 4, userGroups[groupC], true) // Comes from the default permission on group C. }; - // Permissions for Id 4 + // Permissions for document 4 var result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds); Assert.AreEqual(4, result.EntityId); var allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); - // Permissions for Id 3 + // - has S, D, F from group A, R from group B (specific permission on document 2), Q, Z from group C (specific permission on document 3). + // - doesn't have K or G due to specific permissions set on group B, document 2. + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "R", "Q", "Z"])); + + // Permissions for document 3 result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(1).ToArray()); Assert.AreEqual(3, result.EntityId); allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); - // Permissions for Id 2 + // - has S, D, F from group A, R from group B (specific permission on document 2), Q, Z from group C (specific permission on document 3). + // - doesn't have K or G due to specific permissions set on group B, document 2. + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "R", "Q", "Z"])); + + // Permissions for document 2 result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(2).ToArray()); Assert.AreEqual(2, result.EntityId); allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "R" })); - // Permissions for Id 1 + // - has S, D, F from group A, G from group C, R from group B (specific permission on document 2). + // - doesn't have K due to specific permissions set on group B, document 2. + // - this would remove G too, but it's there as it's also defined in group C that doesn't have a specific permission override. + // - doesn't have Q, Z as these specific permissions are added to a document further down the path. + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "G", "R"])); + + // Permissions for document 1 result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(3).ToArray()); Assert.AreEqual(1, result.EntityId); allPermissions = result.GetAllPermissions().ToArray(); Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); - Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" })); + + // - has S, D, F from group A, G, K from group B (also G from group C) + Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "G", "K"])); } [Test] @@ -1004,7 +1053,6 @@ public class UserServiceTests : UmbracoIntegrationTest [TestCase("@", UserClientCredentialsOperationStatus.InvalidClientId)] [TestCase("[", UserClientCredentialsOperationStatus.InvalidClientId)] [TestCase("]", UserClientCredentialsOperationStatus.InvalidClientId)] - [TestCase("More_Than_255_characters_012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", UserClientCredentialsOperationStatus.InvalidClientId)] public async Task Can_Use_Only_Unreserved_Characters_For_ClientId(string clientId, UserClientCredentialsOperationStatus expectedResult) { // Arrange @@ -1017,6 +1065,20 @@ public class UserServiceTests : UmbracoIntegrationTest Assert.AreEqual(expectedResult, result); } + [TestCase("Less_Than_100_characters_0123456789012345678901234567890123456789012345678901234567890123456789", UserClientCredentialsOperationStatus.Success)] + [TestCase("More_Than_100_characters_01234567890123456789012345678901234567890123456789012345678901234567890123456789", UserClientCredentialsOperationStatus.InvalidClientId)] + public async Task Cannot_Create_Too_Long_ClientId(string clientId, UserClientCredentialsOperationStatus expectedResult) + { + // Arrange + var user = await CreateTestUser(UserKind.Api); + + // Act + var result = await UserService.AddClientIdAsync(user.Key, clientId); + + // Assert + Assert.AreEqual(expectedResult, result); + } + private Content[] BuildContentItems(int numberToCreate) { var template = TemplateBuilder.CreateTextPageTemplate();