52 - Add POST /api/register endpoint with validation
This commit is contained in:
parent
f76c7b9ecd
commit
5777946faf
5 changed files with 522 additions and 0 deletions
53
src/Controller/RegisterController.php
Normal file
53
src/Controller/RegisterController.php
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Dto\RegisterRequest;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
final class RegisterController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private UserPasswordHasherInterface $passwordHasher,
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private ValidatorInterface $validator,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/register', name: 'api_register', methods: ['POST'])]
|
||||||
|
public function register(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$content = $request->toArray();
|
||||||
|
|
||||||
|
$requestDto = new RegisterRequest($content['email'] ?? '', $content['password'] ?? '');
|
||||||
|
$violations = $this->validator->validate($requestDto);
|
||||||
|
|
||||||
|
if (\count($violations) > 0) {
|
||||||
|
$errors = [];
|
||||||
|
foreach ($violations as $violation) {
|
||||||
|
$errors[$violation->getPropertyPath()][] = $violation->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json(['errors' => $errors], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setEmail($requestDto->email);
|
||||||
|
$user->setPassword($this->passwordHasher->hashPassword($user, $requestDto->password));
|
||||||
|
|
||||||
|
$this->entityManager->persist($user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'id' => (string) $user->getId(),
|
||||||
|
'email' => $user->getEmail(),
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Dto/RegisterRequest.php
Normal file
27
src/Dto/RegisterRequest.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
#[UniqueEntity(entityClass: User::class, fields: ['email'], message: 'This email is already registered.')]
|
||||||
|
class RegisterRequest
|
||||||
|
{
|
||||||
|
#[Assert\NotBlank]
|
||||||
|
#[Assert\Email]
|
||||||
|
public string $email;
|
||||||
|
|
||||||
|
#[Assert\Length(min: 8)]
|
||||||
|
#[Assert\NotBlank]
|
||||||
|
public string $password;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $email,
|
||||||
|
string $password,
|
||||||
|
) {
|
||||||
|
$this->email = $email;
|
||||||
|
$this->password = $password;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
tests/Entity/UserTest.php
Normal file
96
tests/Entity/UserTest.php
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Entity;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Uid\UuidV7;
|
||||||
|
|
||||||
|
final class UserTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetIdReturnsAUuidV7AssignedAtConstruction(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(UuidV7::class, $user->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRolesAlwaysIncludesRoleUserEvenWhenNoRolesAreSet(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
$this->assertContains('ROLE_USER', $user->getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRolesMergesExplicitlySetRolesWithRoleUser(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$user->setRoles(['ROLE_ADMIN']);
|
||||||
|
|
||||||
|
$roles = $user->getRoles();
|
||||||
|
|
||||||
|
$this->assertContains('ROLE_ADMIN', $roles);
|
||||||
|
$this->assertContains('ROLE_USER', $roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRolesDeduplicatesRoleUserWhenExplicitlySet(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$user->setRoles(['ROLE_USER']);
|
||||||
|
|
||||||
|
$roles = $user->getRoles();
|
||||||
|
|
||||||
|
$this->assertSame(['ROLE_USER'], array_values(array_unique($roles)));
|
||||||
|
$this->assertCount(1, array_keys($roles, 'ROLE_USER'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetUserIdentifierReturnsTheEmail(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$user->setEmail('alice@example.com');
|
||||||
|
|
||||||
|
$this->assertSame('alice@example.com', $user->getUserIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmailGetterAndSetterRoundTrip(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$returned = $user->setEmail('bob@example.com');
|
||||||
|
|
||||||
|
$this->assertSame('bob@example.com', $user->getEmail());
|
||||||
|
$this->assertSame($user, $returned, 'setEmail should return $this for fluent chaining.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRolesGetterAndSetterRoundTrip(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$returned = $user->setRoles(['ROLE_ADMIN', 'ROLE_USER']);
|
||||||
|
|
||||||
|
$this->assertContains('ROLE_ADMIN', $user->getRoles());
|
||||||
|
$this->assertSame($user, $returned, 'setRoles should return $this for fluent chaining.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPasswordGetterAndSetterRoundTrip(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$returned = $user->setPassword('hashed-password');
|
||||||
|
|
||||||
|
$this->assertSame('hashed-password', $user->getPassword());
|
||||||
|
$this->assertSame($user, $returned, 'setPassword should return $this for fluent chaining.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSerializeReplacesThePasswordWithItsCrc32cHashAndDropsTheRawValue(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$user->setEmail('alice@example.com');
|
||||||
|
$user->setPassword('raw-hashed-password');
|
||||||
|
|
||||||
|
$serialized = $user->__serialize();
|
||||||
|
|
||||||
|
$key = "\0".User::class."\0password";
|
||||||
|
|
||||||
|
$this->assertArrayHasKey($key, $serialized);
|
||||||
|
$this->assertSame(hash('crc32c', 'raw-hashed-password'), $serialized[$key]);
|
||||||
|
$this->assertNotSame('raw-hashed-password', $serialized[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
312
tests/Functional/RegisterApiTest.php
Normal file
312
tests/Functional/RegisterApiTest.php
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Functional;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
|
||||||
|
|
||||||
|
final class RegisterApiTest extends WebTestCase
|
||||||
|
{
|
||||||
|
private const PASSWORD = 'correct-horse-battery-staple';
|
||||||
|
|
||||||
|
public function testItCreatesAUserAndReturns201WithExactlyIdAndEmail(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'new-user@example.com',
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
201,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'A successful registration must return 201 Created.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The register response must be a JSON object.');
|
||||||
|
|
||||||
|
self::assertArrayHasKey('email', $body, 'The register response must include the email field.');
|
||||||
|
self::assertSame('new-user@example.com', $body['email'], 'The register response must return the registered email.');
|
||||||
|
|
||||||
|
self::assertArrayHasKey('id', $body, 'The register response must include the id (uuid) field.');
|
||||||
|
self::assertMatchesRegularExpression(
|
||||||
|
'/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
|
||||||
|
(string) $body['id'],
|
||||||
|
'The register response id must be a valid UUIDv7.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$keys = array_keys($body);
|
||||||
|
sort($keys);
|
||||||
|
self::assertSame(
|
||||||
|
['email', 'id'],
|
||||||
|
$keys,
|
||||||
|
'The register response body must contain EXACTLY the keys [email, id] — no more, no less.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$responseText = (string) $response->getContent();
|
||||||
|
self::assertStringNotContainsString(
|
||||||
|
'$2',
|
||||||
|
$responseText,
|
||||||
|
'The register response must never expose a bcrypt hash (starts with $2).',
|
||||||
|
);
|
||||||
|
self::assertStringNotContainsString(
|
||||||
|
'$argon',
|
||||||
|
$responseText,
|
||||||
|
'The register response must never expose an Argon2 hash (starts with $argon).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItPersistsAHashedPasswordThatAuthenticatesViaLogin(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'login-after-register@example.com',
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
201,
|
||||||
|
$client->getResponse()->getStatusCode(),
|
||||||
|
'Registration must succeed before testing login.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/login', [
|
||||||
|
'username' => 'login-after-register@example.com',
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$loginResponse = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertLessThan(
|
||||||
|
300,
|
||||||
|
$loginResponse->getStatusCode(),
|
||||||
|
'Logging in with the just-registered credentials must succeed — proving the password was hashed, not stored plaintext.',
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertNotNull(
|
||||||
|
$loginResponse->headers->get('Set-Cookie'),
|
||||||
|
'A successful login after registration must set a session cookie.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsADuplicateEmailWith422(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$factory = new PasswordHasherFactory([
|
||||||
|
User::class => new NativePasswordHasher(cost: 4),
|
||||||
|
]);
|
||||||
|
$hasher = new UserPasswordHasher($factory);
|
||||||
|
|
||||||
|
$existing = new User();
|
||||||
|
$existing->setEmail('already-registered@example.com');
|
||||||
|
$existing->setPassword($hasher->hashPassword($existing, 'some-other-password'));
|
||||||
|
|
||||||
|
/** @var EntityManagerInterface $em */
|
||||||
|
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$em->persist($existing);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'already-registered@example.com',
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'Registering an already-used email must return 422 Unprocessable Entity, not 500.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey(
|
||||||
|
'email',
|
||||||
|
$body['errors'],
|
||||||
|
'The error response must name the "email" field as the source of the violation.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsMissingOrBlankFieldsWith422(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => '',
|
||||||
|
'password' => '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'Blank email and password must return 422 Unprocessable Entity.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey('email', $body['errors'], 'Blank email must produce a violation named "email".');
|
||||||
|
self::assertArrayHasKey('password', $body['errors'], 'Blank password must produce a violation named "password".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsAnInvalidEmailFormatWith422(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'not-an-email',
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'An invalid email format must return 422 Unprocessable Entity.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey('email', $body['errors'], 'An invalid email format must produce a violation named "email".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsATooShortPasswordWith422(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'short-password@example.com',
|
||||||
|
'password' => 'short',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'A too-short password must return 422 Unprocessable Entity.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey('password', $body['errors'], 'A too-short password must produce a violation named "password".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsAnAbsentEmailFieldWith422(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'A request body that omits the "email" key entirely must return 422 Unprocessable Entity, not 500.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey('email', $body['errors'], 'An absent email key must produce a violation named "email".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsAnAbsentPasswordFieldWith422(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'absent-password@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'A request body that omits the "password" key entirely must return 422 Unprocessable Entity, not 500.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey('password', $body['errors'], 'An absent password key must produce a violation named "password".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReturnsAllViolationMessagesForAFieldAsAnArray(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'multi-violation@example.com',
|
||||||
|
'password' => '',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->getResponse();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
422,
|
||||||
|
$response->getStatusCode(),
|
||||||
|
'A blank password must return 422 Unprocessable Entity.',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_decode((string) $response->getContent(), true);
|
||||||
|
|
||||||
|
self::assertIsArray($body, 'The error response must be a JSON object.');
|
||||||
|
self::assertArrayHasKey('errors', $body, 'The error response must have an "errors" key.');
|
||||||
|
self::assertArrayHasKey('password', $body['errors'], 'A blank password must produce a violation named "password".');
|
||||||
|
|
||||||
|
self::assertIsArray(
|
||||||
|
$body['errors']['password'],
|
||||||
|
'Each field in "errors" must map to an ARRAY of messages, not a single string — a blank password triggers both NotBlank and Length violations.',
|
||||||
|
);
|
||||||
|
self::assertGreaterThan(
|
||||||
|
1,
|
||||||
|
\count($body['errors']['password']),
|
||||||
|
'A blank password violates both NotBlank and Length(min: 8) — both messages must survive, not just the last one written.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItIsReachableWithoutAuthentication(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/register', [
|
||||||
|
'email' => 'anon-reachable@example.com',
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertNotSame(
|
||||||
|
401,
|
||||||
|
$client->getResponse()->getStatusCode(),
|
||||||
|
'/api/register must be publicly reachable — an unauthenticated request must not be blocked with 401 by access_control.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
tests/Security/LogoutSubscriberTest.php
Normal file
34
tests/Security/LogoutSubscriberTest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Security;
|
||||||
|
|
||||||
|
use App\Security\LogoutSubscriber;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||||
|
|
||||||
|
final class LogoutSubscriberTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetSubscribedEventsMapsLogoutEventToOnLogout(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(
|
||||||
|
[LogoutEvent::class => 'onLogout'],
|
||||||
|
LogoutSubscriber::getSubscribedEvents(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnLogoutSetsAnEmptyNoContentResponseOnTheEvent(): void
|
||||||
|
{
|
||||||
|
$event = new LogoutEvent(new Request(), null);
|
||||||
|
$subscriber = new LogoutSubscriber();
|
||||||
|
|
||||||
|
$subscriber->onLogout($event);
|
||||||
|
|
||||||
|
$response = $event->getResponse();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Response::class, $response);
|
||||||
|
$this->assertSame(Response::HTTP_NO_CONTENT, $response->getStatusCode());
|
||||||
|
$this->assertSame('', $response->getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue