diff --git a/features/main/headers.feature b/features/main/headers.feature index 785839b4ea1..d61e6769574 100644 --- a/features/main/headers.feature +++ b/features/main/headers.feature @@ -6,3 +6,9 @@ Feature: Headers addition When I send a "GET" request to "/dummy_cars" Then the response status code should be 200 And the header "Sunset" should be equal to "Sat, 01 Jan 2050 00:00:00 +0000" + + Scenario: Declare headers from resource + When I send a "GET" request to "/redirect_to_foobar" + Then the response status code should be 301 + And the header "Location" should be equal to "/foobar" + And the header "Hello" should be equal to "World" diff --git a/src/Metadata/ApiResource.php b/src/Metadata/ApiResource.php index 1f8a1836fc6..504399842fa 100644 --- a/src/Metadata/ApiResource.php +++ b/src/Metadata/ApiResource.php @@ -38,6 +38,7 @@ class ApiResource extends Metadata /** * @param array|array|Operations|null $operations Operations is a list of HttpOperation * @param array|array|string[]|string|null $uriVariables + * @param array $headers * @param string|callable|null $provider * @param string|callable|null $processor * @param mixed|null $mercure @@ -314,6 +315,7 @@ public function __construct( * - With GraphQL, the [`isDeprecated` and `deprecationReason` properties](https://facebook.github.io/graphql/June2018/#sec-Deprecation) will be added to the schema */ protected ?string $deprecationReason = null, + protected ?array $headers = null, protected ?array $cacheHeaders = null, protected ?array $normalizationContext = null, protected ?array $denormalizationContext = null, @@ -1280,6 +1282,19 @@ public function withController(string $controller): self return $self; } + public function getHeaders(): ?array + { + return $this->headers; + } + + public function withHeaders(array $headers): self + { + $self = clone $this; + $self->headers = $headers; + + return $self; + } + public function getCacheHeaders(): ?array { return $this->cacheHeaders; diff --git a/src/Metadata/Delete.php b/src/Metadata/Delete.php index b381971746d..34c2617ad31 100644 --- a/src/Metadata/Delete.php +++ b/src/Metadata/Delete.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -115,6 +116,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/Error.php b/src/Metadata/Error.php index 988521523a3..6cf423f14cd 100644 --- a/src/Metadata/Error.php +++ b/src/Metadata/Error.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -114,6 +115,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/Extractor/XmlResourceExtractor.php b/src/Metadata/Extractor/XmlResourceExtractor.php index c5b7881b3b0..3f24be9b14a 100644 --- a/src/Metadata/Extractor/XmlResourceExtractor.php +++ b/src/Metadata/Extractor/XmlResourceExtractor.php @@ -96,6 +96,7 @@ private function buildExtendedBase(\SimpleXMLElement $resource): array 'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'), 'stateOptions' => $this->buildStateOptions($resource), 'links' => $this->buildLinks($resource), + 'headers' => $this->buildHeaders($resource), ]); } @@ -465,7 +466,6 @@ private function buildStateOptions(\SimpleXMLElement $resource): ?OptionsInterfa */ private function buildLinks(\SimpleXMLElement $resource): ?array { - $links = $resource->links ?? null; if (!$resource->links) { return null; } @@ -477,4 +477,21 @@ private function buildLinks(\SimpleXMLElement $resource): ?array return $links; } + + /** + * @return array + */ + private function buildHeaders(\SimpleXMLElement $resource): ?array + { + if (!$resource->headers) { + return null; + } + + $headers = []; + foreach ($resource->headers as $header) { + $headers[(string) $header->header->attributes()->key] = (string) $header->header->attributes()->value; + } + + return $headers; + } } diff --git a/src/Metadata/Extractor/YamlResourceExtractor.php b/src/Metadata/Extractor/YamlResourceExtractor.php index ea8812a0355..2858752cd24 100644 --- a/src/Metadata/Extractor/YamlResourceExtractor.php +++ b/src/Metadata/Extractor/YamlResourceExtractor.php @@ -123,6 +123,7 @@ private function buildExtendedBase(array $resource): array 'outputFormats' => $this->buildArrayValue($resource, 'outputFormats'), 'stateOptions' => $this->buildStateOptions($resource), 'links' => $this->buildLinks($resource), + 'headers' => $this->buildHeaders($resource), ]); } @@ -432,4 +433,21 @@ private function buildLinks(array $resource): ?array return $links; } + + /** + * @return array + */ + private function buildHeaders(array $resource): ?array + { + if (!isset($resource['headers']) || !\is_array($resource['headers'])) { + return null; + } + + $headers = []; + foreach ($resource['headers'] as $key => $value) { + $headers[$key] = $value; + } + + return $headers; + } } diff --git a/src/Metadata/Extractor/schema/resources.xsd b/src/Metadata/Extractor/schema/resources.xsd index 1d5f4998e66..dbc818d5fe8 100644 --- a/src/Metadata/Extractor/schema/resources.xsd +++ b/src/Metadata/Extractor/schema/resources.xsd @@ -407,6 +407,19 @@ + + + + + + + + + + + + + @@ -439,6 +452,7 @@ + diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php index ad1e6d08408..d2992dc2892 100644 --- a/src/Metadata/Get.php +++ b/src/Metadata/Get.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -114,6 +115,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/GetCollection.php b/src/Metadata/GetCollection.php index b9b2073dabc..9de5b536766 100644 --- a/src/Metadata/GetCollection.php +++ b/src/Metadata/GetCollection.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -115,6 +116,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index ed77bb58604..6136fa48e4e 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -55,6 +55,7 @@ class HttpOperation extends Operation * stale_while_revalidate?: int, * stale-if-error?: int, * }|null $cacheHeaders {@see https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers} + * @param array|null $headers * @param array{ * field: string, * direction: string, @@ -144,6 +145,7 @@ public function __construct( protected ?array $schemes = null, protected ?string $condition = null, protected ?string $controller = null, + protected ?array $headers = null, protected ?array $cacheHeaders = null, protected ?array $paginationViaCursor = null, protected ?array $hydraContext = null, @@ -511,6 +513,19 @@ public function withController(string $controller): self return $self; } + public function getHeaders(): ?array + { + return $this->headers; + } + + public function withHeaders(array $headers): self + { + $self = clone $this; + $self->headers = $headers; + + return $self; + } + public function getCacheHeaders(): ?array { return $this->cacheHeaders; diff --git a/src/Metadata/NotExposed.php b/src/Metadata/NotExposed.php index f7fa24af895..ba11f94fe2d 100644 --- a/src/Metadata/NotExposed.php +++ b/src/Metadata/NotExposed.php @@ -50,6 +50,7 @@ public function __construct( array $schemes = null, string $condition = null, ?string $controller = 'api_platform.action.not_exposed', + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, @@ -128,6 +129,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/Patch.php b/src/Metadata/Patch.php index ba6e61f196a..9d4a244acc3 100644 --- a/src/Metadata/Patch.php +++ b/src/Metadata/Patch.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -115,6 +116,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/Post.php b/src/Metadata/Post.php index feba797ab9e..7c9996f7b94 100644 --- a/src/Metadata/Post.php +++ b/src/Metadata/Post.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -116,6 +117,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php index 3157ddf1fea..4662558fe24 100644 --- a/src/Metadata/Put.php +++ b/src/Metadata/Put.php @@ -39,6 +39,7 @@ public function __construct( array $schemes = null, string $condition = null, string $controller = null, + array $headers = null, array $cacheHeaders = null, array $paginationViaCursor = null, array $hydraContext = null, @@ -116,6 +117,7 @@ public function __construct( schemes: $schemes, condition: $condition, controller: $controller, + headers: $headers, cacheHeaders: $cacheHeaders, paginationViaCursor: $paginationViaCursor, hydraContext: $hydraContext, diff --git a/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php b/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php index 98432490002..dba114e1f54 100644 --- a/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php +++ b/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php @@ -507,6 +507,20 @@ private function buildLinks(\SimpleXMLElement $resource, array $values = null): $childNode->addAttribute('href', $values[0]['href']); } + private function buildHeaders(\SimpleXMLElement $resource, array $values = null): void + { + if (!$values) { + return; + } + + $node = $resource->addChild('headers'); + foreach ($values as $key => $value) { + $childNode = $node->addChild('header'); + $childNode->addAttribute('key', $key); + $childNode->addAttribute('value', $value); + } + } + private function parse($value): ?string { if (null === $value) { diff --git a/src/Metadata/Tests/Extractor/Adapter/resources.xml b/src/Metadata/Tests/Extractor/Adapter/resources.xml index 09fa161a791..6b751472cbc 100644 --- a/src/Metadata/Tests/Extractor/Adapter/resources.xml +++ b/src/Metadata/Tests/Extractor/Adapter/resources.xml @@ -1,3 +1,3 @@ -someirischemaanotheririschemaCommentapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetapplication/merge-patch+json+ldapplication/merge-patch+json+ld_foo\d+bazhttps60120AuthorizationAccept-LanguageAcceptcomment:read_collectioncomment:writebazbazbazbarcomment.another_custom_filteruserIdLorem ipsum dolor sit ametDolor sit ametbarapplication/vnd.ms-excelapplication/merge-patch+jsonapplication/merge-patch+jsonpouet\d+barhttphttps60120AuthorizationAccept-Languagecomment:readcomment:writecomment:custombazbazbazbarcomment.custom_filterfoobarcustombazcustomquxcomment:read_collectioncomment:writebarcomment.another_custom_filteruserIdLorem ipsum dolor sit ametDolor sit ametbar/v1/v1Lorem ipsum dolor sit ametDolor sit amet/v1Lorem ipsum dolor sit ametDolor sit amet/v1Lorem ipsum dolor sit ametDolor sit ametLorem ipsum dolor sit ametDolor sit amet +someirischemaanotheririschemaCommentapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetapplication/merge-patch+json+ldapplication/merge-patch+json+ld_foo\d+bazhttps
60120AuthorizationAccept-LanguageAcceptcomment:read_collectioncomment:writebazbazbazbarcomment.another_custom_filteruserIdLorem ipsum dolor sit ametDolor sit ametbarapplication/vnd.ms-excelapplication/merge-patch+jsonapplication/merge-patch+jsonpouet\d+barhttphttps60120AuthorizationAccept-Languagecomment:readcomment:writecomment:custombazbazbazbarcomment.custom_filterfoobarcustombazcustomquxcomment:read_collectioncomment:writebarcomment.another_custom_filteruserIdLorem ipsum dolor sit ametDolor sit ametbar/v1/v1Lorem ipsum dolor sit ametDolor sit amet/v1Lorem ipsum dolor sit ametDolor sit amet/v1Lorem ipsum dolor sit ametDolor sit ametLorem ipsum dolor sit ametDolor sit amet diff --git a/src/Metadata/Tests/Extractor/Adapter/resources.yaml b/src/Metadata/Tests/Extractor/Adapter/resources.yaml index 9de5f0e600a..698b6213871 100644 --- a/src/Metadata/Tests/Extractor/Adapter/resources.yaml +++ b/src/Metadata/Tests/Extractor/Adapter/resources.yaml @@ -44,6 +44,8 @@ resources: host: api-platform.com schemes: - https + headers: + key: value condition: "request.headers.has('Accept')" controller: App\Controller\CustomController class: ApiPlatform\Metadata\GetCollection @@ -165,6 +167,7 @@ resources: class: null urlGenerationStrategy: 1 deprecationReason: 'This resource is deprecated' + headers: null cacheHeaders: max_age: 60 shared_max_age: 120 diff --git a/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php b/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php index 0dce9014fa9..0e20d227e8d 100644 --- a/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php +++ b/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php @@ -341,6 +341,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'status' => 204, 'host' => 'api-platform.com', 'schemes' => ['https'], + 'headers' => ['key' => 'value'], 'condition' => 'request.headers.has(\'Accept\')', 'controller' => 'App\Controller\CustomController', 'class' => GetCollection::class, @@ -506,6 +507,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'paginationViaCursor', 'stateOptions', 'links', + 'headers', ]; /** diff --git a/src/Metadata/Tests/Extractor/XmlExtractorTest.php b/src/Metadata/Tests/Extractor/XmlExtractorTest.php index 2b0fd95549a..a041e495ec3 100644 --- a/src/Metadata/Tests/Extractor/XmlExtractorTest.php +++ b/src/Metadata/Tests/Extractor/XmlExtractorTest.php @@ -101,6 +101,7 @@ public function testValidXML(): void 'write' => null, 'stateOptions' => null, 'links' => null, + 'headers' => null, ], [ 'uriTemplate' => '/users/{author}/comments{._format}', @@ -273,6 +274,7 @@ public function testValidXML(): void 'itemUriTemplate' => null, 'stateOptions' => null, 'links' => null, + 'headers' => ['hello' => 'world'], ], [ 'name' => null, @@ -373,6 +375,7 @@ public function testValidXML(): void 'provider' => null, 'stateOptions' => null, 'links' => null, + 'headers' => ['hello' => 'world'], ], ], 'graphQlOperations' => null, @@ -383,6 +386,7 @@ public function testValidXML(): void 'write' => null, 'stateOptions' => null, 'links' => null, + 'headers' => ['hello' => 'world'], ], ], ], $extractor->getResources()); diff --git a/src/Metadata/Tests/Extractor/YamlExtractorTest.php b/src/Metadata/Tests/Extractor/YamlExtractorTest.php index 39f062ae05b..1b56bf8c0d0 100644 --- a/src/Metadata/Tests/Extractor/YamlExtractorTest.php +++ b/src/Metadata/Tests/Extractor/YamlExtractorTest.php @@ -101,6 +101,7 @@ public function testValidYaml(): void 'write' => null, 'stateOptions' => null, 'links' => null, + 'headers' => null, ], ], Program::class => [ @@ -172,6 +173,7 @@ public function testValidYaml(): void 'write' => null, 'stateOptions' => null, 'links' => null, + 'headers' => null, ], [ 'uriTemplate' => '/users/{author}/programs{._format}', @@ -314,6 +316,7 @@ public function testValidYaml(): void 'itemUriTemplate' => null, 'stateOptions' => null, 'links' => null, + 'headers' => ['hello' => 'world'], ], [ 'name' => null, @@ -397,6 +400,7 @@ public function testValidYaml(): void 'provider' => null, 'stateOptions' => null, 'links' => null, + 'headers' => ['hello' => 'world'], ], ], 'graphQlOperations' => null, @@ -406,6 +410,7 @@ public function testValidYaml(): void 'write' => null, 'stateOptions' => null, 'links' => null, + 'headers' => ['hello' => 'world'], ], ], SingleFileConfigDummy::class => [ @@ -477,6 +482,7 @@ public function testValidYaml(): void 'write' => null, 'stateOptions' => null, 'links' => null, + 'headers' => null, ], ], ], $extractor->getResources()); diff --git a/src/Metadata/Tests/Extractor/xml/valid.xml b/src/Metadata/Tests/Extractor/xml/valid.xml index d1ef2e81d1e..52276015291 100644 --- a/src/Metadata/Tests/Extractor/xml/valid.xml +++ b/src/Metadata/Tests/Extractor/xml/valid.xml @@ -45,6 +45,10 @@ + +
+ + diff --git a/src/Metadata/Tests/Extractor/yaml/valid.yaml b/src/Metadata/Tests/Extractor/yaml/valid.yaml index 2de7d73eb3f..7ed798ccfa6 100644 --- a/src/Metadata/Tests/Extractor/yaml/valid.yaml +++ b/src/Metadata/Tests/Extractor/yaml/valid.yaml @@ -7,6 +7,8 @@ resources: uriVariables: ['author'] types: ['someirischema'] description: User programs + headers: + hello: 'world' normalizationContext: groups: ['foo', 'bar'] enabled: false diff --git a/src/Metadata/composer.json b/src/Metadata/composer.json index dd23bfb5ffe..f94fc17d488 100644 --- a/src/Metadata/composer.json +++ b/src/Metadata/composer.json @@ -31,8 +31,8 @@ "doctrine/inflector": "^2.0", "psr/cache": "^3.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/property-info": "^6.1", - "symfony/string": "^6.1" + "symfony/property-info": "^6.1 || ^7.0", + "symfony/string": "^6.1 || ^7.0" }, "require-dev": { "api-platform/json-schema": "*@dev || ^3.1", @@ -41,12 +41,12 @@ "phpspec/prophecy-phpunit": "^2.0", "phpstan/phpdoc-parser": "^1.16", "sebastian/comparator": "<5.0", - "symfony/config": "^6.1", - "symfony/phpunit-bridge": "^6.1", - "symfony/routing": "^6.1", - "symfony/var-dumper": "^6.3", - "symfony/web-link": "^6.3", - "symfony/yaml": "^6.1" + "symfony/config": "^6.1 || ^7.0", + "symfony/phpunit-bridge": "^6.1 || ^7.0", + "symfony/routing": "^6.1 || ^7.0", + "symfony/var-dumper": "^6.3 || ^7.0", + "symfony/web-link": "^6.3 || ^7.0", + "symfony/yaml": "^6.1 || ^7.0" }, "suggest": { "phpstan/phpdoc-parser": "For PHP documentation support.", diff --git a/src/State/Processor/RespondProcessor.php b/src/State/Processor/RespondProcessor.php index 0dec1faae72..bd0eb898bff 100644 --- a/src/State/Processor/RespondProcessor.php +++ b/src/State/Processor/RespondProcessor.php @@ -71,6 +71,10 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $headers = array_merge($headers, $exceptionHeaders); } + if ($operationHeaders = $operation->getHeaders()) { + $headers = array_merge($headers, $operationHeaders); + } + $status = $operation->getStatus(); if ($sunset = $operation->getSunset()) { @@ -86,7 +90,8 @@ public function process(mixed $data, Operation $operation, array $uriVariables = if ($hasData = ($this->resourceClassResolver && $originalData && \is_object($originalData) && $this->resourceClassResolver->isResourceClass($this->getObjectClass($originalData))) && $this->iriConverter) { if ( - 300 <= $status && $status < 400 + !isset($headers['Location']) + && 300 <= $status && $status < 400 && (($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) || ($operation->getExtraProperties()['canonical_uri_template'] ?? null)) ) { $canonicalOperation = $operation; @@ -102,11 +107,11 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $status ??= self::METHOD_TO_CODE[$method] ?? 200; - if ($hasData && $this->iriConverter) { + if ($hasData && $this->iriConverter && !isset($headers['Content-Location'])) { $iri = $this->iriConverter->getIriFromResource($originalData); $headers['Content-Location'] = $iri; - if ((201 === $status || (300 <= $status && $status < 400)) && 'POST' === $method) { + if ((201 === $status || (300 <= $status && $status < 400)) && 'POST' === $method && !isset($headers['Location'])) { $headers['Location'] = $iri; } } diff --git a/src/State/Provider/ReadProvider.php b/src/State/Provider/ReadProvider.php index a8b6d6e21f2..11209c9e050 100644 --- a/src/State/Provider/ReadProvider.php +++ b/src/State/Provider/ReadProvider.php @@ -49,11 +49,17 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $request = ($context['request'] ?? null); - if (!$operation->canRead()) { return null; } + $output = $operation->getOutput() ?? []; + if (\array_key_exists('class', $output) && null === $output['class']) { + $request?->attributes->set('data', null); + + return null; + } + if (null === $filters = $request?->attributes->get('_api_filters')) { $queryString = RequestParser::getQueryString($request); $filters = $queryString ? RequestParser::parseRequestParams($queryString) : null; diff --git a/tests/Fixtures/TestBundle/ApiResource/Headers.php b/tests/Fixtures/TestBundle/ApiResource/Headers.php new file mode 100644 index 00000000000..c84441e586b --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Headers.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; + +#[ApiResource( + headers: ['Location' => '/foobar', 'Hello' => 'World'], + status: 301, + output: false, + operations: [ + new Get(uriTemplate: 'redirect_to_foobar'), + ] +)] +class Headers +{ +} diff --git a/tests/State/RespondProcessorTest.php b/tests/State/RespondProcessorTest.php index 89ace0125fd..615b4b49b79 100644 --- a/tests/State/RespondProcessorTest.php +++ b/tests/State/RespondProcessorTest.php @@ -113,4 +113,18 @@ public function testAddsExceptionHeaders(): void $this->assertSame('32', $response->headers->get('retry-after')); } + + public function testAddsHeaders(): void + { + $operation = new Get(headers: ['foo' => 'bar']); + + /** @var ProcessorInterface $respondProcessor */ + $respondProcessor = new RespondProcessor(); + $req = new Request(); + $response = $respondProcessor->process('content', $operation, context: [ + 'request' => $req, + ]); + + $this->assertSame('bar', $response->headers->get('foo')); + } }