Mock MdW v2.2.1 â Guide
Présentation
Simulateur REST du Middleware MA, conforme au CI AppMNGâMdW v2.0.0. Deux modes : mock (logique locale + envoi auto vers Mock MK6) ou recette (proxy transparent vers le vrai MdW Ignition/Operametrix).
Démarrage rapide
pip install fastapi uvicorn httpx markdown
# HTTPS mode recette (dĂ©faut â pointe vers Ignition interne)
python mock_mdw.py --port 9000 --ssl
# HTTP développement local (mode mock)
python mock_mdw.py --port 9000 --mode mock
# Surcharge URL Ignition via CLI
python mock_mdw.py --port 9000 --ssl --mode recette \
--mdw-url https://cot-scada-frc.inxops.com/system/webdev/Middleware
Configuration
Cascade de priorités
Le mock résout chaque paramÚtre selon cette cascade (priorité décroissante) :
CLI args â ENV vars â Fichier config "runtime" â Defaults hardcodĂ©s
(priorité max) (priorité min)
ParamĂštres configurables
| ParamĂštre | CLI | ENV | Config runtime | Default |
|---|---|---|---|---|
| Chemin config | --config /path |
MOCK_MDW_CONFIG_PATH |
â | ./DEFAULT_CONFIG.json |
| Mode | --mode recette |
MOCK_MDW_MODE |
runtime.mode |
recette |
| URL MdW Ignition (interne) | --mdw-url https://... |
MOCK_MDW_URL |
runtime.mdw_url |
https://cot-scada-frc.inxops.com/.../Middleware |
| URL publique (navigateur) | â | â | runtime.public_base_url |
https://manege.inxops.com |
| URL MK6 fallback | â | MOCK_MK6_URL |
â | https://localhost:9001 |
Architecture des URLs â interne vs publique
Le mock gĂšre deux circuits d'URL distincts :
| Circuit | Usage | Résolution | Exemple |
|---|---|---|---|
| Interne (proxy) | Appels httpx serveur â Ignition | DNS interne Azure / Docker | https://cot-scada-frc.inxops.com/system/webdev/Middleware |
| Publique (navigateur) | Liens <a href>, messages confirm, ancres đ |
DNS public / IP accessible au navigateur | https://manege.inxops.com/system/webdev/Middleware |
RĂšgle : tout appel qui passe par le serveur (proxy recette, mk6-proxy, execute) utilise l'URL interne. Les URLs publiques ne sont jamais envoyĂ©es Ă un proxy â elles peuplent uniquement le DOM HTML.
L'URL publique est résolue selon cette cascade :
1. Champ IHM "URL publique" (si rempli)
2. runtime.public_base_url dans DEFAULT_CONFIG.json
3. window.location.hostname du navigateur (fallback auto)
Fichier DEFAULT_CONFIG.json
Trois rĂŽles dans un seul fichier :
- Topologie (obligatoire) : raccordements[], mk6_url
- Runtime (optionnel, section "runtime") : mode, mdw_url, public_base_url, proxy_whitelist
- Whitelist SSRF (optionnel) : URLs supplémentaires pour les proxys recette
{
"runtime": {
"mode": "recette",
"mdw_url": "https://cot-scada-frc.inxops.com/system/webdev/Middleware",
"public_base_url": "https://manege.inxops.com",
"proxy_whitelist": ["https://20.19.89.53:8088", "http://mock-mdw:9000"]
},
"mk6_url": "https://localhost:9001",
"raccordements": [
{
"raccordement_id": "ROU1.1",
"mk6_id": "MK6_ROU1.1",
"mk6_url": "https://localhost:9001",
"prm_status": [
{ "prm_id": "50048589698480", "alias": "ROU1 PDL1", "pmax_kw": 8340, "sycode": "8FV898VMwWX0", "role": "thissite" }
]
}
]
}
Note : le fichier
DEFAULT_CONFIG_HTTPS.jsona ete supprime en v1.0.2. Toutes les URLs MK6 sont en HTTPS par defaut dansDEFAULT_CONFIG.json(la VM recette tourne integralement en HTTPS).
Exemple Docker Compose
services:
mock-mdw:
image: python:3.12-slim
command: python mock_mdw.py --port 9000 --ssl
environment:
- MOCK_MDW_MODE=recette
- MOCK_MDW_URL=https://ignition.dev.operalink.fr/system/webdev/Middleware
- MOCK_MK6_URL=https://mock-mk6:9001
volumes:
- ./my_config.json:/app/DEFAULT_CONFIG.json
ports:
- "9000:9000"
Docker et URLs publiques : dans
my_config.json, renseignerruntime.public_base_urlavec l'URL accessible au navigateur de l'opĂ©rateur (ex:https://manege.inxops.com). Les URLs internes (mock-mk6:9001,ignition.dev.operalink.fr) ne sont pas accessibles au navigateur â elles ne servent qu'aux proxys serveur.
Modes de fonctionnement
Mode mock (développement local)
Le simulateur valide la commande localement (DEC-047), la stocke, la traduit en
appels MK6 (CI GWâMK6) et les exĂ©cute vers le Mock MK6.
Utile pour développer sans Ignition. Activable via --mode mock ou dans l'IHM.
Mode recette (dĂ©faut â proxy transparent)
La commande est envoyée telle quelle au vrai MdW Ignition via state.mdw_url (URL interne).
Aucune validation locale â c'est Ignition qui valide. Cela permet de tester
des cas invalides (value_kw négatif, > Pmax) et d'observer la réaction du vrai MdW.
Le simulateur récupÚre ensuite les logs /health MdW et GW pour les afficher.
Les liens cliquables dans les résultats utilisent l'URL publique (public_base_url).
API Reference
Endpoints mĂ©tier â CI AppMNGâMdW v2.0.0
| Méthode | Endpoint | Description | Référence CI |
|---|---|---|---|
| POST | /api/v1/commands |
Soumettre une commande de consigne | §4.1 |
| GET | /api/v1/health |
Health check (degraded/error + details R3) | §4.4 |
| GET | /api/v1/status |
Supervision PRM (état, disponibilité, streem_connected) | §4.2 |
| GET | /api/v1/planning |
Planning consolidé (filtrable par prm_id) | §4.3 |
| GET | /api/v1/gw/health |
ConnectivitĂ© GWâMK6 par raccordement (R3-E6-S03) | DEC-071 |
Validation POST /commands â ordre DEC-047
Le mock applique les validations dans cet ordre strict (court-circuit Ă la premiĂšre erreur) :
- 400 â Structure : champs requis, format ISO 8601
- 404 â PRM inexistant
- 503 â PRM indisponible (supervision > 20 min)
- 422 â SĂ©mantique : type inconnu, value_kw hors bornes, chronologie
- 409 â Anti-replay : order_id dĂ©jĂ acceptĂ©
En mode recette, cette validation est bypassée.
Endpoints mock (pilotage)
| Méthode | Endpoint | Description |
|---|---|---|
| POST | /mock/reset |
Purge + re-provision depuis le fichier config |
| GET/POST | /mock/config |
Configuration raccordements/PRM |
| GET/POST | /mock/time |
Horloge simulée (figer/libérer) |
| GET/POST | /mock/mode |
Basculer mock â recette |
| GET/POST | /mock/auth |
Configurer Basic Auth + scope (DEC-155, R1) |
| POST | /mock/prm/{id}/fault |
Marquer un PRM en dĂ©faut matĂ©riel (â 503) |
| DELETE | /mock/prm/{id}/fault |
Rétablir un PRM faulté |
| POST | /mock/prm/{id}/expire |
Expirer supervision PRM (â 503) |
| POST | /mock/prm/{id}/restore |
Restaurer supervision PRM |
| POST | /mock/prm/bulk-supervision |
Expire/restore en masse (tout ou liste) |
| POST | /mock/sync-mk6 |
Sync supervision + monitoring depuis MK6 réels |
| GET/POST | /mock/proxy-whitelist |
Whitelist URLs proxy recette (anti-SSRF) |
| GET/POST | /mock/error-config |
Configuration des erreurs simulées |
| POST | /mock/error-config/reset |
Désactiver toutes les erreurs + purge R3 |
| POST/DELETE/GET | /mock/bdd-fault |
Simuler BDD inaccessible (R3-E6-S02) |
| POST/DELETE | /mock/pmax-override |
Override Pmax par source (R3-E6-S01) |
| POST | /mock/silence-alert-min |
Configurer seuil silence Streem (R3-E2-S02) |
| GET | /mock/state |
Ătat complet (PRM, supervision, planning, SIEM, R3) |
| GET | /mock/tls |
Ătat TLS de l'instance |
Endpoints simulation
| Méthode | Endpoint | Description |
|---|---|---|
| GET | /simulation |
Page HTML de simulation |
| GET | /simulation/api/config |
Config PRM pour la page |
| POST | /simulation/api/execute |
Exécution E2E (mock ou recette) |
| POST | /simulation/api/recette-health |
Proxy health (évite CORS, anti-SSRF) |
| POST | /simulation/api/recette-proxy |
Proxy generique vers MdW/GW (anti-SSRF) |
| POST | /simulation/api/passthrough |
Proxy direct avec headers custom (v1.1.0 â cyber C01-C06) |
| POST | /simulation/api/mk6-proxy |
Proxy actions MK6 (Docker-safe) |
| GET | /simulation/api/mk6-tree |
Etat agrege de tous les MK6 |
| POST | /simulation/api/mk6-reset-all |
Reset tous les MK6 |
| GET | /simulation/api/state |
Etat simulation complet |
| POST | /simulation/api/run-scenario |
Scenario temporise synchrone (C3) |
| GET | /simulation/api/export-results |
Export JSON/CSV (C4 â deprecie, utiliser campagne) |
| POST | /mock/test/campaign |
Lancer campagne de test async (CI + IHM) â v1.1.0 |
| GET | /mock/test/campaign/{campaign_id} |
Poll resultat campagne (rapport complet) |
| GET | /mock/test/campaigns |
Liste des campagnes |
| GET | /static/{filename} |
JS modules (sim_core, sim_queue, sim_assert, sim_execute) |
| GET | /scenarios |
Page scénarios curl (rendu Markdown) |
| GET | /simulation/help |
Manuel utilisateur simulateur (rendu Markdown) |
Interface de simulation
File de requĂȘtes
L'interface permet de construire des commandes graphiquement et de les empiler dans une file d'exécution. Fonctionnalités :
- Ătoiles
*rouges sur les champs requis CI (non bloquant â permet de tester les malformations) - Avertissements CI non-bloquants (dates incohĂ©rentes, champs vides)
- Ădition JSON directe des items non envoyĂ©s (bouton âïž â textarea)
- Clone des items envoyĂ©s (bouton đ â copie en mode Ă©dition)
- RĂ©ordonnement âČ/⌠de la file
- PrĂ©-remplissage du formulaire depuis un item (bouton đ)
- Import/export JSON de la file complĂšte
- Points d'arrĂȘt sur les items
- Pré-remplissage end_time automatique (+30 min) à l'ajout de segment
Injection d'erreurs
Le panneau "Erreurs simulées" permet d'activer des scénarios d'erreur cÎté MdW
(health dégradé, 503, timeouts) via /mock/error-config.
Horloge simulée
Figer l'horloge permet de tester des scénarios temporels sans attendre. L'horloge est synchronisée avec les Mock MK6 (broadcast automatique).
Actions MK6 (panneau Init / Erreurs)
Le simulateur permet de piloter les Mock MK6 sans quitter la page :
- Reset + Init : réinitialise tous les MK6 et les reprovisionne automatiquement depuis la config MdW (raccordements, sycodes, Pmax)
- Réseau : online / offline / unreachable par MK6
- Fault : injection de défaut par site (comm_failure, site_not_found, etc.)
- State : forcer le state d'un site (0 = pas de comm, 1 = comm OK sans contrĂŽle, 2 = nominal)
- Contrainte externe : injecter une contrainte DEIE/SCADA par site avec source, puissance en kW, et fenĂȘtre temporelle optionnelle (start/end)
Mode d'exécution MK6
Deux modes pour l'envoi des consignes au MK6 aprĂšs validation MdW :
- ⥠Auto : les appels MK6 (POST /requests) sont envoyĂ©s automatiquement â rĂ©sultat affichĂ© dans le bloc rĂ©sultat
- â Manuel : les appels MK6 sont affichĂ©s mais pas envoyĂ©s â boutons "Envoyer" individuels pour exĂ©cuter un par un (utile pour le debug pas-Ă -pas)
Compatibilité Docker
Toutes les actions MK6 (init, reset, fault, network, state, contrainte externe) passent par un proxy cÎté serveur (
/simulation/api/mk6-proxy). Le serveur MdW rĂ©sout l'URL MK6 via sa configuration interne. Le browser n'appelle jamais directement le mock MK6.Implication : en Docker Compose, les URLs MK6 peuvent ĂȘtre des noms de service Docker (ex:
http://mock-mk6:9001) dansDEFAULT_CONFIG.jsonâ le proxy cĂŽtĂ© serveur rĂ©sout le DNS Docker correctement.Endpoints proxy disponibles : -
POST /simulation/api/mk6-proxyâ forward une requĂȘte vers un MK6 -GET /simulation/api/mk6-treeâ agrĂšge l'Ă©tat de tous les MK6 -POST /simulation/api/mk6-reset-allâ reset tous les MK6
Raccourcis clavier (M2)
| Raccourci | Action |
|---|---|
Ctrl+Enter |
Ajouter la commande Ă la file |
Ctrl+Shift+Enter |
Exécuter toute la file (ou reprendre si en pause) |
Ctrl+Shift+Delete |
Vider la file |
Escape |
Fermer les panneaux ouverts (erreurs, console) |
Authentification (DEC-155, R1)
Le mock simule la Basic Auth Ignition natif. Par défaut désactivée.
Scope par endpoint
Le scope détermine quels endpoints sont protégés. Deux profils prédéfinis :
| Profil | Scope | Contexte |
|---|---|---|
| R2 (défaut MVP) | ["commands", "health"] |
Health = seul endpoint de supervision avant R3 |
| R3 (post GO LIVE) | ["commands", "status", "planning"] |
Status/planning prennent le relais, health redevient libre |
Activer
# Profil R2 (défaut)
curl -X POST http://localhost:9000/mock/auth \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "username": "admin", "password": "mock_mdw_2026"}'
# Profil R3
curl -X POST http://localhost:9000/mock/auth \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "scope": ["commands", "status", "planning"]}'
# Scope custom (tout protéger)
curl -X POST http://localhost:9000/mock/auth \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "scope": ["commands", "health", "status", "planning"]}'
Dans le simulateur, le bloc đ dans âïž Configuration permet de basculer entre profils R2/R3 en un clic.
Comportement
| Situation | Réponse | Log SIEM |
|---|---|---|
| Auth dĂ©sactivĂ©e | Transparent (pas de check) | â |
| Endpoint hors scope | Transparent (pas de check) | â |
Pas de header Authorization |
401 UNAUTHORIZED |
AUTH_FAILURE |
| Schéma non-Basic (ex: Bearer) | 401 UNAUTHORIZED |
AUTH_FAILURE |
| Credentials invalides | 403 FORBIDDEN |
AUTH_FAILURE |
| Credentials valides | Traitement normal | AUTH_SUCCESS |
Credentials par défaut : admin / mock_mdw_2026 (configurables via /mock/auth).
Protection SSRF (proxy whitelist)
Les endpoints proxy recette (/simulation/api/recette-health, /simulation/api/recette-proxy) valident l'URL cible contre une whitelist dynamique. URLs non autorisĂ©es â HTTP 403.
La whitelist ne contient que des URLs internes (atteignables par le serveur Python). Les URLs publiques (navigateur) n'y figurent jamais â elles ne sont pas utilisĂ©es par les proxys.
Sources de la whitelist
state.mdw_urlâ vrai MdW Ignition (configurĂ© via mode/CLI/ENV)state.mk6_url+state.mk6_urlsâ MK6 cibles (topologie)state.proxy_whitelistâ URLs additionnelles (Docker, ports alternatifs)localhost/127.0.0.1/::1â fallback dev local
Configurer des URLs supplémentaires
Via DEFAULT_CONFIG.json section runtime.proxy_whitelist (au démarrage) :
"runtime": {
"proxy_whitelist": ["https://20.19.89.53:8088", "http://mock-mdw:9000", "http://mock-mk6:9001"]
}
Ou Ă chaud :
curl -X POST http://localhost:9000/mock/proxy-whitelist \
-H 'Content-Type: application/json' \
-d '{"urls": ["https://new-env:8088"]}'
Supervision PRM (disponibilité)
Le mock MdW maintient un état de supervision par PRM qui détermine la réponse
à POST /commands : 200 (disponible) ou 503 PRM_UNAVAILABLE (expiré/faulté).
ModĂšle
Par défaut, tous les PRM sont disponibles aprÚs provisioning (Reset+Init). Deux mécanismes pour rendre un PRM indisponible :
| Mécanisme | Endpoint | Simule | Réversible via |
|---|---|---|---|
| Fault | POST /mock/prm/{id}/fault |
Défaut matériel | DELETE /mock/prm/{id}/fault |
| Expire | POST /mock/prm/{id}/expire |
Timeout supervision (20 min sans signal) | POST /mock/prm/{id}/restore |
Synchronisation MK6âMdW
Le bouton đ Sync MK6âMdW (ou POST /mock/sync-mk6) interroge chaque mock MK6 :
GET /mock/mk6/{id}/stateâ met Ă jour la disponibilitĂ© PRM (MK6 offline, site en fault, state=0 â PRM expirĂ©)GET /api/v1/mk6/{id}/monitoring/sitesâ cache les donnĂ©es opĂ©rationnelles (vent, puissance, contraintes) pourGET /status
AprÚs un sync, la supervision PRM dans GET /status reflÚte l'état réel des MK6
(PRM offline â status KO + anomalies, PRM online â status OK).
ContrĂŽle en masse
# Tout expirer (simule perte de communication généralisée)
curl -X POST http://localhost:9000/mock/prm/bulk-supervision \
-H 'Content-Type: application/json' -d '{"available": false}'
# Tout restaurer
curl -X POST http://localhost:9000/mock/prm/bulk-supervision \
-H 'Content-Type: application/json' -d '{"available": true}'
# Expirer certains PRM uniquement
curl -X POST http://localhost:9000/mock/prm/bulk-supervision \
-H 'Content-Type: application/json' \
-d '{"prm_ids": ["50048589698480"], "available": false}'
Dans le simulateur
Le panneau đ Supervision PRM affiche l'Ă©tat de chaque PRM avec :
- Toggle â /â± par clic (immĂ©diat)
- Boutons đ„ expire / đ„ restore pour ajouter Ă la file (queueable, importable en JSON)
- Boutons Tout dispo / Tout expiré en masse
- RafraĂźchissement automatique aprĂšs chaque sync
Docker / recette
POST /mock/sync-mk6 résout les URLs MK6 via state.get_mk6_url() cÎté serveur.
Les noms Docker (http://mock-mk6:9001) fonctionnent. Les endpoints
expire/restore/bulk sont purement internes au MdW, aucun appel externe.
Translation MK6
Le mock traduit les commandes MdW en appels MK6 conformément aux décisions :
| MdW | MK6 | Décision |
|---|---|---|
command_type: "psetpoint" |
set: "Psetpoint" (P majuscule) |
CI GWâMK6 |
command_type: "stop" |
set: "stop" |
CI GWâMK6 |
command_type: "clear" |
DELETE /planning/{id} |
DEC-108/117 |
preset: true/false |
preset: "auto"/"none" |
DEC-120 |
prm_id â lookup |
site: sycode |
Données de référence |
Spécifications API (Swagger / OpenAPI)
Swagger de production MdW (Ă venir)
Le fichier swagger_mdw_production.yaml.placeholder est un emplacement réservé.
Il sera remplacé par le swagger officiel du MdW Ignition une fois le développement
Operametrix (Benjamin Freeman) terminé.
Ce swagger de production décrira les endpoints exposés par le module WebDev Ignition :
- POST /api/v1/commands (CI AppMNGâMdW §4.1)
- GET /api/v1/health (CI AppMNGâMdW §4.4)
- GET /api/v1/status (CI AppMNGâMdW §4.2)
- GET /api/v1/planning (CI AppMNGâMdW §4.3)
Pour l'intégrer : renommer en swagger_mdw_production.yaml et supprimer le .placeholder.
# Générer un client depuis le swagger de production (quand disponible)
openapi-generator-cli generate -i swagger_mdw_production.yaml -g python -o client_mdw/
Swagger du mock (en ligne)
Le mock expose un Swagger UI interactif sur /docs et la spec JSON brute sur
/openapi.json (auto-générés par FastAPI).
Pas de fichier statique dans l'archive â la spec est toujours Ă jour sur le mock en cours d'exĂ©cution.
Environnement de développement
VS Code
- Installer Python 3.11+ et les dépendances (
pip install -r requirements.txt) - Ouvrir le dossier dans VS Code
- Créer
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Mock MdW (HTTP :9000)",
"type": "debugpy",
"request": "launch",
"program": "mock_mdw.py",
"args": ["--port", "9000"]
},
{
"name": "Mock MdW (HTTPS :9000)",
"type": "debugpy",
"request": "launch",
"program": "mock_mdw.py",
"args": ["--port", "9000", "--ssl"]
}
]
}
- F5 pour lancer, ouvrir
http://localhost:9000/simulation
Docker
docker build -t mock-mdw .
docker run -p 9000:9000 mock-mdw
Docker Compose avec mock MK6 :
services:
mock-mdw:
build: ./mock-mdw
ports: ["9000:9000"]
mock-mk6:
build: ./mock-mk6
ports: ["9001:9001","9002:9002","9003:9003","9004:9004","9005:9005"]
Mode développement (rechargement à chaud)
Par dĂ©faut, simulation.html est mis en cache au premier chargement. Pour recharger Ă chaque requĂȘte (utile quand on modifie le HTML) :
MOCK_MDW_DEV=1 python mock_mdw.py --port 9000
Tests
# Tests de régression Python (208 tests, TestClient, rapide)
python test_mock_mdw_regression.py
# Tests d'intégration Python (164 tests, serveur uvicorn réel, HTTPS/TLS)
python test_mock_mdw_integration.py
# Tests JS de simulation.html (94 tests)
npm install jsdom && node test_simulation_js.js
# Structure documentaire (19 tests)
python test_doc_structure.py
Dépannage
| SymptĂŽme | Cause probable | Solution |
|---|---|---|
Connection refused port 9001 |
Mock MK6 non lancé | Lancer python mock_mk6.py --port 9001 |
| URL MdW reset aprĂšs rechargement | sessionStorage vide | Configurer via ENV ou section runtime |
| 422 en mode recette | state.mode pas "recette" |
Vérifier GET /mock/mode |
| 401 sur POST /commands | Auth activée | POST /mock/auth {"enabled": false} |
| 403 sur recette-proxy | URL hors whitelist | POST /mock/proxy-whitelist {"urls": [...]} |
| HTML inchangé aprÚs modif | Cache statique | Relancer avec MOCK_MDW_DEV=1 |
Latence simulée (C2)
Permet de simuler une latence réseau réaliste (100-500ms vers Azure, 200-1000ms MK6 via VPN).
# Activer 200ms de latence
curl -X POST http://localhost:9000/mock/error-config \
-H 'Content-Type: application/json' \
-d '{"simulated_latency_ms": 200}'
# Désactiver
curl -X POST http://localhost:9000/mock/error-config \
-H 'Content-Type: application/json' \
-d '{"simulated_latency_ms": 0}'
La latence s'ajoute avant chaque réponse POST /commands et avant chaque appel MK6
dans execute_mk6_actions(). Configurable aussi dans le simulateur (âïž Configuration â â± Latence).
Un badge rouge â > 2s apparaĂźt automatiquement dans les rĂ©sultats si la latence totale dĂ©passe 2 secondes (CI §4.1 / DEC-038).
Campagne de test (CI)
Pour integration continue et campagnes de recette formelles. Meme endpoint que l'IHM (bouton "Campagne").
Lancer une campagne (v1.1.0 â format handoff)
# Envoyer un fichier de campagne JSON directement
curl -s -X POST http://localhost:9000/mock/test/campaign \
-H "Content-Type: application/json" \
-d '{"name": "recette_P1", "items": [
{"_method": "COMMENT", "_label": "Setup"},
{"_method": "ACTION", "_target": "mk6", "_http_method": "POST",
"_endpoint": "/mock/mk6/MK6_ROU1.1/provision",
"_body": {"sites": [{"sycode": "8FV898VMwWX0", "name": "ROU1", "role": "thissite", "pmax_kw": 8340, "prm_id": "50048589698480"}]},
"_expected_status": 200},
{"prm_id": "50048589698480", "order_id": "CAMP-001", "source": "MA",
"start_time": "2026-03-20T14:00:00Z", "preset": false,
"commands": [{"command_type": "psetpoint", "end_time": "2026-03-20T18:00:00Z", "preset": false, "value_kw": 5000}],
"_expected_status": 200, "_x_request_id": "camp-001"}
], "delay_between_items_ms": 300}'
# -> 202 {"campaign_id": "camp-abc12345", "poll_url": "/mock/test/campaign/camp-abc12345"}
Polling des resultats
# Boucle CI
while true; do
RESULT=$(curl -s http://localhost:9000/mock/test/campaign/camp-abc12345)
STATUS=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('status'))")
if [ "$STATUS" = "completed" ]; then break; fi
sleep 1
done
# Lire le summary
echo $RESULT | python3 -c "
import sys,json
d = json.load(sys.stdin)
s = d['summary']
print(f'PASS:{s[\"pass\"]} FAIL:{s[\"fail\"]} SKIP:{s[\"skip\"]} success:{s[\"success\"]}')
for f in s.get('failures', []):
print(f' FAIL #{f[\"step\"]}: {f[\"label\"]}')
a = f['assertion']
for detail in a.get('details', []):
if not detail['pass']:
print(f' {detail[\"field\"]}: attendu={detail[\"expected\"]} obtenu={detail[\"actual\"]}')
"
Types d'items supportes
| Type | Detection | Description |
|---|---|---|
| COMMENT | _method: "COMMENT" |
Separateur visuel, toujours SKIP |
| ACTION | _method: "ACTION" |
Appel HTTP vers MK6 ou MdW (setup, teardown, injection faute) |
| GET | _method: "GET" |
Lecture endpoint (health, planning, monitoring) |
| COMMAND | Presence de prm_id |
POST /commands vers MdW (mock ou recette) |
Chaque item peut porter des champs _expected_* pour produire un verdict PASS/FAIL.
Champs d'assertion (v1.2.0) :
| Catégorie | Champs |
|---|---|
| HTTP | _expected_status (int), _expected_error_code (string) |
| SIEM | _expected_siem_category, _expected_siem_fields ({key:val}), _expected_siem_count ({category, min}), _expected_log_order ([catégories]) |
| Body | _expected_body_contains ({key:val} récursif), _expected_body_not_contains, _expected_body_array_min_length (int, supporte dict+planning[]), _expected_body_field_present ([champs]) |
| Timing | _wait_seconds (pause avant exécution) |
| Méta | _label, _breakpoint, _x_request_id, _passthrough, _passthrough_headers |
Voir SIMULATION_HELP.md pour la référence complÚte avec exemples.
Nouveautés v1.2.0 :
- _expected_body_array_min_length supporte les réponses MK6 ({sycode, planning: [...]})
- _expected_body_field_present vérifie l'existence de champs sans valeur exacte (v1.1.3)
- Logs MdW+GW mergés en mode campagne (fetch GW health avec 3 retries à 1.5s)
- Nommage résultats EXEC_{source}-{PASS}PASS-{FAIL}FAIL-{timestamp}.json
- run_tag ne suffixe plus les order_id/x_request_id vides (fix tests 400 MISSING_FIELD)
Filtrage logs SIEM (v1.1.1) : GET /health?include_logs=true&log_filter=xxx&log_count=50 filtre les logs par substring match sur tous les champs string. En mode recette, log_filter est passé avec le command_trace_id de chaque item pour ne récupérer que les logs liés à ce test. En mode autotest (mock standalone), le mock reproduit ce comportement.
Limitations connues (B1)
- TODO CI v2.0 â preset dans GET /planning. L'exemple CI §4.3 ne montre pas le champ
presetdans la timeline, mais le mock le retourne (cohĂ©rent avec DEC-112 et le flux descendant CI MdWâGW §3.5). Ă corriger dans la prochaine version du CI AppMNGâMdW. - Anti-replay bornĂ© Ă 5000 order_ids. En production, la protection est persistante (BDD). Dans le mock, les order_ids sont stockĂ©s en mĂ©moire avec Ă©viction FIFO au-delĂ de 5000 entrĂ©es. ConsĂ©quence : en sessions de test trĂšs longues (>5000 commandes), un order_id recyclĂ© peut ĂȘtre acceptĂ© Ă tort (faux nĂ©gatif sur la 409).
- Pas de polling automatique MK6. La disponibilitĂ© PRM est statique aprĂšs provisioning. Utiliser le bouton đ Sync MK6âMdW ou les endpoints expire/restore pour la faire Ă©voluer.
- Planning MdW indépendant du MK6.
GET /planningretourne le planning MdW local. La réconciliation avec le planning MK6 réel est prévue en R4. - X-Request-ID propagé aux MK6 (A4). Les appels MK6 dans
execute_mk6_actions()incluent le headerX-Request-IDpour traçabilité bout en bout (R1-E4-S01). Visible dans la console technique MK6. - Supervision PRM statique par défaut dans
GET /status(tous OK aprĂšs provisioning). AprĂšs unPOST /mock/sync-mk6, l'Ă©tat reflĂšte les MK6 rĂ©els (offline â KO + anomalies).
Décisions implémentées
DEC-045 (start passé accepté), DEC-047 (validation séquentielle), DEC-080 (consolidation légÚre), DEC-089 (ROUVAI = 5 MK6), DEC-108/117 (clear = DELETE), DEC-112/113 (preset boolean), DEC-120 (translation preset auto/none), DEC-155 (Basic Auth Ignition).
Architecture fichiers (M5/I4)
Structure v1.1.1
mock-mdw/
âââ mock_mdw.py # Serveur FastAPI â endpoints + rĂšgles mĂ©tier MA
âââ mock_shared.py # Utilitaires partagĂ©s (SSL, CSS, filtres uvicorn)
âââ mock_framework.py # Moteur rĂ©utilisable (AssertionEngine, HttpRunner, etc.)
âââ sim_assert.py # Shim rĂ©tro-compatible (re-exports depuis mock_framework)
âââ simulation.html # IHM â structure HTML + CSS
âââ sim_core.js # JS â globals, helpers, time, config, MK6, auth
âââ sim_queue.js # JS â form builder, file, import/export, date shift
âââ sim_execute.js # JS â exĂ©cution, rendu rĂ©sultats, console debug
âââ sim_assert.js # JS â rendu badges verdict, click-to-cycle
âââ DEFAULT_CONFIG.json # Config ROUVAI par dĂ©faut
âââ Dockerfile
âââ requirements.txt
âââ test_mock_mdw_regression.py # Tests rĂ©gression
âââ test_mock_mdw_integration.py # Tests intĂ©gration
âââ test_e2e_mdw_mk6.py # Tests cross-mock
âââ test_sim_assert.py # Tests assertion engine
âââ test_simulation_js.js # Tests JS (JSDOM)
âââ test_doc_structure.py # Tests structure docs
âââ docs (GUIDE, SCENARIOS, README, CHANGELOG, HELP, DOC_GUIDELINES)
Séparation code métier vs utilitaire
| Fichier | RÎle | Réutilisable ? |
|---|---|---|
mock_shared.py |
SSL auto-signĂ©, CSS Markdown, filtres uvicorn, CORS | â Tout projet |
mock_framework.py |
OrderTracker (anti-replay FIFO), TimeController, SupervisionTracker, AuthController, ProxyGuard | â Tout mock REST |
mock_mdw.py |
Config ROUVAI, validation DEC-047, translation MK6, SIEM MA | â SpĂ©cifique MA |
JS modules servis par /static/{filename}
En mode dev (MOCK_MDW_DEV=1), les fichiers JS sont rechargĂ©s Ă chaque requĂȘte.
En mode normal, ils sont cachés 1h cÎté navigateur.
Isolation des tests (D3)
Chaque test de régression appelle setup() qui remet le state global à zéro.
Sans setup(), les tests peuvent collisionner (ex: un test qui change
auth_scope impacte le suivant).
RĂšgles :
- Toujours appeler setup() en début de section de test
- Remettre à l'état initial les champs modifiés si setup() n'est pas appelé
- known_order_ids utilise OrderTracker du framework (D3) avec éviction FIFO correcte
- Les tests ne sont pas parallélisables tant que le state est un singleton global
Parking lot v1.0.3 : rendre le state injectable (MdWState(config) au lieu du singleton state) pour permettre des tests isolés et parallÚles.