diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index a3af95d66c0..9144cb4d8e7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -19,17 +19,24 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\InvalidArgumentException; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; +use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\Helpers\TestingNodeAggregateId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\EventStore\EventNormalizer; +use Neos\ContentRepository\Core\EventStore\EventPersister; +use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; +use Neos\ContentRepository\Core\Projection\Projections; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables; use Neos\Error\Messages\Error; use Neos\Error\Messages\Result; +use Neos\EventStore\Model\Event; +use Neos\EventStore\Model\EventEnvelope; use PHPUnit\Framework\Assert; /** @@ -344,4 +351,45 @@ public function iExpectIntegrityViolationDetectionResultErrorNumberNToHaveCodeX( $error->getCode() ); } + + /** + * @Given /^the event NodeAggregateWasMoved is hacky directly applied with payload:$/ + * @param TableNode $payloadTable + * @throws \Exception + */ + public function applyNodeAggregateWasMoved(TableNode $payloadTable) + { + $eventPayload = $this->readPayloadTable($payloadTable); + $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); + $eventType = 'NodeAggregateWasMoved'; + + $artificiallyConstructedEvent = new Event( + Event\EventId::create(), + Event\EventType::fromString($eventType), + Event\EventData::fromString(json_encode($eventPayload)), + Event\EventMetadata::fromArray([]) + ); + /** @var EventPersister $eventPersister */ + $eventPersister = (new \ReflectionClass($this->currentContentRepository))->getProperty('eventPersister') + ->getValue($this->currentContentRepository); + /** @var EventNormalizer $eventNormalizer */ + $eventNormalizer = (new \ReflectionClass($eventPersister))->getProperty('eventNormalizer') + ->getValue($eventPersister); + /** @var Projections $projections */ + $projections = (new \ReflectionClass($eventPersister))->getProperty('projections') + ->getValue($eventPersister); + + $event = $eventNormalizer->denormalize($artificiallyConstructedEvent); + + $envelope = new EventEnvelope( + $artificiallyConstructedEvent, + $streamName->getEventStreamName(), + Event\Version::first(), + Event\SequenceNumber::none(), + new \DateTimeImmutable() + ); + + $projections->get(DoctrineDbalContentGraphProjection::class)->apply($event, $envelope); + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 85572eaf35b..178844f3f63 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -196,17 +196,8 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate $ancestorNodeAggregateIds = []; while ($stack !== []) { $nodeAggregate = array_shift($stack); - - // Prevent infinite loops - // NOTE: Normally, the content graph cannot contain cycles. However, during the - // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" - // and in case of bugs, it could have actually cycles. - // The content cache catchup hook leverage this method and would otherwise be hanging up in an endless loop. - // That's why we track the seen NodeAggregateIds to be sure we don't travers them multiple times. - if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } + $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; + array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); } return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 8c97ac3eebc..3f48750e3fc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -191,17 +191,8 @@ public function findAncestorNodeAggregateIds(NodeAggregateId $entryNodeAggregate $ancestorNodeAggregateIds = []; while ($stack !== []) { $nodeAggregate = array_shift($stack); - - // Prevent infinite loops - // NOTE: Normally, the content graph cannot contain cycles. However, during the - // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" - // and in case of bugs, it could have actually cycles. - // The content cache catchup hook leverage this method and would otherwise be hanging up in an endless loop. - // That's why we track the seen NodeAggregateIds to be sure we don't travers them multiple times. - if (!in_array($nodeAggregate->nodeAggregateId, $ancestorNodeAggregateIds, false)) { - $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; - array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); - } + $ancestorNodeAggregateIds[] = $nodeAggregate->nodeAggregateId; + array_push($stack, ...iterator_to_array($this->findParentNodeAggregates($nodeAggregate->nodeAggregateId))); } return NodeAggregateIds::fromArray($ancestorNodeAggregateIds); } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature index cbf6e2c55b3..b37843a6eb3 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature @@ -52,7 +52,8 @@ Feature: Run projection integrity violation detection regarding root connection | parentNodeAggregateId | "sir-david-nodenborough" | | nodeName | "child-document" | | nodeAggregateClassification | "regular" | - And the event NodeAggregateWasMoved was published with payload: + # we must apply the event directly as the state is so corrupt that catchup hooks might fail like the content cache flusher + And the event NodeAggregateWasMoved is hacky directly applied with payload: | Key | Value | | workspaceName | "live" | | contentStreamId | "cs-identifier" | diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php index c8e2118d5e4..f4b58bffb20 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php @@ -135,7 +135,7 @@ protected function publishEvent(string $eventType, StreamName $streamName, array /** @var EventPersister $eventPersister */ $eventPersister = (new \ReflectionClass($this->currentContentRepository))->getProperty('eventPersister') ->getValue($this->currentContentRepository); - /** @var EventNormalizer $eventPersister */ + /** @var EventNormalizer $eventNormalizer */ $eventNormalizer = (new \ReflectionClass($eventPersister))->getProperty('eventNormalizer') ->getValue($eventPersister); $event = $eventNormalizer->denormalize($artificiallyConstructedEvent);