Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
105 lines
3.3 KiB
PHP
105 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Functional\Administration\Api;
|
|
|
|
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
|
|
/**
|
|
* Tests that password reset endpoints are accessible without authentication.
|
|
*
|
|
* These endpoints MUST be public because users who forgot their password
|
|
* cannot authenticate to request a reset.
|
|
*/
|
|
final class PasswordResetEndpointsTest extends ApiTestCase
|
|
{
|
|
/**
|
|
* Opt-in for API Platform 5.0 behavior where kernel boot is explicit.
|
|
*
|
|
* @see https://github.com/api-platform/core/issues/6971
|
|
*/
|
|
protected static ?bool $alwaysBootKernel = true;
|
|
|
|
#[Test]
|
|
public function passwordForgotEndpointIsAccessibleWithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
|
|
$response = $client->request('POST', '/api/password/forgot', [
|
|
'json' => ['email' => 'test@example.com'],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
],
|
|
]);
|
|
|
|
// Should NOT return 401 Unauthorized
|
|
// It should return 200 (success) or 429 (rate limited), but never 401
|
|
$status = $response->getStatusCode();
|
|
self::assertNotEquals(401, $status, 'Password forgot endpoint should be accessible without JWT');
|
|
self::assertContains($status, [200, 201, 429], 'Expected 200/201 (success) or 429 (rate limited)');
|
|
}
|
|
|
|
#[Test]
|
|
public function passwordResetEndpointIsAccessibleWithoutAuthentication(): void
|
|
{
|
|
$client = static::createClient();
|
|
|
|
$response = $client->request('POST', '/api/password/reset', [
|
|
'json' => [
|
|
'token' => 'invalid-token-for-test',
|
|
'password' => 'NewSecurePassword123!',
|
|
],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
],
|
|
]);
|
|
|
|
// Should NOT return 401 Unauthorized
|
|
// It should return 400 (invalid token) or 410 (expired), but never 401
|
|
self::assertNotEquals(401, $response->getStatusCode(), 'Password reset endpoint should be accessible without JWT');
|
|
|
|
// With an invalid token, we expect a 400 Bad Request
|
|
self::assertResponseStatusCodeSame(400);
|
|
}
|
|
|
|
#[Test]
|
|
public function passwordForgotValidatesEmailFormat(): void
|
|
{
|
|
$client = static::createClient();
|
|
|
|
$response = $client->request('POST', '/api/password/forgot', [
|
|
'json' => ['email' => 'not-an-email'],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
],
|
|
]);
|
|
|
|
// Invalid email format returns validation error (422)
|
|
// This is acceptable because it doesn't reveal user existence
|
|
// (format validation happens before checking if user exists)
|
|
self::assertResponseStatusCodeSame(422);
|
|
}
|
|
|
|
#[Test]
|
|
public function passwordResetValidatesPasswordRequirements(): void
|
|
{
|
|
$client = static::createClient();
|
|
|
|
// Password too short
|
|
$response = $client->request('POST', '/api/password/reset', [
|
|
'json' => [
|
|
'token' => 'some-token',
|
|
'password' => 'short',
|
|
],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
],
|
|
]);
|
|
|
|
// Should return 422 Unprocessable Entity for validation errors
|
|
self::assertResponseStatusCodeSame(422);
|
|
}
|
|
}
|