Story 1.7 - Implémente un système complet d'audit trail pour tracer toutes les actions sensibles (authentification, modifications de données, exports) avec immuabilité garantie par PostgreSQL. Fonctionnalités principales: - Table audit_log append-only avec contraintes PostgreSQL (RULE) - AuditLogger centralisé avec injection automatique du contexte - Correlation ID pour traçabilité distribuée (HTTP + async) - Handlers pour événements d'authentification - Commande d'archivage des logs anciens - Pas de PII dans les logs (emails/IPs hashés) Infrastructure: - Middlewares Messenger pour propagation du Correlation ID - HTTP middleware pour génération/propagation du Correlation ID - Support multi-tenant avec TenantResolver
106 lines
3.0 KiB
PHP
106 lines
3.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Functional\Administration\Api;
|
|
|
|
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
|
|
/**
|
|
* [P0] Functional tests for the refresh token endpoint.
|
|
*
|
|
* Verifies:
|
|
* - Endpoint accessibility
|
|
* - Missing token handling (401)
|
|
* - Invalid token handling
|
|
* - Cookie-based authentication
|
|
*/
|
|
final class RefreshTokenEndpointTest extends ApiTestCase
|
|
{
|
|
protected static ?bool $alwaysBootKernel = true;
|
|
|
|
#[Test]
|
|
public function refreshEndpointReturns401WithoutCookie(): void
|
|
{
|
|
// GIVEN: No refresh_token cookie
|
|
$client = static::createClient();
|
|
|
|
// WHEN: Calling refresh endpoint
|
|
$response = $client->request('POST', '/api/token/refresh', [
|
|
'json' => [],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
],
|
|
]);
|
|
|
|
// THEN: Returns 401 Unauthorized
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
|
|
#[Test]
|
|
public function logoutEndpointIsAccessibleWithoutToken(): void
|
|
{
|
|
// GIVEN: No authentication
|
|
$client = static::createClient();
|
|
|
|
// WHEN: Calling logout endpoint
|
|
$response = $client->request('POST', '/api/token/logout', [
|
|
'json' => [],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
],
|
|
]);
|
|
|
|
// THEN: Returns 200 OK (idempotent - no token to invalidate)
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
#[Test]
|
|
public function logoutEndpointClearsCookies(): void
|
|
{
|
|
// GIVEN: A client
|
|
$client = static::createClient();
|
|
|
|
// WHEN: Calling logout
|
|
$response = $client->request('POST', '/api/token/logout', [
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
'Cookie' => 'refresh_token=some-token-value',
|
|
],
|
|
]);
|
|
|
|
// THEN: Response sets expired cookies
|
|
$setCookieHeaders = $response->getHeaders(false)['set-cookie'] ?? [];
|
|
$this->assertNotEmpty($setCookieHeaders);
|
|
|
|
$hasClearedCookie = false;
|
|
foreach ($setCookieHeaders as $cookie) {
|
|
if (str_contains($cookie, 'refresh_token=') && str_contains($cookie, 'expires=')) {
|
|
$hasClearedCookie = true;
|
|
break;
|
|
}
|
|
}
|
|
$this->assertTrue($hasClearedCookie, 'Should set expired refresh_token cookie');
|
|
}
|
|
|
|
#[Test]
|
|
public function refreshEndpointWithInvalidTokenReturns401(): void
|
|
{
|
|
// GIVEN: An invalid/malformed token in cookie
|
|
$client = static::createClient();
|
|
|
|
// WHEN: Calling refresh with invalid cookie
|
|
$response = $client->request('POST', '/api/token/refresh', [
|
|
'json' => [],
|
|
'headers' => [
|
|
'Host' => 'localhost',
|
|
'Cookie' => 'refresh_token=invalid-token-format',
|
|
],
|
|
]);
|
|
|
|
// THEN: Returns 401 Unauthorized
|
|
self::assertResponseStatusCodeSame(401);
|
|
}
|
|
}
|