Keep configured auth transports when faking an actor (#91)
Assisted-By: Claude Fable 5.1 Co-authored-by: roxblnfk <roxblnfk@ya.ru>
This commit is contained in:
1 parent
fdcc6b7728
commit
0171b86dd8
4 files changed
+176
-3
No files matched your search
+28
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spiral\Testing\Tests\App\Bootloader;
|
||||
|
||||
use Spiral\Auth\Transport\HeaderTransport;
|
||||
use Spiral\Boot\Bootloader\Bootloader;
|
||||
use Spiral\Bootloader\Auth\HttpAuthBootloader;
|
||||
|
||||
/**
|
||||
* Registers an auth transport under a name that does not exist in the default auth config.
|
||||
*/
|
||||
final class CustomAuthTransportBootloader extends Bootloader
|
||||
{
|
||||
public const TRANSPORT = 'custom';
|
||||
|
||||
public function defineDependencies(): array
|
||||
{
|
||||
return [HttpAuthBootloader::class];
|
||||
}
|
||||
|
||||
public function init(HttpAuthBootloader $auth): void
|
||||
{
|
||||
$auth->addTransport(self::TRANSPORT, new HeaderTransport(header: 'X-Custom-Token'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spiral\Testing\Tests\Http;
|
||||
|
||||
use Spiral\Auth\Middleware\AuthMiddleware;
|
||||
use Spiral\Auth\Middleware\AuthTransportMiddleware;
|
||||
use Spiral\Auth\TransportRegistry;
|
||||
use Spiral\Core\Container\Autowire;
|
||||
use Spiral\Testing\Tests\App\Bootloader\CustomAuthTransportBootloader;
|
||||
use Spiral\Testing\Tests\Http\Stub\ActorReportingMiddleware;
|
||||
use Spiral\Testing\Tests\TestCase;
|
||||
|
||||
final class FakeHttpAuthTransportTest extends TestCase
|
||||
{
|
||||
private const HEADER_AUTH = 'auth.header';
|
||||
private const CUSTOM_AUTH = 'auth.custom';
|
||||
|
||||
public function defineBootloaders(): array
|
||||
{
|
||||
return [...parent::defineBootloaders(), CustomAuthTransportBootloader::class];
|
||||
}
|
||||
|
||||
public function testCustomTransportIsConfigured(): void
|
||||
{
|
||||
$transports = $this->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]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Spiral\Testing\Tests\Http\Stub;
|
||||
|
||||
use Nyholm\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Spiral\Auth\AuthContextInterface;
|
||||
use Spiral\Auth\Middleware\AuthMiddleware;
|
||||
|
||||
final class ActorReportingMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public const NO_ACTOR = 'guest';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$context = $request->getAttribute(AuthMiddleware::ATTRIBUTE);
|
||||
$actor = $context instanceof AuthContextInterface ? $context->getActor() : null;
|
||||
|
||||
return new Response(200, body: $actor === null ? self::NO_ACTOR : $actor::class);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user