# Tests d'endpoints HTTP (OpenAPI)

La librairie fournit un builder fluide pour tester des endpoints HTTP dans les projets Symfony,
en chargeant les exemples de requête/réponse directement depuis les fichiers OAS (OpenAPI Specification).

## Mise en place dans la classe de base de test

Ajouter le trait `MakesApiRequests` à la classe abstraite de test fonctionnel du projet :

```php
use NoahVet\Reef\Infrastructure\Phpunit\Traits\MakesApiRequests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

abstract class AbstractHttp extends WebTestCase
{
    use MakesApiRequests;

    protected KernelBrowser $client;

    protected function setUp(): void
    {
        parent::setUp();
        $this->client = static::createClient();
        $this->client->disableReboot();
    }
}
```

Le trait expose la méthode `endpoint()` qui utilise `$this->client` et `static::getContainer()`
(disponibles dans tout sous-classe de `WebTestCase`).

## Structure des fichiers OAS

Le chargeur lit la clé `value` à l'intérieur de chaque exemple OAS :

```yaml
# doc/OpenAPI/Example/Request/Post/workspace_request.yaml
components:
  examples:
    WithMinimumDataRequestExample:
      summary: Création avec les données minimales
      value:
        name: Mon espace de travail
        description: Une description

# doc/OpenAPI/Example/Response/Post/workspace_response.yaml
components:
  examples:
    WithMinimumDataResponseExample:
      summary: Réponse à la création
      value:
        id: 01924b6c-dead-beef-0000-000000000001
        name: Mon espace de travail
        description: Une description
        createdDate: "2024-01-01T00:00:00+00:00"
```

## Utilisation du builder

```php
class CreateWorkspaceTest extends AbstractHttp
{
    protected string $fileNamePrefix = 'workspace';

    public function testCreateWorkspaceWithMinimumData(): void
    {
        $this->endpoint('POST', '/workspaces')
            ->withRequestExample($this->getRequestFileName(), 'WithMinimumDataRequestExample')
            ->withResponseExample($this->getResponseFileName(), 'WithMinimumDataResponseExample')
            ->withPermissions([
                new PermissionFixture(
                    Resource::fromString('reef:mkt:app', self::MARKETPLACE_APPLICATION_ID),
                    ['reef:pwm:app:perm:writeWorkspace'],
                ),
            ])
            ->withBlacklist(['id', 'createdDate', 'updatedDate'])
            ->execute()
            ->assertCreated()
            ->assertMatchesExpectedBody();
    }
}
```

Les méthodes `getRequestFileName()` et `getResponseFileName()` retournent par convention
`{fileNamePrefix}_request.yaml` et `{fileNamePrefix}_response.yaml`. Le chemin vers le fichier OAS
peut être absolu, relatif au répertoire de travail, ou relatif à un répertoire de base
configuré via `withBasePath()` (voir ci-dessous).

## Options du builder

| Méthode | Description |
|---|---|
| `withBasePath(string $basePath)` | Répertoire de base préfixant tous les chemins de fichiers OAS |
| `withRequestExample(string $filePath, string $componentName)` | Corps de requête chargé depuis l'exemple OAS |
| `withResponseExample(string $filePath, string $componentName)` | Corps de réponse attendu chargé depuis l'exemple OAS |
| `withPermissions(PermissionFixture[] $permissions)` | Permissions à accorder à l'utilisateur de test |
| `withSuperAdminPermissions()` | Accorde toutes les permissions (super-admin), sans avoir à les lister |
| `withBlacklist(string[] $fields)` | Champs ignorés lors de la comparaison (défaut : `id`, `createdDate`, `updatedDate`, …) |
| `withHeaders(array $headers)` | En-têtes serveur supplémentaires (format `HTTP_*`, ex. `HTTP_ACCEPT_LANGUAGE`) |
| `execute()` | Exécute la requête et retourne un `EndpointTestResponse` |

## Répertoire de base partagé (`withBasePath`)

Lorsque tous les fichiers OAS d'une suite de tests se trouvent dans le même répertoire racine,
`withBasePath()` évite de répéter le préfixe sur chaque appel :

