> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/fr/development/testing.md).

# Tests

MyIP utilise le **l'outil de test intégré de Node.js**. Il n'y a ni Jest, ni Vitest, ni aucune dépendance à un framework de test.

```bash
pnpm test     # node --test tests/*.test.js
```

Chaque spec se trouve dans `tests/`, à plat, nommée `<subject>.test.js`. Les specs de composables sont préfixées : `tests/composable-status-tone.test.js`, `tests/composable-refresh-orchestrator.test.js`.

Pour exécuter un fichier pendant l'itération :

```bash
node --test tests/guards.test.js
```

## Structure d'une spec

`node:test` pour la structure, `node:assert/strict` pour les assertions. Pas d'aides personnalisées au-delà de ce dont un fichier a besoin :

{% code title="tests/composable-status-tone.test.js" %}

```js
// Tests pour ipFieldTone — le mappage unifié « chaîne d'état → tonalité » que
// WebRtcTest / DnsLeaksTest / RuleTest / ConnectivityTest utilisent tous.

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { ipFieldTone } from '../frontend/composables/use-status-tone.js';

describe('ipFieldTone()', () => {
  it('renvoie "wait" lorsque la valeur est égale à l'étiquette d'attente', () => {
    assert.equal(ipFieldTone('En attente', { waitLabels: 'En attente', errorLabels: 'Erreur' }), 'wait');
  });
});
```

{% endcode %}

Comme tout autre fichier du projet, une spec s'ouvre par un commentaire d'en-tête indiquant ce qu'elle couvre.

## Ce qui mérite une spec

{% hint style="success" %}
**Toute logique non visuelle exécutable sans appel réseau est livrée avec une spec — dans la même modification.**
{% endhint %}

En pratique :

* **Fonctions pures** — validateurs, formateurs, analyseurs. `tests/valid-ip.test.js`, `tests/bgp-prefix.test.js`, `tests/mtr-parse.test.js`.
* **Transformations** — tout ce qui remodèle les données amont. `tests/transform-ip-data.test.js`, `tests/service-status-transform.test.js`.
* **Composables avec entrées simulables** — logique que l'on peut piloter en passant des valeurs. `tests/composable-achievement-engine.test.js`, `tests/composable-info-mask.test.js`.
* **Middleware** — `tests/guards.test.js` couvre chaque garde dans `common/guards.js` avec `(req, res, next)` mocks.
* **Fichiers de données statiques** — forme et intégrité. `tests/changelog.test.js`, `tests/achievements.test.js`, `tests/sections.test.js`, `tests/ip-databases.test.js`.
* **Gestionnaires d'API** — couverture légère uniquement, voir ci-dessous.

Quand le comportement change, mettez à jour les specs concernées **dans la même modification**. Ne différez pas.

### Specs passerelle

Quand un helper vit dans `common/` et est réexporté via `frontend/utils/`, la spec importe **les deux chemins** et vérifie qu'ils concordent. `tests/valid-ip.test.js` le fait, ce qui empêche une passerelle de regénérer silencieusement une implémentation en double.

## Ce qui ne reçoit pas de spec

| Hors du périmètre   | Pourquoi                                                |
| ------------------- | ------------------------------------------------------- |
| Rendu Vue           | Le runner Node ne monte rien — ni DOM, ni composants.   |
| Appels réseau réels | Aucune spec ne doit toucher un upstream en direct.      |
| API de navigateur   | WebRTC, `navigator`, l'empreinte du canvas et consorts. |

{% hint style="warning" %}
**Les changements visuels ne peuvent pas s'auto-tester.** Si votre changement est visuel, dites-le explicitement lorsque vous le remettez et laissez un humain le regarder dans `pnpm dev`. Un résultat vert `pnpm check` ne prouve rien sur l'apparence de l'interface.
{% endhint %}

## Tests de fumée pour les gestionnaires d'API

Chaque gestionnaire sous `api/` a une couverture de fumée dans `tests/api-handlers.test.js`. La philosophie est étroite et stricte :

{% hint style="danger" %}
**N'appelez jamais un upstream réel.** N'affirmez que sur les branches qui renvoient **avant** le premier `fetchUpstream` appel.
{% endhint %}

Cela laisse trois types d'assertion :

