Le dry run est automatisé en test (MigrationDryRunTests) : il appelle le
contrôleur directement, ce qui évite la chaîne login + JWT SuperAdmin, et se
saute si aucun Mongo n'écoute sur MIGRATION_TEST_MONGO — la suite reste donc
verte sans dépendance. Les tailles de fichier sont court-circuitées : un HEAD
sur 2374 ressources prendrait des minutes pour une information que ce test ne
vérifie pas.
Il a trouvé du premier coup ce qu'aucune lecture de code n'avait vu : 327
sections dans Mongo, 307 migrées, et Erreurs : 0.
Les 20 manquantes référencent deux configurations supprimées dans Mongo — 19
articles et un slider, du contenu MDLF. La migration a raison de ne pas les
reprendre : les sections sont collectées configuration par configuration,
donc les orphelines ne sont jamais énumérées, et la FK ConfigurationId les
refuserait. Le défaut était le silence — elles partent désormais dans
Skipped, avec le détail de la configuration introuvable.
L'invariant affirmé par le test n'est donc pas « tout est migré » mais
« rien ne disparaît sans être signalé » : migrées + signalées = Mongo.
dotnet test 144/144.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Trois n'existaient pas — vérifié dans l'export Mongo plutôt que dans la doc :
- (a) « BuildSection ne couvre que 11 types sur 13 » : les 327 sections de
l'export ne contiennent que les types 0 à 10. SectionEvent et
SectionParcours sont nés avec Postgres v3, le default est un filet.
- (f) IsQRCode/IsSearchText/IsSearchNumber n'existent pas dans Mongo, false
est le bon défaut. Contrôle inverse fait : les cinq réglages qui existent
vraiment sont bien mappés sur AppConfigurationLink.
- (g) était tombé avec le rename du lot B.
Les cinq réels :
- (b) QuizQuestions = new() jetait les questions. Mesuré : 5 sections quiz
en portent 41, réponses comprises. Migrées en MultipleChoice, le type de
validation n'existant pas dans l'ancien modèle. EventAgendas et l'absence
de GuidedPath sont corrects : Mongo n'a ni événement ni parcours.
- (c) la source ne contient que 4 champs, il n'y avait rien à mapper de
plus. PublicApiKey générée (même schéma que l'onboarding), WebSlug via
SlugHelper, drapeaux de plateforme dérivés des configurations, plan
affecté par nom avec repli sur Pro, IsAssistant allumé seulement si le
plan donne des jetons.
- (d) Mongo n'a pas de champ Role : le ContentEditor en dur dégradait les
10 utilisateurs en silence, plus personne n'aurait pu gérer les
utilisateurs de son instance. Passé à InstanceAdmin.
- (e) StoragePath n'était pas écrit du tout. Passe par ResourceStorage, le
calculateur commun à la création et au backfill. L'échec du HEAD, jusqu'ici
avalé, remonte dans le rapport.
- (h) transaction unique sur les neuf étapes, et 500 au lieu de 200 OK sur
échec fatal — un script de bascule testant le code HTTP concluait au
succès alors que l'erreur n'était que dans le corps.
ApplyPlanQuotas est dupliqué depuis InstanceController plutôt qu'appelé : la
migration ne doit pas casser si ce contrôleur change de forme.
dotnet build vert, dotnet test 143/143. Reste le dry run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LOT B — une seule migration EF (LotB_FreezeSchema) :
- SectionMap.MapResourceId → IconResourceId. L'écart (g) de la bascule tombe
avec. Il fallait renommer aussi la propriété de navigation MapResource :
la convention EF l'appariait au FK, la laisser aurait fabriqué un FK
fantôme. Elle n'était utilisée nulle part ailleurs.
- SectionEvent.ParcoursIds supprimé (champ, DTO, SectionFactory, et une
initialisation dans un montage de test).
- Instance.IsImageWatermark remplace le `instanceId == "633ee379…"` en dur
de ResourceController.
EF a généré un RenameColumn, pas un drop+add : les icônes déjà configurées
survivent. L'avertissement de perte de données ne porte que sur le DropColumn
de ParcoursIds, ce qui est l'intention.
Non fait, et c'était une erreur de doc : « supprimer SectionEvent.IconResourceId ».
Ce champ n'existe pas — la ligne visée appartient à la classe imbriquée
MapAnnotation, partagée par SectionEvent, SectionAgenda et SectionMap, lue par
cinq contrôleurs et par GetReferencedResourceIds. La supprimer aurait cassé
les icônes d'annotation des trois types et la collecte offline.
SÉCURITÉ (lot A, même repo) :
- AuthenticationController.Authenticate : un bloc #if DEBUG écrasait l'email
et le mot de passe reçus par un compte de test, donc toute compilation en
Debug authentifiait n'importe quelle saisie. Retiré.
- EnableSensitiveDataLogging (qui écrit les valeurs des paramètres dans les
logs) passe sous #if DEBUG, l'idiome déjà employé dans Startup.cs pour le
CORS et Hangfire. Le Dockerfile publiant en -c Release, c'est un verrou réel.
LOT C1 :
- Calculateur StoragePath/SizeBytes extrait dans Helpers/ResourceStorage.cs,
avec 13 tests fixant l'invariant des types URL. Il ferme le lien L5 : le
backfill (C2) et l'écart (e) de la migration appelleront le même code.
- L'extraction a révélé la divergence qu'elle devait empêcher : des deux
chemins de création de ResourceController, le chemin multipart écrivait
SizeBytes mais laissait StoragePath nul.
dotnet build Debug et Release verts, dotnet test 143/143 (130 + 13).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>