diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature index 8591753b47e..2500f103872 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature @@ -121,22 +121,28 @@ Feature: Discard individual nodes (basics) And I expect this node to have the following properties: | Key | Value | | image | "Initial image" | - Scenario: Discard no node, non existing ones or unchanged nodes is a no-op - # no node - When the command DiscardIndividualNodesFromWorkspace is executed with payload: + + Scenario: Discard no node is not allowed + When the command DiscardIndividualNodesFromWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | nodesToDiscard | [] | | newContentStreamId | "user-cs-identifier-new" | - Then I expect the content stream "user-cs-identifier-new" to not exist + Then the last command should have thrown an exception of type "InvalidArgumentException" with code 1737448741 + Scenario: Discard non existing nodes or unchanged nodes is skipped (via exception) # unchanged or non existing nodes - When the command DiscardIndividualNodesFromWorkspace is executed with payload: + When the command DiscardIndividualNodesFromWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | nodesToDiscard | ["non-existing-node", "sir-unchanged"] | | newContentStreamId | "user-cs-identifier-new-two" | + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1737477674 and message: + """ + No nodes matched in workspace "user-test" the filter non-existing-node,sir-unchanged. + """ + # all nodes are still on the original user cs When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier;sir-david-nodenborough;{} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W11-ChangeBaseWorkspace/02-BasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W11-ChangeBaseWorkspace/02-BasicFeatures.feature index bc4616b5ce6..9ab32ea2c20 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W11-ChangeBaseWorkspace/02-BasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W11-ChangeBaseWorkspace/02-BasicFeatures.feature @@ -49,11 +49,15 @@ Feature: Change base workspace works :D what else | baseWorkspaceName | "live" | | newContentStreamId | "shared-cs-identifier" | - Scenario: Change base workspace is a no-op if the base already matches - When the command ChangeBaseWorkspace is executed with payload: + Scenario: Change base workspace is skipped (via exception) if the base already matches + When the command ChangeBaseWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | baseWorkspaceName | "live" | + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1737534132 and message: + """ + Skipped changing the base workspace to "live" from workspace "user-test" because its already set. + """ Then I expect exactly 1 event to be published on stream "Workspace:user-test" And event at index 0 is of type "WorkspaceWasCreated" with payload: @@ -65,38 +69,6 @@ Feature: Change base workspace works :D what else Given I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} - Scenario: Change base workspace is a no-op if the base already matches but the workspace is outdated - When the command CreateNodeAggregateWithNode is executed with payload: - | Key | Value | - | workspaceName | "live" | - | nodeAggregateId | "holy-nody" | - | nodeTypeName | "Neos.ContentRepository.Testing:Content" | - | originDimensionSpacePoint | {} | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | initialPropertyValues | {"text": "New node in live"} | - - Then workspaces user-test has status OUTDATED - - When the command ChangeBaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | - | baseWorkspaceName | "live" | - - Then workspaces user-test has status OUTDATED - - Then I expect exactly 1 event to be published on stream "Workspace:user-test" - And event at index 0 is of type "WorkspaceWasCreated" with payload: - | Key | Expected | - | workspaceName | "user-test" | - | baseWorkspaceName | "live" | - | newContentStreamId | "user-cs-identifier" | - - Given I am in workspace "user-test" and dimension space point {} - Then I expect node aggregate identifier "holy-nody" to lead to no node - - Given I am in workspace "live" and dimension space point {} - Then I expect node aggregate identifier "holy-nody" to lead to node cs-identifier;holy-nody;{} - Scenario: Change base workspace if user has no changes and is up to date with new base When the command ChangeBaseWorkspace is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/01-BasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/01-BasicFeatures.feature index 0e75f159f10..0b56e0f12a9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/01-BasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/01-BasicFeatures.feature @@ -41,11 +41,15 @@ Feature: Rebasing with no conflict Then workspaces live,user-test have status UP_TO_DATE - Scenario: Rebase is a no-op if there are no changes - When the command RebaseWorkspace is executed with payload: + Scenario: Rebase is skipped (via exception) if there are no changes + When the command RebaseWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | rebasedContentStreamId | "user-cs-rebased" | + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1730463693 and message: + """ + Skipped rebase workspace "user-test" because it is not outdated. + """ Then I expect the content stream "user-cs-rebased" to not exist When I am in workspace "live" and dimension space point {} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature index b1fb6e65855..58f1c7b58f2 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature @@ -160,7 +160,7 @@ Feature: Workspace based content publishing When the command SetNodeProperties is executed with payload: | Key | Value | - | workspaceName | "live" | + | workspaceName | "user-test" | | nodeAggregateId | "nody-mc-nodeface" | | originDimensionSpacePoint | {} | | propertyValues | {"text": "Modified anew"} | @@ -177,15 +177,20 @@ Feature: Workspace based content publishing | Key | Value | | text | "Modified anew" | - Scenario: Publish is a no-op if there are no changes + Scenario: Publish is skipped (via exception) if there are no changes And I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} - And the command PublishWorkspace is executed with payload: + And the command PublishWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | newContentStreamId | "user-cs-new" | + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1730463156 and message: + """ + Skipped publish workspace "user-test" without any publishable changes. + """ + # the user and live workspace are unchanged Then I expect exactly 1 event to be published on stream "Workspace:user-test" Then I expect exactly 1 event to be published on stream "ContentStream:user-cs-identifier" @@ -196,7 +201,7 @@ Feature: Workspace based content publishing Then I expect exactly 4 events to be published on stream "ContentStream:cs-identifier" Then I expect exactly 1 event to be published on stream "Workspace:live" - Scenario: Publish is a no-op if there are no changes (and the workspace is outdated) + Scenario: Publish is skipped (via exception) if there are no changes (and the workspace is outdated) And the command SetNodeProperties is executed with payload: | Key | Value | | workspaceName | "live" | @@ -204,14 +209,19 @@ Feature: Workspace based content publishing | originDimensionSpacePoint | {} | | propertyValues | {"text": "Modified in live workspace"} | - And the command PublishWorkspace is executed with payload: + And the command PublishWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | newContentStreamId | "user-cs-new" | + + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1730463156 and message: + """ + Skipped publish workspace "user-test" without any publishable changes. + """ + Then workspaces user-test has status OUTDATED Then I expect exactly 1 events to be published on stream with prefix "Workspace:user-test" - And I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature index fb1cb45054b..cab2e745825 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature @@ -71,7 +71,7 @@ Feature: Individual node publication Then I expect a node identified by cs-identifier;sir-david-nodenborough;{} to exist in the content graph - Scenario: Partial publish is a no-op if the workspace doesnt contain any changes (and the workspace is outdated) + Scenario: Partial publish is skipped (via exception) if the workspace doesnt contain any changes (and the workspace is outdated) When the command CreateNodeAggregateWithNode is executed with payload: | Key | Value | @@ -85,11 +85,17 @@ Feature: Individual node publication And I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node - When the command PublishIndividualNodesFromWorkspace is executed with payload: + When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | nodesToPublish | ["non-existing"] | | contentStreamIdForRemainingPart | "user-cs-new" | + + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1730463156 and message: + """ + Skipped publish workspace "user-test" without any publishable changes. + """ + Then workspaces user-test has status OUTDATED And I am in workspace "user-test" and dimension space point {} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature index cc0cc931a40..910ebf23246 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature @@ -129,21 +129,28 @@ Feature: Publishing individual nodes (basics) | Key | Value | | image | "Modified image" | - Scenario: Publish no node, non existing ones or unchanged nodes is a no-op - # no node - When the command PublishIndividualNodesFromWorkspace is executed with payload: + Scenario: Publish no node is not allowed + When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | nodesToPublish | [] | | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | - Then I expect the content stream "user-cs-identifier-remaining" to not exist + Then the last command should have thrown an exception of type "InvalidArgumentException" with code 1737448717 + Scenario: Publish non existing nodes or unchanged nodes is skipped (via exception) # unchanged or non existing nodes - When the command PublishIndividualNodesFromWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | + When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught: + | Key | Value | + | workspaceName | "user-test" | | nodesToPublish | ["non-existing-node", "sir-unchanged"] | - | contentStreamIdForRemainingPart | "user-cs-identifier-remaining-two" | + | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | + + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1737477674 and message: + """ + No nodes matched in workspace "user-test" the filter non-existing-node,sir-unchanged. + """ + + Then I expect the content stream "user-cs-identifier-remaining" to not exist When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} @@ -288,11 +295,17 @@ Feature: Publishing individual nodes (basics) | originDimensionSpacePoint | {} | | propertyValues | {"text": "Modified in live workspace"} | - When the command PublishIndividualNodesFromWorkspace is executed with payload: + When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | nodesToPublish | ["non-existing"] | | contentStreamIdForRemainingPart | "user-cs-new" | + + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1737477674 and message: + """ + No nodes matched in workspace "user-test" the filter non-existing. + """ + Then workspaces user-test has status OUTDATED Then I expect exactly 1 events to be published on stream with prefix "Workspace:user-test" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature index 67aecf8f51d..6b8202ee3e3 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature @@ -167,10 +167,16 @@ Feature: Workspace discarding - basic functionality | originDimensionSpacePoint | {} | | propertyValues | {"text": "Modified in live workspace"} | - And the command DiscardWorkspace is executed with payload: + And the command DiscardWorkspace is executed with payload and exceptions are caught: | Key | Value | | workspaceName | "user-test" | | newContentStreamId | "user-cs-two-discarded" | + + Then the last command should have thrown an exception of type "WorkspaceCommandSkipped" with code 1730463156 and message: + """ + Skipped discard workspace "user-test" without any publishable changes. + """ + Then workspaces user-test has status OUTDATED Then I expect exactly 1 events to be published on stream with prefix "Workspace:user-test" diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 37277f312e2..ff3b6c5fac9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -192,8 +192,7 @@ private function handlePublishWorkspace( $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); if (!$workspace->hasPublishableChanges()) { - // no-op - return; + throw WorkspaceCommandSkipped::becauseWorkspaceToPublishIsEmpty($command->workspaceName); } $workspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($workspace, $commandHandlingDependencies); $baseWorkspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($baseWorkspace, $commandHandlingDependencies); @@ -343,8 +342,8 @@ private function handleRebaseWorkspace( $workspace->status === WorkspaceStatus::UP_TO_DATE && $command->rebaseErrorHandlingStrategy !== RebaseErrorHandlingStrategy::STRATEGY_FORCE ) { - // no-op if workspace is not outdated and not forcing it - return; + // skipped rebase, when not forcing it + throw WorkspaceCommandSkipped::becauseWorkspaceToRebaseIsNotOutdated($command->workspaceName); } if (!$workspace->hasPublishableChanges()) { @@ -436,9 +435,9 @@ private function handlePublishIndividualNodesFromWorkspace( ): \Generator { $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); - if ($command->nodesToPublish->isEmpty() || !$workspace->hasPublishableChanges()) { - // noop - return; + + if (!$workspace->hasPublishableChanges()) { + throw WorkspaceCommandSkipped::becauseWorkspaceToPublishIsEmpty($command->workspaceName); } $workspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($workspace, $commandHandlingDependencies); @@ -454,8 +453,7 @@ private function handlePublishIndividualNodesFromWorkspace( [$matchingCommands, $remainingCommands] = $rebaseableCommands->separateMatchingAndRemainingCommands($command->nodesToPublish); if ($matchingCommands->isEmpty()) { - // almost a noop (e.g. random node ids were specified) ;) - return; + throw WorkspaceCommandSkipped::becauseFilterDidNotMatch($command->workspaceName, $command->nodesToPublish); } yield $this->closeContentStream( @@ -558,9 +556,8 @@ private function handleDiscardIndividualNodesFromWorkspace( $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies); $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); - if ($command->nodesToDiscard->isEmpty() || !$workspace->hasPublishableChanges()) { - // noop - return; + if (!$workspace->hasPublishableChanges()) { + throw WorkspaceCommandSkipped::becauseWorkspaceToDiscardIsEmpty($command->workspaceName); } $workspaceContentStreamVersion = $this->requireOpenContentStreamAndVersion($workspace, $commandHandlingDependencies); @@ -577,8 +574,7 @@ private function handleDiscardIndividualNodesFromWorkspace( [$commandsToDiscard, $commandsToKeep] = $rebaseableCommands->separateMatchingAndRemainingCommands($command->nodesToDiscard); if ($commandsToDiscard->isEmpty()) { - // if we have nothing to discard, we can just keep all. (e.g. random node ids were specified) It's almost a noop ;) - return; + throw WorkspaceCommandSkipped::becauseFilterDidNotMatch($command->workspaceName, $command->nodesToDiscard); } yield $this->closeContentStream( @@ -660,7 +656,7 @@ private function handleDiscardWorkspace( $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies); if (!$workspace->hasPublishableChanges()) { - return; + throw WorkspaceCommandSkipped::becauseWorkspaceToDiscardIsEmpty($command->workspaceName); } $this->requireContentStreamToNotBeClosed($workspace->currentContentStreamId, $commandHandlingDependencies); @@ -723,8 +719,7 @@ private function handleChangeBaseWorkspace( $this->requireContentStreamToNotBeClosed($workspace->currentContentStreamId, $commandHandlingDependencies); if ($currentBaseWorkspace->workspaceName->equals($command->baseWorkspaceName)) { - // no-op - return; + throw WorkspaceCommandSkipped::becauseTheBaseWorkspaceIsUnchanged($command->baseWorkspaceName, $command->workspaceName); } $this->requireEmptyWorkspace($workspace); diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandSkipped.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandSkipped.php new file mode 100644 index 00000000000..1405bcc457e --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandSkipped.php @@ -0,0 +1,77 @@ +hasPublishableChanges()) { + * $contentRepository->handle(...); + * } + * + * *Workspace rebase* + * + * Command skipped if the workspace is not outdated. + * + * *Workspace base change* + * + * Command skipped when attempting to change the base workspace to the currently set base workspace. + * + * Note: + * + * The case is not handled gracefully with a no-op as there would be no traces (emitted events) of the handled command, + * and the original content stream id is kept (for publish operations). + * This exception denoting the operation is obsolete should harden the interaction and make behaviour more explicit. + * + * @api thrown as part of command handling in case of a workspace no-op + */ +class WorkspaceCommandSkipped extends \RuntimeException +{ + public static function becauseWorkspaceToPublishIsEmpty(WorkspaceName $workspaceName): self + { + return new self(sprintf('Skipped publish workspace "%s" without any publishable changes.', $workspaceName->value), 1730463156); + } + + public static function becauseWorkspaceToDiscardIsEmpty(WorkspaceName $workspaceName): self + { + return new self(sprintf('Skipped discard workspace "%s" without any publishable changes.', $workspaceName->value), 1730463156); + } + + public static function becauseFilterDidNotMatch(WorkspaceName $workspaceName, NodeAggregateIds $selectedNodeAggregateIds): self + { + return new self(sprintf('No nodes matched in workspace "%s" the filter %s.', $workspaceName->value, join(',', $selectedNodeAggregateIds->toStringArray())), 1737477674); + } + + public static function becauseWorkspaceToRebaseIsNotOutdated(WorkspaceName $workspaceName): self + { + return new self(sprintf('Skipped rebase workspace "%s" because it is not outdated.', $workspaceName->value), 1730463693); + } + + public static function becauseTheBaseWorkspaceIsUnchanged(WorkspaceName $baseWorkspaceName, WorkspaceName $workspaceName): self + { + return new self(sprintf('Skipped changing the base workspace to "%s" from workspace "%s" because its already set.', $baseWorkspaceName->value, $workspaceName->value), 1737534132); + } +} diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Exception/WorkspaceIsNotEmptyException.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Exception/WorkspaceIsNotEmptyException.php index 6bfb5ff94b9..31e408ecb35 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Exception/WorkspaceIsNotEmptyException.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceModification/Exception/WorkspaceIsNotEmptyException.php @@ -5,7 +5,7 @@ namespace Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception; /** - * @api + * @api thrown in case a base workspace change is attempted while the workspace still has pending changes */ final class WorkspaceIsNotEmptyException extends \RuntimeException { diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/DiscardIndividualNodesFromWorkspace.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/DiscardIndividualNodesFromWorkspace.php index 220a65aba96..97b6cc9b5e4 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/DiscardIndividualNodesFromWorkspace.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/DiscardIndividualNodesFromWorkspace.php @@ -36,6 +36,9 @@ private function __construct( public NodeAggregateIds $nodesToDiscard, public ContentStreamId $newContentStreamId ) { + if ($this->nodesToDiscard->isEmpty()) { + throw new \InvalidArgumentException(sprintf('The command "DiscardIndividualNodesFromWorkspace" for workspace %s must contain nodes to publish', $this->workspaceName->value), 1737448741); + } } /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/PublishIndividualNodesFromWorkspace.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/PublishIndividualNodesFromWorkspace.php index 77407e412b2..e61cb79dd80 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/PublishIndividualNodesFromWorkspace.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspacePublication/Command/PublishIndividualNodesFromWorkspace.php @@ -36,6 +36,9 @@ private function __construct( public NodeAggregateIds $nodesToPublish, public ContentStreamId $contentStreamIdForRemainingPart ) { + if ($this->nodesToPublish->isEmpty()) { + throw new \InvalidArgumentException(sprintf('The command "PublishIndividualNodesFromWorkspace" for workspace %s must contain nodes to publish', $this->workspaceName->value), 1737448717); + } } /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Dto/RebaseErrorHandlingStrategy.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Dto/RebaseErrorHandlingStrategy.php index 4656abea441..7a3697193dc 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Dto/RebaseErrorHandlingStrategy.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Dto/RebaseErrorHandlingStrategy.php @@ -14,6 +14,8 @@ namespace Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; + /** * The strategy how to handle errors during workspace rebase * @@ -25,7 +27,7 @@ enum RebaseErrorHandlingStrategy: string implements \JsonSerializable { /** - * This strategy rebasing will fail if conflicts are detected and the "WorkspaceRebaseFailed" event is added. + * This strategy rebasing will fail if conflicts are detected and the {@see WorkspaceRebaseFailed} exception is thrown. */ case STRATEGY_FAIL = 'fail'; diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Exception/WorkspaceRebaseFailed.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Exception/WorkspaceRebaseFailed.php index 45849b859df..8f6c8ae4169 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Exception/WorkspaceRebaseFailed.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/Exception/WorkspaceRebaseFailed.php @@ -14,9 +14,32 @@ namespace Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardIndividualNodesFromWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishIndividualNodesFromWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvents; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; /** + * Thrown if the workspace was outdated and an automatic rebase failed due to conflicts. + * + * No changes to the workspace/content-stream were made and the operation was aborted. + * + * Affected workspace operations: + * + * *Workspace publish* + * Via the commands {@see PublishWorkspace} or {@see PublishIndividualNodesFromWorkspace}. + * + * *Workspace discard* + * Via the commands {@see DiscardWorkspace} or {@see DiscardIndividualNodesFromWorkspace}. + * + * *Workspace rebase* + * Via the command {@see RebaseWorkspace}, if the strategy was set to {@see RebaseErrorHandlingStrategy::STRATEGY_FAIL}. + * + * Related: {@see PartialWorkspaceRebaseFailed} + * * @api this exception contains information about what exactly went wrong during rebase */ final class WorkspaceRebaseFailed extends \RuntimeException diff --git a/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php index 49c2e5ce40c..64d31d69318 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspacePublishingService.php @@ -15,7 +15,10 @@ namespace Neos\Neos\Domain\Service; use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\Feature\WorkspaceCommandSkipped; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeBaseWorkspace; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\BaseWorkspaceEqualsWorkspaceException; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\CircularRelationBetweenWorkspacesException; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\WorkspaceIsNotEmptyException; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardIndividualNodesFromWorkspace; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardWorkspace; @@ -23,18 +26,19 @@ use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\PublishWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\PartialWorkspaceRebaseFailed; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; -use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace as ContentRepositoryWorkspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; +use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace as ContentRepositoryWorkspace; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; @@ -57,7 +61,6 @@ public function __construct( ) { } - /** * @internal experimental api, until actually used by the Neos.Ui */ @@ -77,8 +80,7 @@ public function countPendingWorkspaceChanges(ContentRepositoryId $contentReposit } /** - * @throws WorkspaceRebaseFailed is thrown if there are conflicts and the rebase strategy was {@see RebaseErrorHandlingStrategy::STRATEGY_FAIL} - * The workspace will be unchanged for this case. + * @throws WorkspaceRebaseFailed|WorkspaceCommandSkipped */ public function rebaseWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, RebaseErrorHandlingStrategy $rebaseErrorHandlingStrategy = RebaseErrorHandlingStrategy::STRATEGY_FAIL): void { @@ -87,8 +89,7 @@ public function rebaseWorkspace(ContentRepositoryId $contentRepositoryId, Worksp } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be published for this case. + * @throws WorkspaceRebaseFailed|WorkspaceCommandSkipped */ public function publishWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): PublishingResult { @@ -103,8 +104,7 @@ public function publishWorkspace(ContentRepositoryId $contentRepositoryId, Works } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be published for this case. + * @throws WorkspaceRebaseFailed|PartialWorkspaceRebaseFailed|WorkspaceCommandSkipped */ public function publishChangesInSite(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, NodeAggregateId $siteId): PublishingResult { @@ -137,8 +137,7 @@ public function publishChangesInSite(ContentRepositoryId $contentRepositoryId, W } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be published for this case. + * @throws WorkspaceRebaseFailed|PartialWorkspaceRebaseFailed|WorkspaceCommandSkipped */ public function publishChangesInDocument(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, NodeAggregateId $documentId): PublishingResult { @@ -170,6 +169,9 @@ public function publishChangesInDocument(ContentRepositoryId $contentRepositoryI ); } + /** + * @throws WorkspaceRebaseFailed|WorkspaceCommandSkipped + */ public function discardAllWorkspaceChanges(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): DiscardingResult { $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); @@ -183,8 +185,7 @@ public function discardAllWorkspaceChanges(ContentRepositoryId $contentRepositor } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be discarded for this case. + * @throws WorkspaceRebaseFailed|PartialWorkspaceRebaseFailed|WorkspaceCommandSkipped */ public function discardChangesInSite(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, NodeAggregateId $siteId): DiscardingResult { @@ -213,8 +214,7 @@ public function discardChangesInSite(ContentRepositoryId $contentRepositoryId, W } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be discarded for this case. + * @throws WorkspaceRebaseFailed|WorkspaceCommandSkipped */ public function discardChangesInDocument(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, NodeAggregateId $documentId): DiscardingResult { @@ -243,7 +243,7 @@ public function discardChangesInDocument(ContentRepositoryId $contentRepositoryI } /** - * @throws WorkspaceIsNotEmptyException in case a switch is attempted while the workspace still has pending changes + * @throws WorkspaceCommandSkipped|WorkspaceIsNotEmptyException|BaseWorkspaceEqualsWorkspaceException|CircularRelationBetweenWorkspacesException */ public function changeBaseWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceName $newBaseWorkspaceName): void { @@ -258,8 +258,7 @@ public function changeBaseWorkspace(ContentRepositoryId $contentRepositoryId, Wo } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be discarded for this case. + * @throws WorkspaceRebaseFailed|PartialWorkspaceRebaseFailed|WorkspaceCommandSkipped */ private function discardNodes( ContentRepository $contentRepository, @@ -275,8 +274,7 @@ private function discardNodes( } /** - * @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. - * No changes would be published for this case. + * @throws WorkspaceRebaseFailed|PartialWorkspaceRebaseFailed|WorkspaceCommandSkipped */ private function publishNodes( ContentRepository $contentRepository, diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature index 276437c37de..e1078c44183 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature @@ -41,10 +41,6 @@ Feature: Create node aggregate with node without dimensions | workspaceName | "user-workspace" | | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {} Scenario: Nodes on live workspace have been created Given I am in workspace "live" diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature index bd793a4e12d..da50b77ce19 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature @@ -43,10 +43,6 @@ Feature: Create node aggregate with node with dimensions | workspaceName | "user-workspace" | | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {"language": "de"} Scenario: Nodes on live workspace have been created Given I am in workspace "live" diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature index 4b538399636..a81ce1ee2e2 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature @@ -43,10 +43,6 @@ Feature: Publish nodes without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -79,20 +75,12 @@ Feature: Publish nodes without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" And I am in dimension space point {} diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature index d2b09329dcf..6da3ad7cae3 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature @@ -45,9 +45,6 @@ Feature: Publish nodes with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: @@ -85,20 +82,12 @@ Feature: Publish nodes with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" Then I am in dimension space point {"language": "de"} @@ -153,9 +142,6 @@ Feature: Publish nodes with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: @@ -188,4 +174,4 @@ Feature: Publish nodes with dimensions | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | | asset-1 | sir-david-nodenborough | asset | live | {"language": "en"} | | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | - | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | \ No newline at end of file + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature index e6c292625a1..42c9c02e139 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature @@ -43,10 +43,6 @@ Feature: Publish nodes partially without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -79,20 +75,12 @@ Feature: Publish nodes partially without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" And I am in dimension space point {} diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature index db01883e695..ff35bf1640e 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature @@ -45,9 +45,6 @@ Feature: Publish nodes partially with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: @@ -85,20 +82,12 @@ Feature: Publish nodes partially with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" Then I am in dimension space point {"language": "de"} @@ -153,9 +142,6 @@ Feature: Publish nodes partially with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/01-DiscardWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/01-DiscardWorkspace_WithoutDimensions.feature index fd633631147..740b007f072 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/01-DiscardWorkspace_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/01-DiscardWorkspace_WithoutDimensions.feature @@ -48,10 +48,6 @@ Feature: Discard workspace without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -80,11 +76,6 @@ Feature: Discard workspace without dimensions | workspaceName | "review-workspace" | | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And I am in workspace "review-workspace" Then the following CreateNodeAggregateWithNode commands are executed: @@ -96,14 +87,8 @@ Feature: Discard workspace without dimensions | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" - And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/02-DiscardWorkspace_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/02-DiscardWorkspace_WithDimensions.feature index c3c6559a152..254a1dce3b2 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/02-DiscardWorkspace_WithDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/02-DiscardWorkspace_WithDimensions.feature @@ -49,9 +49,6 @@ Feature: Discard workspace with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: @@ -85,20 +82,11 @@ Feature: Discard workspace with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" Then I am in dimension space point {"language": "de"} @@ -148,9 +136,6 @@ Feature: Discard workspace with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: @@ -188,9 +173,6 @@ Feature: Discard workspace with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} @@ -213,4 +195,4 @@ Feature: Discard workspace with dimensions And I expect the AssetUsageService to have the following AssetUsages: | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | - | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | \ No newline at end of file + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/03-DiscardIndividualNodesFromWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/03-DiscardIndividualNodesFromWorkspace_WithoutDimensions.feature index 94f90ccf63a..235420c06ea 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/03-DiscardIndividualNodesFromWorkspace_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/03-DiscardIndividualNodesFromWorkspace_WithoutDimensions.feature @@ -47,10 +47,6 @@ Feature: Discard nodes partially without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -84,10 +80,6 @@ Feature: Discard nodes partially without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -120,10 +112,6 @@ Feature: Discard nodes partially without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And I am in workspace "review-workspace" Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -135,10 +123,6 @@ Feature: Discard nodes partially without dimensions | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" And I am in dimension space point {} @@ -173,10 +157,6 @@ Feature: Discard nodes partially without dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And I am in workspace "review-workspace" Then the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | @@ -188,10 +168,6 @@ Feature: Discard nodes partially without dimensions | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" And I am in dimension space point {} diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/04-DiscardIndividualNodesFromWorkspace_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/04-DiscardIndividualNodesFromWorkspace_WithDimensions.feature index ce7811f4421..e9cc0bc847d 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/04-DiscardIndividualNodesFromWorkspace_WithDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W02-WorkspaceDiscarding/04-DiscardIndividualNodesFromWorkspace_WithDimensions.feature @@ -45,9 +45,6 @@ Feature: Discard nodes partially with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | And I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | Then I am in dimension space point {"language": "de"} And the following CreateNodeAggregateWithNode commands are executed: @@ -84,20 +81,12 @@ Feature: Discard nodes partially with dimensions | baseWorkspaceName | "live" | | newContentStreamId | "review-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - And the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "review-workspace" | | newContentStreamId | "user-workspace-cs-id" | - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - And I am in workspace "user-workspace" Then I am in dimension space point {"language": "de"} diff --git a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature index f9532c61317..2f244d43d41 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature @@ -265,9 +265,7 @@ Feature: Workspace permission related features | CreateRootNodeAggregateWithNode | {"nodeAggregateId":"c","nodeTypeName":"Neos.Neos:CustomRoot"} | | MoveDimensionSpacePoint | {"source":{"language":"de"},"target":{"language":"ch"}} | | UpdateRootNodeAggregateDimensions | {"nodeAggregateId":"root"} | - | DiscardWorkspace | {} | - | DiscardIndividualNodesFromWorkspace | {"nodesToDiscard":["a1"]} | - | RebaseWorkspace | {} | + | RebaseWorkspace | {"rebaseErrorHandlingStrategy": "force"} | # note, creating a core workspace will not grant permissions to it to the current user: Missing "read" permissions for base workspace "new-workspace" | CreateWorkspace | {"workspaceName":"new-workspace","baseWorkspaceName":"workspace","newContentStreamId":"any"} | @@ -322,3 +320,55 @@ Feature: Workspace permission related features | user | | owner | | collaborator | + + Scenario Outline: Discarding a workspace without WRITE permissions + # make changes as owner + Given I am authenticated as owner + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | workspaceName | originDimensionSpacePoint | + | shernode-homes | Neos.Neos:Document | a | workspace | {"language":"de"} | + | other-node | Neos.Neos:Document | a | workspace | {"language":"de"} | + + # someone else attempts to discard + Given I am authenticated as + + And the command DiscardIndividualNodesFromWorkspace is executed with payload and exceptions are caught: + | Key | Value | + | workspaceName | "workspace" | + | nodesToDiscard | ["shernode-homes"] | + Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686 + + And the command DiscardWorkspace is executed with payload and exceptions are caught: + | Key | Value | + | workspaceName | "workspace" | + Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686 + + Examples: + | user | + | restricted_editor | + | simple_user | + | uninvolved_editor | + | admin | + + Scenario Outline: Discarding a workspace with WRITE permissions + Given I am authenticated as + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | workspaceName | originDimensionSpacePoint | + | shernode-homes | Neos.Neos:Document | a | workspace | {"language":"de"} | + | other-node | Neos.Neos:Document | a | workspace | {"language":"de"} | + + And the command DiscardIndividualNodesFromWorkspace is executed with payload: + | Key | Value | + | workspaceName | "workspace" | + | nodesToDiscard | ["shernode-homes"] | + + And the command DiscardWorkspace is executed with payload: + | Key | Value | + | workspaceName | "workspace" | + + Examples: + | user | + | owner | + | collaborator |