test(hosts): pin the symlink guard and the regex anchor of the temp sweep (#38) #43

Merged
maximus merged 1 commit from issue-38-sweep-symlink-anchor-tests into main 2026-10-01 23:27:52 +00:00
Owner

Fixes #38

Two promises of sweepStaleTempFiles() were held by no test (review of PR #37). Tests only: index.js is unchanged.

Added cases (__tests__/hosts.test.js)

  • Symlink: a link .thinkpad.4242.1700000000000.tmp to a regular file two hours old. The link and its target survive the sweep, with no console.error. The link itself is aged with lutimesSync, so the age check cannot be what spares it.
  • Anchor: prefix.thinkpad.4242.1700000000000.tmp added to the "never touches" cases.

Mutations on index.js

State hosts.test.js Red tests
Before (main) 171 / 171 none (suite 353 / 353)
lstatSync -> statSync 172 / 173 symlink case: the sweep returns the link name, the link is unlinked
^ removed from TEMP_FILE_RE 172 / 173 prefix case: the file is unlinked
!stat.isFile() dropped 171 / 173 symlink case and the existing directory case
After (restored) 173 / 173 none (suite 355 / 355)

Under the statSync mutation, the red comes from the return value and the vanished link. The target survives and nothing is logged either way: those two assertions are what the issue asks for, not what detects the mutation.

The third mutation is beyond the issue: it follows from aging the link.

index.js was restored from a backup copy and checked byte-identical (cmp, git diff --quiet). bash test-curl.sh 45 / 45, scripts/boot-from-copy.sh PASS.

Out of scope, as in the issue: the exact one-hour threshold (<= vs <).

CLAUDE.md: counts 353 -> 355 and 171 -> 173.

🤖 Generated with Claude Code

Fixes #38 Two promises of `sweepStaleTempFiles()` were held by no test (review of PR #37). Tests only: `index.js` is unchanged. ## Added cases (`__tests__/hosts.test.js`) - **Symlink**: a link `.thinkpad.4242.1700000000000.tmp` to a regular file two hours old. The link and its target survive the sweep, with no `console.error`. The link itself is aged with `lutimesSync`, so the age check cannot be what spares it. - **Anchor**: `prefix.thinkpad.4242.1700000000000.tmp` added to the "never touches" cases. ## Mutations on `index.js` | State | hosts.test.js | Red tests | |---|---|---| | Before (main) | 171 / 171 | none (suite 353 / 353) | | `lstatSync` -> `statSync` | 172 / 173 | symlink case: the sweep returns the link name, the link is unlinked | | `^` removed from `TEMP_FILE_RE` | 172 / 173 | prefix case: the file is unlinked | | `!stat.isFile()` dropped | 171 / 173 | symlink case and the existing directory case | | After (restored) | 173 / 173 | none (suite 355 / 355) | Under the `statSync` mutation, the red comes from the return value and the vanished link. The target survives and nothing is logged either way: those two assertions are what the issue asks for, not what detects the mutation. The third mutation is beyond the issue: it follows from aging the link. `index.js` was restored from a backup copy and checked byte-identical (`cmp`, `git diff --quiet`). `bash test-curl.sh` 45 / 45, `scripts/boot-from-copy.sh` PASS. Out of scope, as in the issue: the exact one-hour threshold (`<=` vs `<`). `CLAUDE.md`: counts 353 -> 355 and 171 -> 173. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-01 22:10:51 +00:00
test(hosts): pin the symlink guard and the regex anchor of the temp sweep (#38)
All checks were successful
CI / verification (pull_request) Successful in 20s
CI / verification (push) Successful in 20s
198e1cf586
Two promises of sweepStaleTempFiles() were held by no test:

- lstat, not stat: a symlink named like a temp file is neither followed
  nor removed. The link is aged too, so the isFile() guard is what keeps it.
- the ^ anchor of TEMP_FILE_RE: a temp name behind a prefix is never touched.

Each new case goes red under its mutation (statSync, anchor removed,
isFile() dropped); index.js itself is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Revue adversariale : APPROVE

Résumé. Les deux cas demandés par #38 sont présents et rougissent sous leur mutation, rejouée par le relecteur sur une copie hors dépôt de la tête 198e1cf. index.js n'est pas modifié par la PR ; le diff se limite à __tests__/hosts.test.js et aux compteurs de CLAUDE.md.

Ce qui a été exécuté

État (copie hors dépôt) hosts.test.js Tests rouges
Tête, sans mutation suite complète 355 / 355 aucun
lstatSync -> statSync (import ajouté, pour ne pas rougir sur un ReferenceError) 172 / 173 cas symlink
^ retiré de TEMP_FILE_RE 172 / 173 cas « behind a prefix »
!stat.isFile() retiré 171 / 173 cas symlink et cas dossier
!stat.isFile() -> stat.isDirectory() (mutation du relecteur) 172 / 173 cas symlink seul

Les trois premières lignes confirment le tableau de la PR. La quatrième est nouvelle : une garde qui n'écarterait que les dossiers laissait la suite verte avant cette PR, le cas symlink la ferme, grâce au vieillissement du lien par lutimesSync.

Copie restaurée et vérifiée par cmp ; l'arbre du dépôt n'a pas été touché.

  • CI de la tête 198e1cf : CI / verification (pull_request) en success (statut de commit).
  • Timeline de #38 : pull_ref de la PR #43 avec ref_action: closes, l'issue se fermera au merge.
  • Commit unique, format conventionnel, compteurs CLAUDE.md (355 et 173) conformes au résultat mesuré.
  • Aucun skip / only, aucun secret, pas de code de production modifié.

Non rejoués par le relecteur : bash test-curl.sh et scripts/boot-from-copy.sh (couverts par la CI verte, et la PR ne touche aucun fichier qu'ils exercent).

Suggestions (non bloquantes)

  1. __tests__/hosts.test.js, cas symlink : la cible target.json n'a pas un nom de fichier temporaire, donc sa survie ne dépend pas du code testé. L'assertion répond à la lettre de l'issue, et le corps de la PR le dit déjà ; rien à changer, à garder en tête si le cas est repris.
  2. Toujours hors périmètre, comme dans l'issue : le seuil d'exactement une heure (<= contre <) reste sans test.
## Revue adversariale : APPROVE **Résumé.** Les deux cas demandés par #38 sont présents et rougissent sous leur mutation, rejouée par le relecteur sur une copie hors dépôt de la tête `198e1cf`. `index.js` n'est pas modifié par la PR ; le diff se limite à `__tests__/hosts.test.js` et aux compteurs de `CLAUDE.md`. ### Ce qui a été exécuté | État (copie hors dépôt) | `hosts.test.js` | Tests rouges | |---|---|---| | Tête, sans mutation | suite complète 355 / 355 | aucun | | `lstatSync` -> `statSync` (import ajouté, pour ne pas rougir sur un `ReferenceError`) | 172 / 173 | cas symlink | | `^` retiré de `TEMP_FILE_RE` | 172 / 173 | cas « behind a prefix » | | `!stat.isFile()` retiré | 171 / 173 | cas symlink et cas dossier | | `!stat.isFile()` -> `stat.isDirectory()` (mutation du relecteur) | 172 / 173 | cas symlink seul | Les trois premières lignes confirment le tableau de la PR. La quatrième est nouvelle : une garde qui n'écarterait que les dossiers laissait la suite verte avant cette PR, le cas symlink la ferme, grâce au vieillissement du lien par `lutimesSync`. Copie restaurée et vérifiée par `cmp` ; l'arbre du dépôt n'a pas été touché. - CI de la tête `198e1cf` : `CI / verification (pull_request)` en `success` (statut de commit). - Timeline de #38 : `pull_ref` de la PR #43 avec `ref_action: closes`, l'issue se fermera au merge. - Commit unique, format conventionnel, compteurs `CLAUDE.md` (355 et 173) conformes au résultat mesuré. - Aucun `skip` / `only`, aucun secret, pas de code de production modifié. Non rejoués par le relecteur : `bash test-curl.sh` et `scripts/boot-from-copy.sh` (couverts par la CI verte, et la PR ne touche aucun fichier qu'ils exercent). ### Suggestions (non bloquantes) 1. `__tests__/hosts.test.js`, cas symlink : la cible `target.json` n'a pas un nom de fichier temporaire, donc sa survie ne dépend pas du code testé. L'assertion répond à la lettre de l'issue, et le corps de la PR le dit déjà ; rien à changer, à garder en tête si le cas est repris. 2. Toujours hors périmètre, comme dans l'issue : le seuil d'exactement une heure (`<=` contre `<`) reste sans test.
maximus merged commit 198e1cf586 into main 2026-10-01 23:27:52 +00:00
maximus deleted branch issue-38-sweep-symlink-anchor-tests 2026-10-01 23:27:52 +00:00
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#43
No description provided.