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 "..." --resetau moins une fois, puisdemo:hospital 985 "..." --mergepour observer les comportements merge.
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.
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.
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.
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.
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.
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.
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 :
actives > 0 et un nombre de tombstones cohérent avec les suppressions PO effectuéesPhase 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/.