fix: CI lint — config-ai-keys.test.mjs rejoint l'étape JSDOM (complément BUG-082)
CI / security (push) Successful in 1m37s
CI / lint (push) Successful in 2m18s
CI / test (push) Successful in 4m8s
CI / build (push) Successful in 1m38s
CI / e2e (push) Successful in 14m28s

This commit is contained in:
2026-09-27 09:44:18 -04:00
parent 7dfe26c83d
commit 14b8032635
12 changed files with 66 additions and 35 deletions
+2 -1
View File
@@ -47,7 +47,6 @@ jobs:
node tests/frontend/pretty.test.mjs
node tests/frontend/media-viewer.test.mjs
node tests/frontend/mfa-settings.test.mjs
node tests/frontend/config-ai-keys.test.mjs
- name: Frontend JSDOM tests (PaneManager + Excalidraw + Plugins + AI + SW + Collab + Mobile + Semantic + Desktop + Inline edition + Upload)
run: |
@@ -68,6 +67,7 @@ jobs:
node editor-inline.test.mjs
node ai-quick-actions.test.mjs
node upload.test.mjs
node config-ai-keys.test.mjs
else
echo "tests/frontend/node_modules missing - installing jsdom"
npm install --no-audit --no-fund --silent
@@ -86,6 +86,7 @@ jobs:
node editor-inline.test.mjs
node ai-quick-actions.test.mjs
node upload.test.mjs
node config-ai-keys.test.mjs
fi
# ── Tests ─────────────────────────────────────────────────────────
+12 -8
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.28.10**.
> [Unreleased](#unreleased). La dernière version livrée est **2.28.11**.
---
@@ -14,6 +14,10 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.28.11] — 2026-09-27
---
## [2.28.10] — 2026-09-27
### Corrigé
@@ -33,13 +37,13 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Corrigé
- **BUG-082 — CI `lint` rouge : `upload.test.mjs` exige `jsdom`.**
`tests/frontend/upload.test.mjs` (import statique `jsdom`, introduit par
`#89`) était exécuté dans l'étape frontend racine où `jsdom` n'est jamais
installé (`ERR_MODULE_NOT_FOUND`, rouge depuis `7bee4a2`). Déplacé dans
l'étape JSDOM (les deux branches, après install si besoin) ; seul fichier
de l'étape racine avec import statique `jsdom`, les autres suites racine
n'en ont pas besoin.
- **BUG-082 — CI `lint` rouge : suites frontend exigeant `jsdom`.**
`tests/frontend/upload.test.mjs` puis `config-ai-keys.test.mjs` (imports
statiques `jsdom`, introduits par `#89`) étaient exécutés dans l'étape
frontend racine où `jsdom` n'est jamais installé (`ERR_MODULE_NOT_FOUND`,
rouge depuis `7bee4a2`). Déplacés dans l'étape JSDOM (les deux branches,
après install si besoin) ; garde-fou `tests/test_ci_workflow.py` :
aucun fichier de l'étape racine ne doit importer `jsdom` statiquement.
---
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.28.10-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.28.11-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.10).
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.11).
---
*Projet : ObsiGate | Version : 2.28.10 | Dernière mise à jour : Septembre 2026*
*Projet : ObsiGate | Version : 2.28.11 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.28.10-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.28.11-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.10).
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.11).
---
*Project: ObsiGate | Version: 2.28.10 | Last updated: September 2026*
*Project: ObsiGate | Version: 2.28.11 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.28.10
2.28.11
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
version = "2.28.10"
version = "2.28.11"
dependencies = [
"chrono",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
version = "2.28.10"
version = "2.28.11"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate",
"version": "2.28.10",
"version": "2.28.11",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
+2 -2
View File
@@ -189,7 +189,7 @@ Avant de corriger quoi que ce soit, un agent IA doit :
| *BUG-077* | [🟡 IMPORTANT] Assistant IA : aucun bouton « Stop » pour arrêter l'exécution de l'agent à tout moment | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js` | Mode agent : lancer une longue tâche → le bouton Envoyer est désactivé, impossible d'arrêter (seule la fermeture du panneau abort) | `frontend/js/bookslm.js` + `frontend/style.css` : bouton d'envoi → Stop (`_syncSendButton`/`_stopGeneration`/`_markStopped`), i18n `ai.stop`/`ai.stopped`. Tests : `tests/frontend/ai.test.mjs` (+2) | `_abortCtrl` n'est déclenché que par `close()` ; aucun signal d'arrêt côté client pendant le stream |
| *BUG-078* | [🟡 IMPORTANT] Fichiers de code : la coloration syntaxique (highlight.js) disparaît — les feuilles de thème sont basculées à partir de la **clé** de thème au lieu du **mode** | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/themes.js`, `frontend/js/ui.js`, `tests/frontend/unit.test.mjs` | Ouvrir un fichier `.py`/`.sh`/`.ps1`/`.yml` : le code s'affiche en texte brut, sans couleurs | `frontend/js/themes.js` : `applyTheme` bascule `hljs-theme-dark`/`hljs-theme-light` selon le **mode** (`isDark`). `frontend/js/ui.js` : `initTheme`/`applyTheme` résolvent le mode persisté (`obsigate-theme-mode`) au lieu de traiter la clé (`defaut-obsigate`) comme un mode. Test : `unit.test.mjs` (+1). | Les deux feuilles étaient désactivées car `defaut-obsigate !== "dark"` et `!== "light"` ; résultat **non déterministe** selon l'ordre `UI.initTheme()` (clé) / `Sync.init()` → `themes.initThemes()` (mode). Vérifié Playwright : 5/5 chargements colorés (`.py`), sépia/contraste élevé sur la palette claire |
| *BUG-083* | Job CI `security` rouge : le runner Gitea Act tronque le script `pip-audit` au premier `#` (citation de l'echo non fermée → `unexpected EOF while looking for matching '"'`) | 🟢 corrigé | P0 | 📦 build | IA | `.gitea/workflows/ci.yml`, `tests/test_ci_workflow.py` (nouveau) | CI job `security`, étape `Pip-audit` | `run:` assaini (echo sans `#`, réf `#87` en commentaire YAML) ; `tests/test_ci_workflow.py` (2 tests : aucun `#` dans le code des `run:`, `upload.test.mjs` verrouillé en étape JSDOM) ; vérifié : 56 passed (ci_workflow + e2e_harness + version), contre-preuve OK sur l'ancien `ci.yml` | Seul `run:` du workflow contenant un `#` (`see #87` dans l'echo). Les `#` des noms d'étapes (Bandit, Npm audit) sont inoffensifs (ces étapes passent). Correctif : echo sans `#`, réf `#87` en commentaire YAML |
| *BUG-082* | CI `lint` rouge : `tests/frontend/upload.test.mjs` (import statique `jsdom`) exécuté dans l'étape racine où `jsdom` n'est jamais installé | 🟢 corrigé | P0 | 🧩 tests | IA | `.gitea/workflows/ci.yml`, `tests/frontend/upload.test.mjs` | CI job `lint` → `ERR_MODULE_NOT_FOUND: jsdom` (introduit par `7bee4a2`, jamais vert depuis) | `upload.test.mjs` déplacé dans l'étape JSDOM (les deux branches) ; vérifié : étape racine verte + `upload` vert depuis `tests/frontend/` | Seul fichier de l'étape racine avec import statique jsdom ; `jsdom` ne vit que dans `tests/frontend/node_modules` (installé par l'étape JSDOM). Correctif : déplacer `upload.test.mjs` dans l'étape JSDOM |
| *BUG-082* | CI `lint` rouge : suites frontend à import statique `jsdom` exécutées dans l'étape racine où `jsdom` n'est jamais installé | 🟢 corrigé | P0 | 🧩 tests | IA | `.gitea/workflows/ci.yml`, `tests/frontend/upload.test.mjs`, `tests/frontend/config-ai-keys.test.mjs`, `tests/test_ci_workflow.py` | CI job `lint` → `ERR_MODULE_NOT_FOUND: jsdom` (introduit par `7bee4a2`, jamais vert depuis ; d'abord `upload.test.mjs`, puis `config-ai-keys.test.mjs` révélé après le 1er fix) | `upload.test.mjs` + `config-ai-keys.test.mjs` déplacés dans l'étape JSDOM (les deux branches) ; garde-fou `test_ci_workflow.py` (aucun fichier racine à import statique jsdom + suites verrouillées en JSDOM) ; vérifié : étape racine verte + `upload` et `config-ai-keys` verts depuis `tests/frontend/` | `jsdom` ne vit que dans `tests/frontend/node_modules` (installé par l'étape JSDOM). Correctif : déplacer les suites concernées dans l'étape JSDOM |
| *BUG-081* | `GET /api/auth/mfa/status` → 500 quand l'auth est désactivée (`user` None, `AttributeError` sur `user.get`) | 🔴 ouvert | P1 | ⚙️ backend | IA | `backend/auth/router.py::mfa_status` (l.821-831) | Auth désactivée : `curl http://127.0.0.1:2029/api/auth/mfa/status` → 500 (reproduit live 2026-09-27, `e2e-server.err.log` l.116-183) | — | `require_auth` laisse passer le pseudo-user anonymous, `get_user(username)` → None non gardé. Trouvé via les logs E2E pendant BUG-080 |
| *BUG-080* | [🔴 BLOQUANT] E2E locaux bloqués toute la nuit : `npm run test:e2e:ps` ne termine jamais (serveurs orphelins sur le port 2029, `npx playwright install` sans `--yes` ni garde-fou, suite ~130 tests sans timeout global) | 🟢 corrigé | P0 | 🧩 tests | IA | `scripts/run-e2e-local.ps1`, `scripts/run-e2e-local.sh`, `scripts/e2e-server.ps1`, `playwright.config.ts`, `tests/test_e2e_harness.py` (nouveau) | `npm run test:e2e:ps` | `run-e2e-local` : `npx --yes`, skip install Chromium si présent (`E2E_INSTALL_BROWSERS=1`), timeouts `E2E_TIMEOUT_SEC` (900)/`E2E_BROWSER_INSTALL_TIMEOUT_SEC` (600, exit 124) ; `playwright.config.ts` : `globalTimeout` 15 min local / 30 min CI (`E2E_GLOBAL_TIMEOUT_MS`) ; `e2e-server.ps1` : pidfile = vrai owner du port, `stop` tue l'arbre complet. Tests : `tests/test_e2e_harness.py` (8/8), cycle start/stop live (pidfile cohérent, port libéré) | Constat 2026-09-27 : `e2e-server.ps1 start` OK (READY 12 s) mais run suivant pendu toute la nuit ; 2 python orphelins (PID 81180 parent + 81936 sur le port, pidfile périmé). Double processus systématique (parent `.venv` parqué + enfant qui sert — aussi sur flowdeck/3.13 : environnemental, sans impact après correctif). Trouvé au passage : BUG-081 (`/api/auth/mfa/status` → 500 auth désactivée) |
| *BUG-079* | `GET /api/diagnostics` → 500 « dictionary changed size during iteration » (stats d'index) | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/main.py` | Charger la page de diagnostic pendant une indexation : `GET /api/diagnostics` → 500 | `backend/main.py` (`api_diagnostics`) : snapshot avant itération — `list(index.items())` et `inv.word_index.copy()` (copie C atomique sous le GIL) ; test de non-régression `tests/test_api_main.py::TestConfig::test_diagnostics_concurrent_index_writes` | Le handler itérait les dicts en direct alors que l'indexeur les modifiait depuis un autre thread (rebuild initial dans `_search_executor`, hooks incrémentaux `add_document`/`remove_document`) → `RuntimeError` dans le générateur → 500. Test déterministe (`RaceDict` fait grossir le dict en cours d'itération) : échoue sans le correctif, passe avec. Vérifié : pytest 1305 passed / 6 skipped, ruff 0, mypy 0 |
@@ -278,7 +278,7 @@ Avant de corriger quoi que ce soit, un agent IA doit :
| 2026-09-24 | #115, #117, BUG-078 | Feature + correction | `frontend/js/themes.js`, `frontend/js/ui.js`, `frontend/js/viewer.js`, `frontend/js/config.js`, `frontend/index.html`, `frontend/style.css`, `frontend/popout.html`, `frontend/locales/{fr,en}.json`, `frontend/icons/avatar/*` (nouveau), `tests/frontend/unit.test.mjs`, `tests/frontend/toolbar-order.test.mjs`, `tests/frontend/settings-order-avatar.test.mjs`, `docs/features/viewer-toolbar-highlight-avatars.md` (nouvelle), `docs/ROADMAP.md`, `CHANGELOG.md` | **#115** barre d'outils de lecture épinglée : `viewer.js`/`popout.html` sortent `.file-actions` de `.file-header` dans un `.file-toolbar` enfant direct de `.content-area` (`position: sticky; top: 0`), masqué en mode lecture. **BUG-078** coloration syntaxique : le basculement des feuilles highlight.js suit le **mode** (`themes.applyTheme` + `ui.initTheme/applyTheme` lisent `obsigate-theme-mode`) au lieu de la clé de thème qui désactivait les deux feuilles. **#117** avatars prédéfinis : galerie de 12 images (`frontend/icons/avatar/`) dans `#cfg-profile`, clic → recadrage 256 px (pipeline import) + `PATCH /api/auth/me`, avatars actifs surlignés (`obsigate-avatar-preset`), import personnalisé et suppression conservés. Vérifié : Playwright (coloration 5/5 déterministe, toolbar épinglée à `barTop` constant au défilement), `unit.test.mjs` 12/12, `toolbar-order` 13/13, `settings-order-avatar` 12/12, JSDOM editor-inline/pane-manager/mobile-editor/image-viewer/pdf-viewer/config-mobile/media-viewer/excalidraw verts, pytest 1304 passed / 6 skipped, ruff/mypy 0, validate-imports 40 modules. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-24 | BUG-079 | Correction | `backend/main.py`, `tests/test_api_main.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-079** : `GET /api/diagnostics` renvoyait 500 « dictionary changed size during iteration ». Le handler itérait `inv.word_index.values()` et `index.items()` en direct alors que l'indexeur les modifiait depuis un autre thread (rebuild initial dans `_search_executor`, hooks incrémentaux `add_document`/`remove_document`) → `RuntimeError` dans le générateur. Correctif : **snapshot avant itération** (`list(index.items())`, `inv.word_index.copy()`) — copie C atomique sous le GIL, pas de verrou ajouté. Test de non-régression déterministe (`RaceDict` fait grossir le dict pendant l'itération ; échoue sans le correctif, passe avec). Vérifié : pytest 1305 passed / 6 skipped, ruff 0, mypy 0 (80 fichiers), validate-imports 40 modules, unit 12/12. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-27 | BUG-080, BUG-081 | Correction + enregistrement | `scripts/run-e2e-local.ps1`, `scripts/run-e2e-local.sh`, `scripts/e2e-server.ps1`, `playwright.config.ts`, `tests/test_e2e_harness.py` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-080** : run E2E local pendu toute la nuit → harnais anti-blocage : `npx --yes` (plus de prompt interactif), install Chromium sautée si présent (`E2E_INSTALL_BROWSERS=1`), timeouts `E2E_TIMEOUT_SEC` (900)/`E2E_BROWSER_INSTALL_TIMEOUT_SEC` (600, exit 124), `globalTimeout` Playwright (15 min local / 30 min CI, `E2E_GLOBAL_TIMEOUT_MS`), pidfile resynchronisé sur le vrai owner du port + `stop` qui tue l'arbre complet (orphelins 81180/81936 nettoyés, port 2029 libéré). Diagnostic : double processus systématique (parent `.venv` parqué + enfant qui sert — environnemental, aussi sur flowdeck/3.13). **BUG-081** (ouvert, non traité) : `GET /api/auth/mfa/status` → 500 auth désactivée (`user` None, `router.py:827`, reproduit live). Vérifié : `test_e2e_harness.py` 8/8, cycle start/stop live (pidfile cohérent, port libéré). | 🟢 corrigé (en attente vérif utilisateur) ; BUG-081 🔴 ouvert |
| 2026-09-27 | BUG-082 | Correction CI | `.gitea/workflows/ci.yml`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-082** : `lint` rouge (`ERR_MODULE_NOT_FOUND: jsdom` sur `upload.test.mjs`, rouge depuis `7bee4a2`) — seul fichier de l'étape frontend racine avec import statique `jsdom`, alors que `jsdom` n'est installé que dans `tests/frontend/node_modules` (étape JSDOM). `upload.test.mjs` déplacé dans l'étape JSDOM (les deux branches). Vérifié : étape racine verte (validate-imports, unit, pretty, media-viewer, mfa-settings, config-ai-keys) + `upload` vert depuis `tests/frontend/`. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-27 | BUG-082 | Correction CI | `.gitea/workflows/ci.yml`, `tests/test_ci_workflow.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-082** : `lint` rouge (`ERR_MODULE_NOT_FOUND: jsdom`, rouge depuis `7bee4a2`) — les fichiers de l'étape frontend racine à import statique `jsdom` (`upload.test.mjs`, puis `config-ai-keys.test.mjs` révélé par le CI après le 1er fix), alors que `jsdom` n'est installé que dans `tests/frontend/node_modules` (étape JSDOM). Les deux déplacés dans l'étape JSDOM (les deux branches) ; garde-fou `test_ci_workflow.py` généralisé (aucun fichier racine à import statique jsdom + suites verrouillées en JSDOM, contre-preuve OK). Vérifié : étape racine verte (11 suites) + `upload` et `config-ai-keys` verts depuis `tests/frontend/`. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-27 | BUG-083 | Correction CI | `.gitea/workflows/ci.yml`, `tests/test_ci_workflow.py` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-083** : job `security` rouge — le runner Gitea Act tronque naïvement au premier `#` (même entre guillemets) : `echo "... see #87)"` devenait une citation non fermée (`unexpected EOF while looking for matching '"'"`, `/var/run/act/workflow/4` ligne 2). Seul `run:` du workflow avec un `#` (les `#` des noms d'étapes Bandit/Npm audit sont inoffensifs, ces étapes passent). Correctif : echo sans `#` (réf `#87` en commentaire YAML). Garde-fou `test_ci_workflow.py` (aucun `#` dans le code des `run:`, `upload.test.mjs` verrouillé en étape JSDOM — BUG-082) + contre-preuve sur l'ancien `ci.yml`. Vérifié : 56 passed. | 🟢 corrigé (en attente vérif utilisateur) |
---
+1 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
> **Version :** 2.28.10 | **Dernière mise à jour :** 2026-09-27
> **Version :** 2.28.11 | **Dernière mise à jour :** 2026-09-27
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "obsigate",
"version": "2.28.10",
"version": "2.28.11",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js",
"directories": {
+38 -12
View File
@@ -9,6 +9,7 @@ import re
from pathlib import Path
CI_YML = Path(__file__).resolve().parent.parent / ".gitea" / "workflows" / "ci.yml"
REPO_ROOT = Path(__file__).resolve().parent.parent
def _run_bodies() -> list[tuple[int, str]]:
@@ -60,19 +61,44 @@ class TestRunnerProofScripts:
)
class TestUploadInJsdomStep:
def test_upload_runs_with_jsdom_available(self):
"""BUG-082 : `upload.test.mjs` (import statique `jsdom`) ne tourne
que dans l'étape JSDOM, où `tests/frontend/node_modules` existe."""
class TestFrontendStepsHaveTheirDeps:
@staticmethod
def _root_step_files() -> list[str]:
"""Fichiers `node tests/frontend/<f>` de l'étape racine (sans jsdom)."""
text = CI_YML.read_text(encoding="utf-8")
assert "Frontend JSDOM tests" in text
root_part, jsdom_part = text.split("Frontend JSDOM tests", 1)
root_part = text.split("Frontend JSDOM tests", 1)[0]
root_steps = root_part.split("Frontend unit tests", 1)[1]
assert "upload.test" not in root_steps, (
"BUG-082 : `upload.test.mjs` ne doit pas tourner dans l'étape "
"racine (jsdom indisponible)"
return re.findall(r"node tests/frontend/(\S+\.mjs)", root_steps)
@staticmethod
def _has_static_jsdom_import(rel: str) -> bool:
path = REPO_ROOT / "tests" / "frontend" / rel
return any(
re.match(r"^\s*import\b.*\bfrom\s+['\"]jsdom['\"]", line)
or re.match(r"""\brequire\(\s*['"]jsdom['"]\s*\)""", line)
for line in path.read_text(encoding="utf-8").splitlines()
)
assert jsdom_part.count("node upload.test.mjs") >= 2, (
"BUG-082 : `node upload.test.mjs` attendu dans les deux branches "
"de l'étape JSDOM"
def test_root_step_files_need_no_jsdom(self):
"""BUG-082 : l'étape racine tourne sans `tests/frontend/node_modules`
(installé seulement par l'étape JSDOM) : aucun de ses fichiers ne
doit importer `jsdom` statiquement — sinon `ERR_MODULE_NOT_FOUND`
et `lint` rouge (cas `upload.test.mjs`, puis `config-ai-keys.test.mjs`).
"""
offenders = [f for f in self._root_step_files() if self._has_static_jsdom_import(f)]
assert not offenders, (
"BUG-082 : ces fichiers importent `jsdom` mais tournent dans "
"l'étape racine (sans node_modules) — les déplacer dans l'étape "
f"JSDOM :\n" + "\n".join(offenders)
)
def test_jsdom_dependent_tests_run_in_jsdom_step(self):
"""BUG-082 : les suites à import statique `jsdom` tournent bien dans
l'étape JSDOM (les deux branches)."""
text = CI_YML.read_text(encoding="utf-8")
jsdom_part = text.split("Frontend JSDOM tests", 1)[1]
for suite in ("node upload.test.mjs", "node config-ai-keys.test.mjs"):
assert jsdom_part.count(suite) >= 2, (
f"BUG-082 : `{suite}` attendu dans les deux branches de "
"l'étape JSDOM"
)