From 86d47b17ae838d7e9ed351dd2121420128694fc4 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 23:11:05 +0200 Subject: [PATCH] Keep configured auth transports when faking an actor withActor() replaced the whole TransportRegistry with a fresh one holding a single transport named "testing". AuthTransportMiddleware, which is how HttpAuthBootloader wires auth into an application, asks the registry for a transport by name, so a request through it died with Spiral\Auth\Exception\TransportException: Undefined auth transport header AuthMiddleware kept working because it iterates over every transport, which is why the failure looked like it only happened with a custom transport. The fake registry is now built from the names the application has configured, each mapped to the same fake HeaderTransport that reads X-Test-Token, so whichever name the middleware asks for it gets a transport that finds the test token. "testing" stays and becomes the default. An application with no registry bound behaves as before. The test app had no withActor() coverage at all, so the three tests come with two middleware stubs: one that runs AuthTransportMiddleware('header') resolved from the http scope, one that reports the actor into the response body. For spiral/framework#1231 --- src/Http/FakeHttp.php | 29 +++++++++- tests/src/Http/FakeHttpAuthTransportTest.php | 55 +++++++++++++++++++ .../Http/Stub/ActorReportingMiddleware.php | 26 +++++++++ tests/src/Http/Stub/HeaderAuthMiddleware.php | 37 +++++++++++++ 4 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 tests/src/Http/FakeHttpAuthTransportTest.php create mode 100644 tests/src/Http/Stub/ActorReportingMiddleware.php create mode 100644 tests/src/Http/Stub/HeaderAuthMiddleware.php diff --git a/src/Http/FakeHttp.php b/src/Http/FakeHttp.php index 2c20cd6..4a1b4c9 100644 --- a/src/Http/FakeHttp.php +++ b/src/Http/FakeHttp.php @@ -421,9 +421,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 +459,29 @@ 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 { + $configured = $this->container->get(TransportRegistry::class); + foreach (\array_keys($configured->getTransports()) as $name) { + $registry->setTransport((string) $name, $transport); + } + } catch (\Throwable) { + // 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/src/Http/FakeHttpAuthTransportTest.php b/tests/src/Http/FakeHttpAuthTransportTest.php new file mode 100644 index 0000000..3c34f1b --- /dev/null +++ b/tests/src/Http/FakeHttpAuthTransportTest.php @@ -0,0 +1,55 @@ +fakeHttp() + ->withActor($actor) + ->withMiddleware(ActorReportingMiddleware::class, HeaderAuthMiddleware::class) + ->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, HeaderAuthMiddleware::class) + ->get('/get/query-params'); + + $response->assertOk(); + $response->assertBodySame(ActorReportingMiddleware::NO_ACTOR); + } +} 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); + } +} diff --git a/tests/src/Http/Stub/HeaderAuthMiddleware.php b/tests/src/Http/Stub/HeaderAuthMiddleware.php new file mode 100644 index 0000000..5998142 --- /dev/null +++ b/tests/src/Http/Stub/HeaderAuthMiddleware.php @@ -0,0 +1,37 @@ +container->get(ScopeInterface::class), + $this->container->get(ActorProviderInterface::class), + $this->container->get(TokenStorageInterface::class), + $this->container->get(TransportRegistry::class), + ); + + return $middleware->process($request, $handler); + } +}