08.07 — Gaps spec (G-SPEC-1 à G-SPEC-3)#

Trois gaps sur le comportement métier vs la spec attendue d’un système de santé.

G-SPEC-1 — History trié par ordre de soumission, pas par date#

Source#

// views/page_history.php
foreach ($_SESSION['history'] as $appointment) {
    // ... render ...
}

Pas de usort. Pas de tri. Aucune étape de tri n’existe nulle part.

appointment.php push avec array_push($_SESSION['history'], $_POST) — append en queue (c’est un pushback).

Spec non-respectée#

REQ-HIST-3 des SFD :

Most recent calendar date first

(Spec reconstruite par l’IA — typique d’un système d’historique.)

Symptôme#

Les RDV apparaissent dans leur ordre de confirmation par le système, indépendamment de visit_date. Une demande de RDV pour le 2026-12-31, envoyée aujourd’hui (2026-05-22), et une demande de RDV pour la semaine prochaine, envoyée trois jours plus tard (2026-05-25) finissent affichées avec le RDV pour décembre au-dessus du rendez-vous pour la semaine prochaine.

Test#

# src/tests/scenarios/journey/history_ordering.py
"""History ordered most-recent calendar date first (REQ-HIST-3 / §9.8 gap).

Flow:
  login → book #1 (visit_date = in 30 days) → book #2 (visit_date = tomorrow)
  → open history → verify card order: #2 above #1 (most recent first)
  (intentional FAIL — CURA renders in submission order, §9.8 gap)

Pre-fragments: login_as_demo_user
Post-fragments: (none)
"""

→ Test intentionnellement en échec. booking#1 (date lointaine) puis booking#2 (date proche) devrait rendre avec #2 affiché en premier dans le listing. CURA les affiche selon leur ordre d’insertion (donc, ici, #1 en haut). Échec documenté.

G-SPEC-2 — Page de profil = placeholder#

Source#

// views/page_profile.php
// renders the #profile section but contains no editable fields,
// no form, no API endpoint.

Symptôme#

Les users loggés voient « Profile » dans le menu, naviguent pour aller dessus, et trouvent une page vide. Pas de formulaire, pas de bouton, juste un texte (placeholder).

Test#

# src/tests/scenarios/profile/view_profile.py
def scenario_view_profile(driver, logger):
    on_profile = ProfilePage(driver=driver)
    return [
        drive_page(
            act(on_profile, open_profile_page)...,
            act(on_profile, verify_profile_is_placeholder)...,    # PASS = documente le placeholder
        ),
    ]

Ce test PASSE. Il documente le comportement actuel.
Ce n’est pas un gap test au sens « échec intentionnel » ; c’est une trace d’un test exploratoire.

Si CURA un jour ajoute une vraie page de profil, verify_profile_is_placeholder casserait et on réviserait le test pour vérifier la nouvelle page.

G-SPEC-3 — Un accès non autorisé redirige vers /, pas vers la page de connexion#

Source#

// appointment.php
if (!$session_check) {
    header("Location: " . SITE_URL);                  // ← redirige vers /
    exit;
}

history.php fait pareil.

Spec attendue#

Un système devrait rediriger vers /profile.php#login (la page de connexion), pas vers / (la page d’accueil).
Sinon l’utilisateur perd le contexte (« je voulais accéder à mon historique »). C’est un défaut d’ergonomie.

Symptôme#

User non-authentifié clique sur un deep-link qui le dirige vers /appointment.php ou /history.php → il atterit sur la page d’accueil → il doit alors cliquer sur « Make Appointment » à nouveau pour atteindre la page de connexion.

Test#

# src/tests/scenarios/login/unauthenticated_appointment_access.py
def scenario_unauthenticated_appointment_access(driver, logger):
    on_appointment = AppointmentPage(driver=driver)
    on_home = HomePage(driver=driver)
    return [
        drive_page(
            act(on_appointment, open_appointment_page)...,
            act(on_appointment, verify_redirected_to_home)...,    # PASS = documente le comportement
        ),
        drive_page(
            act(on_home, verify_home_page)...,
        ),
    ]

unauthenticated_appointment_access.py, unauthenticated_history_access.py, unauthenticated_profile_access.py.

Tous ces tests PASSENT, ils documentent de l’exploration.
Ici, la SFD étant reconstituée et ne faisant pas foi, on ne peut qualifier ce comportement comme étant une anomalie flagrante. On ne peut que formuler une préconisation et documenter.

Pattern : gap test vs exploratory pass#

Les gaps spec sont en grande partie exploratory pass (le test passe parce qu’il documente le comportement actuel) plutôt que intentional fail (le test fail parce qu’il assert un blocage qui devrait arriver mais que le système n’impose pas).

G-DATA-*G-SPEC-*
« CURA accepte X qu’il devrait rejeter »« CURA fait X au lieu de faire Y »
Gap test : assert rejection, fail intentionnelTest documentaire : assert le comportement actuel, pass

Les gap tests « assertive » sont réservés aux cas où pour tel parcours utilisateur, un système de santé devrait clairement l’envoyer bouler. Pour les divergences de spec mineures (ordre de l’historique, page de profil en construction, page de redirection qui pourrait être mieux choisie sans que ce ne soit critique), on documente plutôt qu’on assert.

L’art de choisir#

CURA_TEST_STRATEGY.md :

When to add one [exploratory test]:

  • A business-logic constraint is plausible but absent from the SFD (“can I book the same date twice in different slots?”).
  • Decide before writing: is the current behaviour reasonable (→ exploratory / pass) or a gap (→ assertive / intentional fail)?
  • Always pair with an SFD update.

Décision humaine avant l’écriture. Pas de bascule automatique. C’est ce qui rend la stratégie de test lisible pour un nouveau venu : chaque test est dans la catégorie qui correspond à sa décision.

IDENTIFIED_GAPS.md#

Tout gap (DATA, SPEC, SEC) a une entrée dans IDENTIFIED_GAPS.md. Le nom du test, la docstring, et l’entrée gaps doivent être synchronisés.

CLAUDE.md :

No SFD references outside # comments. Test names, log messages, exception messages, docstrings — none contain SFD §x.x. Those references belong in CURA_FRD.md and in inline comments. The SFD number means nothing to someone reading a test report.

→ Les noms de tests sont lisibles par un humain (Past date booking accepted, pas Test_FRD_9_7). La référence §9.7 vit dans le code en tant que commentaire et dans IDENTIFIED_GAPS.md.