Skip to content

Commit

Permalink
FEATURE: Throw WorkspaceRebaseFailed during publication or partial …
Browse files Browse the repository at this point in the history
…discard as well

... instead of enforcing to rebase from outside.
This speeds things up as we need one fork less;)

see also neos#5300
  • Loading branch information
mhsdesign committed Oct 26, 2024
1 parent 3679157 commit 8ad0a3e
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ Feature: Workspace discarding - complex chained functionality
| workspaceName | "user-ws" |
| nodesToDiscard | [{"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}, {"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint"
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
| SequenceNumber | Command | Exception |
| 11 | CreateNodeVariant | NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint |

When the command DiscardWorkspace is executed with payload:
| Key | Value |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,57 @@ Feature: Workspace publication - complex chained functionality
| nodeTypeName | "Neos.ContentRepository:Root" |

And the following CreateNodeAggregateWithNode commands are executed:
| nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds |
| sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document | {"tethered": "nodewyn-tetherton"} |
| nody-mc-nodeface | Neos.ContentRepository.Testing:Content | nodewyn-tetherton | grandchild | {} |
| nodeAggregateId | nodeTypeName | parentNodeAggregateId | tetheredDescendantNodeAggregateIds | properties |
| sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | {"tethered": "nodewyn-tetherton"} | |
| sir-nodebelig | Neos.ContentRepository.Testing:Content | lady-eleonode-rootford | | |
| nobody-node | Neos.ContentRepository.Testing:Content | lady-eleonode-rootford | | |
| nody-mc-nodeface | Neos.ContentRepository.Testing:Content | nodewyn-tetherton | | |

And the command CreateWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-ws" |
| baseWorkspaceName | "live" |
| newContentStreamId | "user-cs-id" |

Scenario: Deleted nodes cannot be edited
When the command RemoveNodeAggregate is executed with payload:
| Key | Value |
| workspaceName | "live" |
| nodeAggregateId | "sir-nodebelig" |
| coveredDimensionSpacePoint | {"language": "de"} |
| nodeVariantSelectionStrategy | "allVariants" |

When the command RemoveNodeAggregate is executed with payload:
| Key | Value |
| workspaceName | "live" |
| nodeAggregateId | "nobody-node" |
| coveredDimensionSpacePoint | {"language": "de"} |
| nodeVariantSelectionStrategy | "allVariants" |

And the command SetNodeProperties is executed with payload:
| Key | Value |
| workspaceName | "user-ws" |
| nodeAggregateId | "sir-nodebelig" |
| originDimensionSpacePoint | {"language": "de"} |
| propertyValues | {"text": "Modified text"} |

And the command SetNodeProperties is executed with payload:
| Key | Value |
| workspaceName | "user-ws" |
| nodeAggregateId | "nobody-node" |
| originDimensionSpacePoint | {"language": "de"} |
| propertyValues | {"text": "Modified text"} |

When the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught:
| Key | Value |
| workspaceName | "user-ws" |
| nodesToPublish | [{"dimensionSpacePoint": {"language": "de"}, "nodeAggregateId": "sir-nodebelig"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
| SequenceNumber | Command | Exception |
| 13 | SetSerializedNodeProperties | NodeAggregateCurrentlyDoesNotExist |
| 14 | SetSerializedNodeProperties | NodeAggregateCurrentlyDoesNotExist |

Scenario: Vary to generalization, then publish only the child node so that an exception is thrown. Ensure that the workspace recovers from this
When the command CreateNodeVariant is executed with payload:
| Key | Value |
Expand All @@ -65,7 +106,9 @@ Feature: Workspace publication - complex chained functionality
| workspaceName | "user-ws" |
| nodesToPublish | [{"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "nody-mc-nodeface"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint"
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
| SequenceNumber | Command | Exception |
| 13 | CreateNodeVariant | NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint |

When the command PublishWorkspace is executed with payload:
| Key | Value |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ Feature: Workspace discarding - basic functionality
| Key | Value |
| workspaceName | "user-ws-two" |
| rebasedContentStreamId | "user-cs-two-rebased" |
Then the last command should have thrown an exception of type "WorkspaceRebaseFailed"
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
| SequenceNumber | Command | Exception |
| 13 | SetSerializedNodeProperties | NodeAggregateCurrentlyDoesNotExist |

Then workspace user-ws-two has status OUTDATED

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Neos\ContentRepository\Core\EventStore\EventNormalizer;
use Neos\ContentRepository\Core\EventStore\EventsToPublish;
use Neos\ContentRepository\Core\Feature\RebaseableCommand;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\CommandsThatFailedDuringRebase;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\CommandThatFailedDuringRebase;
use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjectionInterface;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\EventStore\Helper\InMemoryEventStore;
Expand Down Expand Up @@ -41,15 +43,18 @@
*
* @internal
*/
final readonly class CommandSimulator
final class CommandSimulator
{
private CommandsThatFailedDuringRebase $commandsThatFailedDuringRebase;

public function __construct(
private ContentGraphProjectionInterface $contentRepositoryProjection,
private EventNormalizer $eventNormalizer,
private CommandBus $commandBus,
private InMemoryEventStore $inMemoryEventStore,
private WorkspaceName $workspaceNameToSimulateIn,
private readonly ContentGraphProjectionInterface $contentRepositoryProjection,
private readonly EventNormalizer $eventNormalizer,
private readonly CommandBus $commandBus,
private readonly InMemoryEventStore $inMemoryEventStore,
private readonly WorkspaceName $workspaceNameToSimulateIn,
) {
$this->commandsThatFailedDuringRebase = new CommandsThatFailedDuringRebase();
}

/**
Expand All @@ -74,7 +79,20 @@ private function handle(RebaseableCommand $rebaseableCommand): void
// when https://github.com/neos/neos-development-collection/pull/5298 is merged
$commandInWorkspace = $rebaseableCommand->originalCommand->createCopyForWorkspace($this->workspaceNameToSimulateIn);

$eventsToPublish = $this->commandBus->handle($commandInWorkspace);
try {
$eventsToPublish = $this->commandBus->handle($commandInWorkspace);
} catch (\Exception $exception) {
$this->commandsThatFailedDuringRebase = $this->commandsThatFailedDuringRebase->withAppended(
new CommandThatFailedDuringRebase(
$rebaseableCommand->originalSequenceNumber,
$rebaseableCommand->originalCommand,
$exception
)
);

return;
}

if (!$eventsToPublish instanceof EventsToPublish) {
throw new \RuntimeException(sprintf('CommandSimulator expects direct EventsToPublish to be returned when handling %s', $rebaseableCommand->originalCommand::class));
}
Expand Down Expand Up @@ -109,7 +127,7 @@ private function handle(RebaseableCommand $rebaseableCommand): void

// fetch all events that were now committed. Plus one because the first sequence number is one too otherwise we get one event to many.
// (all elephants shall be placed shamefully placed on my head)
$eventStream = $this->inMemoryEventStore->load(VirtualStreamName::all())->withMinimumSequenceNumber(
$eventStream = $this->eventStream()->withMinimumSequenceNumber(
$sequenceNumberBeforeCommit->next()
);

Expand All @@ -136,4 +154,14 @@ public function eventStream(): EventStreamInterface
{
return $this->inMemoryEventStore->load(VirtualStreamName::all());
}

public function hasCommandsThatFailed(): bool
{
return !$this->commandsThatFailedDuringRebase->isEmpty();
}

public function getCommandsThatFailed(): CommandsThatFailedDuringRebase
{
return $this->commandsThatFailedDuringRebase;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Neos\ContentRepository\Core\Feature\Common\RebasableToOtherWorkspaceInterface;
use Neos\EventStore\Model\Event\EventId;
use Neos\EventStore\Model\Event\EventMetadata;
use Neos\EventStore\Model\Event\SequenceNumber;

/**
* @internal
Expand All @@ -20,11 +21,11 @@
public function __construct(
public RebasableToOtherWorkspaceInterface $originalCommand,
public EventMetadata $initiatingMetaData,
// todo SequenceNumber $originalSequenceNumber
public SequenceNumber $originalSequenceNumber
) {
}

public static function extractFromEventMetaData(EventMetadata $eventMetadata): self
public static function extractFromEventMetaData(EventMetadata $eventMetadata, SequenceNumber $sequenceNumber): self
{
if (!isset($eventMetadata->value['commandClass'])) {
throw new \RuntimeException('Command cannot be extracted from metadata, missing commandClass.', 1729847804);
Expand All @@ -45,7 +46,8 @@ public static function extractFromEventMetaData(EventMetadata $eventMetadata): s
$commandInstance = $commandToRebaseClass::fromArray($commandToRebasePayload);
return new self(
$commandInstance,
InitiatingEventMetadata::extractInitiatingMetadata($eventMetadata)
InitiatingEventMetadata::extractInitiatingMetadata($eventMetadata),
$sequenceNumber
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static function extractFromEventStream(EventStreamInterface $eventStream)
$commands = [];
foreach ($eventStream as $eventEnvelope) {
if ($eventEnvelope->event->metadata && isset($eventEnvelope->event->metadata?->value['commandClass'])) {
$commands[$eventEnvelope->sequenceNumber->value] = RebaseableCommand::extractFromEventMetaData($eventEnvelope->event->metadata);
$commands[] = RebaseableCommand::extractFromEventMetaData($eventEnvelope->event->metadata, $eventEnvelope->sequenceNumber);
}
}

Expand Down
Loading

0 comments on commit 8ad0a3e

Please sign in to comment.