Isi-APP Docs fonctionnelles
Toutes les docs
Markdown brut
Demo Merge — Scénarios de Validation SQL
Actif demo functional Revu le 2026-04-24 demo/demo-merge-tests.md

Demo Merge — Scénarios de Validation SQL

Scénarios de validation pour le mode --merge (Phase 15). Exécuter via tinker ou database-query sur un environnement de test avec un IDC de démo connu (ex: 985).

Prérequis : avoir lancé demo:hospital 985 "..." --reset au moins une fois, puis demo:hospital 985 "..." --merge pour observer les comportements merge.


Scénario 11 — NonIdempotent : lignes seed tracées dans seed_map

Après un merge, toutes les lignes NonIdempotent réinsérées doivent être tracées dans demo_seed_map avec deleted_by_po=0.

-- Vérifier que demo_seed_map contient des entrées pour les tables NonIdempotent
SELECT table_name, COUNT(*) AS nb_entries, SUM(deleted_by_po) AS nb_tombstones
FROM demo_seed_map
WHERE idc = 985
  AND table_name IN (
    'cyber_ev', 'customer_event', 'dsi_hw', 'dsi_lic', '_com_interv',
    '_com_contr', 'equipment_types', 'crm_aff', 'cust_suivi', 'dsi_hw_users',
    'project_step', 'project_news', 'project_meeting', 'project_backlog',
    'equipments', 'project_kpival', '_com_task', '_com_livrable', 'equipment_counters'
  )
GROUP BY table_name
ORDER BY table_name;

Résultat attendu : chaque table NonIdempotent présente dans le template apparaît avec nb_entries > 0 et nb_tombstones = 0.


Scénario 12 — Tombstone hard delete : ligne supprimée détectée

Simuler un hard delete d'une ligne seed, puis relancer le merge.

-- 1. Repérer une entrée seed dans dsi_hw pour idc=985
SELECT dsm.seed_key, dsm.real_id, dh.idhw, dh.lbhw
FROM demo_seed_map dsm
JOIN dsi_hw dh ON dh.idhw = dsm.real_id
WHERE dsm.idc = 985
  AND dsm.table_name = 'dsi_hw'
  AND dsm.deleted_by_po = 0
LIMIT 1;

-- 2. Supprimer physiquement la ligne (simule un hard delete PO)
-- DELETE FROM dsi_hw WHERE idhw = {real_id_trouvé};

-- 3. Relancer : php artisan demo:hospital 985 "..." --merge

-- 4. Vérifier que le tombstone est enregistré
SELECT seed_key, real_id, deleted_by_po, deleted_at
FROM demo_seed_map
WHERE idc = 985
  AND table_name = 'dsi_hw'
  AND deleted_by_po = 1;

Résultat attendu : la ligne supprimée apparaît dans demo_seed_map avec deleted_by_po=1 et deleted_at renseigné. Elle n'est pas réinsérée lors du merge suivant.


Scénario 13 — Tombstone soft delete : ligne dtdel détectée

Simuler un soft delete (mise à jour dtdel) sur une ligne seed.

-- 1. Repérer une entrée seed dans _com_interv pour idc=985
SELECT dsm.seed_key, dsm.real_id, ci.idinterv, ci.lbinterv, ci.dtdel
FROM demo_seed_map dsm
JOIN _com_interv ci ON ci.idinterv = dsm.real_id
WHERE dsm.idc = 985
  AND dsm.table_name = '_com_interv'
  AND dsm.deleted_by_po = 0
LIMIT 1;

-- 2. Soft-supprimer la ligne (simule une suppression PO)
-- UPDATE _com_interv SET dtdel = NOW() WHERE idinterv = {real_id_trouvé};

-- 3. Relancer : php artisan demo:hospital 985 "..." --merge

-- 4. Vérifier que le tombstone est enregistré avec deleted_at
SELECT seed_key, real_id, deleted_by_po, deleted_at
FROM demo_seed_map
WHERE idc = 985
  AND table_name = '_com_interv'
  AND deleted_by_po = 1;

Résultat attendu : deleted_by_po=1 avec deleted_at correspondant à la valeur dtdel de la ligne.


Scénario 14 — force-restore : tombstone ignoré et ligne recréée

Après la création d'un tombstone (scénario 12 ou 13), vérifier que --force-restore recrée la ligne.

-- Avant force-restore : vérifier l'état du tombstone
SELECT seed_key, real_id, deleted_by_po
FROM demo_seed_map
WHERE idc = 985
  AND table_name = 'dsi_hw'
  AND deleted_by_po = 1;

-- Lancer : php artisan demo:hospital 985 "..." --merge --force-restore

-- Après force-restore : le tombstone doit avoir disparu (deleted_by_po remis à 0 ou nouvelle entrée)
SELECT dsm.seed_key, dsm.real_id, dsm.deleted_by_po, dh.lbhw
FROM demo_seed_map dsm
JOIN dsi_hw dh ON dh.idhw = dsm.real_id
WHERE dsm.idc = 985
  AND dsm.table_name = 'dsi_hw'
