feat(m6): missions + snapshot CRUD, membership visibility, status state machine
Adds the mission layer that materialises template snapshots, plus the SPA list / 3-step wizard / detail page. Backend: - app/services/missions.py — create_mission snapshots scenarios, tests, MITRE tags in a 4-query write; list/get apply a non-admin membership filter that collapses to 404 (no existence leak); status state machine enforces draft → in_progress → completed → archived with archived as a sink; the non-admin creator is auto-added as role_hint='red' to retain visibility. - app/api/missions.py — 8 endpoints (list, get, create, update, add scenarios, set members, transition, soft-delete) with strict pydantic schemas. The transition endpoint splits the perm gate manually so archive requires mission.archive while other targets use mission.update. - app/api/users.py — new GET /users/roster returning (id, email, display_name) only, gated by user.read OR mission.create OR mission.update — lets non-admin wizard users see assignable peers without exposing the admin /users payload. - app/api/diag.py — /diag/reset truncates the mission_* tables before the template tables because the source_*_template_id FKs are ON DELETE SET NULL, which is cheaper to short-circuit by removing the children first. Frontend: - lib/missions.ts — typed client, queryKey factory, status accent map. - pages/MissionsListPage.tsx — list cards with status accent + filters (q, client, status). - pages/MissionsCreatePage.tsx — 3-step wizard (meta → scenarios → members) with member roster fed by /users/roster. - pages/MissionDetailPage.tsx — header + transition buttons (legal next states only) + Tests/Members/Synthesis/Export tabs. - Routes + nav entry (visible to anyone with mission.read or admin). Tests: - backend/tests/test_missions.py — 22 pytest covering snapshot fidelity, MITRE propagation, membership visibility, transition state machine, perm gating, member set replace, append scenarios, soft-delete, partial update, inverted-date rejection. - e2e/tests/m6-missions.spec.ts — 5 Playwright (snapshot freezing, non-admin visibility, status transitions + 409, SPA wizard end-to-end, list filter). Docs: - CHANGELOG, tasks/testing-m6.md, tasks/lessons.md (snapshot tradeoffs, membership=404 pattern, /diag/reset order, auto-creator add). - README + tasks/todo.md updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
124
tasks/testing-m6.md
Normal file
124
tasks/testing-m6.md
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
type: testing
|
||||
milestone: M6
|
||||
date: "2026-05-13"
|
||||
project: Metamorph
|
||||
---
|
||||
|
||||
# Testing M6 — Missions & snapshot
|
||||
|
||||
## 1. Lancement de la stack
|
||||
|
||||
```bash
|
||||
make clean
|
||||
make up
|
||||
make migrate
|
||||
make seed-mitre # MITRE tags are snapshotted onto mission_tests; without them
|
||||
# the snapshot will simply have an empty mitre_tags array
|
||||
```
|
||||
|
||||
> L'admin stable `admin@metamorph.local / AdminPass1234!` est restauré
|
||||
> automatiquement par le hook `afterAll` du spec e2e M6, mais la 1ʳᵉ fois,
|
||||
> bootstrappe-le via `/setup` (ou laisse les tests faire le travail).
|
||||
|
||||
## 2. Tests automatisés
|
||||
|
||||
```bash
|
||||
make test-api # 103 tests pytest dont 22 M6 (snapshot, membership, transitions, members CRUD, perm gating)
|
||||
make e2e # 43 tests Playwright dont 5 M6 (snapshot freezing, non-admin visibility, transitions, wizard, list filter)
|
||||
```
|
||||
|
||||
Rapport HTML : `e2e/playwright-report/`.
|
||||
|
||||
## 3. Smoke navigateur
|
||||
|
||||
### Pré-requis
|
||||
- Stack `make up` + admin loggé.
|
||||
- MITRE seedé (`/mitre` montre 15 tactics).
|
||||
- Au moins **1 test_template** et **1 scenario_template** dans le catalogue M5
|
||||
(pour avoir quelque chose à snapshotter).
|
||||
|
||||
### 3.1 Liste & création (`/missions`)
|
||||
1. Cliquer **Missions** dans la nav (visible si tu as la perm `mission.read` ou tu es admin) → la liste s'affiche avec un message vide la 1ʳᵉ fois.
|
||||
2. Cliquer **+ New mission** → page wizard `/missions/new`.
|
||||
3. **Étape 1 — Metadata** :
|
||||
- `Name` (requis) → `purple-2026-Q2`
|
||||
- `Client / target` → `Acme Corp`
|
||||
- `Start date` / `End date` → si tu inverses, un message en rouge apparaît et **Next** est désactivé.
|
||||
- `ROE / Description` (markdown) → optionnel.
|
||||
4. **Next** → **Étape 2 — Scenarios** :
|
||||
- Le catalogue M5 s'affiche en grille de boutons. Cliquer un scénario le sélectionne (bordure cyan).
|
||||
- Le sous-titre du Card affiche le total de tests qui seront snapshotés.
|
||||
5. **Next** → **Étape 3 — Members** :
|
||||
- Le roster (issu de `/users/roster`) liste les utilisateurs actifs.
|
||||
- Pour chaque user, deux boutons **Red** / **Blue** togglent l'inclusion + le rôle. ✕ retire.
|
||||
- Si tu es un redteamer non-admin, tu es pré-sélectionné en `red` (auto-add côté backend si tu oublies).
|
||||
6. **Create mission** → redirection vers `/missions/<id>`. La nouvelle mission apparaît en haut de la liste après retour.
|
||||
|
||||
### 3.2 Filtres (`/missions`)
|
||||
- **Search** : full-text sur `name` / `description_md`.
|
||||
- **Client** : LIKE sur `client_target`.
|
||||
- **Status** : select draft / in_progress / completed / archived.
|
||||
- Les filtres sont combinés en AND (ex : `status=in_progress & client=acme`).
|
||||
|
||||
### 3.3 Page détail (`/missions/<id>`)
|
||||
1. En-tête : nom + status pill + boutons de transition.
|
||||
- **draft** → boutons `→ In Progress` et `→ Archived`.
|
||||
- **in_progress** → `→ Completed` et `→ Archived`.
|
||||
- **completed** → `→ Archived` uniquement.
|
||||
- **archived** → aucun bouton.
|
||||
2. Cliquer un bouton → status update immédiat (cache invalidé, badge re-rendu).
|
||||
3. **Delete** (en rose) → confirm prompt → soft-delete → redirige vers `/missions`. Réapparait via `?include_deleted=true` (admin only).
|
||||
4. **Tabs** :
|
||||
- **tests** : tableau par scénario avec `# | Test | MITRE | OPSEC | State`. Les MITRE chips affichent l'external_id frozen.
|
||||
- **members** : pills Red/Blue avec email + display_name.
|
||||
- **synthesis** : placeholder « lands in M10 ».
|
||||
- **export** : placeholder « lands in M11 ».
|
||||
|
||||
## 4. Vérification du snapshot (DoD)
|
||||
|
||||
1. Crée une mission qui référence un scenario_template `sc1` contenant `test_template_t1`.
|
||||
2. Aller dans `/admin/tests`, éditer `test_template_t1` : changer le nom et les tags MITRE.
|
||||
3. Retour sur `/missions/<id>` (rafraîchir si la cache TanStack tient encore) → la table montre **toujours** l'ancien nom et l'ancien tag MITRE. Le snapshot est gelé. ✅
|
||||
|
||||
## 5. Vérification visibilité par membership
|
||||
|
||||
1. Login en admin, créer 2 missions :
|
||||
- `m-only-admin` sans aucun membre.
|
||||
- `m-shared` avec Alice (red) en membre.
|
||||
2. Login en Alice.
|
||||
3. `/missions` → seule `m-shared` apparaît dans la liste. `GET /api/v1/missions/<m-only-admin>` retourne **404** (pas 403 — pas de fuite d'existence).
|
||||
4. Alice tente de PUT/transition/delete sur `m-only-admin` → 404 idem.
|
||||
|
||||
## 6. Vérification transitions
|
||||
|
||||
| from | to | result |
|
||||
|-------------|---------------|--------|
|
||||
| draft | in_progress | 200 |
|
||||
| draft | archived | 200 |
|
||||
| draft | completed | **409 invalid_transition** |
|
||||
| in_progress | completed | 200 |
|
||||
| in_progress | archived | 200 |
|
||||
| completed | archived | 200 |
|
||||
| completed | in_progress | **409** |
|
||||
| archived | (anything) | **409** |
|
||||
| any | (same status) | 200 (no-op) |
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
|
||||
-d '{"status":"completed"}' \
|
||||
http://localhost:8080/api/v1/missions/<id>/transition
|
||||
```
|
||||
|
||||
## 7. Quick teardown
|
||||
|
||||
```bash
|
||||
make down
|
||||
# ou pour un reset complet :
|
||||
curl -X POST http://localhost:8080/api/v1/diag/reset # test-only, wipes everything
|
||||
```
|
||||
|
||||
> Reminder: `make test-api` and `make e2e` **share the dev DB container** —
|
||||
> running them mid-session WILL wipe user data. The M6 spec's `afterAll`
|
||||
> restores the stable admin and re-seeds MITRE, but custom templates / missions
|
||||
> you've created by hand are lost. Cf. `tasks/lessons.md` (M5 lessons section).
|
||||
Reference in New Issue
Block a user