Merge pull request 'fix(ci): e2e DinD par DNS réseau — fin du blocage 'Start ObsiGate'' (#60) from fix/ci-e2e-resseau-dns into main
CI / lint (push) Successful in 1m40s
CI / security (push) Successful in 35s
CI / test (push) Successful in 4m25s
CI / build (push) Successful in 23s
CI / e2e (push) Successful in 16m57s

This commit was merged in pull request #60.
This commit is contained in:
2026-11-03 14:08:36 -05:00
11 changed files with 39 additions and 27 deletions
+29 -17
View File
@@ -12,7 +12,7 @@ on:
jobs: jobs:
# ── Lint ────────────────────────────────────────────────────────── # ── Lint ──────────────────────────────────────────────────────────
lint: lint:
runs-on: ubuntu-latest runs-on: ci-deepin
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -100,7 +100,7 @@ jobs:
# ── Tests ───────────────────────────────────────────────────────── # ── Tests ─────────────────────────────────────────────────────────
test: test:
needs: lint needs: lint
runs-on: ubuntu-latest runs-on: ci-deepin
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -127,7 +127,7 @@ jobs:
# ── Security scan ───────────────────────────────────────────────── # ── Security scan ─────────────────────────────────────────────────
security: security:
runs-on: ubuntu-latest runs-on: ci-deepin
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -209,7 +209,7 @@ jobs:
# ── Docker build ────────────────────────────────────────────────── # ── Docker build ──────────────────────────────────────────────────
build: build:
needs: test needs: test
runs-on: ubuntu-latest runs-on: ci-deepin
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -252,7 +252,7 @@ jobs:
# ── E2E Tests (Playwright) ───────────────────────────────────────── # ── E2E Tests (Playwright) ─────────────────────────────────────────
e2e: e2e:
needs: build needs: build
runs-on: ubuntu-latest runs-on: ci-deepin
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -272,7 +272,13 @@ jobs:
- name: Start ObsiGate - name: Start ObsiGate
run: | run: |
docker rm -f obsigate-e2e 2>/dev/null || true docker rm -f obsigate-e2e 2>/dev/null || true
docker create --name obsigate-e2e -p 2029:8080 \ # DinD robuste : PAS de port publié (les ports 2029 fuités par des
# runs antérieurs font échouer « docker start », et l'IP passerelle
# du job n'est pas toujours joignable → boucle de health curls sans
# timeout qui pendaient 150 s chacun). À la place, l'app est
# rattachée au réseau du job et jointable par nom DNS embarqué.
JOBNET=$(docker inspect -f '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' "$(hostname)" 2>/dev/null | awk '{print $1}')
docker create --name obsigate-e2e \
-v $(pwd)/test_vault:/vaults/TestVault \ -v $(pwd)/test_vault:/vaults/TestVault \
-v $(pwd)/test_dir:/vaults/TestDir \ -v $(pwd)/test_dir:/vaults/TestDir \
-e VAULT_1_NAME=TestVault \ -e VAULT_1_NAME=TestVault \
@@ -288,17 +294,22 @@ jobs:
# fixtures avant le démarrage (l'indexeur scanne au démarrage). # fixtures avant le démarrage (l'indexeur scanne au démarrage).
docker cp test_vault/. obsigate-e2e:/vaults/TestVault docker cp test_vault/. obsigate-e2e:/vaults/TestVault
docker cp test_dir/. obsigate-e2e:/vaults/TestDir docker cp test_dir/. obsigate-e2e:/vaults/TestDir
if [ -n "$JOBNET" ]; then docker network connect "$JOBNET" obsigate-e2e || true; fi
docker start obsigate-e2e docker start obsigate-e2e
# Le port publié est joignable via l'IP de la passerelle (localhost if [ -n "$JOBNET" ]; then
# du job container ne voit pas le port publié sur l'hôte). APP_URL="http://obsigate-e2e:8080"
GW=$(ip route show default | awk '{print $3}' || echo 172.17.0.1) else
echo "Gateway IP: $GW" echo "Réseau du job introuvable (JOBNET vide) — impossible de localiser l'app sans port publié"
# Wait for health check exit 1
for i in $(seq 1 30); do fi
if curl -sf "http://$GW:2029/api/health"; then echo "Health OK"; break; fi echo "APP_URL: $APP_URL"
# --max-time : ne JAMAIS pendre sans timeout (bug d'origine : curls
# sans timeout = 150 s × 31 tentatives, job « bloqué »).
for i in $(seq 1 40); do
if curl -sf --max-time 3 "$APP_URL/api/health"; then echo "Health OK"; break; fi
sleep 1 sleep 1
done done
curl -sf "http://$GW:2029/api/health" >/dev/null || { echo "App not reachable at $GW:2029"; exit 1; } curl -sf --max-time 5 "$APP_URL/api/health" >/dev/null || { echo "App not reachable at $APP_URL"; docker logs --tail 40 obsigate-e2e; exit 1; }
# Les fixtures sont copiées via `docker cp` en root → rendre le vault # Les fixtures sont copiées via `docker cp` en root → rendre le vault
# inscriptible par l'utilisateur non-root de l'app (UID 1000), sinon # inscriptible par l'utilisateur non-root de l'app (UID 1000), sinon
# toute création/édition de fichier renvoie 403 « Vault is read-only ». # toute création/édition de fichier renvoie 403 « Vault is read-only ».
@@ -306,9 +317,10 @@ jobs:
- name: Run E2E tests - name: Run E2E tests
run: | run: |
GW=$(ip route show default | awk '{print $3}' || echo 172.17.0.1) BASE_URL="http://obsigate-e2e:8080"
echo "Using BASE_URL=http://$GW:2029" docker exec obsigate-e2e true >/dev/null 2>&1 || { echo "obsigate-e2e n'est pas en marche"; exit 1; }
BASE_URL="http://$GW:2029" npx playwright test --project=chromium-desktop --reporter=list echo "Using BASE_URL=$BASE_URL"
BASE_URL="$BASE_URL" npx playwright test --project=chromium-desktop --reporter=list
- name: Upload test results - name: Upload test results
if: always() if: always()
+1 -1
View File
@@ -8493,7 +8493,7 @@ body.popup-mode .content-area {
box-shadow: 0 2px 16px rgba(0,0,0,0.35); box-shadow: 0 2px 16px rgba(0,0,0,0.35);
width: fit-content; width: fit-content;
max-width: 95%; max-width: 95%;
z-index: 50; z-index: 66; /* au-dessus de .draco-spark-flottant (65) sinon la barre flottante DRACO intercepte les clics des flèches de recherche */
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
} }
.results-bar-count { .results-bar-count {
+1 -1
View File
@@ -25,7 +25,7 @@ async function login(page) {
await page.click('#login-btn'); await page.click('#login-btn');
} }
// Wait until app init() resolved (all handlers bound). // Wait until app init() resolved (all handlers bound).
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
} }
async function openFile(page, vault, filePath) { async function openFile(page, vault, filePath) {
+1 -1
View File
@@ -28,7 +28,7 @@ async function login(page) {
await page.fill('#login-password', CREDS.password); await page.fill('#login-password', CREDS.password);
await page.click('#login-btn'); await page.click('#login-btn');
} }
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
} }
async function authEnabled(page) { async function authEnabled(page) {
+1 -1
View File
@@ -26,7 +26,7 @@ async function login(page) {
await page.fill('#login-password', CREDS.password); await page.fill('#login-password', CREDS.password);
await page.click('#login-btn'); await page.click('#login-btn');
} }
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
} }
async function openFile(page, vault, filePath) { async function openFile(page, vault, filePath) {
+1 -1
View File
@@ -25,7 +25,7 @@ async function login(page) {
await page.fill('#login-password', CREDS.password); await page.fill('#login-password', CREDS.password);
await page.click('#login-btn'); await page.click('#login-btn');
} }
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
} }
async function openFile(page, vault, filePath) { async function openFile(page, vault, filePath) {
+1 -1
View File
@@ -12,7 +12,7 @@ import { test, expect } from '@playwright/test';
async function boot(page) { async function boot(page) {
await page.goto('/'); await page.goto('/');
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
await page.waitForSelector('#dashboard-home, #search-input', { timeout: 15000 }); await page.waitForSelector('#dashboard-home, #search-input', { timeout: 15000 });
} }
+1 -1
View File
@@ -9,7 +9,7 @@ const BASE = process.env.BASE_URL || 'http://localhost:2029';
async function goHome(page) { async function goHome(page) {
await page.goto(BASE); await page.goto(BASE);
// Wait until app init() resolved (all handlers bound, incl. search). // Wait until app init() resolved (all handlers bound, incl. search).
await page.waitForFunction(() => (window as any).__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
await page.waitForSelector('#dashboard-home, #search-input', { timeout: 15000 }); await page.waitForSelector('#dashboard-home, #search-input', { timeout: 15000 });
} }
+1 -1
View File
@@ -30,7 +30,7 @@ async function login(page) {
await page.fill('#login-password', CREDS.password); await page.fill('#login-password', CREDS.password);
await page.click('#login-btn'); await page.click('#login-btn');
} }
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
} }
async function openFile(page, vault, filePath) { async function openFile(page, vault, filePath) {
+1 -1
View File
@@ -57,7 +57,7 @@ async function login(page) {
await page.fill('#login-password', process.env.OBSIGATE_PASS || 'test123'); await page.fill('#login-password', process.env.OBSIGATE_PASS || 'test123');
await page.click('#login-btn'); await page.click('#login-btn');
} }
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
} }
async function openFixture(page) { async function openFixture(page) {
+1 -1
View File
@@ -117,7 +117,7 @@ test.describe('XSS — lecteur markdown (BUG-021)', () => {
await api(request, 'POST', `/api/file/${VAULT}`, { path: XSS_FILE, content: `${XSS_BODY}\n` }); await api(request, 'POST', `/api/file/${VAULT}`, { path: XSS_FILE, content: `${XSS_BODY}\n` });
await page.goto(BASE); await page.goto(BASE);
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 }); await page.waitForFunction(() => document.readyState === 'complete' && !!document.querySelector('.tree-item'), { timeout: 20000 });
await openFile(page, VAULT, XSS_FILE); await openFile(page, VAULT, XSS_FILE);
const content = page.locator('#content-area'); const content = page.locator('#content-area');