MdW v1.1.2

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

URLs (visibles en mode Recette)

URL Mock 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 ★)

Les Ă©toiles sont non bloquantes — on peut soumettre un formulaire incomplet pour tester les validations.

Segments

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


Résultat (bloc central)

AprÚs envoi, le bloc résultat affiche :

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


Horloge simulée

Le panneau horloge permet de figer le temps à une valeur précise :

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 :

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

  1. Valeurs psetpoint uniques par scĂ©nario — Ă©vite les faux positifs body_contains
  2. Clear avant chaque bloc — purge l'Ă©tat GW Ignition (la re-provision ne purge que le mock MK6, pas la BDD GW)
  3. PRM/MK6 distincts quand possible — isolation totale par sycode
  4. _wait_seconds suffisants — 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 :

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)"