Simulateur MdW â Manuel utilisateur
Vue d'ensemble
La page Simulation (/simulation) est l'interface graphique du Mock MdW.
Elle permet de construire, envoyer et diagnostiquer des commandes de puissance
sans écrire de curl. Toutes les actions sont exécutables depuis un navigateur
â y compris en Docker Compose.
Panneau Configuration (haut de page)
Mode
- Mock : le MdW simule les réponses localement, traduit les commandes et les envoie au Mock MK6
- Recette (défaut) : le simulateur est un proxy transparent vers un vrai MdW/GW Ignition
URLs (visibles en mode Recette)
- URL MdW (interne) : URL du vrai MdW Ignition, utilisĂ©e par les proxys serveur (httpx). Le backend rĂ©sout cette URL â elle peut ĂȘtre un hostname interne non accessible au navigateur (ex:
cot-scada-frc.inxops.com). Modifiable, synchro vers le serveur viaPOST /mock/mode. - URL publique (navigateur) : Base URL pour les liens cliquables dans les résultats (ancres
đ, messages confirm). Doit ĂȘtre accessible au navigateur. Si vide, fallback surwindow.location.hostname. Configurable aussi dansDEFAULT_CONFIG.json â runtime.public_base_url.
URL Mock MK6
- Un champ URL de base + deux boutons :
- ⥠Appliquer à tous : tous les MK6 utilisent cette URL unique (test MK6 unique, ou MK6 Synapse réel)
- đ Restaurer ROUVAI : reset + recharge
DEFAULT_CONFIG.jsonâ 5 MK6 sur 5 ports (topologie ROUVAI MVP) - Ligne de statut : affiche le routage courant (tous vers une URL, ou ROUVAI avec les URLs complĂštes par MK6)
PRM
Le dropdown liste tous les PRM configurés. Sélectionner un PRM pré-remplit pmax_kw et prm_id dans le formulaire.
Formulaire de commande
Champs obligatoires (Ă©toile rouge â )
prm_id,order_id,source,start_time: requis par le CI AppMNGâMdW §3.1- Au moins 1 segment avec
command_typeetend_time
Les Ă©toiles sont non bloquantes â on peut soumettre un formulaire incomplet pour tester les validations.
Segments
- + Segment : ajouter un segment.
end_timeest pré-rempli à +30 min - Types :
psetpoint(puissance en kW),stop(arrĂȘt),clear(retour Pmax) preset: segment prioritaire (boolean)
File de requĂȘtes
Les commandes s'empilent dans une file avant envoi :
| Bouton | Action |
|---|---|
| ⶠTout envoyer | Envoie toute la file séquentiellement |
| âïž | Ăditer le JSON brut d'un item |
| đ | Cloner un item envoyĂ© (copie en mode Ă©dition) |
| đ | PrĂ©-remplir le formulaire depuis un item |
| âČ âŒ | RĂ©ordonner la file |
| âž | Point d'arrĂȘt â pause avant cet item |
Import / Export
- đ„ Importer : charger un fichier JSON (tableau d'items)
- đ€ Exporter : sauvegarder la file en JSON
- Date shift : à l'import, décaler toutes les dates pour commencer "maintenant" (panneau dépliable)
Résultat (bloc central)
AprÚs envoi, le bloc résultat affiche :
- Réponse MdW : status HTTP, body JSON, temps de réponse
- Actions MK6 : les appels traduits vers le MK6 (POST /requests, DELETE /planning, etc.)
Mode Auto vs Manuel
| Mode | Comportement |
|---|---|
| ⥠Auto | Les actions MK6 sont envoyées automatiquement aprÚs la réponse MdW |
| â Manuel | Les actions MK6 sont affichĂ©es avec un bouton "Envoyer" par action â utile pour le debug pas-Ă -pas |
Panneau MK6 (Init / Ătat / Erreurs)
Boutons Init
| Bouton | Action |
|---|---|
| đ Reset + Init | Reset tous les MK6 puis reprovisionne depuis la config MdW |
| đ§ Init MK6 | Provisionne sans reset (ajout/mise Ă jour) |
| đïž Reset MK6 | Remet Ă zĂ©ro tous les MK6 (planning, faults, horloge) |
| ⳠRefresh | Rafraßchir l'état sans modifier |
ContrĂŽles par site (panneau Erreurs MK6)
Chaque MK6 provisionné affiche ses sites avec des contrÎles :
Réseau : dropdown online / offline / unreachable par MK6
Fault : injection de dĂ©faut par site (dropdown + bouton ⥠Set / â Clear)
State : forcer le state d'un site (0 = pas de comm, 1 = pas de contrĂŽle, 2 = nominal)
Contrainte externe (Ext) : simuler DEIE/SCADA
- Source : DEIE ou SCADA (dropdown)
- Puissance : en kW
- DĂ©but / Fin : fenĂȘtre temporelle (optionnel â si vide, permanent)
- ⥠pour injecter, â pour supprimer
Horloge simulée
Le panneau horloge permet de figer le temps à une valeur précise :
- Figer : toutes les requĂȘtes utilisent cette heure fixe
- Libérer : retour au temps réel
- Broadcast : l'horloge est automatiquement synchronisée vers tous les Mock MK6
Utile pour tester des scĂ©narios avec des fenĂȘtres temporelles (contraintes externes, planning 48h).
Injection d'erreurs MdW
Le panneau "Erreurs simulées" permet de configurer des réponses dégradées du MdW :
- Health dégradé (503)
- Timeouts simulés
- Codes erreur forcés sur
/commands
Configuration via /mock/error-config.
Compatibilité Docker
Toutes les actions MK6 transitent par un proxy cĂŽtĂ© serveur â le browser
n'appelle jamais directement le Mock MK6. Les URLs MK6 dans DEFAULT_CONFIG.json
peuvent ĂȘtre des noms de service Docker (ex: http://mock-mk6:9001).
Assertions automatiques (v1.1.0 â v1.2.0)
Principe
Les fichiers JSON de campagne peuvent contenir des champs _expected_* sur chaque item.
Le backend évalue les assertions et retourne un verdict PASS / FAIL / SKIP.
L'IHM affiche un badge coloré (vert/rouge/gris) et une barre de compteurs en haut.
Verdicts
| Verdict | Signification |
|---|---|
| PASS | Au moins 1 champ _expected_* présent, tous matchent |
| FAIL | Au moins 1 champ _expected_* présent, au moins 1 ne matche pas |
| SKIP | Aucun champ _expected_* sur cet item (COMMENT, ACTION setup) |
summary.success = true ssi fail == 0. Les SKIP n'impactent pas le success.
Référence complÚte des assertions
1. Assertions HTTP
| Champ | Type | Sur quel item | Ce que le backend vérifie |
|---|---|---|---|
_expected_status |
int |
COMMAND, GET, ACTION | Code HTTP == valeur (200, 400, 422, 503âŠ) |
_expected_error_code |
string |
COMMAND (erreurs) | body.error_code == valeur |
{"_expected_status": 422, "_expected_error_code": "OVER_PMAX"}
2. Assertions SIEM (logs /health)
En mode campagne (v1.1.4), les logs MdW et GW sont fetchés et mergés automatiquement.
Les catégories GW (MK6_SUBMIT, GW_CLEAR_SENT, etc.) sont disponibles dans les assertions.
| Champ | Type | Ce que le backend vérifie |
|---|---|---|
_expected_siem_category |
string |
Au moins 1 log avec cette catégorie dans les logs mergés MdW+GW |
_expected_siem_fields |
{key: val} |
Le log trouvé contient ces champs:valeurs exacts |
_expected_siem_count |
{category, min} |
Au moins min logs matchant la catégorie (v1.1.1) |
_expected_log_order |
[string] |
Séquence de catégories dans l'ordre temporel des logs mergés (v1.1.1) |
_expected_siem_not_present |
string |
Vérifie qu'aucun log n'a cette catégorie. Rollback, isolation CB (v1.2.0) |
_expected_siem_fields_absent |
[string] |
Vérifie que les champs listés sont absents des logs matchés par _expected_siem_category (v1.2.0) |
{
"_expected_siem_category": "COMMAND_ACCEPTED",
"_expected_siem_fields": {"order_id": "MY-ORDER", "prm_id": "50048589698480"},
"_expected_log_order": ["COMMAND_RECEIVED", "PLANNING_PUBLISHED", "COMMAND_ACCEPTED", "MK6_SUBMIT"]
}
Ordre rĂ©el pipeline Ignition : COMMAND_RECEIVED â PLANNING_PUBLISHED â COMMAND_ACCEPTED â MK6_SUBMIT.
Le MdW publie sur l'EventBus avant de répondre 200 au client.
Comptage multi-segments :
{"_expected_siem_count": {"category": "MK6_SUBMIT", "min": 3}}
Vérifie qu'une commande 3 segments génÚre au moins 3 envois MK6.
Note v1.1.4 : en mode campagne, le backend fetch les logs GW avec 3 retries à 1.5s. En mode "Exécuter tout" (formulaires), les logs GW sont pré-affichés mais les assertions SIEM ne portent que sur les logs MdW.
3. Assertions sur le body de réponse
| Champ | Type | Ce que le backend vérifie |
|---|---|---|
_expected_body_contains |
{key: val} |
Recherche récursive dans le body JSON. Cherche dans objets imbriqués et tableaux (planning[]). Tolérance numérique : 5000 == 5000.0 |
_expected_body_not_contains |
{key: val} |
Vérifie l'absence d'une combinaison clé:valeur. Utile pour vérifier qu'une ancienne consigne a été remplacée |
_expected_body_array_min_length |
int |
Au moins N éléments. Supporte list (body = tableau) et dict avec planning[] (body MK6 = {sycode, planning: [...]}) (fix v1.1.4) |
_expected_body_array_exact_length |
int |
Exactement N Ă©lĂ©ments. MĂȘme logique de rĂ©solution que _min_length (v1.2.0) |
_expected_body_array_contains |
{key: val} |
Au moins un élément du tableau (planning, available_prm, items) matche les critÚres (deep search). Tolérance numérique (v1.2.0) |
_expected_body_field_present |
[string] |
Les champs listés existent (non-null) quelque part dans le body, recherche récursive. Utile pour vérifier start_time/end_time sans connaßtre la valeur post-shift (v1.1.3) |
5. Assertion SLA (v1.2.0)
| Champ | Type | Ce que le backend vérifie |
|---|---|---|
_expected_response_time_max_ms |
int |
Temps de réponse time_ms †valeur. Ignoré (PASS) si time_ms non disponible |
{"_expected_status": 200, "_expected_response_time_max_ms": 500}
Exemple vérification planning MK6 (superposition + absence) :
{
"_expected_status": 200,
"_expected_body_contains": {"set": "Psetpoint", "psetpoint": 5000, "preset": "none"},
"_expected_body_not_contains": {"psetpoint": 3000},
"_expected_body_array_min_length": 2,
"_expected_body_field_present": ["start_time", "end_time"]
}
4. ContrĂŽle de timing
| Champ | Type | Description |
|---|---|---|
_wait_seconds |
int |
Pause en secondes avant exĂ©cution. Le rĂ©sultat contient waited_seconds. Pas de rĂ©sultat sĂ©parĂ© â alignement results[N] â items[N] garanti 1:1 |
5. Méta-champs (non-assertion)
| Champ | Type | Description |
|---|---|---|
_label |
string |
Nom affiché dans l'IHM et le rapport. Convention : [R1] [profondeur-cible] description |
_breakpoint |
bool |
Pause avant cet item en mode "Exécuter tout" (ignoré en campagne) |
_x_request_id |
string |
Corrélation logs. En campagne, suffixé automatiquement avec un run_tag unique (anti-collision). Les valeurs vides ne sont pas suffixées (v1.1.4) |
_passthrough |
bool |
Envoi direct vers la cible avec headers personnalisés (tests auth P10) |
_passthrough_headers |
{key: val} |
Headers HTTP Ă envoyer en mode passthrough |
Tagging R1/R2
Les labels portent [R1] ou [R2] indiquant la release minimale :
- [R1] â feature livrĂ©e en R1, doit PASS
- [R2] â feature R2 (503, 422, 409, CB, authâŠ), FAIL attendu en R1
Nommage des résultats (v1.1.4)
Le fichier téléchargé : EXEC_{source}-{PASS}PASS-{FAIL}FAIL-{YYYYMMDDHHMM}.json.
Le nom source est capturé à l'import du fichier JSON.
Isolation des tests â bonnes pratiques
- Valeurs psetpoint uniques par scĂ©nario â Ă©vite les faux positifs
body_contains - Clear avant chaque bloc â purge l'Ă©tat GW Ignition (la re-provision ne purge que le mock MK6, pas la BDD GW)
- PRM/MK6 distincts quand possible â isolation totale par sycode
_wait_secondssuffisants â la pipeline GW est asynchrone (clear ~8s, verify ~10-15s)
Rétro-compatibilité
Si un item n'a aucun champ _expected_*, le backend ne retourne pas de clé assertion.
Comportement identique Ă v1.0.2.
Deux modes d'execution
Executer tout (bouton vert)
Execution item par item cote client. Les breakpoints sont respectes. L'execution s'arrete
a chaque _breakpoint: true et attend un clic "Continuer". Les assertions sont evaluees par
le backend a chaque appel et les badges PASS/FAIL s'affichent en temps reel.
C'est le mode debug / developpement â utile pour Benjamin et pour le diagnostic.
Campagne (bouton violet)
Envoie toute la file au backend via POST /mock/test/campaign. Le backend execute tous les
items sequentiellement cote serveur, ignore les breakpoints, et produit un rapport formel.
L'IHM poll le backend toutes les 500ms et affiche les resultats progressivement.
Apres completion, le bouton Rapport (vert) apparait. Il telecharge le JSON complet du rapport de campagne, incluant :
summary.pass,summary.fail,summary.skip,summary.totalsummary.success:truesi aucun FAILsummary.failures[]: acces direct aux FAIL â step, label, status_code, assertion details
C'est le mode recette formelle â produit un rapport exploitable sans filtrage.
Banner de fin (v1.1.2) : Ă la fin de la campagne, un bloc visuel s'affiche sous les rĂ©sultats avec l'icĂŽne â (succĂšs) ou â (Ă©chec), le rĂ©sumĂ© PASS/FAIL/SKIP, et la liste des Ă©checs. Un bouton « TĂ©lĂ©charger le rapport » est intĂ©grĂ© dans le banner.
_wait_seconds (v1.1.2) : le résultat réel contient waited_seconds (durée d'attente appliquée).
Aucun rĂ©sultat WAIT supplĂ©mentaire n'est insĂ©rĂ© â l'alignement results[N] â items[N] est garanti 1:1.
Structure du rapport
{
"campaign_id": "camp-abc12345",
"status": "completed",
"summary": {
"total": 16, "pass": 3, "fail": 1, "skip": 12,
"success": false,
"failures": [
{
"step": 9,
"label": "[U-E any] V12 -- value_kw > pmax -> HTTP 422",
"status_code": 200,
"assertion": {
"verdict": "FAIL",
"expected": {"status": 422, "error_code": "VALUE_EXCEEDS_PMAX"},
"actual": {"status": 200, "error_code": null},
"details": [...]
}
}
]
},
"results": [ ... tous les items avec details complets ... ]
}
Pour exploiter le rapport : summary.success donne le GO/NO-GO. Si false,
summary.failures[] liste chaque FAIL avec son step, son label, et le detail attendu/obtenu.
Passthrough (tests cybersecurite)
Pour les tests C01-C06 (auth, rate limit) qui ont besoin de controler les headers HTTP :
{
"_passthrough": true,
"_http_method": "POST",
"_target_url": "https://cot-scada-frc.inxops.com/.../api/v1/commands",
"_headers": {"Authorization": "Basic dXNlcjpwYXNz"},
"prm_id": "50048589698480", "order_id": "C01", ...
"_expected_status": 200
}
Le simulateur envoie directement vers l'URL cible avec les headers fournis, sans passer par
le proxy /simulation/api/execute qui bypasse la couche auth.
API CI/CD
Meme endpoint que l'IHM â meme format, meme rapport.
# Lancer une campagne
curl -s -X POST http://localhost:9000/mock/test/campaign \
-H "Content-Type: application/json" \
-d '{"name": "recette_P1", "items": [...fichier JSON...], "delay_between_items_ms": 300}'
# Poller le resultat
curl -s http://localhost:9000/mock/test/campaign/{campaign_id}
# Verifier le success
curl -s http://localhost:9000/mock/test/campaign/{campaign_id} \
| python3 -c "import sys,json; d=json.load(sys.stdin); s=d['summary']; \
print(f'PASS:{s[\"pass\"]} FAIL:{s[\"fail\"]} SKIP:{s[\"skip\"]}'); \
[print(f' FAIL #{f[\"step\"]}: {f[\"label\"]}') for f in s.get('failures',[])]; \
exit(0 if s['success'] else 1)"