-
-
Notifications
You must be signed in to change notification settings - Fork 221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BUGFIX: Duplicated content stream in import and export #4914
Merged
mhsdesign
merged 7 commits into
neos:9.0
from
mhsdesign:bugfix/4298-dublicated-contentstream-in-import-and-export
Mar 11, 2024
Merged
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2c975b5
TASK: Add behaviour tests for `Neos.ContentRepository.Export`
mhsdesign 7ce2827
TASK: Add behaviour tests to document current faulty cs duplication date
mhsdesign 918f131
TASK: Export format should not contain `ContentStreamWasCreated` event
mhsdesign f3cb5a3
Merge remote-tracking branch 'origin/9.0' into bugfix/4298-dublicated…
ahaeslich a8488c1
TASK: Stricter import handling for illegal events
ahaeslich b941be3
Fix pipeline
ahaeslich 75e53d0
Fix pipeline 2
mhsdesign File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,18 +36,20 @@ | |
"test:behavioral": [ | ||
"@test:behat-cli -c Neos.ContentRepository.BehavioralTests/Tests/Behavior/behat.yml.dist", | ||
"@test:behat-cli -c Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/behat.yml.dist", | ||
"../../flow doctrine:migrate --quiet; ../../flow cr:setup", | ||
"@test:behat-cli -c Neos.Neos/Tests/Behavior/behat.yml", | ||
"@test:behat-cli -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist", | ||
"@test:behat-cli -c Neos.TimeableNodeVisibility/Tests/Behavior/behat.yml.dist" | ||
"@test:behat-cli -c Neos.ContentRepository.Export/Tests/Behavior/behat.yml.dist", | ||
"@test:behat-cli -c Neos.TimeableNodeVisibility/Tests/Behavior/behat.yml.dist", | ||
"../../flow doctrine:migrate --quiet; ../../flow cr:setup", | ||
"@test:behat-cli -c Neos.Neos/Tests/Behavior/behat.yml" | ||
], | ||
"test:behavioral:stop-on-failure": [ | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.BehavioralTests/Tests/Behavior/behat.yml.dist", | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/behat.yml.dist", | ||
"../../flow doctrine:migrate --quiet; ../../flow cr:setup", | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.Neos/Tests/Behavior/behat.yml", | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist", | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.TimeableNodeVisibility/Tests/Behavior/behat.yml.dist" | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.Export/Tests/Behavior/behat.yml.dist", | ||
"@test:behat-cli -vvv --stop-on-failure -c Neos.TimeableNodeVisibility/Tests/Behavior/behat.yml.dist", | ||
"../../flow doctrine:migrate --quiet; ../../flow cr:setup", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .and this |
||
"@test:behat-cli -vvv --stop-on-failure -c Neos.Neos/Tests/Behavior/behat.yml" | ||
], | ||
"test": [ | ||
"@test:unit", | ||
|
216 changes: 216 additions & 0 deletions
216
Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Neos.ContentGraph.DoctrineDbalAdapter package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap; | ||
|
||
use Behat\Gherkin\Node\PyStringNode; | ||
use Behat\Gherkin\Node\TableNode; | ||
use League\Flysystem\Filesystem; | ||
use League\Flysystem\InMemory\InMemoryFilesystemAdapter; | ||
use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory; | ||
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; | ||
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; | ||
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; | ||
use Neos\ContentRepository\Export\Event\ValueObject\ExportedEvents; | ||
use Neos\ContentRepository\Export\ProcessorResult; | ||
use Neos\ContentRepository\Export\Processors\EventExportProcessor; | ||
use Neos\ContentRepository\Export\Processors\EventStoreImportProcessor; | ||
use Neos\ContentRepository\Export\Severity; | ||
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables; | ||
use PHPUnit\Framework\Assert; | ||
|
||
/** | ||
* @todo move this class somewhere where its autoloaded | ||
*/ | ||
trait CrImportExportTrait | ||
{ | ||
use CRTestSuiteRuntimeVariables; | ||
|
||
private Filesystem $crImportExportTrait_filesystem; | ||
|
||
private ?ProcessorResult $crImportExportTrait_lastMigrationResult = null; | ||
|
||
/** @var array<string> */ | ||
private array $crImportExportTrait_loggedErrors = []; | ||
|
||
/** @var array<string> */ | ||
private array $crImportExportTrait_loggedWarnings = []; | ||
|
||
public function setupCrImportExportTrait() | ||
{ | ||
$this->crImportExportTrait_filesystem = new Filesystem(new InMemoryFilesystemAdapter()); | ||
} | ||
|
||
/** | ||
* @When /^the events are exported$/ | ||
*/ | ||
public function theEventsAreExportedIExpectTheFollowingJsonl() | ||
{ | ||
$eventExporter = $this->getContentRepositoryService( | ||
new class ($this->crImportExportTrait_filesystem) implements ContentRepositoryServiceFactoryInterface { | ||
public function __construct(private readonly Filesystem $filesystem) | ||
{ | ||
} | ||
public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): EventExportProcessor { | ||
return new EventExportProcessor( | ||
$this->filesystem, | ||
$serviceFactoryDependencies->contentRepository->getWorkspaceFinder(), | ||
$serviceFactoryDependencies->eventStore | ||
); | ||
} | ||
} | ||
); | ||
assert($eventExporter instanceof EventExportProcessor); | ||
|
||
$eventExporter->onMessage(function (Severity $severity, string $message) { | ||
if ($severity === Severity::ERROR) { | ||
$this->crImportExportTrait_loggedErrors[] = $message; | ||
} elseif ($severity === Severity::WARNING) { | ||
$this->crImportExportTrait_loggedWarnings[] = $message; | ||
} | ||
}); | ||
$this->crImportExportTrait_lastMigrationResult = $eventExporter->run(); | ||
} | ||
|
||
/** | ||
* @When /^I import the events\.jsonl(?: into "([^"]*)")?$/ | ||
*/ | ||
public function iImportTheFollowingJson(?string $contentStreamId = null) | ||
{ | ||
$eventImporter = $this->getContentRepositoryService( | ||
new class ($this->crImportExportTrait_filesystem, $contentStreamId ? ContentStreamId::fromString($contentStreamId) : null) implements ContentRepositoryServiceFactoryInterface { | ||
public function __construct( | ||
private readonly Filesystem $filesystem, | ||
private readonly ?ContentStreamId $contentStreamId | ||
) { | ||
} | ||
public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): EventStoreImportProcessor { | ||
return new EventStoreImportProcessor( | ||
false, | ||
$this->filesystem, | ||
$serviceFactoryDependencies->eventStore, | ||
$serviceFactoryDependencies->eventNormalizer, | ||
$this->contentStreamId | ||
); | ||
} | ||
} | ||
); | ||
assert($eventImporter instanceof EventStoreImportProcessor); | ||
|
||
$eventImporter->onMessage(function (Severity $severity, string $message) { | ||
if ($severity === Severity::ERROR) { | ||
$this->crImportExportTrait_loggedErrors[] = $message; | ||
} elseif ($severity === Severity::WARNING) { | ||
$this->crImportExportTrait_loggedWarnings[] = $message; | ||
} | ||
}); | ||
$this->crImportExportTrait_lastMigrationResult = $eventImporter->run(); | ||
} | ||
|
||
/** | ||
* @Given /^using the following events\.jsonl:$/ | ||
*/ | ||
public function usingTheFollowingEventsJsonl(PyStringNode $string) | ||
{ | ||
$this->crImportExportTrait_filesystem->write('events.jsonl', $string->getRaw()); | ||
} | ||
|
||
/** | ||
* @AfterScenario | ||
*/ | ||
public function failIfLastMigrationHasErrors(): void | ||
{ | ||
if ($this->crImportExportTrait_lastMigrationResult !== null && $this->crImportExportTrait_lastMigrationResult->severity === Severity::ERROR) { | ||
throw new \RuntimeException(sprintf('The last migration run led to an error: %s', $this->crImportExportTrait_lastMigrationResult->message)); | ||
} | ||
if ($this->crImportExportTrait_loggedErrors !== []) { | ||
throw new \RuntimeException(sprintf('The last migration run logged %d error%s', count($this->crImportExportTrait_loggedErrors), count($this->crImportExportTrait_loggedErrors) === 1 ? '' : 's')); | ||
} | ||
} | ||
|
||
/** | ||
* @Then I expect the following jsonl: | ||
*/ | ||
public function iExpectTheFollowingJsonL(PyStringNode $string): void | ||
{ | ||
if (!$this->crImportExportTrait_filesystem->has('events.jsonl')) { | ||
Assert::fail('No events were exported'); | ||
} | ||
|
||
$jsonL = $this->crImportExportTrait_filesystem->read('events.jsonl'); | ||
|
||
$exportedEvents = ExportedEvents::fromJsonl($jsonL); | ||
$eventsWithoutRandomIds = []; | ||
|
||
foreach ($exportedEvents as $exportedEvent) { | ||
// we have to remove the event id in \Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher::enrichWithCommand | ||
// and the initiatingTimestamp to make the events diff able | ||
$eventsWithoutRandomIds[] = $exportedEvent | ||
->withIdentifier('random-event-uuid') | ||
->processMetadata(function (array $metadata) { | ||
$metadata['initiatingTimestamp'] = 'random-time'; | ||
return $metadata; | ||
}); | ||
} | ||
|
||
Assert::assertSame($string->getRaw(), ExportedEvents::fromIterable($eventsWithoutRandomIds)->toJsonl()); | ||
} | ||
|
||
/** | ||
* @Then I expect the following errors to be logged | ||
*/ | ||
public function iExpectTheFollowingErrorsToBeLogged(TableNode $table): void | ||
{ | ||
Assert::assertSame($table->getColumn(0), $this->crImportExportTrait_loggedErrors, 'Expected logged errors do not match'); | ||
$this->crImportExportTrait_loggedErrors = []; | ||
} | ||
|
||
/** | ||
* @Then I expect the following warnings to be logged | ||
*/ | ||
public function iExpectTheFollowingWarningsToBeLogged(TableNode $table): void | ||
{ | ||
Assert::assertSame($table->getColumn(0), $this->crImportExportTrait_loggedWarnings, 'Expected logged warnings do not match'); | ||
$this->crImportExportTrait_loggedWarnings = []; | ||
} | ||
|
||
/** | ||
* @Then I expect a MigrationError | ||
* @Then I expect a MigrationError with the message | ||
*/ | ||
public function iExpectAMigrationErrorWithTheMessage(PyStringNode $expectedMessage = null): void | ||
{ | ||
Assert::assertNotNull($this->crImportExportTrait_lastMigrationResult, 'Expected the previous migration to contain errors, but no migration has been executed'); | ||
Assert::assertSame(Severity::ERROR, $this->crImportExportTrait_lastMigrationResult->severity, sprintf('Expected the previous migration to contain errors, but it ended with severity "%s"', $this->crImportExportTrait_lastMigrationResult->severity->name)); | ||
if ($expectedMessage !== null) { | ||
Assert::assertSame($expectedMessage->getRaw(), $this->crImportExportTrait_lastMigrationResult->message); | ||
} | ||
$this->crImportExportTrait_lastMigrationResult = null; | ||
} | ||
|
||
/** | ||
* @template T of object | ||
* @param class-string<T> $className | ||
* | ||
* @return T | ||
*/ | ||
abstract private function getObject(string $className): object; | ||
|
||
protected function getTableNamePrefix(): string | ||
{ | ||
return DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( | ||
$this->currentContentRepository->id | ||
); | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/FeatureContext.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
require_once(__DIR__ . '/CrImportExportTrait.php'); | ||
|
||
use Behat\Behat\Context\Context as BehatContext; | ||
use Behat\Behat\Hook\Scope\BeforeScenarioScope; | ||
use Neos\Behat\FlowBootstrapTrait; | ||
use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\CrImportExportTrait; | ||
use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\CRBehavioralTestsSubjectProvider; | ||
use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinPyStringNodeBasedNodeTypeManagerFactory; | ||
use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\GherkinTableNodeBasedContentDimensionSourceFactory; | ||
use Neos\ContentRepository\Core\ContentRepository; | ||
use Neos\ContentRepository\Core\Factory\ContentRepositoryId; | ||
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; | ||
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; | ||
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteTrait; | ||
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; | ||
|
||
/** | ||
* Features context | ||
*/ | ||
class FeatureContext implements BehatContext | ||
{ | ||
use FlowBootstrapTrait; | ||
use CrImportExportTrait; | ||
use CRTestSuiteTrait; | ||
use CRBehavioralTestsSubjectProvider; | ||
|
||
protected ContentRepositoryRegistry $contentRepositoryRegistry; | ||
|
||
public function __construct() | ||
{ | ||
self::bootstrapFlow(); | ||
$this->contentRepositoryRegistry = $this->getObject(ContentRepositoryRegistry::class); | ||
|
||
$this->setupCRTestSuiteTrait(); | ||
$this->setupCrImportExportTrait(); | ||
} | ||
|
||
/** | ||
* @BeforeScenario | ||
*/ | ||
public function resetContentRepositoryComponents(BeforeScenarioScope $scope): void | ||
{ | ||
GherkinTableNodeBasedContentDimensionSourceFactory::reset(); | ||
GherkinPyStringNodeBasedNodeTypeManagerFactory::reset(); | ||
} | ||
|
||
protected function getContentRepositoryService( | ||
ContentRepositoryServiceFactoryInterface $factory | ||
): ContentRepositoryServiceInterface { | ||
return $this->contentRepositoryRegistry->buildService( | ||
$this->currentContentRepository->id, | ||
$factory | ||
); | ||
} | ||
|
||
protected function createContentRepository( | ||
ContentRepositoryId $contentRepositoryId | ||
): ContentRepository { | ||
$this->contentRepositoryRegistry->resetFactoryInstance($contentRepositoryId); | ||
$contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); | ||
GherkinTableNodeBasedContentDimensionSourceFactory::reset(); | ||
GherkinPyStringNodeBasedNodeTypeManagerFactory::reset(); | ||
|
||
return $contentRepository; | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
Neos.ContentRepository.Export/Tests/Behavior/Features/Export/Export.feature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
@contentrepository | ||
Feature: As a user of the CR I want to export the event stream | ||
Background: | ||
Given using the following content dimensions: | ||
| Identifier | Values | Generalizations | | ||
| language | de, gsw, fr | gsw->de | | ||
And using the following node types: | ||
"""yaml | ||
'Neos.ContentRepository.Testing:Document': [] | ||
""" | ||
And using identifier "default", I define a content repository | ||
And I am in content repository "default" | ||
And the command CreateRootWorkspace is executed with payload: | ||
| Key | Value | | ||
| workspaceName | "live" | | ||
| workspaceTitle | "Live" | | ||
| workspaceDescription | "The live workspace" | | ||
| newContentStreamId | "cs-identifier" | | ||
And the graph projection is fully up to date | ||
And the command CreateRootNodeAggregateWithNode is executed with payload: | ||
| Key | Value | | ||
| contentStreamId | "cs-identifier" | | ||
| nodeAggregateId | "lady-eleonode-rootford" | | ||
| nodeTypeName | "Neos.ContentRepository:Root" | | ||
And the event NodeAggregateWithNodeWasCreated was published with payload: | ||
| Key | Value | | ||
| contentStreamId | "cs-identifier" | | ||
| nodeAggregateId | "nody-mc-nodeface" | | ||
| nodeTypeName | "Neos.ContentRepository.Testing:Document" | | ||
| originDimensionSpacePoint | {"language":"de"} | | ||
| coveredDimensionSpacePoints | [{"language":"de"},{"language":"gsw"},{"language":"fr"}] | | ||
| parentNodeAggregateId | "lady-eleonode-rootford" | | ||
| nodeName | "child-document" | | ||
| nodeAggregateClassification | "regular" | | ||
And the graph projection is fully up to date | ||
|
||
Scenario: Export the event stream | ||
Then I expect exactly 3 events to be published on stream with prefix "ContentStream:cs-identifier" | ||
When the events are exported | ||
Then I expect the following jsonl: | ||
""" | ||
{"identifier":"random-event-uuid","type":"RootNodeAggregateWithNodeWasCreated","payload":{"contentStreamId":"cs-identifier","nodeAggregateId":"lady-eleonode-rootford","nodeTypeName":"Neos.ContentRepository:Root","coveredDimensionSpacePoints":[{"language":"de"},{"language":"gsw"},{"language":"fr"}],"nodeAggregateClassification":"root"},"metadata":{"commandClass":"Neos\\ContentRepository\\Core\\Feature\\RootNodeCreation\\Command\\CreateRootNodeAggregateWithNode","commandPayload":{"contentStreamId":"cs-identifier","nodeAggregateId":"lady-eleonode-rootford","nodeTypeName":"Neos.ContentRepository:Root","tetheredDescendantNodeAggregateIds":[]},"initiatingUserId":"system","initiatingTimestamp":"random-time"}} | ||
{"identifier":"random-event-uuid","type":"NodeAggregateWithNodeWasCreated","payload":{"contentStreamId":"cs-identifier","nodeAggregateId":"nody-mc-nodeface","nodeTypeName":"Neos.ContentRepository.Testing:Document","originDimensionSpacePoint":{"language":"de"},"coveredDimensionSpacePoints":[{"language":"de"},{"language":"gsw"},{"language":"fr"}],"parentNodeAggregateId":"lady-eleonode-rootford","nodeName":"child-document","initialPropertyValues":[],"nodeAggregateClassification":"regular","succeedingNodeAggregateId":null},"metadata":{"initiatingTimestamp":"random-time"}} | ||
|
||
""" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This puzzles me!?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for keeping things tidy. Its different than you think ;)
I just moved the
"../../flow doctrine:migrate --quiet; ../../flow cr:setup
part two lines below as its only seemingly required for the Neos.Neos tests and not the rest.We should definitely get rid of this hack and its only needed in the ci but idk why;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah and believe me im also confused about the fact its in the default development context ... seems to fix a glitch will take care of this at one point ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right, this wasn't introduced with your PR but much earlier
But..
in development context has nothing to do with the behat context – or only by accident.
Are you sure that this fixes a glitch, or did we just drag this along?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhsdesign lets see what the CI says: #5005