From c9cca4defe174b6ee8c61558e896d260e39fc913 Mon Sep 17 00:00:00 2001 From: Timon Heuser Date: Tue, 18 Aug 2026 15:22:24 +0200 Subject: [PATCH] TESTS: fix behat tests --- README.md | 22 +++- .../Features/Bootstrap/FeatureContext.php | 118 ++++++++++++++---- .../Features/ContentStore/Basics.feature | 1 + .../ContentStore/ErrorsWhileRendering.feature | 1 + .../ContentStore/IncrementalRendering.feature | 15 ++- .../ContentStore/QuickRelease.feature | 1 + .../Features/ContentStore/Renamings.feature | 19 +-- .../Fixtures/StubPrunnerApiService.php | 11 +- 8 files changed, 145 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 85736d9..f5d9337 100644 --- a/README.md +++ b/README.md @@ -727,25 +727,37 @@ The orchestrator's exit codes: `1` release already completed, `2` empty enumerat ### Testing the Rendering -For executing behavioral tests, install the `neos/behat` package and run `./flow behat:setup`. Then: +The behavioral tests need the `neos/behat` package (`composer require --dev neos/behat`), which brings Behat itself +along. Behat is used from the main composer installation: ```bash cd Packages/Application/Flowpack.DecoupledContentStore/Tests/Behavior -../../../../bin/behat -c behat.yml.dist +../../../../../bin/behat -c behat.yml.dist ``` +(five levels up is the installation root - adjust the path if the package sits somewhere else, for example as a symlink +into a `DistributionPackages` checkout) + +The tests bootstrap the `Testing/Behat` context, so the database and the Redis instances they work on are the ones +configured in `Configuration/Testing/Behat/`. + +**Every feature file is tagged `@resetRedis`, and that hook calls `FLUSHALL`** on the primary content store - not just +the configured database, but every database on that Redis server. Point the Behat context at a Redis instance whose +contents you are willing to lose; if it is the same server a development content store uses, running the tests wipes it, +including caches other applications keep there. + Behat also supports running single tests or single files - they need to be specified after the config file, e.g. ```bash # run all scenarios in a given folder -../../../../bin/behat -c behat.yml.dist Features/ContentStore/ +../../../../../bin/behat -c behat.yml.dist Features/ContentStore/ # run all scenarios in the single feature file -../../../../bin/behat -c behat.yml.dist Features/ContentStore/Basics.feature +../../../../../bin/behat -c behat.yml.dist Features/ContentStore/Basics.feature # run the scenario starting at line 66 -../../../../bin/behat -c behat.yml.dist Features/ContentStore/Basics.feature:66 +../../../../../bin/behat -c behat.yml.dist Features/ContentStore/Basics.feature:66 ``` In case of exceptions, it might be helpful to run the tests with `--stop-on-failure`, which stops the test cases at the first diff --git a/Tests/Behavior/Features/Bootstrap/FeatureContext.php b/Tests/Behavior/Features/Bootstrap/FeatureContext.php index 4c720e8..8cad297 100644 --- a/Tests/Behavior/Features/Bootstrap/FeatureContext.php +++ b/Tests/Behavior/Features/Bootstrap/FeatureContext.php @@ -1,20 +1,20 @@ initializeFlow(); - } - $this->objectManager = self::$bootstrap->getObjectManager(); + $this->objectManager = self::bootstrapFlow()->getObjectManager(); $this->setupSecurity(); // for testing, we use Private/EndToEndTestFusion as fusion folder to load. @@ -116,6 +133,63 @@ public function getObjectManager(): ObjectManagerInterface return $this->objectManager; } + /** + * Persist any changes - part of what the content repository's step definitions expect from their context. + */ + public function persistAll(): void + { + $persistenceManager = $this->getObject(PersistenceManagerInterface::class); + $persistenceManager->persistAll(); + $persistenceManager->clearState(); + + // roles are read through the policy service, which holds on to them across a database reset + $this->policyService->reset(); + } + + /** + * The authentication a scenario gets through "I am authenticated with role" needs a security context which has + * a request, and a TestingProvider to hand the account to. + */ + private function setupSecurity(): void + { + $this->policyService = $this->getObject(PolicyService::class); + $this->authenticationManager = $this->getObject(AuthenticationProviderManager::class); + + // asking for the providers is what builds them, and with them the singleton TestingProvider + $providers = $this->getObject(TokenAndProviderFactoryInterface::class)->getProviders(); + $this->testingProvider = $providers['TestingProvider']; + + $httpRequest = $this->getObject(ServerRequestFactoryInterface::class) + ->createServerRequest('GET', 'http://localhost/'); + $this->securityActionRequest = ActionRequest::fromHttpRequest($httpRequest); + + $this->securityContext = $this->getObject(SecurityContext::class); + $this->securityContext->clearContext(); + $this->securityContext->setRequest($this->securityActionRequest); + } + + /** + * @Given /^I am authenticated with role "([^"]*)"$/ + */ + public function iAmAuthenticatedWithRole(string $roleIdentifier): void + { + $roles = []; + foreach (Arrays::trimExplode(',', $roleIdentifier) as $roleName) { + $roles[] = $this->policyService->getRole($roleName); + } + + $account = new Account(); + $account->setAccountIdentifier('TestAccount'); + $account->setRoles($roles); + + $this->testingProvider->setAuthenticationStatus(TokenInterface::AUTHENTICATION_SUCCESSFUL); + $this->testingProvider->setAccount($account); + + $this->securityContext->clearContext(); + $this->securityContext->setRequest($this->securityActionRequest); + $this->authenticationManager->authenticate(); + } + /** * @BeforeScenario @resetRedis */ @@ -331,7 +405,7 @@ public function theEnumerationContainsNode($contentReleaseIdentifier, $expectedC } )() ); - Assert::assertCount($expectedCount, $enumerationAsArray); + Assert::assertCount((int)$expectedCount, $enumerationAsArray); } /** @@ -488,7 +562,7 @@ public function duringRenderingOfContentReleaseSomeErrorsOccured($contentRelease $contentReleaseIdentifier = ContentReleaseIdentifier::fromString($contentReleaseIdentifier); $redisRenderingErrorManager = $this->getObjectManager()->get(RedisRenderingErrorManager::class); $renderingErrors = $redisRenderingErrorManager->getRenderingErrors($contentReleaseIdentifier); - Assert::assertCount($expectedNumberOfErrors, $renderingErrors); + Assert::assertCount((int)$expectedNumberOfErrors, $renderingErrors); } private const DEFAULT_NODETYPES_CONFIG = << BROKEN LINK which goes live. + # - Cache is also flushed for /sites/test and /sites/test/sub, because both link to sub2 and therefore carry its + # NodeDynamicTag_ (added by Neos.Neos:ConvertUris while rendering the link) + # - however, both have already been added to the content release with the old URI -> BROKEN LINKS which go live. And I run the renderer for content release "6" until the queue is empty When I continue running the render-orchestrator control loop Then I expect the render-orchestrator control loop to exit with status code 0 Then during rendering of content release "6", 0 errors occured Then I expect the content release "6" to not contain anything for URI "http://test.de/de/nested2" - # no broken link :-) + # THIS IS THE BROKEN LINK Then I expect the content release "6" to contain the following HTML content for URI "http://test.de/de" at CSS selector "body .neos-contentcollection": """ - BEFOREHallo - this is rendered. Link to /nested2AFTER + BEFOREHallo - this is rendered. Link to /nested2AFTER """ # THIS IS THE BROKEN LINK Then I expect the content release "6" to contain the following HTML content for URI "http://test.de/de/nested" at CSS selector "body .neos-contentcollection": @@ -314,7 +317,7 @@ Feature: Renamings before rendering, and during a rendering. Then I expect the render-orchestrator control loop to exit with status code 0 Then during rendering of content release "7", 0 errors occured Then I expect the content release "6" to not contain anything for URI "http://test.de/de/nested2" - # no broken link :-) + # BROKEN LINK -> fixed Then I expect the content release "7" to contain the following HTML content for URI "http://test.de/de" at CSS selector "body .neos-contentcollection": """ BEFOREHallo - this is rendered. Link to /nested2AFTER diff --git a/Tests/Behavior/Fixtures/StubPrunnerApiService.php b/Tests/Behavior/Fixtures/StubPrunnerApiService.php index b8c2c63..e8d0e2c 100644 --- a/Tests/Behavior/Fixtures/StubPrunnerApiService.php +++ b/Tests/Behavior/Fixtures/StubPrunnerApiService.php @@ -7,17 +7,22 @@ use Flowpack\Prunner\PrunnerApiService; use Flowpack\Prunner\ValueObject\JobId; use Flowpack\Prunner\ValueObject\PipelineName; +use Flowpack\Prunner\ValueObject\QueuePartitionName; class StubPrunnerApiService extends PrunnerApiService { public array $calls = []; - public function schedulePipeline(PipelineName $pipeline, array $variables): JobId - { + public function schedulePipeline( + PipelineName $pipeline, + array $variables, + ?QueuePartitionName $queuePartition = null + ): JobId { $this->calls[] = [ 'method' => 'schedulePipeline', 'pipeline' => $pipeline, - 'variables' => $variables + 'variables' => $variables, + 'queuePartition' => $queuePartition ]; return JobId::create('STUB' . count($this->calls));