diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 1a8e5920..6a266a3a 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -127,6 +127,7 @@ use Swag\AgenticCommerce\Ucp\Mcp\Tool\UcpOrderGetTool; use Swag\AgenticCommerce\Ucp\Negotiation\VersionNegotiationCounter; use Swag\AgenticCommerce\Ucp\Payment\ShopwareInvoicePaymentHandler; +use Swag\AgenticCommerce\Ucp\Profile\UcpProfileRouteScopeWhitelist; use Swag\AgenticCommerce\Ucp\SalesChannel\SalesChannelDomainResolver; use Swag\AgenticCommerce\Ucp\SalesChannel\SalesChannelDomainResolverCacheInvalidator; use Swag\AgenticCommerce\Ucp\SalesChannel\SalesChannelViewProvider; @@ -357,6 +358,9 @@ $services->set(StoreApiMcpRouteScopeWhitelist::class) ->tag('shopware.route_scope_whitelist'); + $services->set(UcpProfileRouteScopeWhitelist::class) + ->tag('shopware.route_scope_whitelist'); + // Public controllers. $services->set(UcpMcpProxyController::class) diff --git a/src/Ucp/Profile/UcpProfileRouteScopeWhitelist.php b/src/Ucp/Profile/UcpProfileRouteScopeWhitelist.php new file mode 100644 index 00000000..cc4f62d2 --- /dev/null +++ b/src/Ucp/Profile/UcpProfileRouteScopeWhitelist.php @@ -0,0 +1,24 @@ +setUcpActive(true); + + $browser = KernelLifecycleManager::createBrowser($this->getKernel()); + $browser->request('GET', $this->baseUrl().'/.well-known/ucp'); + $response = $browser->getResponse(); + + static::assertSame(Response::HTTP_OK, $response->getStatusCode()); + static::assertSame('application/json', $response->headers->get('Content-Type')); + + $payload = json_decode((string) $response->getContent(), true, 512, \JSON_THROW_ON_ERROR); + + static::assertArrayHasKey('ucp', $payload); + static::assertIsArray($payload['ucp']); + static::assertArrayHasKey('capabilities', $payload['ucp']); + } + public function testItReturns404ForAnUnexposedSalesChannel(): void { // The sales channel is not exposed for agentic commerce (UCP config inactive). diff --git a/tests/Unit/UcpProfileRouteScopeWhitelistTest.php b/tests/Unit/UcpProfileRouteScopeWhitelistTest.php new file mode 100644 index 00000000..7aacecc0 --- /dev/null +++ b/tests/Unit/UcpProfileRouteScopeWhitelistTest.php @@ -0,0 +1,93 @@ +push($mainRequest); + + $whitelist = new UcpProfileRouteScopeWhitelist(); + $listener = new RouteScopeListener( + new RouteScopeRegistry([new StorefrontRouteScope()]), + $requestStack, + [$whitelist], + ); + + $listener->checkScope($this->controllerEvent()); + + static::assertTrue($whitelist->applies(ProfileController::class)); + static::assertFalse($whitelist->applies(self::class)); + } + + private function controllerEvent(): ControllerEvent + { + $request = Request::create('https://shop.example/.well-known/ucp'); + $request->attributes->set('_route', 'ucp_sdk_symfony_profile__invoke'); + $request->attributes->set(PlatformRequest::ATTRIBUTE_ROUTE_SCOPE, [StorefrontRouteScope::ID]); + + return new ControllerEvent( + $this->createStub(HttpKernelInterface::class), + \Closure::fromCallable($this->profileController()), + $request, + HttpKernelInterface::MAIN_REQUEST, + ); + } + + private function profileController(): ProfileController + { + return new ProfileController( + $this->createStub(ProfileBuilderInterface::class), + new UcpSdkConfiguration( + '2026-08-25', + null, + [], + 'strict', + [], + true, + 86400, + 1048576, + 300, + 86400, + 300, + 300, + [], + true, + 'default', + 'ES256', + '+30 days', + '+30 days', + 1048576, + 5, + false, + 'sqlite:///:memory:', + ), + $this->createStub(RuntimeConfigurationResolverInterface::class), + ); + } +}