--- type: testing project: Metamorph milestone: M0 date: "2026-05-10" --- # Comment tester M0 (bootstrap) > Procédure de validation manuelle + automatisée pour le milestone M0. Toutes les commandes se lancent depuis la racine du repo. ## 0. Prérequis Au choix entre Docker **ou** Podman — le Makefile détecte automatiquement (override `ENGINE=docker` ou `ENGINE=podman` si les deux sont installés). | Outil | Version min | Vérifier | |--------------------------------|-------------------|-------------------------------------------| | **Docker Engine** *(option A)* | 24+ | `docker --version` + `docker compose version` | | **Podman** *(option B)* | 4.0+ avec plugin compose, ou podman-compose 1.0.6+ | `podman --version` + `podman compose version` (ou `podman-compose --version`) | | GNU make | 4+ | `make --version` | | curl, jq | n'importe | `curl --version` | | Node.js (pour les e2e) | 20+ | `node --version` | Vérifier le moteur que le Makefile utilisera : ```bash make engine # ENGINE=podman # COMPOSE=podman compose ``` Override possible : `make up ENGINE=docker COMPOSE="docker compose"`. ## 1. Bootstrap de l'environnement ```bash make env # crée .env depuis .env.example $EDITOR .env # vérifie : APP_ENV=dev OK pour la machine, sinon set des secrets forts ``` Variables critiques de `.env` : - `APP_ENV` — `dev` autorise les placeholders ; `prod` ou `staging` exigent `JWT_SECRET >=32 chars` + `POSTGRES_PASSWORD` non-default (sinon l'API refuse de booter). - `JWT_SECRET` — pour M0 le démon ne signe rien, mais autant le mettre propre tout de suite : `python3 -c "import secrets; print(secrets.token_urlsafe(64))"`. - `HOST_FRONT_PORT` / `HOST_API_PORT` — modifie si 8080/8000 sont déjà occupés. ## 2. Build & démarrage ```bash make up # build des 3 images + démarrage make ps # vérifie que les 3 services tournent ``` **Attendu** : ``` metamorph-db postgres:16-alpine Up (healthy) metamorph-api metamorph-api Up metamorph-front metamorph-front Up (healthy) ``` Si `db` reste en `starting` au-delà de 30 s : `make logs` pour voir l'erreur (généralement un mismatch de credentials dans `.env`). ## 3. Tests fonctionnels manuels ### 3.1 — Health API direct (port 8000) ```bash curl -s http://localhost:8000/api/v1/health | jq ``` **Attendu** : ```json { "status": "ok", "version": "0.1.0" } ``` ### 3.2 — Health API via le proxy nginx (port 8080) ```bash curl -s http://localhost:8080/api/v1/health | jq ``` Doit renvoyer le **même** JSON. Cela valide la conf nginx qui proxifie `/api/* → api:8000`. ### 3.3 — SPA dans le navigateur Ouvrir . **Vérifications visuelles** : - [ ] Header centré, fond `#0a0e1a`, titre « Metamorph Purple Team Platform » avec « Meta » en rouge et « Purple Team Platform » en violet. - [ ] Section `// System Health` avec une card bordée vert affichant `version 0.1.0` et `status: ok`. - [ ] Section `// Design Tokens` montrant les tags colorés (EVASION, C2, LATERAL…), la flow chain `recon → phish → c2 → lateral → impact`, et 3 boutons. - [ ] Footer en mono dim avec la mention M0 bootstrap. - [ ] Toutes les polices sont chargées (titres en JetBrains Mono, body en IBM Plex Sans). Onglet Network : **aucune** requête vers `fonts.googleapis.com` ou `fonts.gstatic.com`. - [ ] Console JS sans aucune erreur. ### 3.4 — Logs structurés JSON ```bash make logs-api # tail uniquement le container api (engine-agnostic) ``` **Attendu** : chaque ligne est un objet JSON avec au minimum `ts`, `level`, `logger`, `message`. Exemple : ```json {"ts":"2026-05-10 14:21:33,012","level":"INFO","logger":"metamorph.boot","message":"metamorph.api.boot","cors_origins":["http://localhost:8080"],"log_level":"INFO","evidence_dir":"/data/evidence"} ``` Si tu vois du texte non-JSON, c'est gunicorn qui parle ; vérifier que l'app est bien chargée via `app.main:app` (le formatter doit s'appliquer). ### 3.5 — Healthchecks containers ```bash make inspect-health ``` **Attendu** : `healthy` pour les trois containers (à 30 s près après le boot). ``` metamorph-db healthy metamorph-api healthy metamorph-front healthy ``` ### 3.6 — Garde APP_ENV (sécurité) Test négatif : on prouve que l'API refuse de booter en non-dev avec un secret faible. ```bash make down APP_ENV=prod JWT_SECRET=trop-court $(make engine | sed -n 's/^COMPOSE=//p') up api 2>&1 | head -30 # ou plus simplement, en explicitant ton moteur : # APP_ENV=prod JWT_SECRET=trop-court docker compose up api # APP_ENV=prod JWT_SECRET=trop-court podman compose up api ``` **Attendu** : trace d'erreur Pydantic mentionnant *"JWT_SECRET is missing, default, or shorter than 32 chars"*. L'API doit s'arrêter, pas démarrer. Reset : ```bash make down && make up ``` ### 3.7 — CORS ```bash curl -is -H 'Origin: http://localhost:8080' http://localhost:8080/api/v1/health \ | grep -i access-control-allow-origin ``` **Attendu** : un header `Access-Control-Allow-Origin: http://localhost:8080`. ```bash curl -is -H 'Origin: http://evil.example' http://localhost:8080/api/v1/health \ | grep -i access-control-allow-origin || echo "no CORS allow header (expected)" ``` **Attendu** : pas de header (origine non-allowée). ### 3.8 — Volumes persistants ```bash make volumes ``` **Attendu** : deux volumes nommés (le préfixe peut varier selon le moteur) : ``` metamorph_db metamorph_evidence ``` Test de persistance basique : `make down && make up` ne doit pas effacer les volumes ; seul `make clean` le fait (destructeur, demande explicite). ## 4. Tests automatisés (Playwright) ```bash make e2e-install # à faire une seule fois (download chromium + deps OS) make up # si la stack n'est pas déjà up make e2e # lance la suite make e2e-report # ouvre le rapport HTML ``` **Suite M0** (`e2e/tests/m0-smoke.spec.ts`) — 8 tests : | # | Test | Couvre | |---|-----------------------------------------------------------|-------------------------------------------------| | 1 | home page loads and renders the RTOps header | Front + nginx + assets statiques | | 2 | API health card eventually shows OK | Front → API via proxy `/api/*` | | 3 | design system primitives render with the expected accents | Card / Tag / FlowNode / Button | | 4 | body uses self-hosted IBM Plex Sans, no Google Fonts | Spec §7 « pas de CDN runtime » | | 5 | background uses the RTOps deep navy token | Token `--bg = #0a0e1a` appliqué | | 6 | no JS console errors on first load | Pas de regression silencieuse côté SPA | | 7 | API health endpoint returns the expected JSON shape | Contrat API direct | | 8 | CORS headers are set when the SPA origin asks for them | flask-cors configuré sur `FRONT_ORIGIN` | Le rapport HTML (`e2e/playwright-report/index.html`) inclut, pour chaque test : steps, screenshots sur échec, vidéo sur retry, trace Playwright (timeline réseau + DOM). Le rapport JUnit XML (`e2e/playwright-report/junit.xml`) est consommable directement par GitLab CI / GitHub Actions / Jenkins. ## 5. Tests unitaires backend ```bash make test-api ``` **Attendu** : `tests/test_health.py::test_health_returns_ok PASSED`. ## 6. Lint & typecheck ```bash make lint ``` Lance ruff (back), eslint + tsc --noEmit (front). Tout doit passer. ## 7. Critères de DoD M0 (extraits de `tasks/todo.md`) - [ ] `make up` démarre les 3 conteneurs - [ ] `curl http://localhost:8080/api/v1/health` → `{"status":"ok","version":"…"}` - [ ] Front affiche la home RTOps (manuel + e2e #1, #3, #5) - [ ] Logs JSON sur stdout (manuel #3.4) - [ ] Volumes nommés présents (manuel #3.8) - [ ] Suite Playwright M0 verte - [ ] Rapport HTML disponible dans `e2e/playwright-report/` ## 8. Si quelque chose casse | Symptôme | Diagnostic | |-------------------------------------------------------|---------------------------------------------------------| | `make up` plante en build du back | Probablement un download `uv` lent ; relancer ou `make rebuild` | | API réponse 502 via le front | api pas encore healthy ; `make logs api` | | Page blanche, console : `Failed to load module …` | Le bundle Vite n'a pas été produit ; `make rebuild` | | Polices custom non chargées (fallback sans-serif visible) | Vérifier que `@fontsource/*` est bien dans `node_modules` du build context | | Tests Playwright `Timeout … API health card` | API pas joignable depuis le navigateur ; tester `curl` d'abord | | `make volumes` ne montre rien | Vérifier que la stack est `make up`. Sous Podman rootless, les volumes vivent dans `~/.local/share/containers/storage/volumes/`. | | `make engine` annonce le mauvais moteur | Override : `make up ENGINE=docker COMPOSE="docker compose"` ou inverse pour podman. | | `podman compose` indisponible mais `podman-compose` oui | Le Makefile fallback automatiquement, ou force-le : `COMPOSE=podman-compose make up`. | ## 9. Pièges connus (validés sur podman 5.x / Fedora 43) - **Short-name resolution sous Podman** : si tu remplaces une image par son nom court (`postgres:16-alpine`), Podman échoue avec `short-name resolution enforced but cannot prompt without a TTY`. **Toujours utiliser `docker.io/library/:`** (Docker accepte le préfixe transparente). - **Premier `make up`** : compte ~3 min pour télécharger `postgres:16-alpine` + builder les images custom. Les builds suivants sont quasi instantanés grâce au cache. - **`make inspect-health` montre `(no-healthcheck)` malgré le Dockerfile** : podman-compose 1.x ne propage pas les healthchecks du Dockerfile. Le projet redéclare les healthchecks dans `docker-compose.yml` pour cette raison. - **`api` reste en `starting` ~15 s** avant de basculer healthy : c'est le `start_period: 10s` du healthcheck + 1 round de polling. Normal. - **Volumes Podman rootless** : `~/.local/share/containers/storage/volumes/` au lieu de `/var/lib/docker/volumes/`. `make volumes` liste les bons volumes peu importe l'engine. ## 10. Teardown ```bash make down # garde les volumes make clean # supprime aussi les volumes (DESTRUCTEUR) ```