ORDER BY dsm.seed_key;

-- La ligne doit exister en DB
SELECT idhw, lbhw, dtdel
FROM dsi_hw
WHERE idc = 985;

Résultat attendu : la ligne est réinsérée en DB, demo_seed_map n'a plus d'entrée deleted_by_po=1 pour cette seed_key, et dtdel est NULL.


Scénario 15 — SkipIfExists : données PO protégées

Vérifier que les tickets créés par le PO ne sont pas écrasés lors d'un merge.

-- 1. Compter les tickets actifs pour idc=985 avant merge
SELECT COUNT(*) AS nb_tickets_before
FROM customer_ask
WHERE idc = 985
  AND deleted_at IS NULL;

-- 2. Vérifier l'absence d'entrées seed_map pour 'tickets' (SkipIfExists ne trace pas)
SELECT COUNT(*) AS nb_seed_tickets
FROM demo_seed_map
WHERE idc = 985
  AND table_name = 'tickets';

-- 3. Lancer : php artisan demo:hospital 985 "..." --merge

-- 4. Recompter — le count doit être identique si le PO avait des tickets
SELECT COUNT(*) AS nb_tickets_after
FROM customer_ask
WHERE idc = 985
  AND deleted_at IS NULL;

Résultat attendu : si nb_tickets_before > 0, alors nb_tickets_after = nb_tickets_before. Les tickets PO sont intacts. Si l'instance est fraîche (tickets créés uniquement par le template), le premier merge les insère et les suivants les skippent.


Scénario 16 — NonIdempotent : collision PK non-AI skippée

Pour les tables à PK non auto-increment, une collision avec une ligne PO doit être ignorée sans erreur.

-- Vérifier que equipment_types utilise une PK non-AI (vérification schéma)
SHOW CREATE TABLE equipment_types\G

-- Après merge, vérifier les warnings de collision dans les logs
-- (les collisions sont comptées dans le résultat de executeTable)

-- Vérifier les lignes equipment_types pour idc=985
SELECT idequipment_type, idc, lbequipment_type
FROM equipment_types
WHERE idc = 985
ORDER BY idequipment_type;

-- Comparer avec les entrées seed_map : toutes les lignes seed doivent être tracées
SELECT seed_key, real_id, deleted_by_po
FROM demo_seed_map
WHERE idc = 985
  AND table_name = 'equipment_types';

Résultat attendu : les lignes seed sont tracées. Si une collision PK a été skippée, le real_id n'est pas dans demo_seed_map pour ce seed_key.


Scénario 17 — Cohérence globale seed_map après merge complet

Validation finale : intégrité du demo_seed_map après un merge complet.

-- 1. Toutes les real_id actives doivent exister en DB (pas de ghost entries)
-- Exemple sur dsi_hw
SELECT dsm.seed_key, dsm.real_id, dh.idhw
FROM demo_seed_map dsm
LEFT JOIN dsi_hw dh ON dh.idhw = dsm.real_id
WHERE dsm.idc = 985
  AND dsm.table_name = 'dsi_hw'
  AND dsm.deleted_by_po = 0
  AND dh.idhw IS NULL;
-- Résultat attendu : 0 lignes (pas de ghost)

-- 2. Pas de doublons seed_key par table
SELECT table_name, seed_key, COUNT(*) AS cnt
FROM demo_seed_map
WHERE idc = 985
GROUP BY table_name, seed_key
HAVING cnt > 1;
-- Résultat attendu : 0 lignes

-- 3. Résumé complet du seed_map pour idc=985
SELECT
  table_name,
  COUNT(*) AS total,
  SUM(CASE WHEN deleted_by_po = 0 THEN 1 ELSE 0 END) AS actives,
  SUM(CASE WHEN deleted_by_po = 1 THEN 1 ELSE 0 END) AS tombstones
FROM demo_seed_map
WHERE idc = 985
GROUP BY table_name
ORDER BY table_name;

Résultat attendu :

  • Scénario 17a : 0 ghost entries
  • Scénario 17b : 0 doublons de seed_key
  • Scénario 17c : toutes les tables NonIdempotent du template apparaissent avec actives > 0 et un nombre de tombstones cohérent avec les suppressions PO effectuées

Phase 15 — Statut

Phase 15 est complète. Toutes les stratégies de merge sont implémentées et testées :

Livrable Statut
L1 — Infrastructure merge (SeedMapRepository, MergeStrategy, SemiIdempotentMergeStrategy, MergeRegistry) ✅ Complet
L2 — StaticMergeStrategy, DatePolicy, DemoImporter merge mode ✅ Complet
L3 — NonIdempotentMergeStrategy, SkipIfExistsMergeStrategy, TombstoneDetector, --force-restore ✅ Complet

Tests automatisés : 29 tests PHPUnit passent (540 assertions) — voir tests/Feature/DemoAccount/.