test(auth): cover the 401 gate on all four read routes (#12) #19

Closed
maximus wants to merge 1 commit from issue-12-auth-tests into issue-11-extract-metrics
Owner

Ajoute __tests__/auth.test.js — 19 tests qui verrouillent la porte d'authentification des quatre routes de lecture.

Maillon 2 d'une pile chainee. Base : issue-11-extract-metrics (PR #18), pas main. A relire apres #18.

Pourquoi maintenant

C'est un filet pose avant le refactor de routage/auth de l'issue #13, pas une tache de finition. Les 14 tests existants frappent tous /defenseurs/findings : /health, /defenseurs et /reports/scans n'avaient aucune couverture d'authentification. Sans ce filet, un cablage manque dans #13 pourrait exposer publiquement les rapports Defenseurs de tout le parc sans faire echouer un seul test.

Ce que couvrent les 19 tests

Groupe Cas
En-tete Authorization absent 401 sur les 4 routes (test.each)
Mauvais token 401 sur les 4 routes (test.each)
En-tete malforme 401 sur /defenseurs : token brut sans schema, bearer minuscule, schema sans token
Fail-closed 401 si HEALTH_TOKEN n'est pas defini, meme avec un bearer bien forme
Routage 404 sur route inconnue, 404 sur POST des 4 routes, + 2 cas non authentifies

Patron repris de findings.test.js : serveur http reel sur port ephemere, repertoire temporaire, re-import frais de index.js a chaque beforeEach.

index.js n'est pas touche

git diff origin/issue-11-extract-metrics ne contient que le nouveau fichier de test. Ces tests decrivent le comportement actuel.

Suite complete : 39 tests (19 auth + 14 findings + 6 health), tous verts.

Deux points a savoir

  1. Le 404 precede l'authentification. La validation route/methode s'execute avant la verification du token (index.js:190 puis :196). Une requete sans token sur une route inconnue — ou avec une mauvaise methode sur une route existante — recoit 404, pas 401 : l'existence des routes est enumerable sans jeton. C'est le comportement actuel, encode tel quel dans deux tests nommes 404 (not 401) .... Le critere de l'issue (404) est respecte ; l'ordre auth/routage, lui, n'y etait pas specifie. A garder en tete pendant #13 : si le refactor inverse l'ordre, ces deux tests virent au rouge volontairement.

  2. Chevauchement assume avec l'existant. findings.test.js couvre deja no-header + mauvais token sur /defenseurs/findings, et health.test.js le mauvais token sur /health — 3 cellules sur 8 se recoupent. Retirer ces cellules aurait laisse une matrice trouee et manque deux criteres d'acceptation ecrits « sur les quatre routes ». La valeur du filet vient de la table complete au meme endroit. Le chevauchement est reduit a une ligne test.each par mode d'echec et documente en tete de fichier. Aucun test existant n'est modifie.

Verification

Mutation test execute sur une copie hors depot : en neutralisant la comparaison de token, 11 des 19 tests virent au rouge. Les 8 autres (routage + fail-closed) ne dependent pas de cette ligne — attendu. Le filet mord donc bien sur la regression qu'il est cense attraper.

Resolves #12


Generated autonomously by /autopilot run of 2026-08-16

Ajoute `__tests__/auth.test.js` — 19 tests qui verrouillent la porte d'authentification des quatre routes de lecture. **Maillon 2 d'une pile chainee.** Base : `issue-11-extract-metrics` (PR #18), pas `main`. A relire apres #18. ## Pourquoi maintenant C'est un filet pose **avant** le refactor de routage/auth de l'issue #13, pas une tache de finition. Les 14 tests existants frappent tous `/defenseurs/findings` : `/health`, `/defenseurs` et `/reports/scans` n'avaient aucune couverture d'authentification. Sans ce filet, un cablage manque dans #13 pourrait exposer publiquement les rapports Defenseurs de tout le parc sans faire echouer un seul test. ## Ce que couvrent les 19 tests | Groupe | Cas | |---|---| | En-tete `Authorization` absent | 401 sur les 4 routes (`test.each`) | | Mauvais token | 401 sur les 4 routes (`test.each`) | | En-tete malforme | 401 sur `/defenseurs` : token brut sans schema, `bearer` minuscule, schema sans token | | Fail-closed | 401 si `HEALTH_TOKEN` n'est pas defini, meme avec un bearer bien forme | | Routage | 404 sur route inconnue, 404 sur `POST` des 4 routes, + 2 cas non authentifies | Patron repris de `findings.test.js` : serveur `http` reel sur port ephemere, repertoire temporaire, re-import frais de `index.js` a chaque `beforeEach`. ## `index.js` n'est pas touche `git diff origin/issue-11-extract-metrics` ne contient que le nouveau fichier de test. Ces tests decrivent le comportement **actuel**. Suite complete : **39 tests** (19 auth + 14 findings + 6 health), tous verts. ## Deux points a savoir 1. **Le 404 precede l'authentification.** La validation route/methode s'execute avant la verification du token (`index.js:190` puis `:196`). Une requete sans token sur une route inconnue — ou avec une mauvaise methode sur une route existante — recoit **404, pas 401** : l'existence des routes est enumerable sans jeton. C'est le comportement actuel, encode tel quel dans deux tests nommes `404 (not 401) ...`. Le critere de l'issue (404) est respecte ; l'ordre auth/routage, lui, n'y etait pas specifie. A garder en tete pendant #13 : si le refactor inverse l'ordre, ces deux tests virent au rouge volontairement. 2. **Chevauchement assume avec l'existant.** `findings.test.js` couvre deja no-header + mauvais token sur `/defenseurs/findings`, et `health.test.js` le mauvais token sur `/health` — 3 cellules sur 8 se recoupent. Retirer ces cellules aurait laisse une matrice trouee et manque deux criteres d'acceptation ecrits « sur les quatre routes ». La valeur du filet vient de la table complete au meme endroit. Le chevauchement est reduit a une ligne `test.each` par mode d'echec et documente en tete de fichier. Aucun test existant n'est modifie. ## Verification Mutation test execute sur une copie hors depot : en neutralisant la comparaison de token, **11 des 19 tests virent au rouge**. Les 8 autres (routage + fail-closed) ne dependent pas de cette ligne — attendu. Le filet mord donc bien sur la regression qu'il est cense attraper. Resolves #12 --- Generated autonomously by /autopilot run of 2026-08-16
maximus added 1 commit 2026-08-16 15:36:42 +00:00
The 14 existing tests all hit /defenseurs/findings — /health, /defenseurs
and /reports/scans had no authentication coverage at all. This is the
safety net for the routing/auth refactor that comes next: a miswiring
could expose the parc-wide Defenseurs reports publicly without turning a
single test red.

Adds __tests__/auth.test.js (19 tests), following the findings.test.js
pattern (real http server + temp dir):
- 401 on all four routes with no Authorization header
- 401 on all four routes with a wrong bearer token
- 401 on malformed headers (no scheme, lowercase scheme, scheme only)
- 401 fail-closed when HEALTH_TOKEN is unset
- 404 on unknown routes and on POST against existing routes

These describe current behaviour: index.js is untouched. Two tests
document that route/method validation runs before authentication, so an
unauthenticated caller gets 404 rather than 401 on those paths.

Resolves #12
maximus added the
autopilot:pending-human
label 2026-08-16 15:36:52 +00:00
Author
Owner

Verdict : APPROVE

Ajout de tests pur, zero ligne de production. Fige l'ordre routage -> auth avant que #20 ne reecrive le dispatch — c'est exactement le bon moment pour poser ce filet.

Verifie

  • 19 cas, conforme au compte de CLAUDE.md.
  • Les deux cas 404 (not 401) figent l'invariant que #20 devra preserver.
  • Cas fail-closed (HEALTH_TOKEN absent -> 401 HEALTH_TOKEN not configured).
  • Toutes les lectures FS pointent vers un repertoire temporaire et LOGTO_HEALTH_URL vers un port ferme : une auth qui s'ouvrirait ne peut pas atteindre le vrai IdP depuis les tests.

Suggestions (non bloquantes)

  1. Aucun controle positif pour /defenseurs et /reports/scans. Verifie sur les trois fichiers de test a ce maillon : rien n'affirme nulle part que ces deux routes repondent 200 avec un token valide. Un refactor qui les casserait en 401 inconditionnel laisserait toute la suite verte. Le fichier se presente comme « la matrice complete route x mode d'echec » — une matrice de modes d'echec sans cas d'ancrage ne sait pas distinguer « refuse correctement » de « refuse tout ».

  2. Le commentaire du port ferme decrit un mecanisme qui n'existe pas. « If the auth gate ever fails open, the request errors out fast » : non — getLogtoHealth() avale toutes les erreurs et retourne {status:"down"}, donc un /health qui s'ouvrirait repondrait 200, pas une erreur. Le test attrape quand meme la regression (il attend 401), et le port ferme reste utile pour ne pas taper le vrai IdP — c'est la justification ecrite qui est fausse, pas la garde.

  3. Couverture de methode limitee a POST. HEAD/PUT/DELETE ne sont exerces nulle part a ce maillon (#21 les ajoutera, mais pour /hosts/<id> seulement).


Revue adversariale — maillon 2/5, revu contre sa base issue-11-extract-metrics.

## Verdict : APPROVE Ajout de tests pur, zero ligne de production. Fige l'ordre routage -> auth avant que #20 ne reecrive le dispatch — c'est exactement le bon moment pour poser ce filet. ### Verifie - 19 cas, conforme au compte de `CLAUDE.md`. - Les deux cas `404 (not 401)` figent l'invariant que #20 devra preserver. - Cas fail-closed (`HEALTH_TOKEN` absent -> 401 `HEALTH_TOKEN not configured`). - Toutes les lectures FS pointent vers un repertoire temporaire et `LOGTO_HEALTH_URL` vers un port ferme : une auth qui s'ouvrirait ne peut pas atteindre le vrai IdP depuis les tests. ### Suggestions (non bloquantes) 1. **Aucun controle positif pour `/defenseurs` et `/reports/scans`.** Verifie sur les trois fichiers de test a ce maillon : rien n'affirme nulle part que ces deux routes repondent 200 avec un token valide. Un refactor qui les casserait en 401 inconditionnel laisserait toute la suite verte. Le fichier se presente comme « la matrice complete route x mode d'echec » — une matrice de modes d'echec sans cas d'ancrage ne sait pas distinguer « refuse correctement » de « refuse tout ». 2. **Le commentaire du port ferme decrit un mecanisme qui n'existe pas.** « If the auth gate ever fails open, the request errors out fast » : non — `getLogtoHealth()` avale toutes les erreurs et retourne `{status:"down"}`, donc un `/health` qui s'ouvrirait repondrait **200**, pas une erreur. Le test attrape quand meme la regression (il attend 401), et le port ferme reste utile pour ne pas taper le vrai IdP — c'est la justification ecrite qui est fausse, pas la garde. 3. Couverture de methode limitee a POST. HEAD/PUT/DELETE ne sont exerces nulle part a ce maillon (#21 les ajoutera, mais pour `/hosts/<id>` seulement). --- *Revue adversariale — maillon 2/5, revu contre sa base `issue-11-extract-metrics`.*
Author
Owner

Mergee localement sur main en fast-forward (pile chainee : l API de merge Forgejo ne peut pas traiter une pile dont la base n est pas main). Tip integre : e181a96. Forgejo ne detecte pas un merge ff local, donc cette PR est fermee a la main — le code EST sur main.

Mergee localement sur `main` en fast-forward (pile chainee : l API de merge Forgejo ne peut pas traiter une pile dont la base n est pas `main`). Tip integre : `e181a96`. Forgejo ne detecte pas un merge ff local, donc cette PR est fermee a la main — le code EST sur `main`.
maximus closed this pull request 2026-08-16 18:27:38 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/vps-health-api#19
No description provided.