From 08193796b7ecf810b39b32dd34c17553b2f9e29f Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 20 Oct 2024 14:11:36 +0200 Subject: [PATCH 1/8] TASK: Introduce full behat test for FrontendNodeController --- .../Bootstrap/Features/NodeCreation.php | 2 +- .../Classes/Domain/Service/FusionService.php | 12 ++ .../Features/Bootstrap/DispatcherTrait.php | 103 ++++++++++++++++ .../Features/Bootstrap/FeatureContext.php | 1 + .../DefaultFusionRendering.feature | 113 ++++++++++++++++++ 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php create mode 100644 Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php index d20ebdab535..5f4a1b8c699 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php @@ -205,7 +205,7 @@ public function theFollowingCreateNodeAggregateWithNodeCommandsAreExecuted(Table if (isset($row['tetheredDescendantNodeAggregateIds'])) { $command = $command->withTetheredDescendantNodeAggregateIds(NodeAggregateIdsByNodePaths::fromJsonString($row['tetheredDescendantNodeAggregateIds'])); } - if (isset($row['nodeName'])) { + if (!empty($row['nodeName'])) { $command = $command->withNodeName(NodeName::fromString($row['nodeName'])); } $this->currentContentRepository->handle($command); diff --git a/Neos.Neos/Classes/Domain/Service/FusionService.php b/Neos.Neos/Classes/Domain/Service/FusionService.php index 528b8315a31..9573ef9befe 100644 --- a/Neos.Neos/Classes/Domain/Service/FusionService.php +++ b/Neos.Neos/Classes/Domain/Service/FusionService.php @@ -44,6 +44,16 @@ class FusionService */ protected $fusionConfigurationCache; + private ?FusionSourceCodeCollection $additionalFusionSourceCode = null; + + /** + * @deprecated fixme!!! + */ + public function unsafeSetAdditionalFusionSourceCodeToThisSingleton(string $additionalFusionSourceCode) + { + $this->additionalFusionSourceCode = FusionSourceCodeCollection::fromString($additionalFusionSourceCode); + } + public function createFusionConfigurationFromSite(Site $site): FusionConfiguration { return $this->fusionConfigurationCache->cacheFusionConfigurationBySite($site, function () use ($site) { @@ -56,6 +66,8 @@ public function createFusionConfigurationFromSite(Site $site): FusionConfigurati ) ->union( FusionSourceCodeCollection::tryFromPackageRootFusion($siteResourcesPackageKey) + )->union( + $this->additionalFusionSourceCode ?? FusionSourceCodeCollection::empty() ) ); }); diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php new file mode 100644 index 00000000000..56a39fa4e9e --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php @@ -0,0 +1,103 @@ + $className + * + * @return T + */ + abstract private function getObject(string $className): object; + + /** + * @BeforeScenario + */ + public function setupDispatcherTest(): void + { + $this->getObject(ContentCache::class)->flush(); + $this->response = null; + } + + /** + * @When the sites Fusion code is: + */ + public function iHaveTheFollowingFusionCodeForTheSite(PyStringNode $fusionCode) + { + $this->getObject( + FusionService::class + )->unsafeSetAdditionalFusionSourceCodeToThisSingleton( + $fusionCode->getRaw() + ); + // $fakeFusionService = new class ($original) extends \Neos\Neos\Domain\Service\FusionService + // { + // public function __construct( + // private \Neos\Neos\Domain\Service\FusionService $original, + // private \Neos\Fusion\Core\FusionSourceCode $additionalFusion + // ) { + // } + // public function createFusionConfigurationFromSite(\Neos\Neos\Domain\Model\Site $site): \Neos\Fusion\Core\FusionConfiguration + // { + // $this->original->createFusionConfigurationFromSite($site)-> ... doest work + // } + // }; + } + + /** + * @When I dispatch the following request :requestUri + */ + public function iDispatchTheFollowingRequest(string $requestUri) + { + $httpRequest = $this->getObject(ServerRequestFactoryInterface::class)->createServerRequest('GET', $requestUri); + + $this->response = $this->getObject(\Neos\Flow\Http\Middleware\MiddlewaresChain::class)->handle( + $httpRequest + ); + } + + /** + * @Then I expect the following response header: + */ + public function iExpectTheFollowingResponseHeader(PyStringNode $expectedResult): void + { + Assert::assertNotNull($this->response); + Assert::assertSame($expectedResult->getRaw(), $this->response->getBody()->getContents()); + } + + /** + * @Then I expect the following response: + */ + public function iExpectTheFollowingResponse(PyStringNode $expectedResult): void + { + Assert::assertNotNull($this->response); + Assert::assertEquals($expectedResult->getRaw(), str_replace("\r\n", "\n", Message::toString($this->response))); + } +} diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php index 9d06ce491eb..b02048f437e 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php @@ -39,6 +39,7 @@ class FeatureContext implements BehatContext use CRBehavioralTestsSubjectProvider; use RoutingTrait; use MigrationsTrait; + use DispatcherTrait; use FusionTrait; use ContentCacheTrait; diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature b/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature new file mode 100644 index 00000000000..cb7689bf7cb --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature @@ -0,0 +1,113 @@ +@flowEntities +Feature: Test the default Fusion rendering for a request + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': {} + 'Neos.Neos:ContentCollection': {} + 'Neos.Neos:Content': {} + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + title: + type: string + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + childNodes: + main: + type: 'Neos.Neos:ContentCollection' + 'Neos.Neos:Test.DocumentType': + superTypes: + 'Neos.Neos:Document': true + childNodes: + main: + type: 'Neos.Neos:ContentCollection' + 'Neos.Neos:Test.ContentType': + superTypes: + 'Neos.Neos:Content': true + properties: + text: + type: string + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And I am in workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "root" | + | nodeTypeName | "Neos.Neos:Sites" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | tetheredDescendantNodeAggregateIds | nodeName | + | a | root | Neos.Neos:Site | {"title": "Node a"} | {} | a | + | a1 | a | Neos.Neos:Test.DocumentType | {"uriPathSegment": "a1", "title": "Node a1"} | {"main": "a-tetherton" } | | + | a1a1 | a-tetherton | Neos.Neos:Test.ContentType | {"text": "my first text"} | {} | | + | a1a2 | a-tetherton | Neos.Neos:Test.ContentType | {"text": "my second text"} | {} | | + And A site exists for node name "a" and domain "http://localhost" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + 'a': + preset: default + uriPathSuffix: '' + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + + Scenario: Default output + And the sites Fusion code is: + """fusion + prototype(Neos.Neos:Test.DocumentType) < prototype(Neos.Neos:Page) { + body { + content = Neos.Fusion:Component { + renderer = afx` + {String.chr(10)}title: {node.properties.title} + {String.chr(10)}children: + {String.chr(10)} + ` + } + } + } + prototype(Neos.Neos:Test.ContentType) < prototype(Neos.Neos:ContentComponent) { + text = Neos.Neos:Editable { + property = 'text' + } + + renderer = afx` + [{props.text}] + ` + } + """ + + When I dispatch the following request "/a1" + Then I expect the following response: + """ + HTTP/1.1 200 OK + Content-Type: text/html + X-Flow-Powered: Flow/dev Neos/dev + Content-Length: 486 + + + + Node a1 + title: Node a1 + children:
[my first text][my second text]
+ + """ From 74b5f5bf94ba039dcf2b9cd921ae5e519da2e606 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 20 Oct 2024 14:14:39 +0200 Subject: [PATCH 2/8] TASK: Fix error message in Runtime method renderResponse was renamed to renderEntryPathWithContext --- Neos.Fusion/Classes/Core/Runtime.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 1d3b304187e..ba8d7d99139 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -967,7 +967,7 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio private function withSimulatedLegacyControllerContext(\Closure $renderer): ResponseInterface|StreamInterface { if ($this->legacyActionResponseForCurrentRendering !== null) { - throw new Exception('Recursion detected in `Runtime::renderResponse`. This entry point is only allowed to be invoked once per rendering.', 1706993940); + throw new Exception('Recursion detected in `Runtime::renderEntryPathWithContext`. This entry point is only allowed to be invoked once per rendering.', 1706993940); } $this->legacyActionResponseForCurrentRendering = new ActionResponse(); @@ -1017,7 +1017,7 @@ public function getControllerContext(): LegacyFusionControllerContext // legacy controller context layer $actionRequest = $this->fusionGlobals->get('request'); if ($this->legacyActionResponseForCurrentRendering === null || !$actionRequest instanceof ActionRequest) { - throw new Exception(sprintf('Fusions simulated legacy controller context is only available inside `Runtime::renderResponse` and when the Fusion global "request" is an ActionRequest.'), 1706458355); + throw new Exception(sprintf('Fusions simulated legacy controller context is only available inside `Runtime::renderEntryPathWithContext` and when the Fusion global "request" is an ActionRequest.'), 1706458355); } return new LegacyFusionControllerContext( From 805694ea56d4933f45562083d18ff564faeef6d8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:55:26 +0200 Subject: [PATCH 3/8] TASK: Add failing test for subrequest on node show action --- .../Features/Bootstrap/DispatcherTrait.php | 36 +++-- .../DefaultFusionRendering.feature | 1 - .../FusionPlugin.feature | 148 ++++++++++++++++++ 3 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/FrontendNodeController/FusionPlugin.feature diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php index 56a39fa4e9e..e1fdd5ceb73 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php @@ -71,6 +71,31 @@ public function iHaveTheFollowingFusionCodeForTheSite(PyStringNode $fusionCode) // }; } + /** + * @When I declare the following controller :fullyQualifiedClassName: + */ + public function iDeclareTheFollowingController(string $fullyQualifiedClassName, PyStringNode $expectedResult): void + { + eval($expectedResult->getRaw()); + + $controllerInstance = new ('\\' . $fullyQualifiedClassName)(); + + if ($controllerInstance instanceof \Neos\Flow\Mvc\Controller\ActionController) { + // inject all the necessary properties of an action controller, as extended classes dont call $this->Flow_Proxy_injectProperties(); + \Neos\Utility\ObjectAccess::setProperty($controllerInstance, 'validatorResolver', $this->getObject(\Neos\Flow\Validation\ValidatorResolver::class), true); + \Neos\Utility\ObjectAccess::setProperty($controllerInstance, 'mvcPropertyMappingConfigurationService', $this->getObject(\Neos\Flow\Mvc\Controller\MvcPropertyMappingConfigurationService::class), true); + \Neos\Utility\ObjectAccess::setProperty($controllerInstance, 'viewConfigurationManager', $this->getObject(\Neos\Flow\Mvc\ViewConfigurationManager::class), true); + \Neos\Utility\ObjectAccess::setProperty($controllerInstance, 'objectManager', $this->getObject(\Neos\Flow\ObjectManagement\ObjectManager::class), true); + } + + + $objectManager = $this->getObject(\Neos\Flow\ObjectManagement\ObjectManager::class); + $objects = \Neos\Utility\ObjectAccess::getProperty($objectManager, 'objects', true); + $objects[get_class($controllerInstance)]['i'] = $controllerInstance; + $objects[get_class($controllerInstance)]['l'] = strtolower(get_class($controllerInstance)); + $objectManager->setObjects($objects); + } + /** * @When I dispatch the following request :requestUri */ @@ -83,21 +108,12 @@ public function iDispatchTheFollowingRequest(string $requestUri) ); } - /** - * @Then I expect the following response header: - */ - public function iExpectTheFollowingResponseHeader(PyStringNode $expectedResult): void - { - Assert::assertNotNull($this->response); - Assert::assertSame($expectedResult->getRaw(), $this->response->getBody()->getContents()); - } - /** * @Then I expect the following response: */ public function iExpectTheFollowingResponse(PyStringNode $expectedResult): void { Assert::assertNotNull($this->response); - Assert::assertEquals($expectedResult->getRaw(), str_replace("\r\n", "\n", Message::toString($this->response))); + Assert::assertEquals($expectedResult->getRaw(), str_replace("\r\n", "\n", Message::toString($this->response->withoutHeader('Content-Length')))); } } diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature b/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature index cb7689bf7cb..2f310073a8f 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature @@ -97,7 +97,6 @@ Feature: Test the default Fusion rendering for a request HTTP/1.1 200 OK Content-Type: text/html X-Flow-Powered: Flow/dev Neos/dev - Content-Length: 486