fix(test-curl): keep the socket pass off the environment (#41) #45
3 changed files with 62 additions and 10 deletions
|
|
@ -62,18 +62,18 @@ curl -H "Authorization: Bearer $(cat ~/.coolify-token)" \
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
- **CI** : `.forgejo/workflows/ci.yml` (issue #24), sur toute PR (pas de filtre `branches:`, pour que les maillons d'une pile soient batis) et sur `push` vers `main`. Un seul job en `container: node:22-alpine`, la base du Dockerfile : `npm ci`, `npm test`, `bash test-curl.sh`, puis `sh scripts/boot-from-copy.sh`. Verdict a lire sur le statut de commit (`GET /repos/maximus/vps-health-api/commits/<sha>/status`), l'API Actions repondant 404 sur cette instance.
|
- **CI** : `.forgejo/workflows/ci.yml` (issue #24), sur toute PR (pas de filtre `branches:`, pour que les maillons d'une pile soient batis) et sur `push` vers `main`. Un seul job en `container: node:22-alpine`, la base du Dockerfile : `npm ci`, `npm test`, `bash test-curl.sh`, puis `sh scripts/boot-from-copy.sh`. Verdict a lire sur le statut de commit (`GET /repos/maximus/vps-health-api/commits/<sha>/status`), l'API Actions repondant 404 sur cette instance.
|
||||||
- `npm test` (vitest) — 360 cas
|
- `npm test` (vitest) — 363 cas
|
||||||
- `__tests__/findings.test.js` — `/defenseurs/findings` (14 cas : auth, validation, filtres severity/category, asymetrie INFO, scan clean vs no_data, JSON corrompu)
|
- `__tests__/findings.test.js` — `/defenseurs/findings` (14 cas : auth, validation, filtres severity/category, asymetrie INFO, scan clean vs no_data, JSON corrompu)
|
||||||
- `__tests__/state.test.js` — `/defenseurs/state` + non-regression `/defenseurs` apres extraction de `serveJsonFile()` (9 cas : corps identique octet pour octet sur un JSON non canonique, no_data, isolation des deux chemins, 401 sans token / mauvais token / token d'ingestion, 404 sur POST)
|
- `__tests__/state.test.js` — `/defenseurs/state` + non-regression `/defenseurs` apres extraction de `serveJsonFile()` (9 cas : corps identique octet pour octet sur un JSON non canonique, no_data, isolation des deux chemins, 401 sans token / mauvais token / token d'ingestion, 404 sur POST)
|
||||||
- `__tests__/health.test.js` — module `metrics.js` + `/health` (6 cas : exports, payload champ pour champ, garde de latence)
|
- `__tests__/health.test.js` — module `metrics.js` + `/health` (6 cas : exports, payload champ pour champ, garde de latence)
|
||||||
- `__tests__/auth.test.js` — filet de securite auth sur les routes de lecture (67 cas : matrice route x mode d'echec, en-tetes malformes (dont le separateur `Bearer` autre qu'un espace), quasi-rates du token sur les 6 routes de lecture, fail-closed, **2 cas `404 (not 401)` qui figent l'ordre routage -> auth**, **un espion sur `timingSafeEqual`** qui exige deux empreintes de 32 octets a chaque requete, et **des epingles sur le source** : aucune egalite contre une chaine `Bearer`, un seul `tokenMatches()` et un seul `return null` dans `checkAuth()`, egalites limitees a une liste blanche, `tokenMatches()` qui hache les deux cotes sans test de longueur)
|
- `__tests__/auth.test.js` — filet de securite auth sur les routes de lecture (67 cas : matrice route x mode d'echec, en-tetes malformes (dont le separateur `Bearer` autre qu'un espace), quasi-rates du token sur les 6 routes de lecture, fail-closed, **2 cas `404 (not 401)` qui figent l'ordre routage -> auth**, **un espion sur `timingSafeEqual`** qui exige deux empreintes de 32 octets a chaque requete, et **des epingles sur le source** : aucune egalite contre une chaine `Bearer`, un seul `tokenMatches()` et un seul `return null` dans `checkAuth()`, egalites limitees a une liste blanche, `tokenMatches()` qui hache les deux cotes sans test de longueur)
|
||||||
- `__tests__/hosts.test.js` — matrice exhaustive des endpoints postes (173 cas : regex de route comme controle anti-traversee, 401 avant 403, cloisonnement des deux tokens, 503 fail-closed, reconstruction par liste blanche, `loadAvg` a exactement 3 elements, 413, `neverSeen`, fraicheur serveur sous horloge figee, degradation de lecture, **balayage des `.tmp` orphelins** au demarrage, prouve sur le vrai point d'entree, symlink ni suivi ni supprime, ancre `^` de `TEMP_FILE_RE`)
|
- `__tests__/hosts.test.js` — matrice exhaustive des endpoints postes (173 cas : regex de route comme controle anti-traversee, 401 avant 403, cloisonnement des deux tokens, 503 fail-closed, reconstruction par liste blanche, `loadAvg` a exactement 3 elements, 413, `neverSeen`, fraicheur serveur sous horloge figee, degradation de lecture, **balayage des `.tmp` orphelins** au demarrage, prouve sur le vrai point d'entree, symlink ni suivi ni supprime, ancre `^` de `TEMP_FILE_RE`)
|
||||||
- `__tests__/agent.test.js` — agent poste (50 cas : contrat de payload verifie contre `sanitizeSnapshot()`, une seule tentative sans rejeu, timeout, codes de sortie, wrapper shell, et **le token absent de toute sortie d'echec, fragments de 5 caracteres compris**)
|
- `__tests__/agent.test.js` — agent poste (50 cas : contrat de payload verifie contre `sanitizeSnapshot()`, une seule tentative sans rejeu, timeout, codes de sortie, wrapper shell, et **le token absent de toute sortie d'echec, fragments de 5 caracteres compris**)
|
||||||
- `__tests__/ci.test.js` — epingles du workflow et des scripts de CI (30 cas : image du `container:` identique au `FROM` du Dockerfile, les quatre etapes presentes, aucun filtre `branches:` sous `pull_request`, `push` limite a `main`, ni `defaults:` ni `working-directory` sur une etape `uses:`, **aucun `continue-on-error`, sous toute ecriture litterale** (une etape rouge passerait au vert en silence ; detecteur sur le mot nu, sans ancrage ni deux-points exiges, lui-meme verifie sur la cle en bloc, en element de liste, en style flow, entre guillemets et via une ancre YAML ; seules les lignes de commentaire entieres en sont exemptees ; une cle ecrite avec une sequence d'echappement, `"continue\u002don-error"`, passe encore : l'epingle lit du texte, pas du YAML), ce que le boot check suppose du Dockerfile : `USER node`, `CMD` en forme exec, le **refus d'un port deja occupe** (voisin HTTP reel, script lance en `spawn` asynchrone), **aucune requete par un proxy de l'environnement** (boot complet derriere un faux proxy qui repond a tout, sa vivacite prouvee d'abord par un `curl` nu, avec un `no_proxy` qui laisse la boucle locale hors liste : exporter `NO_PROXY` seul ne suffit pas), **`test-curl.sh` lance en entier** (#41, 5 cas : derriere le meme faux proxy avec `FORCE_COLOR=3`, rien ne l'atteint et les 45 cas passent, le nombre etant ecrit en dur dans le test ; un **voisin HTTP reel** sur 3099, puis sur 3098 : refus avant tout lancement, le voisin n'ayant vu que la sonde `/` ; puis un faux `node` en tete de `PATH` qui sort en 1 pour le port 3099, puis 3098 : message sur stderr, code 1 en moins de 3 s, aucun serveur laisse sur son port. Ces cas occupent 3099 et 3098 pendant `npm test`, et retirent `BASE_URL` de l'environnement, que vite pose a `/` dans le processus de test), et **des `require` en colonne 0 seulement, sans `import()`**, dans chaque `.js` du `COPY`. Cette derniere epingle est une heuristique sur les appels litteraux, espace avant la parenthese comprise (`require (`, `import (`). Son detecteur, `loadsLate()`, est lui-meme verifie sur six lignes : `require` indente et `import` a toute colonne, avec ou sans espace, signales ; `require` de premier niveau et ligne de commentaire laisses passer. Trous connus, entre autres, laisses ouverts (#35) : une fonction ecrite entiere sur une ligne en colonne 0 (`const lazy = () => require("x");`), un `require(` renvoye a la ligne en colonne 0 dans une fonction (de meme forme que le `} = require("node:fs");` legitime d'`index.js`), et toute indirection, comme un alias de `require`)
|
- `__tests__/ci.test.js` — epingles du workflow et des scripts de CI (33 cas : image du `container:` identique au `FROM` du Dockerfile, les quatre etapes presentes, aucun filtre `branches:` sous `pull_request`, `push` limite a `main`, ni `defaults:` ni `working-directory` sur une etape `uses:`, **aucun `continue-on-error`, sous toute ecriture litterale** (une etape rouge passerait au vert en silence ; detecteur sur le mot nu, sans ancrage ni deux-points exiges, lui-meme verifie sur la cle en bloc, en element de liste, en style flow, entre guillemets et via une ancre YAML ; seules les lignes de commentaire entieres en sont exemptees ; une cle ecrite avec une sequence d'echappement, `"continue\u002don-error"`, passe encore : l'epingle lit du texte, pas du YAML), ce que le boot check suppose du Dockerfile : `USER node`, `CMD` en forme exec, le **refus d'un port deja occupe** (voisin HTTP reel, script lance en `spawn` asynchrone), **aucune requete par un proxy de l'environnement** (boot complet derriere un faux proxy qui repond a tout, sa vivacite prouvee d'abord par un `curl` nu, avec un `no_proxy` qui laisse la boucle locale hors liste : exporter `NO_PROXY` seul ne suffit pas), **`test-curl.sh` lance en entier** (#41, 8 cas : derriere le meme faux proxy avec `FORCE_COLOR=3`, rien ne l'atteint et les 45 cas passent, le nombre etant ecrit en dur dans le test ; un **voisin HTTP reel** sur 3099, puis sur 3098 : refus avant tout lancement, le voisin n'ayant vu que la sonde `/` ; un **occupant non HTTP** sur 3099, qui ferme aussitot (code curl 52 ou 56) puis qui accepte sans repondre (28) : meme refus, la regle etant « tout code autre que 7 » ; un **serveur vivant qui n'ecoute jamais** : message d'echeance et code 1 apres les 5 s d'attente ; puis un faux `node` en tete de `PATH` qui sort en 1 pour le port 3099, puis 3098 : message sur stderr, code 1 en moins de 3 s, aucun serveur laisse sur son port. Ces cas occupent 3099 et 3098 pendant `npm test`, et retirent `BASE_URL` de l'environnement, que vite pose a `/` dans le processus de test), et **des `require` en colonne 0 seulement, sans `import()`**, dans chaque `.js` du `COPY`. Cette derniere epingle est une heuristique sur les appels litteraux, espace avant la parenthese comprise (`require (`, `import (`). Son detecteur, `loadsLate()`, est lui-meme verifie sur six lignes : `require` indente et `import` a toute colonne, avec ou sans espace, signales ; `require` de premier niveau et ligne de commentaire laisses passer. Trous connus, entre autres, laisses ouverts (#35) : une fonction ecrite entiere sur une ligne en colonne 0 (`const lazy = () => require("x");`), un `require(` renvoye a la ligne en colonne 0 dans une fonction (de meme forme que le `} = require("node:fs");` legitime d'`index.js`), et toute indirection, comme un alias de `require`)
|
||||||
- `__tests__/writes.test.js` — seule surface d'ecriture : `HOSTS_DIR` (11 cas : espions sur toutes les fonctions d'ecriture de `node:fs`, formes callback, Sync et `fs.promises`, et sur `child_process`, poses avant un `require` frais apres purge du cache de **tous** les modules runtime (un `metrics.js` reste en cache garderait les espions restaures d'un test precedent) ; demarrage par le balayage des `.tmp` ; les six routes GET n'ecrivent rien, meme une macrotache apres la reponse, contenu reel exige ; `POST /hosts/<id>` n'ecrit que sous `HOSTS_DIR` ; **chemins degrades** : rejets 401/403/404, entrees 400/413, `agents-map.json` corrompu, fichiers Defenseurs absents, snapshot corrompu, avec le contenu degrade exige (`no_data`, `neverSeen`) ; **commandes shell** figees sur `head -1 /proc/stat` et `df -k /`, `/health` servant de temoin ; chaque cas prouve que ses espions sont vivants ; **tout appel est compte, sans attribution par pile** (#40 : une fonction `node:fs` passee telle quelle a `process.nextTick`, a un ecouteur ou a un `.then` s'execute sans frame runtime sur sa pile, et un filtre par pile la laissait passer) ; les retouches de fixtures du cas degrade passent par les fonctions reelles capturees avant la pose des espions, jamais par un `rmSync` recursif, qui appelle en interne le `fs.rmdirSync` public, donc l'espion ; **epingle sur le bloc `require.main`**, ensemble exact des appels, puisqu'il ne tourne jamais dans le processus de test. Limites : un chemin qu'aucun cas n'emprunte, et une ecriture differee au-dela de 20 ms. Contrepartie assumee : une ecriture du lanceur de tests lui-meme pendant un cas le ferait rougir)
|
- `__tests__/writes.test.js` — seule surface d'ecriture : `HOSTS_DIR` (11 cas : espions sur toutes les fonctions d'ecriture de `node:fs`, formes callback, Sync et `fs.promises`, et sur `child_process`, poses avant un `require` frais apres purge du cache de **tous** les modules runtime (un `metrics.js` reste en cache garderait les espions restaures d'un test precedent) ; demarrage par le balayage des `.tmp` ; les six routes GET n'ecrivent rien, meme une macrotache apres la reponse, contenu reel exige ; `POST /hosts/<id>` n'ecrit que sous `HOSTS_DIR` ; **chemins degrades** : rejets 401/403/404, entrees 400/413, `agents-map.json` corrompu, fichiers Defenseurs absents, snapshot corrompu, avec le contenu degrade exige (`no_data`, `neverSeen`) ; **commandes shell** figees sur `head -1 /proc/stat` et `df -k /`, `/health` servant de temoin ; chaque cas prouve que ses espions sont vivants ; **tout appel est compte, sans attribution par pile** (#40 : une fonction `node:fs` passee telle quelle a `process.nextTick`, a un ecouteur ou a un `.then` s'execute sans frame runtime sur sa pile, et un filtre par pile la laissait passer) ; les retouches de fixtures du cas degrade passent par les fonctions reelles capturees avant la pose des espions, jamais par un `rmSync` recursif, qui appelle en interne le `fs.rmdirSync` public, donc l'espion ; **epingle sur le bloc `require.main`**, ensemble exact des appels, puisqu'il ne tourne jamais dans le processus de test. Limites : un chemin qu'aucun cas n'emprunte, et une ecriture differee au-dela de 20 ms. Contrepartie assumee : une ecriture du lanceur de tests lui-meme pendant un cas le ferait rougir)
|
||||||
- Runtime reste 0-dep ; vitest en devDep uniquement
|
- Runtime reste 0-dep ; vitest en devDep uniquement
|
||||||
- `sh scripts/boot-from-copy.sh` — demarre le `CMD` depuis les seules sources du `COPY`, copiees dans un dossier vide, et exige 401 sans token, 200 avec, et des metriques disque/memoire non nulles. Tient lieu de `docker build` en CI : un vrai build demanderait le socket Docker dans le runner d'instance, donc root sur le VPS de prod pour tout workflow de tout depot. Il n'accepte qu'une ligne `COPY` en forme shell sans option, et un `CMD` en forme exec ; toute autre forme echoue bruyamment plutot que de verifier du vide. Port 3097 par defaut (`BOOT_PORT` pour en changer) ; il refuse de demarrer si le port est deja pris (tout code de sortie curl autre que 7, « connexion refusee »), sans quoi ses assertions pourraient interroger un voisin. Il exporte `NO_PROXY='*'` et `no_proxy='*'` en tete : chaque `curl` vise la boucle locale, et un `http_proxy` de l'environnement repondrait a la place du port comme du serveur demarre. Les deux graphies, car curl lit d'abord la minuscule : un `no_proxy` herite l'emporterait sur un `NO_PROXY` exporte seul. Il n'appelle que `/health` : il ne prouve que les modules charges au demarrage, d'ou l'epingle « `require` en colonne 0 » dans `ci.test.js`.
|
- `sh scripts/boot-from-copy.sh` — demarre le `CMD` depuis les seules sources du `COPY`, copiees dans un dossier vide, et exige 401 sans token, 200 avec, et des metriques disque/memoire non nulles. Tient lieu de `docker build` en CI : un vrai build demanderait le socket Docker dans le runner d'instance, donc root sur le VPS de prod pour tout workflow de tout depot. Il n'accepte qu'une ligne `COPY` en forme shell sans option, et un `CMD` en forme exec ; toute autre forme echoue bruyamment plutot que de verifier du vide. Port 3097 par defaut (`BOOT_PORT` pour en changer) ; il refuse de demarrer si le port est deja pris (tout code de sortie curl autre que 7, « connexion refusee »), sans quoi ses assertions pourraient interroger un voisin. Il exporte `NO_PROXY='*'` et `no_proxy='*'` en tete : chaque `curl` vise la boucle locale, et un `http_proxy` de l'environnement repondrait a la place du port comme du serveur demarre. Les deux graphies, car curl lit d'abord la minuscule : un `no_proxy` herite l'emporterait sur un `NO_PROXY` exporte seul. Il n'appelle que `/health` : il ne prouve que les modules charges au demarrage, d'ou l'epingle « `require` en colonne 0 » dans `ci.test.js`.
|
||||||
- `bash test-curl.sh` — passe au niveau socket, complementaire et non redondante (45 cas). Vitest couvre la logique ; ce script couvre ce que seuls un vrai client et un vrai TCP peuvent montrer : le 413 rendu **avant** que la connexion soit coupee, les deux tokens qui se refusent mutuellement leurs routes, le fichier servi octet pour octet par `/defenseurs/state`, et le 503 d'une instance demarree sans `HOSTS_INGEST_TOKEN` (elle tourne sur un second port, le temps du script). Il boote ses propres serveurs sur 3099 et 3098 — aucun service exterieur requis. Trois protections contre l'environnement, en tete (#41) : `NO_PROXY='*'` et `no_proxy='*'` exportes, comme dans `boot-from-copy.sh` (sans eux, derriere un proxy mort, le script sortait en code 7 apres 11 s sans une ligne) ; `unset FORCE_COLOR`, car sous cette variable `node -e 'console.log(3)'` colore le nombre meme dans un tube et 8 cas echouent ; `require_free`, qui exige avant tout lancement que les deux ports refusent la connexion (code curl 7, meme regle que `boot-from-copy.sh`) : c'est lui qui ecarte un voisin deja en place, car le serveur du script ne meurt de son `EADDRINUSE` qu'apres que la boucle d'attente a eu le temps de joindre le voisin ; et `wait_for`, qui arrete le script avec un message si le serveur meurt ou ne repond pas dans les 5 s (borne par horloge), en verifiant le processus (`kill -0`) avant et apres la sonde. Le `trap EXIT` protege chaque `kill` par `|| true` (sous `set -e`, un `kill` en echec sur le premier serveur deja mort terminait le trap et laissait le second tourner sur 3098) et finit par un `wait`, pour qu'un passage lance juste apres trouve les ports libres. Non epingles : l'export de la majuscule `NO_PROXY`, que curl ne lit qu'apres la minuscule, et le second `kill -0` de `wait_for`, qui ne couvre que la fenetre residuelle entre `require_free` et le `listen` du serveur.
|
- `bash test-curl.sh` — passe au niveau socket, complementaire et non redondante (45 cas). Vitest couvre la logique ; ce script couvre ce que seuls un vrai client et un vrai TCP peuvent montrer : le 413 rendu **avant** que la connexion soit coupee, les deux tokens qui se refusent mutuellement leurs routes, le fichier servi octet pour octet par `/defenseurs/state`, et le 503 d'une instance demarree sans `HOSTS_INGEST_TOKEN` (elle tourne sur un second port, le temps du script). Il boote ses propres serveurs sur 3099 et 3098 — aucun service exterieur requis. Quatre protections contre l'environnement (#41) : `NO_PROXY='*'` et `no_proxy='*'` exportes, comme dans `boot-from-copy.sh` (sans eux, derriere un proxy mort, le script sortait en code 7 apres 11 s sans une ligne) ; `unset FORCE_COLOR`, car sous cette variable `node -e 'console.log(3)'` colore le nombre meme dans un tube et 8 cas echouent ; `require_free`, qui exige avant tout lancement que les deux ports refusent la connexion (code curl 7, meme regle que `boot-from-copy.sh`) : c'est lui qui ecarte un voisin deja en place, car le serveur du script ne meurt de son `EADDRINUSE` qu'apres que la boucle d'attente a eu le temps de joindre le voisin ; et `wait_for`, qui arrete le script avec un message si le serveur meurt ou ne repond pas dans les 5 s (borne par horloge), en verifiant le processus (`kill -0`) avant et apres la sonde. Le `trap EXIT` protege chaque `kill` par `|| true` (sous `set -e`, un `kill` en echec sur le premier serveur deja mort terminait le trap et laissait le second tourner sur 3098) et finit par un `wait`, pour qu'un passage lance juste apres trouve les ports libres. Non epingles : l'export de la majuscule `NO_PROXY`, que curl ne lit qu'apres la minuscule, et le second `kill -0` de `wait_for`, qui ne couvre que la fenetre residuelle entre `require_free` et le `listen` du serveur.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,58 @@ describe("test-curl.sh", () => {
|
||||||
expect(await released(3098)).toBe(true);
|
expect(await released(3098)).toBe(true);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
// The rule is "anything but connection refused", not "an HTTP answer": an
|
||||||
|
// occupant that speaks no HTTP holds the port just as well.
|
||||||
|
test.each([
|
||||||
|
["closes at once", (socket) => socket.destroy(), /\(curl exit (52|56)\)/],
|
||||||
|
["accepts and stalls", () => {}, /\(curl exit 28\)/],
|
||||||
|
])("refuses to run when a listener that %s holds 3099", async (_, onConnection, exitCode) => {
|
||||||
|
const sockets = new Set();
|
||||||
|
const occupant = net.createServer((socket) => {
|
||||||
|
sockets.add(socket);
|
||||||
|
socket.on("error", () => {});
|
||||||
|
onConnection(socket);
|
||||||
|
});
|
||||||
|
await new Promise((resolve, reject) => occupant.once("error", reject).listen(3099, resolve));
|
||||||
|
try {
|
||||||
|
const { code, stdout, stderr } = await runSocketPass({});
|
||||||
|
|
||||||
|
expect(stderr).toContain("test-curl.sh: http://localhost:3099 is already taken");
|
||||||
|
expect(stderr).toMatch(exitCode);
|
||||||
|
expect(code).toBe(1);
|
||||||
|
expect(stdout).not.toMatch(/PASS|FAIL/);
|
||||||
|
} finally {
|
||||||
|
for (const socket of sockets) socket.destroy();
|
||||||
|
await new Promise((resolve) => occupant.close(resolve));
|
||||||
|
}
|
||||||
|
expect(await released(3098)).toBe(true);
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
// A server that stays alive and never listens: only the deadline can end the
|
||||||
|
// wait. The fake `node` sleeps for the port named; the script's trap kills it.
|
||||||
|
test("gives up after 5 s on a server that is alive and never answers", async () => {
|
||||||
|
const bin = fs.mkdtempSync(path.join(os.tmpdir(), "vps-health-fake-node-"));
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(bin, "node"),
|
||||||
|
`#!/bin/sh\n[ "$PORT" = "3099" ] && exec sleep 30\nexec "${process.execPath}" "$@"\n`,
|
||||||
|
{ mode: 0o755 },
|
||||||
|
);
|
||||||
|
const { code, stdout, stderr, ms } = await runSocketPass({
|
||||||
|
PATH: `${bin}${path.delimiter}${process.env.PATH}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(stderr).toContain("test-curl.sh: no server answered at http://localhost:3099 within 5 s");
|
||||||
|
expect(code).toBe(1);
|
||||||
|
expect(stdout).not.toMatch(/PASS|FAIL/);
|
||||||
|
expect(ms).toBeGreaterThan(4000);
|
||||||
|
expect(ms).toBeLessThan(10000);
|
||||||
|
expect(await released(3098)).toBe(true);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(bin, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
// A fake `node` first on the PATH stands in for a server that fails to start:
|
// A fake `node` first on the PATH stands in for a server that fails to start:
|
||||||
// it exits at once for the port named, and hands over to the real node
|
// it exits at once for the port named, and hands over to the real node
|
||||||
// otherwise. The script must name the URL instead of dying silently, for the
|
// otherwise. The script must name the URL instead of dying silently, for the
|
||||||
|
|
|
||||||
14
test-curl.sh
14
test-curl.sh
|
|
@ -44,13 +44,6 @@ NOINGEST_PID=""
|
||||||
# gone: a run started right after this one must find the two ports free.
|
# gone: a run started right after this one must find the two ports free.
|
||||||
trap 'rm -rf "$TMP_DIR"; [ -n "$SERVER_PID" ] && { kill "$SERVER_PID" 2>/dev/null || true; }; [ -n "$NOINGEST_PID" ] && { kill "$NOINGEST_PID" 2>/dev/null || true; }; wait 2>/dev/null || true' EXIT
|
trap 'rm -rf "$TMP_DIR"; [ -n "$SERVER_PID" ] && { kill "$SERVER_PID" 2>/dev/null || true; }; [ -n "$NOINGEST_PID" ] && { kill "$NOINGEST_PID" 2>/dev/null || true; }; wait 2>/dev/null || true' EXIT
|
||||||
|
|
||||||
# Fixtures :
|
|
||||||
# - 3 scan reports on 2026-05-07 (booking, simpl-liste, maximus)
|
|
||||||
# - 1 defenseur-auto run report on 2026-05-07 (must be filtered out)
|
|
||||||
# - 1 booking scan report on 2026-05-06 (must be excluded by date filter)
|
|
||||||
# - 1 archived scan report on 2026-05-04 (sergent rotated it post-07:30 UTC)
|
|
||||||
# - 1 archived scan report on 2026-05-07 used to assert top-level priority
|
|
||||||
# when the same filename also exists in REPORTS_DIR (defensive dedupe).
|
|
||||||
# Both ports must be free before anything starts. With a listener already on
|
# Both ports must be free before anything starts. With a listener already on
|
||||||
# one of them, the server started here dies of EADDRINUSE, but only after the
|
# one of them, the server started here dies of EADDRINUSE, but only after the
|
||||||
# wait loop has had time to reach the neighbour: the cases would then run
|
# wait loop has had time to reach the neighbour: the cases would then run
|
||||||
|
|
@ -69,6 +62,13 @@ require_free() {
|
||||||
require_free "$BASE_URL"
|
require_free "$BASE_URL"
|
||||||
require_free "$NOINGEST_URL"
|
require_free "$NOINGEST_URL"
|
||||||
|
|
||||||
|
# Fixtures :
|
||||||
|
# - 3 scan reports on 2026-05-07 (booking, simpl-liste, maximus)
|
||||||
|
# - 1 defenseur-auto run report on 2026-05-07 (must be filtered out)
|
||||||
|
# - 1 booking scan report on 2026-05-06 (must be excluded by date filter)
|
||||||
|
# - 1 archived scan report on 2026-05-04 (sergent rotated it post-07:30 UTC)
|
||||||
|
# - 1 archived scan report on 2026-05-07 used to assert top-level priority
|
||||||
|
# when the same filename also exists in REPORTS_DIR (defensive dedupe).
|
||||||
mkdir -p "$TMP_DIR/reports"
|
mkdir -p "$TMP_DIR/reports"
|
||||||
mkdir -p "$TMP_DIR/reports/archive"
|
mkdir -p "$TMP_DIR/reports/archive"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue