Compare commits
2 commits
7003197abe
...
096f95da05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
096f95da05 | ||
|
|
5912f08e41 |
2 changed files with 31 additions and 35 deletions
|
|
@ -70,7 +70,7 @@ curl -H "Authorization: Bearer $(cat ~/.coolify-token)" \
|
||||||
- `__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 (28 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, 3 cas, environ 1 s chacun : 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 ; puis un faux `node` en tete de `PATH` qui sort en 1 pour le port 3099, puis 3098 : message sur stderr, code 1, 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 (28 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, 3 cas, environ 1 s chacun : 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 ; puis un faux `node` en tete de `PATH` qui sort en 1 pour le port 3099, puis 3098 : message sur stderr, code 1, 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 ; **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)
|
- `__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 ; et `wait_for`, qui verifie le processus du serveur (`kill -0`) avant le port, donc un serveur mort au demarrage arrete le script tout de suite avec un message, et un voisin qui repond sur le meme port ne passe pas pour lui. 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. Non epingle : l'export de la majuscule `NO_PROXY`, que curl ne lit qu'apres la minuscule.
|
- `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 ; et `wait_for`, qui verifie le processus du serveur (`kill -0`) avant le port, donc un serveur mort au demarrage arrete le script tout de suite avec un message, et un voisin qui repond sur le meme port ne passe pas pour lui. 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. Non epingle : l'export de la majuscule `NO_PROXY`, que curl ne lit qu'apres la minuscule.
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,21 @@ const { fileURLToPath } = require("node:url");
|
||||||
//
|
//
|
||||||
// Every node:fs function that can write is spied BEFORE a fresh require,
|
// Every node:fs function that can write is spied BEFORE a fresh require,
|
||||||
// because the runtime modules destructure their fs functions at load time
|
// because the runtime modules destructure their fs functions at load time
|
||||||
// (same technique as the timingSafeEqual spy in auth.test.js). A call is
|
// (same technique as the timingSafeEqual spy in auth.test.js). Every call is
|
||||||
// recorded when its stack goes through a runtime file of the repo, then passed
|
// recorded, whoever makes it, then passed through to the real function. No
|
||||||
// through to the real function. A shell command can write too, out of the fs
|
// attribution by call stack (#40): a function handed as-is to an asynchronous
|
||||||
|
// mechanism, `process.nextTick(writeFileSync, file, data)` or
|
||||||
|
// `res.on("finish", writeFileSync.bind(null, file, data))`, runs with no
|
||||||
|
// runtime frame on its stack, and a stack filter let it through. The price is
|
||||||
|
// that a write made by the test runner itself while a case runs would fail the
|
||||||
|
// case: the right way to fail. A shell command can write too, out of the fs
|
||||||
// spies' sight, so child_process is spied the same way and the set of commands
|
// spies' sight, so child_process is spied the same way and the set of commands
|
||||||
// is pinned to the two read-only ones metrics.js runs.
|
// is pinned to the two read-only ones metrics.js runs.
|
||||||
//
|
//
|
||||||
|
// The test edits its own fixtures in the degraded case. Those edits go through
|
||||||
|
// `real`, the functions taken before any spy exists, so the spies never have
|
||||||
|
// to look away.
|
||||||
|
//
|
||||||
// Limits: a path no case takes stays out of sight, and so does a write
|
// Limits: a path no case takes stays out of sight, and so does a write
|
||||||
// deferred past settle() (a timer longer than 20 ms).
|
// deferred past settle() (a timer longer than 20 ms).
|
||||||
//
|
//
|
||||||
|
|
@ -36,8 +45,14 @@ const TOKEN = "test-token";
|
||||||
const INGEST_TOKEN = "test-ingest-token";
|
const INGEST_TOKEN = "test-ingest-token";
|
||||||
const ORPHAN = ".thinkpad.4242.1700000000000.tmp";
|
const ORPHAN = ".thinkpad.4242.1700000000000.tmp";
|
||||||
|
|
||||||
|
// The real functions, taken at load time, before any spy: the test's own
|
||||||
|
// fixture edits go through them and stay out of the logs. Only single-call
|
||||||
|
// functions belong here. `rmSync(dir, { recursive: true })` would not do: it
|
||||||
|
// calls the public fs.rmdirSync internally, which is the spy by then.
|
||||||
|
const real = { writeFileSync: fs.writeFileSync, rmSync: fs.rmSync, renameSync: fs.renameSync };
|
||||||
|
|
||||||
// Runtime file = under the repo root, outside node_modules and __tests__.
|
// Runtime file = under the repo root, outside node_modules and __tests__.
|
||||||
// Used both to attribute a call to the API and to purge the module cache.
|
// Used to purge the module cache.
|
||||||
function isRuntimeFile(file) {
|
function isRuntimeFile(file) {
|
||||||
const rel = path.relative(REPO_ROOT, file);
|
const rel = path.relative(REPO_ROOT, file);
|
||||||
if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) return false;
|
if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) return false;
|
||||||
|
|
@ -45,24 +60,6 @@ function isRuntimeFile(file) {
|
||||||
return top !== "node_modules" && top !== "__tests__";
|
return top !== "node_modules" && top !== "__tests__";
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromRuntimeFile(stack) {
|
|
||||||
return stack
|
|
||||||
.split("\n")
|
|
||||||
.slice(1)
|
|
||||||
.some((frame) => {
|
|
||||||
const match = /\(?(\/[^()]+?):\d+:\d+\)?$/.exec(frame.trim());
|
|
||||||
return match !== null && isRuntimeFile(match[1]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureStack() {
|
|
||||||
const limit = Error.stackTraceLimit;
|
|
||||||
Error.stackTraceLimit = 50;
|
|
||||||
const { stack } = new Error();
|
|
||||||
Error.stackTraceLimit = limit;
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write APIs of node:fs, by the argument(s) naming what gets written. Each is
|
// Write APIs of node:fs, by the argument(s) naming what gets written. Each is
|
||||||
// spied in its callback, Sync and fs.promises forms, when that form exists.
|
// spied in its callback, Sync and fs.promises forms, when that form exists.
|
||||||
const O_WRITE =
|
const O_WRITE =
|
||||||
|
|
@ -135,10 +132,8 @@ function installWriteSpies() {
|
||||||
if (typeof owner[key] !== "function") continue;
|
if (typeof owner[key] !== "function") continue;
|
||||||
const original = owner[key];
|
const original = owner[key];
|
||||||
vi.spyOn(owner, key).mockImplementation(function (...args) {
|
vi.spyOn(owner, key).mockImplementation(function (...args) {
|
||||||
if (fromRuntimeFile(captureStack())) {
|
const targets = targetsOf(args);
|
||||||
const targets = targetsOf(args);
|
if (targets) log.push({ fn: key, targets });
|
||||||
if (targets) log.push({ fn: key, targets });
|
|
||||||
}
|
|
||||||
return original.apply(this, args);
|
return original.apply(this, args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +145,7 @@ function installCommandSpies() {
|
||||||
if (typeof child_process[key] !== "function") continue;
|
if (typeof child_process[key] !== "function") continue;
|
||||||
const original = child_process[key];
|
const original = child_process[key];
|
||||||
vi.spyOn(child_process, key).mockImplementation(function (...args) {
|
vi.spyOn(child_process, key).mockImplementation(function (...args) {
|
||||||
if (fromRuntimeFile(captureStack())) commandLog.push(commandOf(args));
|
commandLog.push(commandOf(args));
|
||||||
return original.apply(this, args);
|
return original.apply(this, args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +229,7 @@ async function send(route, { method = "GET", auth = `Bearer ${TOKEN}`, body } =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created before the spies: nothing here is attributed to the API.
|
// Created before the spies: nothing here is recorded.
|
||||||
function writeFixtures() {
|
function writeFixtures() {
|
||||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vps-health-writes-test-"));
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vps-health-writes-test-"));
|
||||||
const defenseurs = path.join(tmpDir, "defenseurs");
|
const defenseurs = path.join(tmpDir, "defenseurs");
|
||||||
|
|
@ -389,16 +384,17 @@ describe("fs writes — the API writes only under HOSTS_DIR (#36)", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// A corrupt agents-map.json: where a quarantine rename would go.
|
// A corrupt agents-map.json: where a quarantine rename would go.
|
||||||
fs.writeFileSync(process.env.DEFENSEURS_AGENTS_MAP_PATH, "{corrupt");
|
real.writeFileSync(process.env.DEFENSEURS_AGENTS_MAP_PATH, "{corrupt");
|
||||||
expect((await send("/defenseurs/findings?project=proj")).status).toBe(500);
|
expect((await send("/defenseurs/findings?project=proj")).status).toBe(500);
|
||||||
|
|
||||||
// Missing Defenseurs files and a corrupt snapshot: where a "recreate it"
|
// Missing Defenseurs files and a corrupt snapshot: where a "recreate it"
|
||||||
// write would go. Each route must degrade, with the content that says so.
|
// write would go. Each route must degrade, with the content that says so.
|
||||||
fs.writeFileSync(process.env.DEFENSEURS_AGENTS_MAP_PATH, JSON.stringify({ proj: "defenseur-proj" }));
|
// The reports directory is moved away rather than removed: one call.
|
||||||
fs.rmSync(process.env.DEFENSEURS_STATUS_PATH);
|
real.writeFileSync(process.env.DEFENSEURS_AGENTS_MAP_PATH, JSON.stringify({ proj: "defenseur-proj" }));
|
||||||
fs.rmSync(process.env.DEFENSEURS_FINDINGS_STATE_PATH);
|
real.rmSync(process.env.DEFENSEURS_STATUS_PATH);
|
||||||
fs.rmSync(process.env.REPORTS_DIR, { recursive: true });
|
real.rmSync(process.env.DEFENSEURS_FINDINGS_STATE_PATH);
|
||||||
fs.writeFileSync(path.join(process.env.HOSTS_DIR, "thinkpad.json"), "{corrupt");
|
real.renameSync(process.env.REPORTS_DIR, path.join(tmpDir, "reports-moved-away"));
|
||||||
|
real.writeFileSync(path.join(process.env.HOSTS_DIR, "thinkpad.json"), "{corrupt");
|
||||||
const degraded = [
|
const degraded = [
|
||||||
["/defenseurs", (body) => expect(body.status).toBe("no_data")],
|
["/defenseurs", (body) => expect(body.status).toBe("no_data")],
|
||||||
["/defenseurs/state", (body) => expect(body.status).toBe("no_data")],
|
["/defenseurs/state", (body) => expect(body.status).toBe("no_data")],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue