248 lines
11 KiB
Markdown
248 lines
11 KiB
Markdown
---
|
|
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 <http://localhost:8080>. **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/<image>:<tag>`** (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)
|
|
```
|