* **Vérification du verbe** — `POST` sur un gestionnaire réservé au GET renvoie `405`.
* **Branches des paramètres** — une entrée manquante ou mal formée renvoie `400`.
* **retours précoces « clé API manquante »** — le gestionnaire s'arrête avant d'appeler l'extérieur.

Le fichier fournit deux stubs réutilisés par chaque test de gestionnaire :

{% code title="tests/api-handlers.test.js" %}

```js
function createRequest(options = {}) {
    const method = options.method || 'GET';
    const query = options.query || {};
    const referer = Object.hasOwn(options, 'referer') ? options.referer : 'http://localhost/';
    const headers = {};
    if (referer !== undefined) headers.referer = referer;
    return { method, headers, query, body: options.body };
}

function createResponse() {
    return {
        statusCode: 200,
        body: undefined,
        status(code) { this.statusCode = code; return this; },
        json(payload) { this.body = payload; return this; },
        send(payload) { this.body = payload; return this; },
    };
}
```

{% endcode %}

Un bloc de gestionnaire complet ressemble à ceci :

{% code title="tests/api-handlers.test.js" %}

```js
describe('gestionnaire mac-checker', () => {
    it('rejette l’absence de ?mac', async () => {
        const res = createResponse();
        await macCheckerHandler(createRequest(), res);
        assert.equal(res.statusCode, 400);
        assert.deepEqual(res.body, { error: 'Aucune adresse MAC fournie' });
    });

    it('rejette un format de MAC invalide', async () => {
        const res = createResponse();
        await macCheckerHandler(createRequest({ query: { mac: 'not-a-mac' } }), res);
        assert.equal(res.statusCode, 400);
        assert.deepEqual(res.body, { error: 'Adresse MAC invalide' });
    });
});
```

{% endcode %}

### Variables d'environnement

Les tests qui modifient une variable d'environnement enregistrent son nom dans le `ENV_KEYS` tableau en haut du fichier. `beforeEach` sauvegarde ces clés et `afterEach` les restaure, afin qu'aucune spec ne laisse d'état à la suivante.

### Ne dupliquez pas le middleware

Les vérifications de Referer et la validation des paramètres sont appliquées par le middleware, pas par les gestionnaires. Elles sont couvertes une seule fois, dans `tests/guards.test.js`. Un spec de gestionnaire ne devrait pas réaffirmer « rejette un mauvais domaine » — le gestionnaire n'en voit jamais un.

La convention consiste à le noter dans un commentaire au-dessus du `describe` bloc, comme le fait le bloc du gestionnaire OONI :

```js
// La présence et la forme du domaine sont imposées par le middleware requireValidDomain
// (tests/guards.test.js) ; l'unique branche de pré-récupération du gestionnaire est le
// garde défensive sur le verbe.
```

### Les gardes défensives sur le verbe restent

Certains gestionnaires gardent un `req.method !== 'GET'` contrôle même si la route restreint déjà le verbe. Ces gardes existent parce que les tests de fumée s'y appuient directement. Laissez-les en place.

## Specs connexes à connaître

| Spec                                                            | Couvre                                                                                                |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `tests/guards.test.js`                                          | Chaque middleware dans `common/guards.js`                                                             |
| `tests/fetch-with-timeout.test.js`                              | Comportement d'expiration et d'abandon de l'upstream                                                  |
| `tests/changelog.test.js`                                       | Schéma du changelog et couverture sur quatre locales — voir [i18n](/developer/fr/development/i18n.md) |
| `tests/report-schema.test.js` / `tests/report-builders.test.js` | Le pipeline de rapport de diagnostic partageable                                                      |

## `pnpm check` doit être vert

```bash
pnpm check     # pnpm test && pnpm build
```

Tests plus une vraie build de production. Exécutez-le avant chaque remise — une PR, un commit, une demande de relecture.

La CI exécute les mêmes deux étapes (`pnpm test`, puis `pnpm run build`) à chaque push et pull request sur `main` et `dev`, sur Node 24, en installant avec `pnpm install --frozen-lockfile`. Un local `vert` signifie généralement un passage CI vert.

## Suivant

* [Conventions de codage](/developer/fr/development/coding-conventions.md) — les règles par rapport auxquelles les specs vérifient.
* [Ajout d'un nouvel outil](/developer/fr/development/adding-a-new-tool.md) — où les tests s'intègrent dans une fonctionnalité complète.
* [Backend](/developer/fr/architecture/backend.md) — conception du gestionnaire et du middleware.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/fr/development/testing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
