diff --git a/src/Http/FakeHttp.php b/src/Http/FakeHttp.php index 2c20cd6..6eff49b 100644 --- a/src/Http/FakeHttp.php +++ b/src/Http/FakeHttp.php @@ -7,6 +7,7 @@ namespace Spiral\Testing\Http; use Nyholm\Psr7\ServerRequest; use Nyholm\Psr7\Stream; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; @@ -421,9 +422,7 @@ class FakeHttp $bindings[ActorProviderInterface::class] = new FakeActorProvider($this->actor); $bindings[TokenStorageInterface::class] = new FakeTokenStorage(); - $transport = new TransportRegistry(); - $transport->setTransport('testing', new HeaderTransport(static::AUTH_TOKEN_HEADER_KEY)); - $bindings[TransportRegistry::class] = $transport; + $bindings[TransportRegistry::class] = $this->createFakeTransportRegistry(); } if ($this->session) { @@ -461,4 +460,30 @@ class FakeHttp throw new \InvalidArgumentException('$data should be an array or an object.'); } } + + /** + * The fake token travels in the {@see self::AUTH_TOKEN_HEADER_KEY} header, so every transport the + * application has configured is replaced by one that reads it. The configured names are kept because + * {@see \Spiral\Auth\Middleware\AuthTransportMiddleware} resolves a transport by name. + */ + private function createFakeTransportRegistry(): TransportRegistry + { + $registry = new TransportRegistry(); + $transport = new HeaderTransport(static::AUTH_TOKEN_HEADER_KEY); + + try { + /** @var TransportRegistry $configured */ + $configured = $this->container->get(TransportRegistry::class); + foreach (\array_keys($configured->getTransports()) as $name) { + $registry->setTransport((string) $name, $transport); + } + } catch (NotFoundExceptionInterface) { + // Nothing to copy the names from; the fake transport is all there is. + } + + $registry->setTransport('testing', $transport); + $registry->setDefaultTransport('testing'); + + return $registry; + } } diff --git a/tests/app/Bootloader/CustomAuthTransportBootloader.php b/tests/app/Bootloader/CustomAuthTransportBootloader.php new file mode 100644 index 0000000..6ab6f26 --- /dev/null +++ b/tests/app/Bootloader/CustomAuthTransportBootloader.php @@ -0,0 +1,27 @@ +addTransport(self::TRANSPORT, new HeaderTransport(header: 'X-Custom-Token')); + } +} diff --git a/tests/src/Http/FakeHttpAuthTransportTest.php b/tests/src/Http/FakeHttpAuthTransportTest.php new file mode 100644 index 0000000..ffb823e --- /dev/null +++ b/tests/src/Http/FakeHttpAuthTransportTest.php @@ -0,0 +1,95 @@ +getContainer()->get(TransportRegistry::class)->getTransports(); + + $this->assertArrayHasKey(CustomAuthTransportBootloader::TRANSPORT, $transports); + } + + public function testActorIsAuthenticatedThroughNamedTransport(): void + { + $actor = new \stdClass(); + + $response = $this->fakeHttp() + ->withActor($actor) + ->withMiddleware(ActorReportingMiddleware::class, self::HEADER_AUTH) + ->get('/get/query-params'); + + $response->assertOk(); + $response->assertBodySame($actor::class); + } + + public function testActorIsAuthenticatedThroughCustomTransport(): void + { + $actor = new \stdClass(); + + $response = $this->fakeHttp() + ->withActor($actor) + ->withMiddleware(ActorReportingMiddleware::class, self::CUSTOM_AUTH) + ->get('/get/query-params'); + + $response->assertOk(); + $response->assertBodySame($actor::class); + } + + public function testActorIsAuthenticatedThroughEveryTransport(): void + { + $actor = new \stdClass(); + + $response = $this->fakeHttp() + ->withActor($actor) + ->withMiddleware(ActorReportingMiddleware::class, AuthMiddleware::class) + ->get('/get/query-params'); + + $response->assertOk(); + $response->assertBodySame($actor::class); + } + + public function testRequestWithoutActorStaysUnauthenticated(): void + { + $response = $this->fakeHttp() + ->withMiddleware(ActorReportingMiddleware::class, self::CUSTOM_AUTH) + ->get('/get/query-params'); + + $response->assertOk(); + $response->assertBodySame(ActorReportingMiddleware::NO_ACTOR); + } + + protected function setUp(): void + { + parent::setUp(); + + $binder = $this->getContainer()->getBinder('http'); + $binder->bind( + self::HEADER_AUTH, + new Autowire(AuthTransportMiddleware::class, ['transportName' => 'header']), + ); + $binder->bind( + self::CUSTOM_AUTH, + new Autowire(AuthTransportMiddleware::class, ['transportName' => CustomAuthTransportBootloader::TRANSPORT]), + ); + } +} diff --git a/tests/src/Http/Stub/ActorReportingMiddleware.php b/tests/src/Http/Stub/ActorReportingMiddleware.php new file mode 100644 index 0000000..9066187 --- /dev/null +++ b/tests/src/Http/Stub/ActorReportingMiddleware.php @@ -0,0 +1,26 @@ +getAttribute(AuthMiddleware::ATTRIBUTE); + $actor = $context instanceof AuthContextInterface ? $context->getActor() : null; + + return new Response(200, body: $actor === null ? self::NO_ACTOR : $actor::class); + } +}