ProxyAuthK8S

Tests

Les couches de test côté Rust, leurs commandes et les règles qui les tiennent (RFC 0015)

Cette page est la seule référence des commandes de test ; task --list donne leur description courte. Le front a sa propre couche (RFC 0017), les suites heavy et de performance la leur (RFC 0016).

Les couches

CoucheOùCommandeOù elle tourne
Unitairemod tests dans le fichier, ou un tests.rs voisintask test:unitchaque PR
En processus, sans Redislibs/server/*/tests/*.rs sur le store en mémoiretask test:unitchaque PR
En processus, avec Redisles suites de sécurité (throttle, OAuth, audit) et le contrat du storetask test:allchaque PR
CLI, binaire réelapps/kubectl_proxyauth/tests/ : le binaire contre un ProxyAuthK8S mocké (wiremock), curl -L dans le rôle du navigateur SSO, trousseau keyutils (bus de session pointé nulle part, jamais celui du poste)cargo nextest run -p cli -p cli_trace -p kubectl_proxyauth (curl dans le PATH)chaque PR (back-cargo-test)
Garde-fousauthz_matrix, openapi_contract, variant_coveragetask test:unitchaque PR
envtestlibs/server/api/tests/envtest*.rs contre un vrai kube-apiservertask envtest:runchaque PR
Intégrationtests/integration, banc k3d (Suite d'intégration)task it:up puis task it:testchaque PR, chaque nuit
Heavytests/integration/tests/heavy_*.rs : l'écho amont, le serveur de la version précédente à côté (RFC 0016)task it:up puis task it:heavychaque nuit, à la main, avec le label heavy
Charge HTTPtests/perf/k6/scenarios/, lancés par tests/integration/tests/perf_http.rs (RFC 0016)task it:up puis task perf:http, perf:compare BASE=…chaque nuit, à la main, avec le label perf
Fuzzfuzz/fuzz_targets/task fuzz:check, task fuzz TARGET=…compilation à chaque PR, 60 s par cible chaque nuit
Mutantsles crates de sécurité (.cargo/mutants.toml)task mutants:run, mutants:diff BASE=…, mutants:listchaque semaine sur main, ou avec le label mutants
Mutants, informatifcommon, controller, l'agent (.cargo/mutants-wide.toml)task mutants:widechaque semaine sur main, résumé du run sans issue
Couverturetout le workspace (tiers rapides)task test:coverage, task test:coverage:cichaque PR
Releasele graphe de features du build livrétask test:release-featureschaque PR (job release-features)

task test:unit ne demande ni Redis ni réseau : les tests où Redis n'est qu'un stockage tournent sur InMemoryKv. Les tests qui testent Redis lui-même sautent alors, et la tâche les liste à la fin. task test:all, test:server, test:integration et test:coverage* mettent REQUIRE_TEST_REDIS=1 : là, un Redis absent est un échec.

Mesuré le 2026-10-30 (RFC 0015 §17) : 88,14 % des lignes, COVERAGE_EXCLUDE appliqué, pour un plancher COVERAGE_MIN de 85. Par crate, la plus basse est trace (48 %), puis l'agent (80 %) et controller (85 %). Le périmètre de sécurité compte 350 mutants, le rapport informatif 675.

Heavy et charge HTTP

La suite d'intégration voit ce que reçoit le client ; la suite heavy voit ce que reçoit l'amont. Ses tests mettent un écho (.it/heavy/echo.py, un serveur HTTPS qui écrit chaque requête reçue) à la place de l'apiserver derrière un cluster, envoient leur requête par l'Ingress avec un marqueur dans la query, puis lisent la transcription (heavy::relayed(ns, "marker=…")). Un test exige d'abord sa requête marquée, puis regarde ses en-têtes : une requête qui n'arrive pas n'est jamais un succès, sauf là où c'est la preuve, avec un témoin (le point 17 de la RFC 0014). heavy::Previous démarre l'image du serveur v0.4.2 (server-previous de .it/images.lock) à côté de l'actuelle.

La charge lance k6 sur chaque scénario, à travers l'Ingress, pendant qu'un fil relève la mémoire du back (kubectl top). Le rapport (PERF_OUT, par défaut .it/run/perf) : un NN_*.json par scénario, resources.json, summary.md. Seuls le taux d'erreur (< 1 %) et le refus du throttle bloquent ; les cibles de latence s'appliquent avec PERF_ENFORCE=1. PERF_DURATION (défaut 2m) règle le palier : quelques heures en font un soak. task perf:compare BASE=tests/perf/baselines/<version> compare les p95 (marge 25 %, FAIL=1 pour sortir en erreur). Une base se commite depuis un rapport du runner CI, jamais d'un poste.

Un point §6.P prouvé par l'une de ces deux couches a le statut heavy dans MATRIX.md.

Le Redis de test se règle avec TEST_REDIS_URL (défaut redis://127.0.0.1:6379). Ne le pointez pas sur un Redis partagé : les tests y écrivent leurs clés (préfixées, effacées à la fin, jamais de FLUSHDB). Un Redis jetable suffit :

podman run -d --rm --name test-redis -p 127.0.0.1:6390:6379 \
  docker.io/library/redis:8-alpine redis-server --save '' --appendonly no
TEST_REDIS_URL=redis://127.0.0.1:6390 task test:all

Le store en mémoire

common::kv_memory::InMemoryKv (feature test-util) remplace Redis derrière State. Il copie les commandes et scripts Lua que le serveur utilise (GETDEL, l'arrondi de TTL, l'INCR qui répare un compteur sans échéance, les scripts de présence) ; libs/server/common/tests/kv_contract.rs joue les mêmes cas contre lui et contre un vrai Redis. Il ne couvre pas le mode cluster de Redis (aucun job n'en démarre un).

Dans un test d'api :

let store = memory_store();
seed_proxy(&store, &proxy_fixture(&ns, &cluster, &upstream.uri())).await;
let app = harness::app(memory_state(&store, upstream.uri()));

store.fail_on(KvOp::Exists) fait échouer une opération pendant que le store répond aux autres : c'est ainsi que se testent les branches d'erreur (par exemple THROTTLE_FAIL_CLOSED, throttle_store_failures.rs).

Les règles

  • Un correctif apporte son test de régression, qui échoue sans le correctif. Exception : un correctif de CI, de dépendance ou de doc, dit dans la PR.
  • Un refus a son test négatif, nommé par le refus : <cas>_refused, _rejected, _denied (ou _returns_4xx).
  • Un saut n'est jamais silencieux : common::test_skip::redis_unavailable écrit SKIPPED: <test> — <raison>, et la tâche qui promet le test fait du saut un échec.
  • On exclut, on ne baisse pas : un fichier hors mesure entre dans COVERAGE_EXCLUDE (.tasks/test.yaml) avec sa raison ; COVERAGE_MIN ne descend jamais.
  • L'heure entre par un argument là où le code décide d'une borne (IssuerKeys::current(now), issuer::reusable) ; les caches lisent tokio::time::Instant, figé par #[tokio::test(start_paused = true)]. Pas d'entrée-sortie (wiremock, Redis) sous horloge figée : tokio avance l'heure dès que le runtime attend, et les délais réels du code partent.
  • Rangement : mod tests dans le fichier par défaut ; un tests.rs voisin quand la suite couvre plusieurs fichiers ; une fabrique partagée par crate de tests (tests/harness/ pour api, tests/support/ pour controller).

Les garde-fous

Ils échouent quand un cas est ajouté sans son test ; le message dit quoi faire.

  • Une route (authz_matrix.rs) : chaque route servie a une ligne dans MAIN ou LISTENER, avec sa classe. Public (aucune identité demandée) n'est admis que sous PUBLIC_PREFIXES et jamais pour une opération qui déclare security dans son #[utoipa::path] ; Authenticated (jeton du fournisseur du service) ; ClusterScoped (jeton du cluster et ses règles) ; AgentEnrolled (listener des agents). Le test appelle chaque ligne : le refus de sa classe et un contrôle positif.
  • Une réponse (openapi_contract.rs) : chaque réponse 2xx déclare son body, ou figure dans NO_BODY avec sa raison.
  • Une variante (variant_coverage.rs) : UpstreamAuthMode, Verb et UpstreamTrust sont lus par des match sans _ ; une variante nouvelle ne compile pas tant que son bras ne nomme pas le test qui la couvre.
  • Un mutant (.github/workflows/mutants.yaml) : un mutant qui survit sur l'exécution hebdomadaire ouvre l'issue mutants. On le tue par un test, ou on le liste dans exclude_re de .cargo/mutants.toml avec la raison pour laquelle il est équivalent.
  • Le build livré (task test:release-features) : common/test-util, qui porte le store en mémoire, ne doit pas atteindre cargo build --release --workspace.

On this page