```php
// Sans withBasePath — chemin complet à chaque fois
$this->endpoint('POST', '/workspaces')
    ->withRequestExample('doc/OpenAPI/Example/Request/Post/workspace.yaml', 'WorkspaceCreate')
    ->withResponseExample('doc/OpenAPI/Example/Response/Post/workspace.yaml', 'WorkspaceCreated')
    ->execute();

// Avec withBasePath — chemin relatif au répertoire de base
$this->endpoint('POST', '/workspaces')
    ->withBasePath('doc/OpenAPI/Example')
    ->withRequestExample('Request/Post/workspace.yaml', 'WorkspaceCreate')
    ->withResponseExample('Response/Post/workspace.yaml', 'WorkspaceCreated')
    ->execute();
```

En pratique, `withBasePath()` est typiquement défini une fois dans la classe de test abstraite
et transmis au builder via une méthode helper :

```php
abstract class AbstractHttp extends WebTestCase
{
    use MakesApiRequests;

    private const OAS_BASE_PATH = 'doc/OpenAPI/Example';

    protected function endpoint(string $method, string $path): EndpointTestBuilderInterface
    {
        return parent::endpoint($method, $path)->withBasePath(self::OAS_BASE_PATH);
    }
}
```

## Assertions disponibles sur `EndpointTestResponse`

Toutes les méthodes retournent `static` pour pouvoir être chaînées.

```php
$response = $this->endpoint('GET', '/workspaces/42')->execute();

// Codes HTTP prédéfinis
$response->assertOk();
$response->assertCreated();
$response->assertNoContent();
$response->assertUnauthorized();
$response->assertForbidden();
$response->assertNotFound();
$response->assertUnprocessable();
$response->assertStatus(422);

// Comparaison du corps avec l'exemple OAS (en appliquant la blacklist du builder)
$response->assertMatchesExpectedBody();

// Même chose avec des champs supplémentaires à ignorer au moment de l'assertion
$response->assertExcluding(['code', 'slug']);

// Vérifications partielles
$response->assertJsonContains(['name' => 'Mon espace de travail']);
$response->assertJsonHasKey('id');

// Accès brut au corps JSON décodé
$id = $response->json('id');        // valeur d'une clé
$body = $response->json();          // tableau complet
```

## Test en tant que super-admin

`withSuperAdminPermissions()` crée un utilisateur dont `isSuperAdmin = true`, ce qui
court-circuite toute vérification de permission dans le `MockedIAMVoter`. Utile pour tester
le comportement métier d'un endpoint sans se préoccuper des permissions.

```php
public function testCreateWorkspaceAsSuperAdmin(): void
{
    $this->endpoint('POST', '/workspaces')
        ->withRequestExample($this->getRequestFileName(), 'WithMinimumDataRequestExample')
        ->withResponseExample($this->getResponseFileName(), 'WithMinimumDataResponseExample')
        ->withSuperAdminPermissions()
        ->withBlacklist(['id', 'createdDate', 'updatedDate'])
        ->execute()
        ->assertCreated()
        ->assertMatchesExpectedBody();
}
```

> **Note :** `withSuperAdminPermissions()` et `withPermissions()` peuvent être combinés sans conflit,
> mais en pratique l'un ou l'autre suffit.

## Test de permission refusée

```php
public function testCreateWorkspaceWithoutPermission(): void
{
    $this->endpoint('POST', '/workspaces')
        ->withRequestExample($this->getRequestFileName(), 'WithMinimumDataRequestExample')
        ->withPermissions([]) // aucune permission
        ->execute()
        ->assertForbidden();
}
```

## Chargement direct d'un exemple OAS (`OasExampleLoader`)

Pour charger un exemple OAS en dehors du builder (préparation de fixture, assertion partielle…) :

```php
use NoahVet\Reef\Infrastructure\Phpunit\Loader\OasExampleLoader;

$loader = new OasExampleLoader();
$data = $loader->load('doc/OpenAPI/Example/Response/Post/workspace_response.yaml', 'WithMinimumDataResponseExample');
// $data contient le contenu de la clé "value" de l'exemple
```

Formats supportés : `.yaml`, `.yml`, `.json`.
